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 guias personalizadas é direcionado ao SDK de nível 30 ou mais recente, algumas mudanças podem ser necessárias. Este artigo aborda as mudanças necessárias para esses apps.

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

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

Os apps que iniciam apps usando essa abordagem ou até mesmo adicionam personalizações na interface, como mudar a cor da barra de ferramentas e adicionar um botão de ação, não precisam fazer mudanças no aplicativo.

Preferência por aplicativos nativos

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

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

No Android 11 e versões mais recentes

O Android 11 introduz uma nova flag de intent, FLAG_ACTIVITY_REQUIRE_NON_BROWSER, que é a maneira recomendada de tentar abrir um app nativo, já que o app não exige que ele 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 solicitar que o Android evite navegadores durante a inicialização.

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

Antes do Android 11

Mesmo que o aplicativo possa ser direcionado ao Android 11 ou ao nível 30 da API, as versões anteriores do Android não entenderão a flag FLAG_ACTIVITY_REQUIRE_NON_BROWSER. Portanto, precisamos recorrer à consulta do Gerenciador de pacotes nos casos a seguir:

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 encontrar aplicativos com suporte a uma intent http genérica. Esses aplicativos provavelmente são navegadores.

Em seguida, consulte os aplicativos que processam isso para o URL específico que queremos iniciar. Isso faz com que os navegadores e os aplicativos nativos estejam configurados para processar o URL.

Agora, remova todos os navegadores encontrados na primeira lista da segunda lista e ficaremos apenas com apps nativos.

Se a lista estiver vazia, saberemos que não há gerenciadores nativos e retornaremos "false". Caso contrário, iniciaremos 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 ele for igual ou maior que 30, o Android conhecerá a FLAG_ACTIVITY_REQUIRE_NON_BROWSER e podemos tentar lançar um app nativo com a nova abordagem. Caso contrário, tentaremos iniciar com a abordagem antiga.

Se a inicialização de um aplicativo nativo falhar, iniciamos uma guia personalizada.

Essa prática recomendada envolve um pouco de código boilerplate. Estamos trabalhando para simplificar isso encapsulando a complexidade em uma biblioteca. Fique de olho nas atualizações da Biblioteca de Suporte android-browser-helper.

Detecção de navegadores compatíveis com guias personalizadas

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

Ao segmentar o nível 30 da API, os desenvolvedores vão precisar 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 implementada, o código atual usado para consultar navegadores com suporte a guias personalizadas vai funcionar conforme o esperado.

Perguntas frequentes

P: O código que procura por consultas de provedores de guias personalizadas para aplicativos que podem processar intents https://, mas o filtro de consulta declara apenas uma consulta android.support.customtabs.action.CustomTabsService. Uma consulta para intents https:// não deve ser declarada?

R: Ao declarar um filtro de consulta, ele filtra as respostas para uma consulta para o PackageManager, não a consulta em si. Como os navegadores compatíveis com guias personalizadas declaram que processa o 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 de guias personalizadas em um app Android, comece com o guia de implementação e confira as práticas recomendadas para aprender a criar uma integração de primeira classe.

Entre em contato se tiver dúvidas ou feedback.