Bug #2: O link profundo 'Pague Agora' que morreu silenciosamente
Em nossa tela inicial Epoxy, tocar em 'Pague Agora' dispara um intent implícito para nosso link profundo de Magic Pay. No F31 Pro, nada aconteceu — nenhum gateway de pagamento, nenhuma mensagem de erro, apenas uma piscada e uma página travada.
O que não era: restrições BAL do Android 14
Nossa primeira teoria foi que era o bloqueio de lançamento em segundo plano (BAL). Essa teoria estava errada, e vale a pena explicar por quê: as restrições BAL só se aplicam a lançamentos do background. Um usuário fisicamente pressionando um botão é um lançamento de foreground, iniciado pelo usuário — o sistema nunca bloqueia isso sob BAL. Se você está depurando um link profundo morto atrás de um click listener, BAL é uma falsa pista.
O que era realmente: filtragem de visibilidade do pacote
Desde o Android 11 (API 11), os aplicativos não podem mais ver a lista completa de pacotes instalados. Se você chamar resolveActivity() para uma intenção que visa um aplicativo que você não declarou em um bloco <queries> no seu manifesto, ele retorna null — mesmo quando o aplicativo-alvo está instalado e a intenção seria lançada perfeitamente.
Nosso teste de resolução foi 'defensivo' exatamente da maneira errada: ele detectou corretamente que o SO não podia ver o aplicativo de pagamento, então concluiu incorretamente que o aplicativo não estava instalado. Além disso, a gestão agressiva de processos do ColorOS (restrições de inicialização automática, hibernação profunda) pode matar diretamente o processo do aplicativo-alvo, fazendo com que a resolução cold-start estremeça e adicione ao sintoma 'piscando, travado'.
A correção, parte 1: declarar visibilidade no manifesto
Esta é a metade da correção que a maioria dos posts de blog pula:
<!-- AndroidManifest.xml -->
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="magicpay" />
</intent>
</queries>Sem este bloco, cada chamada de resolveActivity() / queryIntentActivities() para o esquema magicpay:// mente para você em API 30+.
A correção, parte 2: roteamento de intenção defensivo
Com a visibilidade declarada, o click listener se torna genuinamente defensivo em vez de teatralmente defensivo:
// Epoxy Home Controller / Model
payNowButton.setOnClickListener {
val magicPayIntent = Intent(
Intent.ACTION_VIEW,
Uri.parse("magicpay://checkout/transaction")
)
// Agora confiável - porque <queries> está declarado no manifesto
if (magicPayIntent.resolveActivity(context.packageManager) != null) {
try {
context.startActivity(magicPayIntent)
} catch (e: ActivityNotFoundException) {
// Condição de corrida: aplicativo desinstalado entre a verificação e o lançamento
showError("Gateway de pagamento temporariamente indisponível.")
}
} else {
// Realmente não instalado (ou assetlinks quebrados) - degrade graciosamente
promptAppInstallOrWebFallback()
}
}Um aviso pragmático: as próprias diretrizes da Google estão cada vez mais favoráveis a pular a verificação prévia inteiramente e simplesmente capturar ActivityNotFoundException, exatamente porque o filtro de visibilidade faz com que resolveActivity() seja inconsistente quando a declaração no manifesto está ausente. Qualquer um dos padrões funciona — mas apenas se você entender a <queries> necessidade. Mantivemos a verificação prévia porque nos permite rotear para uma alternativa web antes de qualquer tentativa de lançamento, o que é uma melhor experiência do usuário para um fluxo de pagamento.

