Capítulo 18: Permissões em Tempo de Execução, ActivityResultContracts e Câmera
Capítulo 18: Permissões em Tempo de Execução, ActivityResultContracts e Câmera
Especialização em Mobile Nativo com Android • Kotlin 2.0 & Jetpack Compose
🗺️ Mapa Conceitual do Fluxo de Permissões no Android Moderno
flowchart TD
START["Usuário Toca em Ação Sensível (Ex: Tirar Foto)"] --> CHECK{"ContextCompat.checkSelfPermission == GRANTED?"}
CHECK -->|"Sim"| EXECUTE["Acessa Hardware / Executa Câmera com Sucesso"]
CHECK -->|"Não"| RATIONALE{"ActivityCompat.shouldShowRequestPermissionRationale?"}
RATIONALE -->|"Sim (Negou anteriormente)"| DIALOG["Exibe Modal Educativo / Rationale Dialog"]
DIALOG --> LAUNCH["Dispara Launcher de Permissão"]
RATIONALE -->|"Não (Primeira vez)"| LAUNCH
LAUNCH --> RESULT{"Usuário Concedeu no Diálogo do SO?"}
RESULT -->|"Sim"| EXECUTE
RESULT -->|"Não (Negado)"| BLOCKED["Exibe Mensagem de Funcionalidade Restrita"]
RESULT -->|"Não pergunte novamente"| SETTINGS["Direciona para Configurações do Sistema do App"]
style START fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style CHECK fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style EXECUTE fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style DIALOG fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style BLOCKED fill:#fce4ec,stroke:#e91e63,stroke-width:2px
🏛️ 1. O Modelo de Privacidade e Segurança do Android Moderno
O modelo de segurança do Android evoluiu de permissões estáticas concedidas no momento da instalação para um rígido sistema de autorização granular e revogável a qualquer momento pelo usuário:
- Permissões de Instalação vs Permissões Perigosas em Runtime: Permissões como acesso à internet (
INTERNET) são concedidas automaticamente na instalação viaAndroidManifest.xml. No entanto, permissões que acessam dados privados (Câmera, Localização, Microfone, Contatos) exigem confirmação explícita em tempo de execução através de diálogos do sistema. - A Nova Fronteira do Android 13/14+: A partir do Android 13 (API 33), aplicativos não podem emitir notificações sem a permissão explícita
POST_NOTIFICATIONS. Além disso, o acesso a arquivos foi fragmentado em permissões específicas (READ_MEDIA_IMAGES,READ_MEDIA_VIDEO), priorizando o uso do Photo Picker do sistema, que nem sequer exige permissões para que o usuário selecione fotos com segurança. - Activity Result API no Jetpack Compose: O mecanismo antigo baseado em sobrescrever
onRequestPermissionsResultna Activity era frágil e sujeito a acoplamentos desnecessários. No Compose, usamosrememberLauncherForActivityResultcom contratos nativos (ActivityResultContracts.RequestPermissionouTakePicturePreview), desacoplando o ciclo de vida do componente.
📊 2. Comparativo: Modelo Legado vs Activity Result API no Compose
| Critério de Arquitetura | Modelo Legado (Activity / Fragment) | Modelo Moderno com Activity Result API (Compose) |
|---|---|---|
| Ponto de Registro | requestPermissions(arrayOf(...), REQUEST_CODE) |
rememberLauncherForActivityResult(contract) { ... } |
| Tratamento de Retorno | Sobrescrita de métodos com inteiros mágicos (requestCode) |
Callback de função pura tipada (concedido: Boolean) |
| Sobrevivência à Recriação | Complexo e propenso a perdas se a Activity for destruída | O contrato restaura o callback automaticamente pelo framework |
| Acoplamento com UI | Código espalhado entre View e Activity | Totalmente contido no escopo declarativo do Composable |
| Suporte a Câmera/Galeria | Criação manual de Intents implícitas com URIs | Contratos prontos: TakePicture, PickVisualMedia |
💻 3. Laboratório Prático: Solicitação de Permissão com Rationale no Compose
O exemplo a seguir demonstra a implementação idiomática da checagem e solicitação da permissão de câmera, com apresentação de rationale educativo caso tenha havido recusa prévia:
package br.com.portal.mobile.android.cap18
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
@Composable
fun BotaoAcessoCamera(
onPermissaoGarantida: () -> Unit,
modifier: Modifier = Modifier
) {
val context = LocalContext.current
var exibirModalExplicativo by remember { mutableStateOf(false) }
// Launcher do contrato nativo de solicitação de permissão
val permissaoLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestPermission()
) { concedida: Boolean ->
if (concedida) {
onPermissaoGarantida()
} else {
exibirModalExplicativo = true
}
}
Column(modifier = modifier.padding(16.dp)) {
Button(
onClick = {
val status = ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
if (status == PackageManager.PERMISSION_GRANTED) {
onPermissaoGarantida()
} else {
permissaoLauncher.launch(Manifest.permission.CAMERA)
}
},
modifier = Modifier.fillMaxWidth()
) {
Text("Acionar Câmera do Dispositivo")
}
// Rationale Dialog informativo
if (exibirModalExplicativo) {
AlertDialog(
onDismissRequest = { exibirModalExplicativo = false },
title = { Text("Acesso à Câmera Necessário") },
text = {
Text("O recurso de captura de recibos e comprovantes precisa de autorização da câmera para digitalizar os documentos. Por favor, habilite nas permissões do app.")
},
confirmButton = {
TextButton(onClick = { exibirModalExplicativo = false }) {
Text("Compreendi")
}
}
)
}
}
}
⚠️ 4. Armadilhas Comuns no Tratamento de Permissões
- Solicitar Permissões no Arranque Inicial do App: Abrir o aplicativo solicitando Câmera, Localização e Microfone imediatamente na primeira tela assusta os usuários e resulta em alta taxa de recusas. Sempre solicite no momento exato em que o usuário aciona a funcionalidade relacionada (In-Context Permission Request).
- Esquecer de Declarar no
AndroidManifest.xml: O Android só permite solicitar permissões em tempo de execução se elas já estiverem previamente declaradas com a tag<uses-permission>no manifesto. Omitir no XML faz a solicitação ser ignorada pelo sistema sem nenhum aviso. - Não Tratar a Opção “Não Perguntar Novamente”: Quando o usuário seleciona essa opção, o sistema não exibirá mais o diálogo de permissão e retornará
falseimediatamente. Nesses casos, o app deve oferecer um botão que direcione o usuário diretamente para as Configurações do Sistema viaIntent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).