Um serviço de preenchimento automático é um app que facilita o preenchimento de formulários pelos usuários, inserindo dados nas visualizações de outros apps. Os serviços de preenchimento automático também podem extrair dados do usuário das visualizações em um app e os armazenar para uso futuro. Em geral, os serviços de preenchimento automático são fornecidos por apps que gerenciam dados do usuário, por exemplo, gerenciadores de senhas.
O Android facilita o preenchimento de formulários com a Estrutura de preenchimento automático disponível no Android 8.0 (API de nível 26) e versões mais recentes. Os usuários só poderão aproveitar os recursos de preenchimento automático se houver um app que forneça esse tipo de serviço no dispositivo.
Esta página mostra como implementar um serviço de preenchimento automático no seu app. Se você está
procurando um exemplo de código que mostre como implementar um serviço, consulte o
exemplo de AutofillFramework em Java
ou
Kotlin (links em inglês).
Para mais detalhes sobre como os serviços de preenchimento automático funcionam, consulte as páginas
de referência das classes
AutofillService
e AutofillManager
.
Declarações e permissões do manifesto
Os apps que oferecem o recurso de preenchimento automático precisam incluir uma declaração que descreva
a implementação do serviço. Para especificar a declaração, inclua um elemento
<service>
no
manifesto do app. O elemento <service>
precisa
incluir estes atributos e elementos:
- O atributo
android:name
, que aponta para a subclasse deAutofillService
no app responsável pela implementação do serviço. - O atributo
android:permission
, que declara a permissãoBIND_AUTOFILL_SERVICE
. - O elemento
<intent-filter>
, que tem o filho obrigatório<action>
especificando a açãoandroid.service.autofill.AutofillService
. - O elemento opcional
<meta-data>
, que pode ser usado para fornecer parâmetros de configuração extra ao serviço.
O exemplo abaixo mostra uma declaração de serviço de preenchimento automático.
<service
android:name=".MyAutofillService"
android:label="My Autofill Service"
android:permission="android.permission.BIND_AUTOFILL_SERVICE">
<intent-filter>
<action android:name="android.service.autofill.AutofillService" />
</intent-filter>
<meta-data
android:name="android.autofill"
android:resource="@xml/service_configuration" />
</service>
O elemento <meta-data>
inclui um atributo android:resource
que aponta para um recurso XML com mais detalhes sobre o serviço.
O recurso service_configuration
no exemplo anterior especifica uma atividade
que permite que os usuários configurem o serviço. O exemplo abaixo
mostra o recurso XML service_configuration
.
<autofill-service
xmlns:android="http://schemas.android.com/apk/res/android"
android:settingsActivity="com.example.android.SettingsActivity" />
Para saber mais sobre recursos XML, consulte Visão geral dos recursos de aplicativo.
Enviar avisos para ativar o serviço
Um app é usado como serviço de preenchimento automático após declarar
a permissão BIND_AUTOFILL_SERVICE
e depois que o usuário ativa
o recurso nas configurações do dispositivo. Um app pode verificar se está definido como o serviço
ativo no momento, chamando o
método hasEnabledAutofillServices()
da classe
AutofillManager
.
Se o app não estiver definido como o serviço de preenchimento automático atual, ele poderá solicitar que o usuário
mude as configurações de preenchimento automático usando
a intent
ACTION_REQUEST_SET_AUTOFILL_SERVICE
. Se o usuário
selecionou um serviço de preenchimento automático que corresponda ao pacote do autor da chamada, a intent retorna um valor RESULT_OK
.
Preencher visualizações de clientes
O serviço de preenchimento automático recebe solicitações para preencher as visualizações de clientes de acordo com a interação do usuário com outros apps. Se o serviço de preenchimento automático tem dados do usuário que atendem a solicitação, ele envia as informações na resposta. O sistema Android mostra uma interface de preenchimento automático com os dados disponíveis, conforme mostrado na Figura 1:
A Estrutura de preenchimento automático define um fluxo de trabalho para preencher visualizações criadas com o objetivo
de minimizar o tempo de vinculação do sistema Android a esse serviço. Em
cada solicitação, o sistema Android envia um objeto AssistStructure
ao serviço, chamando o método onFillRequest()
.
O serviço de preenchimento automático verifica se ele pode atender a solicitação com dados do usuário
armazenados anteriormente. Se puder atender a solicitação, o serviço vai empacotar
os dados em objetos Dataset
. O serviço chama
o método onSuccess()
transmitindo um objeto FillResponse
, que contém
objetos Dataset
. Se o serviço não
tiver dados para atender a solicitação, ele transmite null
ao método onSuccess()
.
Caso haja um erro ao processar a solicitação, o serviço
chama o método
onFailure()
. Para conferir uma
explicação detalhada do fluxo de trabalho, consulte a descrição na página de
referência de AutofillService
.
O código a seguir mostra um exemplo do método onFillRequest()
.
Kotlin
override fun onFillRequest( request: FillRequest, cancellationSignal: CancellationSignal, callback: FillCallback ) { // Get the structure from the request val context: List<FillContext> = request.fillContexts val structure: AssistStructure = context[context.size - 1].structure // Traverse the structure looking for nodes to fill out val parsedStructure: ParsedStructure = parseStructure(structure) // Fetch user data that matches the fields val (username: String, password: String) = fetchUserData(parsedStructure) // Build the presentation of the datasets val usernamePresentation = RemoteViews(packageName, android.R.layout.simple_list_item_1) usernamePresentation.setTextViewText(android.R.id.text1, "my_username") val passwordPresentation = RemoteViews(packageName, android.R.layout.simple_list_item_1) passwordPresentation.setTextViewText(android.R.id.text1, "Password for my_username") // Add a dataset to the response val fillResponse: FillResponse = FillResponse.Builder() .addDataset(Dataset.Builder() .setValue( parsedStructure.usernameId, AutofillValue.forText(username), usernamePresentation ) .setValue( parsedStructure.passwordId, AutofillValue.forText(password), passwordPresentation ) .build()) .build() // If there are no errors, call onSuccess() and pass the response callback.onSuccess(fillResponse) } data class ParsedStructure(var usernameId: AutofillId, var passwordId: AutofillId) data class UserData(var username: String, var password: String)
Java
@Override public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal, FillCallback callback) { // Get the structure from the request List<FillContext> context = request.getFillContexts(); AssistStructure structure = context.get(context.size() - 1).getStructure(); // Traverse the structure looking for nodes to fill out ParsedStructure parsedStructure = parseStructure(structure); // Fetch user data that matches the fields UserData userData = fetchUserData(parsedStructure); // Build the presentation of the datasets RemoteViews usernamePresentation = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1); usernamePresentation.setTextViewText(android.R.id.text1, "my_username"); RemoteViews passwordPresentation = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1); passwordPresentation.setTextViewText(android.R.id.text1, "Password for my_username"); // Add a dataset to the response FillResponse fillResponse = new FillResponse.Builder() .addDataset(new Dataset.Builder() .setValue(parsedStructure.usernameId, AutofillValue.forText(userData.username), usernamePresentation) .setValue(parsedStructure.passwordId, AutofillValue.forText(userData.password), passwordPresentation) .build()) .build(); // If there are no errors, call onSuccess() and pass the response callback.onSuccess(fillResponse); } class ParsedStructure { AutofillId usernameId; AutofillId passwordId; } class UserData { String username; String password; }
Um serviço pode ter mais de um conjunto de dados que satisfaça à solicitação. Nesse caso, o sistema Android mostrará várias opções, uma para cada conjunto de dados, na interface de preenchimento automático. O exemplo de código a seguir mostra como fornecer vários conjuntos de dados em uma resposta.
Kotlin
// Add multiple datasets to the response val fillResponse: FillResponse = FillResponse.Builder() .addDataset(Dataset.Builder() .setValue(parsedStructure.usernameId, AutofillValue.forText(user1Data.username), username1Presentation) .setValue(parsedStructure.passwordId, AutofillValue.forText(user1Data.password), password1Presentation) .build()) .addDataset(Dataset.Builder() .setValue(parsedStructure.usernameId, AutofillValue.forText(user2Data.username), username2Presentation) .setValue(parsedStructure.passwordId, AutofillValue.forText(user2Data.password), password2Presentation) .build()) .build()
Java
// Add multiple datasets to the response FillResponse fillResponse = new FillResponse.Builder() .addDataset(new Dataset.Builder() .setValue(parsedStructure.usernameId, AutofillValue.forText(user1Data.username), username1Presentation) .setValue(parsedStructure.passwordId, AutofillValue.forText(user1Data.password), password1Presentation) .build()) .addDataset(new Dataset.Builder() .setValue(parsedStructure.usernameId, AutofillValue.forText(user2Data.username), username2Presentation) .setValue(parsedStructure.passwordId, AutofillValue.forText(user2Data.password), password2Presentation) .build()) .build();
Os serviços de preenchimento automático podem navegar pelos objetos ViewNode
na AssistStructure
para extrair os dados
necessários e atender à solicitação. Um serviço pode extrair dados de preenchimento automático usando
métodos da classe ViewNode
,
por exemplo, getAutofillId()
.
Um serviço precisa ser capaz de descrever o conteúdo de uma visualização para verificar se
pode atender a solicitação. O atributo autofillHints
é a primeira abordagem
de um serviço para descrever o conteúdo de uma visualização. No entanto,
os apps clientes precisam fornecer explicitamente o atributo nas próprias visualizações antes de ficarem
disponíveis para o serviço.
Se um app cliente não fornecer o atributo autofillHints
,
um serviço vai precisar usar as próprias heurísticas para descrever o conteúdo.
O serviço pode usar métodos de outras classes, como getText()
ou getHint()
,
para receber informações sobre o conteúdo da visualização.
Para conferir mais informações, consulte Dicas de
preenchimento automático.
O exemplo abaixo mostra como percorrer AssistStructure
e extrair
dados de preenchimento automático de um objeto ViewNode
.
Kotlin
fun traverseStructure(structure: AssistStructure) { val windowNodes: List<AssistStructure.WindowNode> = structure.run { (0 until windowNodeCount).map { getWindowNodeAt(it) } } windowNodes.forEach { windowNode: AssistStructure.WindowNode -> val viewNode: ViewNode? = windowNode.rootViewNode traverseNode(viewNode) } } fun traverseNode(viewNode: ViewNode?) { if (viewNode?.autofillHints?.isNotEmpty() == true) { // If the client app provides autofill hints, you can obtain them using // viewNode.getAutofillHints(); } else { // Or use your own heuristics to describe the contents of a view // using methods such as getText() or getHint() } val children: List<ViewNode>? = viewNode?.run { (0 until childCount).map { getChildAt(it) } } children?.forEach { childNode: ViewNode -> traverseNode(childNode) } }
Java
public void traverseStructure(AssistStructure structure) { int nodes = structure.getWindowNodeCount(); for (int i = 0; i < nodes; i++) { WindowNode windowNode = structure.getWindowNodeAt(i); ViewNode viewNode = windowNode.getRootViewNode(); traverseNode(viewNode); } } public void traverseNode(ViewNode viewNode) { if(viewNode.getAutofillHints() != null && viewNode.getAutofillHints().length > 0) { // If the client app provides autofill hints, you can obtain them using // viewNode.getAutofillHints(); } else { // Or use your own heuristics to describe the contents of a view // using methods such as getText() or getHint() } for(int i = 0; i < viewNode.getChildCount(); i++) { ViewNode childNode = viewNode.getChildAt(i); traverseNode(childNode); } }
Salvar dados do usuário
Um serviço de preenchimento automático precisa de dados do usuário para preencher visualizações em apps. Quando os usuários preenchem manualmente uma visualização, eles são solicitados a salvar os dados no serviço de preenchimento automático atual, como mostra a Figura 2.
Para salvar os dados, o serviço precisa indicar que está interessado em armazenar as
informações para uso futuro. Antes de o sistema Android enviar uma solicitação para salvar os dados,
há uma solicitação de preenchimento para que o serviço possa preencher as
visualizações. Para indicar que está interessado em salvar os dados, o serviço
inclui um objeto SaveInfo
na resposta à solicitação de preenchimento. O objeto SaveInfo
contém pelo menos estes dados:
- O tipo de dados do usuário que vão ser salvos. Para conferir uma lista dos valores
SAVE_DATA
disponíveis, consulteSaveInfo
. - O conjunto mínimo de visualizações que precisam ser alteradas para acionar uma solicitação de salvamento.
Por exemplo, um formulário de login normalmente exige que o usuário atualize as visualizações
username
epassword
para acionar uma solicitação de salvamento.
Um objeto SaveInfo
é associado a um objeto FillResponse
, conforme mostrado neste exemplo
de código:
Kotlin
override fun onFillRequest( request: FillRequest, cancellationSignal: CancellationSignal, callback: FillCallback ) { ... // Builder object requires a non-null presentation val notUsed = RemoteViews(packageName, android.R.layout.simple_list_item_1) val fillResponse: FillResponse = FillResponse.Builder() .addDataset( Dataset.Builder() .setValue(parsedStructure.usernameId, null, notUsed) .setValue(parsedStructure.passwordId, null, notUsed) .build() ) .setSaveInfo( SaveInfo.Builder( SaveInfo.SAVE_DATA_TYPE_USERNAME or SaveInfo.SAVE_DATA_TYPE_PASSWORD, arrayOf(parsedStructure.usernameId, parsedStructure.passwordId) ).build() ) .build() ... }
Java
@Override public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal, FillCallback callback) { ... // Builder object requires a non-null presentation RemoteViews notUsed = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1); FillResponse fillResponse = new FillResponse.Builder() .addDataset(new Dataset.Builder() .setValue(parsedStructure.usernameId, null, notUsed) .setValue(parsedStructure.passwordId, null, notUsed) .build()) .setSaveInfo(new SaveInfo.Builder( SaveInfo.SAVE_DATA_TYPE_USERNAME | SaveInfo.SAVE_DATA_TYPE_PASSWORD, new AutofillId[] {parsedStructure.usernameId, parsedStructure.passwordId}) .build()) .build(); ... }
O serviço de preenchimento automático pode implementar lógica para armazenar os dados do usuário no método
onSaveRequest()
,
que, em geral, é chamado após o término da atividade do cliente ou quando o app
cliente chama commit()
.
O código
abaixo mostra um exemplo do método onSaveRequest()
.
Kotlin
override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) { // Get the structure from the request val context: List<FillContext> = request.fillContexts val structure: AssistStructure = context[context.size - 1].structure // Traverse the structure looking for data to save traverseStructure(structure) // Persist the data - if there are no errors, call onSuccess() callback.onSuccess() }
Java
@Override public void onSaveRequest(SaveRequest request, SaveCallback callback) { // Get the structure from the request List<FillContext> context = request.getFillContexts(); AssistStructure structure = context.get(context.size() - 1).getStructure(); // Traverse the structure looking for data to save traverseStructure(structure); // Persist the data - if there are no errors, call onSuccess() callback.onSuccess(); }
Os serviços de preenchimento automático precisam criptografar dados confidenciais antes de armazená-los. No entanto, os dados do usuário podem incluir etiquetas ou dados não confidenciais. Por exemplo, uma conta de usuário pode incluir uma etiqueta que marque os dados como uma conta de trabalho ou pessoal. Os serviços não podem criptografar etiquetas. Isso permite que elas sejam usadas nas visualizações de apresentação, caso o usuário não tenha realizado o processo de autenticação Em seguida, os serviços podem substituir as etiquetas pelos dados reais após a autenticação.
Adiar a interface de salvamento do preenchimento automático
No Android 10 e versões mais recentes, se você usa várias telas para implementar um fluxo de trabalho
de preenchimento automático (por exemplo, uma tela para o campo de nome de usuário e outra para a
senha), pode adiar a interface de salvamento do preenchimento automático usando a
flag
SaveInfo.FLAG_DELAY_SAVE
.
Se essa flag for definida, a interface de salvamento do preenchimento automático não vai ser acionada quando o contexto
de preenchimento automático associado à resposta SaveInfo
for confirmado. Em vez disso, você poderá
usar uma atividade separada na mesma tarefa para enviar solicitações de preenchimento futuras e,
em seguida, mostrar a interface com uma solicitação de salvamento. Para mais informações, consulte
SaveInfo.FLAG_DELAY_SAVE
:
Exigir autenticação do usuário
Os serviços de preenchimento automático podem fornecer um nível adicional de segurança, exigindo que o usuário faça a autenticação antes de preencher as visualizações. Os cenários a seguir são bons candidatos à implementação da autenticação do usuário.
- Os dados do usuário no app precisam ser desbloqueados usando uma senha principal ou uma verificação de impressão digital.
- Um conjunto de dados específico precisa ser desbloqueado, por exemplo, detalhes de cartão de crédito, usando um Código de segurança do cartão (CVC).
Caso o serviço exija a autenticação do usuário antes de desbloquear
os dados, ele pode apresentar dados padrão ou uma etiqueta e especificar a
Intent
responsável pela autenticação. Se você precisa de mais dados para processar a solicitação após a conclusão do fluxo de autenticação,
pode adicionar essas informações à intent. Sua
atividade de autenticação pode retornar os dados para a classe
AutofillService
no seu app.
O exemplo de código abaixo mostra como especificar que a solicitação exige autenticação:
Kotlin
val authPresentation = RemoteViews(packageName, android.R.layout.simple_list_item_1).apply { setTextViewText(android.R.id.text1, "requires authentication") } val authIntent = Intent(this, AuthActivity::class.java).apply { // Send any additional data required to complete the request putExtra(MY_EXTRA_DATASET_NAME, "my_dataset") } val intentSender: IntentSender = PendingIntent.getActivity( this, 1001, authIntent, PendingIntent.FLAG_CANCEL_CURRENT ).intentSender // Build a FillResponse object that requires authentication val fillResponse: FillResponse = FillResponse.Builder() .setAuthentication(autofillIds, intentSender, authPresentation) .build()
Java
RemoteViews authPresentation = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1); authPresentation.setTextViewText(android.R.id.text1, "requires authentication"); Intent authIntent = new Intent(this, AuthActivity.class); // Send any additional data required to complete the request authIntent.putExtra(MY_EXTRA_DATASET_NAME, "my_dataset"); IntentSender intentSender = PendingIntent.getActivity( this, 1001, authIntent, PendingIntent.FLAG_CANCEL_CURRENT ).getIntentSender(); // Build a FillResponse object that requires authentication FillResponse fillResponse = new FillResponse.Builder() .setAuthentication(autofillIds, intentSender, authPresentation) .build();
Depois que a atividade conclui o fluxo de autenticação, ela precisa chamar o método
setResult()
transmitindo um valor RESULT_OK
e definir o EXTRA_AUTHENTICATION_RESULT
extra para o objeto
FillResponse
que inclui o conjunto de dados
preenchido. O
código abaixo mostra um exemplo de como retornar o resultado após
a conclusão dos fluxos de autenticação.
Kotlin
// The data sent by the service and the structure are included in the intent val datasetName: String? = intent.getStringExtra(MY_EXTRA_DATASET_NAME) val structure: AssistStructure = intent.getParcelableExtra(EXTRA_ASSIST_STRUCTURE) val parsedStructure: ParsedStructure = parseStructure(structure) val (username, password) = fetchUserData(parsedStructure) // Build the presentation of the datasets val usernamePresentation = RemoteViews(packageName, android.R.layout.simple_list_item_1).apply { setTextViewText(android.R.id.text1, "my_username") } val passwordPresentation = RemoteViews(packageName, android.R.layout.simple_list_item_1).apply { setTextViewText(android.R.id.text1, "Password for my_username") } // Add the dataset to the response val fillResponse: FillResponse = FillResponse.Builder() .addDataset(Dataset.Builder() .setValue( parsedStructure.usernameId, AutofillValue.forText(username), usernamePresentation ) .setValue( parsedStructure.passwordId, AutofillValue.forText(password), passwordPresentation ) .build() ).build() val replyIntent = Intent().apply { // Send the data back to the service putExtra(MY_EXTRA_DATASET_NAME, datasetName) putExtra(EXTRA_AUTHENTICATION_RESULT, fillResponse) } setResult(Activity.RESULT_OK, replyIntent)
Java
Intent intent = getIntent(); // The data sent by the service and the structure are included in the intent String datasetName = intent.getStringExtra(MY_EXTRA_DATASET_NAME); AssistStructure structure = intent.getParcelableExtra(EXTRA_ASSIST_STRUCTURE); ParsedStructure parsedStructure = parseStructure(structure); UserData userData = fetchUserData(parsedStructure); // Build the presentation of the datasets RemoteViews usernamePresentation = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1); usernamePresentation.setTextViewText(android.R.id.text1, "my_username"); RemoteViews passwordPresentation = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1); passwordPresentation.setTextViewText(android.R.id.text1, "Password for my_username"); // Add the dataset to the response FillResponse fillResponse = new FillResponse.Builder() .addDataset(new Dataset.Builder() .setValue(parsedStructure.usernameId, AutofillValue.forText(userData.username), usernamePresentation) .setValue(parsedStructure.passwordId, AutofillValue.forText(userData.password), passwordPresentation) .build()) .build(); Intent replyIntent = new Intent(); // Send the data back to the service replyIntent.putExtra(MY_EXTRA_DATASET_NAME, datasetName); replyIntent.putExtra(EXTRA_AUTHENTICATION_RESULT, fillResponse); setResult(RESULT_OK, replyIntent);
No cenário em que um conjunto de dados de cartão de crédito precisa ser desbloqueado, o serviço pode mostrar a interface solicitando o CVC. Você pode ocultar as informações até que o conjunto de dados seja desbloqueado, apresentando informações padrão, como o nome do banco e os quatro últimos dígitos do número do cartão de crédito. O exemplo a seguir mostra como exigir autenticação para um conjunto de dados e ocultar os dados até que o usuário forneça o CVC.
Kotlin
// Parse the structure and fetch payment data val parsedStructure: ParsedStructure = parseStructure(structure) val paymentData: Payment = fetchPaymentData(parsedStructure) // Build the presentation that shows the bank and the last four digits of the // credit card number, such as 'Bank-1234' val maskedPresentation: String = "${paymentData.bank}-" + paymentData.creditCardNumber.substring(paymentData.creditCardNumber.length - 4) val authPresentation = RemoteViews(packageName, android.R.layout.simple_list_item_1).apply { setTextViewText(android.R.id.text1, maskedPresentation) } // Prepare an intent that displays the UI that asks for the CVC val cvcIntent = Intent(this, CvcActivity::class.java) val cvcIntentSender: IntentSender = PendingIntent.getActivity( this, 1001, cvcIntent, PendingIntent.FLAG_CANCEL_CURRENT ).intentSender // Build a FillResponse object that includes a Dataset that requires authentication val fillResponse: FillResponse = FillResponse.Builder() .addDataset( Dataset.Builder() // The values in the dataset are replaced by the actual // data once the user provides the CVC .setValue(parsedStructure.creditCardId, null, authPresentation) .setValue(parsedStructure.expDateId, null, authPresentation) .setAuthentication(cvcIntentSender) .build() ).build()
Java
// Parse the structure and fetch payment data ParsedStructure parsedStructure = parseStructure(structure); Payment paymentData = fetchPaymentData(parsedStructure); // Build the presentation that shows the bank and the last four digits of the // credit card number, such as 'Bank-1234' String maskedPresentation = paymentData.bank + "-" + paymentData.creditCardNumber.subString(paymentData.creditCardNumber.length - 4); RemoteViews authPresentation = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1); authPresentation.setTextViewText(android.R.id.text1, maskedPresentation); // Prepare an intent that displays the UI that asks for the CVC Intent cvcIntent = new Intent(this, CvcActivity.class); IntentSender cvcIntentSender = PendingIntent.getActivity( this, 1001, cvcIntent, PendingIntent.FLAG_CANCEL_CURRENT ).getIntentSender(); // Build a FillResponse object that includes a Dataset that requires authentication FillResponse fillResponse = new FillResponse.Builder() .addDataset(new Dataset.Builder() // The values in the dataset are replaced by the actual // data once the user provides the CVC .setValue(parsedStructure.creditCardId, null, authPresentation) .setValue(parsedStructure.expDateId, null, authPresentation) .setAuthentication(cvcIntentSender) .build()) .build();
Depois que a atividade valida o CVC, ela precisa chamar o método setResult()
transmitindo um valor RESULT_OK
e definir o EXTRA_AUTHENTICATION_RESULT
extra para o objeto
Dataset
que contém o número do cartão de crédito e a data de validade. O
novo conjunto de dados substitui o conjunto de dados que requer
autenticação, e as visualizações são preenchidas imediatamente. O código abaixo mostra um exemplo de como retornar o conjunto de dados depois que o usuário fornece o CVC.
Kotlin
// Parse the structure and fetch payment data. val parsedStructure: ParsedStructure = parseStructure(structure) val paymentData: Payment = fetchPaymentData(parsedStructure) // Build a non-null RemoteViews object to use as the presentation when // creating the Dataset object. This presentation isn't actually used, but the // Builder object requires a non-null presentation. val notUsed = RemoteViews(packageName, android.R.layout.simple_list_item_1) // Create a dataset with the credit card number and expiration date. val responseDataset: Dataset = Dataset.Builder() .setValue( parsedStructure.creditCardId, AutofillValue.forText(paymentData.creditCardNumber), notUsed ) .setValue( parsedStructure.expDateId, AutofillValue.forText(paymentData.expirationDate), notUsed ) .build() val replyIntent = Intent().apply { putExtra(EXTRA_AUTHENTICATION_RESULT, responseDataset) }
Java
// Parse the structure and fetch payment data. ParsedStructure parsedStructure = parseStructure(structure); Payment paymentData = fetchPaymentData(parsedStructure); // Build a non-null RemoteViews object to use as the presentation when // creating the Dataset object. This presentation isn't actually used, but the // Builder object requires a non-null presentation. RemoteViews notUsed = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1); // Create a dataset with the credit card number and expiration date. Dataset responseDataset = new Dataset.Builder() .setValue(parsedStructure.creditCardId, AutofillValue.forText(paymentData.creditCardNumber), notUsed) .setValue(parsedStructure.expDateId, AutofillValue.forText(paymentData.expirationDate), notUsed) .build(); Intent replyIntent = new Intent(); replyIntent.putExtra(EXTRA_AUTHENTICATION_RESULT, responseDataset);
Organizar os dados em grupos lógicos
Os serviços de preenchimento automático precisam organizar os dados em grupos lógicos que isolam conceitos de diferentes domínios. Nesta página, esses grupos lógicos são chamados de partições. A lista abaixo mostra exemplos típicos de partições e campos.
- Credenciais, que incluem campos de nome de usuário e senha.
- Endereço, que inclui campos de rua, cidade, estado e CEP.
- Informações de pagamento, que incluem campos de número do cartão de crédito, data de validade e código de verificação.
Um serviço de preenchimento automático que faz partições da maneira correta pode proteger melhor os dados dos usuários, evitando a exposição de informações de mais de uma partição em um conjunto de dados. Por exemplo, um conjunto de dados que inclui credenciais não precisa incluir informações de pagamento. Organizar os dados em partições permite que seu serviço exponha a quantidade mínima de informações relevantes necessárias para atender a uma solicitação.
Com essa organização, os serviços podem preencher atividades com visualizações de várias partições enquanto enviam a quantidade mínima de dados relevantes para o app cliente. Por exemplo, imagine uma atividade que inclua visualizações para nome de usuário, senha, rua e cidade e um serviço de preenchimento automático que tenha estes dados:
Partição | Campo 1 | Campo 2 |
---|---|---|
Credenciais | work_username | work_password |
personal_username | personal_password | |
Endereço | work_street | work_city |
personal_street | personal_city |
O serviço pode preparar um conjunto de dados que inclua a partição de credenciais para contas de trabalho e pessoais. Quando o usuário escolhe um conjunto de dados, uma resposta subsequente de preenchimento automático pode fornecer o endereço comercial ou pessoal, dependendo da primeira escolha do usuário.
Um serviço pode identificar o campo que originou a solicitação chamando
o método isFocused()
ao
percorrer o objeto AssistStructure
. Isso permite que
o serviço prepare uma FillResponse
com os dados de partição adequados.
Preenchimento automático do código SMS de uso único
Seu serviço de preenchimento automático pode ajudar o usuário a preencher códigos únicos enviados por SMS usando a API SMS Retriever.
Para usar esse recurso, estes requisitos precisam ser atendidos:
- O serviço de preenchimento automático precisa executar o Android 9 (nível 28 da API) ou mais recente.
- O usuário autoriza o serviço de preenchimento automático a ler códigos únicos de SMS.
- O app ao qual você está fornecendo o preenchimento automático não pode já estar usando a API SMS Retriever para ler códigos únicos.
Seu serviço de preenchimento automático pode usar o SmsCodeAutofillClient
, disponível chamando SmsCodeRetriever.getAutofillClient()
no Google Play
Services 19.0.56 ou em versões mais recente.
As principais etapas para usar essa API em um serviço de preenchimento automático são:
- No serviço de preenchimento automático, use o
hasOngoingSmsRequest
deSmsCodeAutofillClient
para determinar se já há solicitações ativas para o nome do pacote do app que você está preenchendo automaticamente. Seu serviço de preenchimento automático só vai poder mostrar uma solicitação de sugestão se o retorno forfalse
. - No serviço de preenchimento automático, use
checkPermissionState
emSmsCodeAutofillClient
para verificar se o serviço de preenchimento automático tem permissão para preencher códigos únicos automaticamente. Esse estado de permissão pode serNONE
,GRANTED
ouDENIED
. O serviço de preenchimento automático precisa mostrar uma solicitação de sugestão para os estadosNONE
eGRANTED
. - Na atividade de autenticação de preenchimento automático, use a permissão
SmsRetriever.SEND_PERMISSION
para registrar umBroadcastReceiver
que ouveSmsCodeRetriever.SMS_CODE_RETRIEVED_ACTION
para receber o resultado do código SMS quando ele estiver disponível. Chame
startSmsCodeRetriever
emSmsCodeAutofillClient
para começar a detectar códigos de uso único enviados por SMS. Se o usuário concede permissões para que o serviço de preenchimento automático extraia códigos únicos de SMS, ele procura as mensagens SMS recebidas nos últimos um a cinco minutos.Se o serviço de preenchimento automático precisar solicitar a permissão do usuário para ler códigos de uso único, a
Task
retornada porstartSmsCodeRetriever
pode falhar com umaResolvableApiException
retornado. Se isso acontecer, chame o métodoResolvableApiException.startResolutionForResult()
para mostrar uma caixa de diálogo de consentimento para a solicitação de permissão.Receba o resultado do código SMS da intent e retorne-o como uma resposta de preenchimento automático.
Cenários avançados de preenchimento automático
- Integrar com teclado
- No Android 11 e versões mais recentes, a plataforma permite que teclados e outros editores de método de entrada (IMEs, na sigla em inglês) mostrem sugestões automáticas de preenchimento automático em vez de usar um menu suspenso. Para mais informações sobre como o serviço de preenchimento automático pode oferecer suporte a essa funcionalidade, consulte Como integrar o preenchimento automático com teclados.
- Paginar conjuntos de dados
- Uma resposta grande de preenchimento automático pode exceder o tamanho de transação permitido pelo objeto
Binder
que representa o objeto remoto necessário para processar a solicitação. Para impedir que o sistema Android gere uma exceção nesses cenários, mantenha umaFillResponse
pequena, adicionando no máximo 20 objetosDataset
por vez. Caso necessário, você pode adicionar à resposta um conjunto de dados que informe aos usuários que há mais informações e extrair o próximo grupo de conjuntos de dados quando selecionado. Para saber mais, consulteaddDataset(Dataset)
. - Salvar dados divididos em várias telas
Em geral, os apps dividem os dados do usuário em várias telas na mesma atividade, especialmente naquelas usadas para criar uma nova conta de usuário. Por exemplo, a primeira tela solicita um nome de usuário e, se ele for fornecido, uma segunda tela solicita uma senha. Nessas situações, o serviço de preenchimento automático precisa esperar até que o usuário insira os dois campos antes que a interface de salvamento do preenchimento automático seja mostrada. Siga estas etapas para lidar com esses cenários:
- Na primeira solicitação de preenchimento, adicione um pacote de estados do cliente à resposta que contém os IDs de preenchimento automático dos campos parciais presentes na tela.
- Na segunda solicitação de preenchimento,
extraia o pacote de estado do cliente, acesse os IDs de preenchimento automático definidos
na solicitação anterior e adicione esses IDs e a flag
FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE
ao objetoSaveInfo
usado na segunda resposta. - Na solicitação de salvamento,
use os objetos
FillContext
corretos para armazenar o valor de cada campo. Há um contexto para cada solicitação de preenchimento.
Para saber mais, consulte Salvar dados divididos em várias telas.
- Inicializar e descartar a lógica de cada solicitação
Sempre que há uma solicitação de preenchimento automático, o sistema Android se vincula ao serviço e chama o método
onConnected()
correspondente. Depois que o serviço processa a solicitação, o sistema Android chama o métodoonDisconnected()
e se desvincula do serviço. Você pode implementaronConnected()
para fornecer o código executado antes de processar uma solicitação eonDisconnected()
para fornecer o código que é executado após o processamento de uma solicitação.- Personalizar a interface de salvamento do preenchimento automático
Os serviços de preenchimento automático podem personalizar a interface de salvamento do preenchimento automático para ajudar os usuários a decidir se querem permitir que o serviço salve os dados. Os serviços podem fornecer mais informações sobre o que seria salvo com um texto simples ou com uma visualização personalizada. Além disso, eles também podem mudar a aparência do botão que cancela a solicitação de salvamento e mostrar uma notificação quando o usuário tocar nele. Para saber mais, consulte a documentação de referência do
SaveInfo
.- Modo de compatibilidade
O modo de compatibilidade permite que os serviços de preenchimento automático usem a estrutura virtual de acessibilidade para fins de preenchimento automático. Esse modo é particularmente útil para oferecer a funcionalidade de preenchimento automático em navegadores que não implementam de forma explícita as APIs de preenchimento automático.
Para testar o serviço de preenchimento automático usando o modo de compatibilidade, inclua de forma explícita o navegador ou app que precisa desse modo à lista de permissões. Você pode verificar quais pacotes já estão na lista de permissões executando o este comando:
$ adb shell settings get global autofill_compat_mode_allowed_packages
Se o pacote que você está testando não estiver listado, adicione-o executando o comando abaixo, em que
pkgX
é o pacote do app:$ adb shell settings put global autofill_compat_mode_allowed_packages pkg1[resId1]:pkg2[resId1,resId2]
Se o aplicativo for um navegador, use
resIdx
para especificar o código do recurso do campo de entrada que contém o URL da página renderizada.
O modo de compatibilidade tem estas limitações:
- Uma solicitação de salvamento é acionada quando o serviço usa a
flag
FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE
ou quando o métodosetTrigger()
é chamado.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE
é definido por padrão ao usar o modo de compatibilidade. - O valor de texto dos nós pode não estar disponível no
método
onSaveRequest(SaveRequest, SaveCallback)
.
Para mais informações sobre o modo de compatibilidade, incluindo as limitações
associadas, consulte a
classe de referência
AutofillService
.