Usare le schede personalizzate con Android 11

Android 11 ha introdotto modifiche al modo in cui le app possono interagire con altre app installate dall'utente sul dispositivo. Puoi scoprire di più su queste modifiche nella documentazione di Android.

Quando un'app per Android che utilizza Custom Tabs 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, Custom Tabs può essere avviato con una riga come questa:

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

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

Preferire le app native

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

La prima best practice pertinente è che le applicazioni devono 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 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 query del gestore del pacchetto.

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 è provare ad avviare l'intent e utilizzare 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à generato un messaggio ActivityNotFoundException.

Prima di Android 11

Anche se l'applicazione può avere come target Android 11 o il livello API 30, le versioni precedenti di Android non interpreteranno il flag FLAG_ACTIVITY_REQUIRE_NON_BROWSER, quindi in questi casi dobbiamo eseguire una query sul gestore del pacchetto:

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 è eseguire una query sul gestore pacchetti per trovare le applicazioni che supportano un'intenthttp generica. Queste applicazioni sono probabilmente browser.

Quindi, esegui una query per le applicazioni che gestiscono gli elementi per l'URL specifico che vogliamo lanciare. Verranno riportati sia i browser sia le applicazioni native configurati per gestire l'URL.

Ora rimuovi dal secondo elenco tutti i browser trovati nel primo elenco e rimarranno solo le app native.

Se l'elenco è vuoto, sappiamo che non ci sono gestori nativi e restituiamo false. In caso contrario, avviamo l'intent per l'handler 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 il FLAG_ACTIVITY_REQUIRE_NON_BROWSER e possiamo provare a lanciare un'app nativa con il nuovo approccio. In caso contrario, proveremo a lanciare il prodotto con il vecchio approccio.

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

Questa best practice prevede del testo standard. Stiamo lavorando per semplificare questa operazione incapsulando la complessità in una libreria. Continua a seguirci per gli aggiornamenti della libreria di supporto android-browser-helper.

Rilevamento dei browser che supportano Custom Tabs

Un altro pattern comune è utilizzare PackageManager per rilevare i browser che supportano Custom Tabs sul dispositivo. Alcuni casi d'uso comuni sono impostare il pacchetto nell'intent per evitare la finestra di dialogo di disambiguazione dell'app o scegliere a quale browser connettersi quando si connetti al servizio Custom Tabs.

Quando scelgono come target il livello API 30, gli sviluppatori devono aggiungere una sezione di query al file Android Manifest, dichiarando un filtro intent che corrisponda ai browser con il supporto di Custom Tabs.

<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 i provider di Custom Tabs esegue query sulle applicazioni che possono gestire intenthttps://, ma il filtro delle query dichiara solo una queryandroid.support.customtabs.action.CustomTabsService. Non dovrebbe essere dichiarata una query per le intenzioni https://?

R: quando dichiari un filtro delle query, vengono filtrate le risposte a una query al PackageManager, non la query stessa. Poiché i browser che supportano Custom Tabs dichiarano di gestire CustomTabsService, non verranno filtrati. I browser che non supportano Custom Tabs verranno esclusi.

Conclusione

Queste sono tutte le modifiche necessarie per adattare un'integrazione di Custom Tabs esistente al funzionamento con Android 11. Per scoprire di più sull'integrazione di Custom Tabs in un'app per Android, consulta la guida all'implementazione, quindi le best practice per scoprire come creare un'integrazione di primo livello.

Non esitare a contattarci per eventuali domande o feedback.