Como criar serviços de preenchimento automático

Um serviço de preenchimento automático é um aplicativo que facilita o preenchimento de formulários pelos usuários, inserindo dados nas visualizações de outros aplicativos. Os serviços de preenchimento automático também podem recuperar dados do usuário das visualizações em um aplicativo e armazená-los para uso posterior. Em geral, os serviços de preenchimento automático são fornecidos por aplicativos que gerenciam dados do usuário, como gerenciadores de senhas.

O Android facilita o preenchimento de formulários com a biblioteca de preenchimento automático disponível no Android 8.0 (API de nível 26) e versões superiores. Os usuários só poderão aproveitar os recursos de preenchimento automático se houver um aplicativo 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 aplicativo. Se você estiver procurando um exemplo de código que mostre como implementar um serviço, consulte a amostra de AutofillFramework do Android. Para saber mais detalhes sobre como os serviços de preenchimento automático funcionam, consulte a documentação de referência das classes AutofillService e AutofillManager.

Declarações e permissões do manifesto

Os aplicativos 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 aplicativo. O elemento <service> deve incluir os seguintes atributos e elementos:

O exemplo a seguir 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 a seguir 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 informações sobre recursos XML, consulte Como fornecer recursos.

Enviar avisos para ativar o serviço

Um aplicativo é usado como serviço de preenchimento automático após declarar a permissão BIND_AUTOFILL_SERVICE e o usuário o ativar o recurso nas configurações do dispositivo. Um aplicativo pode verificar se está definido como o serviço atualmente ativado chamando o método hasEnabledAutofillServices() da classe AutofillManager.

Se o aplicativo não estiver definido como o serviço de preenchimento automático atual, ele poderá solicitar que o usuário altere as configurações de preenchimento automático usando o intent ACTION_REQUEST_SET_AUTOFILL_SERVICE. O intent retornará um valor RESULT_OK se o usuário tiver selecionado um serviço de preenchimento automático que corresponda ao pacote do autor da chamada.

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 aplicativos. Se o serviço de preenchimento automático tiver dados do usuário que atendem à solicitação, ele enviará as informações na resposta. O sistema Android mostra uma IU de preenchimento automático com os dados disponíveis, conforme exibido na imagem 1:

IU de preenchimento automático

Imagem 1. IU de preenchimento automático exibindo um conjunto de dados.

A biblioteca 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 pode atender à solicitação com dados do usuário que foram armazenados anteriormente. Se puder atender à solicitação, o serviço empacotará os dados em objetos Dataset. O serviço chama o método onSuccess() passando um objeto FillResponse, que contém objetos Dataset. Se o serviço não tiver dados para atender à solicitação, ele enviará null ao método onSuccess(). Caso haja um erro ao processar a solicitação, o serviço chamará o método onFailure(). Para ver uma explicação detalhada do fluxo de trabalho, consulte o artigo Uso básico.

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 a solicitação. Nesse caso, o sistema Android mostrará várias opções, uma para cada conjunto de dados, na IU 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 em AssistStructure para recuperar os dados necessários e atender à solicitação. Um serviço pode recuperar dados de preenchimento automático usando métodos da classe ViewNode, como getAutofillId(). Um serviço deve ser capaz de descrever o conteúdo de uma visualização para verificar se pode atender à solicitação. O atributo autofillHints deve ser a primeira abordagem de um serviço para descrever o conteúdo de uma visualização. No entanto, os aplicativos clientes precisam fornecer explicitamente o atributo nas próprias visualizações antes de ficarem disponíveis para o serviço. Se um aplicativo cliente não fornecer o atributo autofillHints, um serviço deverá usar as próprias heurísticas para descrever o conteúdo. O serviço pode usar métodos de outras classes para ter informações sobre o conteúdo da visualização, como getText() ou getHint(). Para mais informações, veja Como fornecer dicas sobre o preenchimento automático. O exemplo a seguir mostra como percorrer AssistStructure e recuperar 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 aplicativos. 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 imagem 2.

IU de salvamento do preenchimento automático

Imagem 2. IU 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 incluirá um objeto SaveInfo na resposta da solicitação de preenchimento precedente. O objeto SaveInfo contém pelo menos os seguintes dados:

  • O tipo de dados do usuário que seriam salvos. Para ver 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, como mostrado no seguinte 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 persistir os dados do usuário no método onSaveRequest(), que geralmente é chamado após o término da atividade do cliente ou quando o aplicativo cliente chama commit(). O código a seguir 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 devem criptografar dados confidenciais antes de persistir. No entanto, os dados do usuário incluem rótulos ou dados que não são confidenciais. Por exemplo, uma conta de usuário pode incluir um rótulo que marque os dados como uma conta profissional ou pessoal. Os serviços não devem criptografar rótulos. Isso permite usar os rótulos nas visualizações de apresentação, caso o usuário não tenha realizado o processo de autenticação, e substituir os rótulos pelos dados reais após a autenticação.

Adiar a IU de salvamento do preenchimento automático

A partir do Android 10, se você usar 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), poderá adiar a IU de salvamento do preenchimento automático usando a sinalização SaveInfo.FLAG_DELAY_SAVE.

Se essa sinalização for definida, a IU de salvamento do preenchimento automático não 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 entregar solicitações de preenchimento futuras e, em seguida, mostrar a IU por meio de uma solicitação de salvamento. Para saber mais, 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 adequados para a implementação da autenticação do usuário:

  • Os dados do usuário no aplicativo precisam ser desbloqueados usando uma senha mestra ou uma verificação por impressão digital.
  • Um conjunto de dados específico precisa ser desbloqueado, por exemplo, detalhes do cartão de crédito usando um código de verificação de cartão (CVC).

