Usare le schede personalizzate con Android 11

Android 11 ha introdotto modifiche al modo in cui le app possono interagire con altre app che l'utente ha installato sul dispositivo. Per saperne di più su queste modifiche, consulta la documentazione di Android.

Quando un'app per Android che utilizza le schede personalizzate ha come target il livello SDK 30 o versioni successive, potrebbero essere necessarie alcune modifiche. Questo articolo illustra le modifiche che potrebbero essere necessarie per queste app.

Nel caso più semplice, le schede personalizzate possono essere lanciate con un messaggio di una riga, ad esempio:

new CustomTabsIntent.Builder().build()
        .launchUrl(this, Uri.parse("https://www.example.com"));

Le applicazioni che avviano applicazioni utilizzando questo approccio o addirittura aggiungono personalizzazioni dell'interfaccia utente come la modifica del colore della barra degli strumenti e l'aggiunta di un pulsante di azione non devono apportare modifiche all'applicazione.

Preferenza di app native

Tuttavia, se hai seguito le best practice, potrebbero essere necessarie alcune modifiche.

La prima best practice pertinente è che le applicazioni dovrebbero preferire un'app nativa per gestire l'intent anziché una scheda personalizzata se è installata un'app in grado di gestirla.

Su Android 11 e versioni successive

Android 11 introduce un nuovo flag di intent, FLAG_ACTIVITY_REQUIRE_NON_BROWSER, che è il modo consigliato per provare ad aprire un'app nativa, in quanto non richiede all'app di dichiarare alcuna query per il gestore di pacchetti.

static boolean launchNativeApi30(Context context, Uri uri) {
    Intent nativeAppIntent = new Intent(Intent.ACTION_VIEW, uri)
            .addCategory(Intent.CATEGORY_BROWSABLE)
            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
                    Intent.FLAG_ACTIVITY_REQUIRE_NON_BROWSER);
    try {
        context.startActivity(nativeAppIntent);
        return true;
    } catch (ActivityNotFoundException ex) {
        return false;
    }
}

La soluzione consiste nel provare ad avviare Intent e usare FLAG_ACTIVITY_REQUIRE_NON_BROWSER per chiedere ad Android di evitare i browser al momento dell'avvio.

Se non viene trovata un'app nativa in grado di gestire questo intent, verrà generata ActivityNotFoundException.

Prima di Android 11

Anche se l'applicazione potrebbe avere come target Android 11 o il livello API 30, le versioni precedenti di Android non comprendono il flag FLAG_ACTIVITY_REQUIRE_NON_BROWSER, quindi in questi casi dobbiamo ricorrere a query al gestore pacchetti:

private static boolean launchNativeBeforeApi30(Context context, Uri uri) {
    PackageManager pm = context.getPackageManager();

    // Get all Apps that resolve a generic url
    Intent browserActivityIntent = new Intent()
            .setAction(Intent.ACTION_VIEW)
            .addCategory(Intent.CATEGORY_BROWSABLE)
            .setData(Uri.fromParts("http", "", null));
    Set<String> genericResolvedList = extractPackageNames(
            pm.queryIntentActivities(browserActivityIntent, 0));

    // Get all apps that resolve the specific Url
    Intent specializedActivityIntent = new Intent(Intent.ACTION_VIEW, uri)
            .addCategory(Intent.CATEGORY_BROWSABLE);
    Set<String> resolvedSpecializedList = extractPackageNames(
            pm.queryIntentActivities(specializedActivityIntent, 0));

    // Keep only the Urls that resolve the specific, but not the generic
    // urls.
    resolvedSpecializedList.removeAll(genericResolvedList);

    // If the list is empty, no native app handlers were found.
    if (resolvedSpecializedList.isEmpty()) {
        return false;
    }

    // We found native handlers. Launch the Intent.
    specializedActivityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(specializedActivityIntent);
    return true;
}

L'approccio utilizzato qui consiste nell'eseguire una query in Gestione pacchetti in cerca di applicazioni che supportano un intent http generico. Queste applicazioni sono probabilmente dei browser.

Quindi, esegui una query per le applicazioni che gestiscono gli itent per l'URL specifico che vuoi avviare. Verranno restituite sia i browser sia le applicazioni native configurate per la gestione dell'URL.

Ora, rimuovi tutti i browser presenti nel primo elenco dal secondo, per lasciare solo le app native.

Se l'elenco è vuoto, sappiamo che non esistono gestori nativi e restituiremo false. In caso contrario, lanciamo l'intent per il gestore nativo.

Riassumendo

Dobbiamo assicurarci di utilizzare il metodo giusto per ogni occasione:

static void launchUri(Context context, Uri uri) {
    boolean launched = Build.VERSION.SDK_INT >= 30 ?
            launchNativeApi30(context, uri) :
            launchNativeBeforeApi30(context, uri);

    if (!launched) {
        new CustomTabsIntent.Builder()
                .build()
                .launchUrl(context, uri);
    }
}

Build.VERSION.SDK_INT fornisce le informazioni di cui abbiamo bisogno. Se è uguale o superiore a 30, Android conosce FLAG_ACTIVITY_REQUIRE_NON_BROWSER e possiamo provare ad avviare un'app nativa con il nuovo approccio. In caso contrario, proviamo a lanciare l'app con l'approccio precedente.

Se l'avvio di un'app nativa non va a buon fine, lanciamo una scheda personalizzata.

Questa best practice prevede alcuni boilerplate. Stiamo lavorando per semplificarla ulteriormente includendo la complessità di una libreria. Continua a seguirci per non perderti gli aggiornamenti alla raccolta di supporto di android-browser-helper.

Rilevamento dei browser che supportano le schede personalizzate

Un altro metodo comune è l'utilizzo di PackageManager per rilevare i browser che supportano le schede personalizzate sul dispositivo. I casi d'uso comuni in questo caso sono l'impostazione del pacchetto sull'intent per evitare la finestra di dialogo di disambiguazione dell'app o la scelta del browser a cui connettersi durante la connessione al servizio Schede personalizzate.

Quando scegli come target il livello API 30, gli sviluppatori dovranno aggiungere una sezione per le query al file manifest Android, dichiarando un filtro per intent che corrisponda ai browser che supportano le schede personalizzate.

<queries>
    <intent>
        <action android:name=
            "android.support.customtabs.action.CustomTabsService" />
    </intent>
</queries>

Una volta implementato il markup, il codice esistente utilizzato per eseguire query sui browser che supportano le schede personalizzate funzionerà come previsto.

Domande frequenti

D: il codice che cerca le query dei provider di schede personalizzate per le applicazioni in grado di gestire gli intent https://, ma il filtro delle query dichiara solo una query android.support.customtabs.action.CustomTabsService. Non dovrebbe essere dichiarata una query relativa agli intent https://?

R. Quando dichiari un filtro query, filtrerà le risposte a una query in PackageManager, non nella query stessa. Poiché i browser che supportano le schede personalizzate dichiarano di gestire il CustomTabsService, non vengono filtrati. I browser che non supportano le schede personalizzate verranno filtrati.

Conclusione

Queste sono tutte le modifiche necessarie per adattare un'integrazione di schede personalizzate esistente al funzionamento con Android 11. Per scoprire di più sull'integrazione delle schede personalizzate in un'app per Android, inizia con la guida all'implementazione, poi consulta le best practice per scoprire come creare un'integrazione di prim'ordine.

Non esitare a contattarci per eventuali domande o feedback.