Criar serviços de preenchimento automático

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 AutofillFramework em Java ou em 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 de AutofillService no app responsável pela implementação do serviço.
  • O atributo android:permission, que declara a permissão BIND_AUTOFILL_SERVICE.
  • O elemento <intent-filter>, que tem o filho obrigatório <action> especificando a ação android.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:

interface de preenchimento automático

Figura 1. Interface de preenchimento automático exibindo um conjunto de dados.

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.

Interface de salvamento do preenchimento automático

Figura 2. Interface de salvamento do preenchimento automático

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, consulte SaveInfo.
  • 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 e password 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:

  1. No serviço de preenchimento automático, use o hasOngoingSmsRequest de SmsCodeAutofillClient 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 for false.
  2. No serviço de preenchimento automático, use checkPermissionState em SmsCodeAutofillClient 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 ser NONE, GRANTED ou DENIED. O serviço de preenchimento automático precisa mostrar uma solicitação de sugestão para os estados NONE e GRANTED.
  3. Na atividade de autenticação de preenchimento automático, use a permissão SmsRetriever.SEND_PERMISSION para registrar um BroadcastReceiver que ouve SmsCodeRetriever.SMS_CODE_RETRIEVED_ACTION para receber o resultado do código SMS quando ele estiver disponível.
  4. Chame startSmsCodeRetriever em SmsCodeAutofillClient 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 por startSmsCodeRetriever pode falhar com uma ResolvableApiException retornado. Se isso acontecer, chame o método ResolvableApiException.startResolutionForResult() para mostrar uma caixa de diálogo de consentimento para a solicitação de permissão.

  5. 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 uma FillResponse pequena, adicionando no máximo 20 objetos Dataset 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, consulte addDataset(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:

  1. 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.
  2. 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 objeto SaveInfo usado na segunda resposta.
  3. 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étodo onDisconnected() e se desvincula do serviço. Você pode implementar onConnected() para fornecer o código executado antes de processar uma solicitação e onDisconnected() 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étodo setTrigger() é 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.