Como usar guias personalizadas com o Android 11

O Android 11 introduziu mudanças na forma como os apps podem interagir com outros apps que o usuário instalou no dispositivo. Leia mais sobre essas mudanças na documentação do Android.

Quando um app Android que usa as guias personalizadas é direcionado ao nível 30 do SDK ou mais recente, algumas mudanças podem ser necessárias. Este artigo aborda as mudanças que podem ser necessárias para esses apps.

No caso mais simples, as guias personalizadas podem ser iniciadas com uma linha, como esta:

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

Os aplicativos que iniciam outros usando essa abordagem ou que adicionam personalizações de interface, como mudar a cor da barra de ferramentas ou adicionar um botão de ação, não precisam fazer nenhuma mudança no aplicativo.

Preferir apps nativos

No entanto, se você seguiu as práticas recomendadas, algumas mudanças podem ser necessárias.

A primeira prática recomendada relevante é que os aplicativos precisam preferir um app nativo para processar a intent em vez de uma guia personalizada se um app capaz de processá-la estiver instalado.

No Android 11 e versões mais recentes

O Android 11 apresenta uma nova flag de intent, FLAG_ACTIVITY_REQUIRE_NON_BROWSER, que é a maneira recomendada de tentar abrir um app nativo, já que não exige que o app declare nenhuma consulta do gerenciador de pacotes.

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;
    }
}

A solução é tentar iniciar a intent e usar FLAG_ACTIVITY_REQUIRE_NON_BROWSER para pedir que o Android evite navegadores ao iniciar.

Se um app nativo que pode processar essa intent não for encontrado, uma ActivityNotFoundException será gerada.

Antes do Android 11

Mesmo que o aplicativo seja direcionado ao Android 11 ou ao nível 30 da API, as versões anteriores do Android não vão entender a flag FLAG_ACTIVITY_REQUIRE_NON_BROWSER. Portanto, precisamos consultar o gerenciador de pacotes nesses casos:

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;
}

A abordagem usada aqui é consultar o Gerenciador de pacotes para aplicativos que oferecem suporte a uma intent http genérica. Esses aplicativos provavelmente são navegadores.

Em seguida, consulte os aplicativos que processam itents para o URL específico que queremos iniciar. Isso vai retornar a configuração de navegadores e aplicativos nativos para processar esse URL.

Agora, remova todos os navegadores encontrados na primeira lista da segunda e só vamos ter apps nativos.

Se a lista estiver vazia, saberemos que não há processadores nativos e retornaremos "false". Caso contrário, vamos iniciar a intent para o gerenciador nativo.

Como tudo funciona em conjunto

Precisamos garantir o uso do método certo para cada ocasião:

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 fornece as informações necessárias. Se for igual ou maior que 30, o Android vai conhecer o FLAG_ACTIVITY_REQUIRE_NON_BROWSER, e podemos tentar iniciar um app nativo com a nova abordagem. Caso contrário, tentamos iniciar com a abordagem antiga.

Se a inicialização de um app nativo falhar, vamos iniciar uma guia personalizada.

Há um modelo envolvido nesta prática recomendada. Estamos trabalhando para simplificar isso encapsulando a complexidade em uma biblioteca. Fique por dentro das atualizações da biblioteca de suporte android-browser-helper.

Como detectar navegadores que oferecem suporte a guias personalizadas

Outro padrão comum é usar o PackageManager para detectar quais navegadores oferecem suporte a guias personalizadas no dispositivo. Os casos de uso comuns para isso são definir o pacote na intent para evitar a caixa de diálogo de desambiguação do app ou escolher qual navegador conectar ao conectar ao serviço de guias personalizadas.

Ao segmentar o nível 30 da API, os desenvolvedores precisam adicionar uma seção de consultas ao manifesto do Android, declarando um filtro de intent que corresponda aos navegadores com suporte a guias personalizadas.

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

Com a marcação em vigor, o código atual usado para consultar navegadores com suporte a guias personalizadas vai funcionar como esperado.

Perguntas frequentes

P: O código que procura provedores de Custom Tabs consulta aplicativos que podem processar intents https://, mas o filtro de consulta declara apenas uma consulta android.support.customtabs.action.CustomTabsService. Não seria necessário declarar uma consulta para intents https://?

A: Ao declarar um filtro de consulta, ele filtra as respostas de uma consulta para o PackageManager, e não a consulta em si. Como os navegadores que oferecem suporte a Custom Tabs declaram o processamento do CustomTabsService, eles não são filtrados. Os navegadores que não oferecem suporte a guias personalizadas serão filtrados.

Conclusão

Essas são todas as mudanças necessárias para adaptar uma integração de guias personalizadas para funcionar com o Android 11. Para saber mais sobre a integração das guias personalizadas em um app Android, comece com o guia de implementação e confira as práticas recomendadas para saber como criar uma integração de primeira classe.

Entre em contato se tiver dúvidas ou feedback.