Utiliser les onglets personnalisés avec Android 11

Android 11 a apporté des modifications à la manière 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 les onglets personnalisés cible le SDK de niveau 30 ou supérieur, certaines modifications peuvent être nécessaires. Cet article passe en revue les modifications qui peuvent être nécessaires pour ces applications.

Dans les cas les plus simples, les onglets personnalisés peuvent être lancés en une ligne comme ceci:

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

Les applications qui lancent des applications en utilisant cette approche ou même l'ajout de personnalisations de l'interface utilisateur comme la modification de la couleur de la barre d'outils, l'ajout d'un bouton d'action n'a 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 pertinente est que les applications doivent privilégier 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 nouvel indicateur d'intent, FLAG_ACTIVITY_REQUIRE_NON_BROWSER. Il s'agit de la méthode recommandée pour essayer d'ouvrir une application native, car elle ne nécessite pas que l'application déclare des requêtes de gestionnaire de packages.

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 d'utiliser FLAG_ACTIVITY_REQUIRE_NON_BROWSER pour demander à Android d'éviter les navigateurs lors du lancement.

Si une application native capable de gérer cet intent n'est pas 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 les cas suivants:

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 packages pour les applications compatibles avec un intent http générique. Ces applications sont probablement des navigateurs.

Recherchez ensuite les applications qui gèrent les requêtes pour l'URL spécifique que nous voulons lancer. Cette commande affiche les navigateurs et les applications natives configurés pour gérer cette URL.

Supprimez maintenant tous les navigateurs de la première liste dans la deuxième liste. Il ne reste plus que les applications natives.

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

Synthèse

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

Si le lancement d'une application native échoue, nous ouvrons un onglet personnalisé.

Cette bonne pratique implique un code récurrent. Nous mettons tout en œuvre pour simplifier ce processus en encapsulant la complexité d'une bibliothèque. Restez à l'affût des mises à jour de la bibliothèque Support android-browser-helper.

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

Un autre schéma courant consiste à utiliser le gestionnaire de packages pour détecter les navigateurs compatibles avec les onglets personnalisés sur l'appareil. Cela permet généralement de définir le package dans l'intent afin d'éviter la boîte de dialogue de sélection de l'application ou de choisir le navigateur auquel se connecter lors de la connexion au service Onglets personnalisés.

Lorsqu'ils ciblent 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 qui correspond 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 rechercher les navigateurs compatibles avec les onglets personnalisés fonctionnera comme prévu.

Questions fréquentes

Q: Le code qui recherche les requêtes des fournisseurs d'onglets personnalisés pour les applications pouvant gérer des intents https://, mais le filtre de requête ne déclare qu'une requête android.support.customtabs.action.CustomTabsService. Une requête pour les intents https:// ne devrait-elle pas être déclarée ?

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

Conclusion

Voici toutes les modifications nécessaires pour adapter une intégration existante d'onglets personnalisés afin qu'elle fonctionne avec Android 11. Pour en savoir plus sur l'intégration des onglets personnalisés à une application Android, commencez par consulter le guide d'implémentation, puis consultez les bonnes pratiques pour découvrir comment créer une intégration de première classe.

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