Caso o serviço exija a autenticação do usuário antes de desbloquear os dados, ele poderá apresentar dados padrão ou um rótulo e especificar o Intent responsável pela autenticação. Se você precisar de dados adicionais para processar a solicitação após a conclusão do fluxo de autenticação, será possível adicionar essas informações ao intent. Sua atividade de autenticação pode retornar os dados para a classe AutofillService no seu aplicativo. O exemplo de código a seguir 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 concluir o fluxo de autenticação, ela deverá chamar o método setResult() passando um valor RESULT_OK e definir o EXTRA_AUTHENTICATION_RESULT adicional para o objeto FillResponse que inclui o conjunto de dados preenchido. O código a seguir 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 poderá exibir a IU solicitando o CVC. Você pode ocultar os dados até que o conjunto de dados seja desbloqueado, apresentando dados 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.
// For example '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.
// For example '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 validar o CVC, ela deverá chamar o método setResult() passando um valor RESULT_OK e definir o EXTRA_AUTHENTICATION_RESULT adicional 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 substituirá o conjunto de dados que requer autenticação e as visualizações serão preenchidas imediatamente. O código a seguir 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 that we can 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 that we can 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 devem organizar os dados em grupos lógicos que isolam conceitos de diferentes domínios. Esses grupos lógicos são referidos como partições nesta página. A lista a seguir 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 necessariamente deve incluir informações de pagamento. Organizar seus dados em partições permite que seu serviço exponha a quantidade mínima de informações 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 para o aplicativo cliente. Por exemplo, considere uma atividade que inclua visualizações de nome de usuário, senha, rua e cidade e um serviço de preenchimento automático com os seguintes 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 as contas profissional e pessoal. Quando o usuário escolher uma dessas alternativas, uma resposta subsequente ao preenchimento automático poderá fornecer o endereço comercial ou pessoal, dependendo da primeira opção 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 os serviços preparem uma FillResponse com os dados de partição adequados.

Cenários avançados de preenchimento automático

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 acione uma exceção nesses cenários, mantenha uma FillResponse pequena adicionando no máximo 20 objetos Dataset por vez. Caso seja necessário, você poderá adicionar à resposta um conjunto de dados que informe aos usuários que há mais informações e recuperar o próximo grupo de conjuntos de dados quando selecionado. Para saber mais, consulte addDataset(Dataset).
Como salvar dados divididos em várias telas

Em geral, os aplicativos dividem os dados do usuário em várias telas na mesma atividade, especialmente nas usadas para criar uma nova conta de usuário. Por exemplo, a primeira tela solicita um nome de usuário e, se essa informação estiver disponível, o processo seguirá para uma segunda tela, que 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 IU de salvamento do preenchimento automático seja exibida. Um serviço pode seguir estas etapas para gerenciar esses cenários:

  1. Na primeira solicitação de preenchimento, o serviço adiciona um pacote de estados do cliente à resposta, contendo os códigos de preenchimento automático dos campos parciais presentes na tela.
  2. Na segunda solicitação de preenchimento, o serviço recupera o pacote de estados do cliente, recebe os códigos de preenchimento automático definidos na solicitação anterior e adiciona esses códigos e a sinalização FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE ao objeto SaveInfo usado na segunda resposta.
  3. Na solicitação de salvamento, o serviço usa os objetos apropriados FillContext para acessar o valor de cada campo. Há um contexto por 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 houver uma solicitação de preenchimento automático, o sistema Android se vinculará ao serviço e chamará o método onConnected() correspondente. Depois que o serviço processar a solicitação, o sistema Android chamará o método onDisconnected() e se desvinculará 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 IU de salvamento do preenchimento automático

Os serviços de preenchimento automático podem personalizar a IU de salvamento para ajudar os usuários a decidir se querem permitir que o serviço salve os dados fornecidos por eles. Os serviços podem fornecer informações adicionais sobre o que seria salvo por meio de um texto simples ou por uma visualização personalizada. Além disso, eles também podem alterar a aparência do botão que cancela a solicitação de salvamento e exibir uma notificação quando o usuário tocar nesse comando. Para saber mais, consulte a documentação de referência SaveInfo.

Modo de compatibilidade

O modo de compatibilidade permite que os serviços de preenchimento automático usem a biblioteca virtual de acessibilidade para fins de preenchimento automático. Esse recurso serve especialmente para fornecer funcionalidade de preenchimento automático a navegadores que ainda não implementaram explicitamente as APIs do serviço.

Para testar seu serviço de preenchimento automático usando o modo de compatibilidade, inclua explicitamente o navegador ou aplicativo que requer o modo de compatibilidade à lista de permissões. Você pode verificar quais pacotes já estão na lista de permissões executando o seguinte comando:

$ adb shell settings get global autofill_compat_mode_allowed_packages

Se o pacote que você está testando não estiver listado, será possível incluí-lo na lista de permissões executando este comando:

$ adb shell settings put global autofill_compat_mode_allowed_packages pkg1[resId1]:pkg2[resId1,resId2]

... em que pkgX é o pacote do aplicativo. 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 as seguintes limitações:

Para mais informações sobre o modo de compatibilidade, incluindo as limitações associadas, consulte a classe de referência AutofillService.