Utiliser les onglets personnalisés avec Android 11

Android 11 a apporté des modifications à la façon dont les applications peuvent interagir avec d'autres applications que l'utilisateur a installées sur l'appareil. Pour en savoir plus sur ces modifications, consultez la documentation Android.

Lorsqu'une application Android utilisant des onglets personnalisés cible le niveau de SDK 30 ou supérieur, des modifications peuvent être nécessaires. Cet article décrit les modifications qui peuvent être nécessaires pour ces applications.

Dans le cas le plus simple, les onglets personnalisés peuvent être lancés avec une seule ligne de code, comme suit:

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

Les applications qui lancent des applications à l'aide de cette approche, ou même qui ajoutent des personnalisations de l'interface utilisateur telles que la modification de la couleur de la barre d'outils ou l'ajout d'un bouton d'action, n'auront pas besoin d'apporter de modifications à l'application.

Privilégier les applications natives

Toutefois, si vous avez suivi les bonnes pratiques, certaines modifications peuvent être nécessaires.

La première bonne pratique consiste à utiliser une application native pour gérer l'intent plutôt qu'un onglet personnalisé si une application capable de le gérer est installée.

Sur Android 11 ou version ultérieure

Android 11 introduit un nouveau flag d'intent, FLAG_ACTIVITY_REQUIRE_NON_BROWSER, qui est le moyen recommandé d'essayer d'ouvrir une application native, car il ne nécessite pas que l'application déclare des requêtes de gestionnaire de paquets.

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 solution consiste à essayer de lancer l'intent et à utiliser FLAG_ACTIVITY_REQUIRE_NON_BROWSER pour demander à Android d'éviter les navigateurs lors du lancement.

Si aucune application native capable de gérer cet intent n'est trouvée, une exception ActivityNotFoundException est générée.

Avant Android 11

Même si l'application peut cibler Android 11 ou le niveau d'API 30, les versions précédentes d'Android ne comprendront pas l'indicateur FLAG_ACTIVITY_REQUIRE_NON_BROWSER. Nous devons donc interroger le Gestionnaire de paquets dans ces cas:

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'approche utilisée ici consiste à interroger le gestionnaire de paquets pour les applications compatibles avec un intent http générique. Il s'agit probablement de navigateurs.

Ensuite, interrogez les applications qui gèrent les éléments pour l'URL spécifique que vous souhaitez lancer. Les navigateurs et les applications natives configurés pour gérer cette URL sont renvoyés.

Supprimez maintenant tous les navigateurs de la première liste de la deuxième liste. Il ne restera alors que les applications natives.

Si la liste est vide, nous savons qu'il n'y a pas de gestionnaires natifs et renvoyons la valeur "false". Sinon, nous lançons l'intent pour le gestionnaire natif.

Synthèse

Nous devons nous assurer d'utiliser la bonne méthode pour chaque occasion:

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 fournit les informations dont nous avons besoin. Si la valeur est égale ou supérieure à 30, Android connaît FLAG_ACTIVITY_REQUIRE_NON_BROWSER et nous pouvons essayer de lancer une application native avec la nouvelle approche. Sinon, nous essayons de lancer l'application avec l'ancienne approche.

Si le lancement d'une application native échoue, nous lançons des onglets personnalisés.

Cette bonne pratique implique des éléments standards. Nous mettons tout en œuvre pour simplifier ce processus en encapsulant la complexité dans une bibliothèque. Nous vous tiendrons informé des mises à jour de la bibliothèque d'assistance android-browser-helper.

Détecter les navigateurs compatibles avec les onglets personnalisés

Un autre modèle courant consiste à utiliser PackageManager pour détecter les navigateurs compatibles avec les onglets personnalisés sur l'appareil. Les cas d'utilisation courants sont le paramétrage du package sur l'intent pour éviter la boîte de dialogue de déambiguïsation de l'application ou le choix du navigateur auquel se connecter lors de la connexion au service Custom Tabs.

Lorsque vous ciblez le niveau d'API 30, les développeurs doivent ajouter une section de requêtes à leur fichier manifeste Android, en déclarant un filtre d'intent correspondant aux navigateurs compatibles avec les onglets personnalisés.

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

Une fois le balisage en place, le code existant utilisé pour interroger les navigateurs compatibles avec les onglets personnalisés fonctionnera comme prévu.

Questions fréquentes

Q: Le code qui recherche des fournisseurs de Custom Tabs interroge les applications pouvant gérer les intents https://, mais le filtre de requête ne déclare qu'une requête android.support.customtabs.action.CustomTabsService. Ne devrait-on pas déclarer une requête pour les intents https:// ?

A: Lorsque vous déclarez un filtre de requête, il filtre les réponses à une requête envoyée au PackageManager, et non la requête elle-même. Étant donné que les navigateurs compatibles avec les onglets personnalisés déclarent gérer CustomTabsService, ils ne seront pas filtrés. Les navigateurs non compatibles avec les onglets personnalisés seront filtrés.

Conclusion

Ce sont toutes les modifications requises pour adapter une intégration de Custom Tabs existante à Android 11. Pour en savoir plus sur l'intégration de Custom Tabs dans une application Android, commencez par le guide d'implémentation, puis consultez les bonnes pratiques pour découvrir comment créer une intégration de premier ordre.

N'hésitez pas à nous contacter si vous avez des questions ou des commentaires.