Cómo compilar servicios de Autocompletar

Un servicio de Autocompletar es una app con la que los usuarios pueden inyectar datos en las vistas de otras apps para rellenar formularios con mayor facilidad. Los servicios de Autocompletar también pueden recuperar datos de usuario de las vistas de una app y almacenarlos para usarlos en otro momento. En general, los servicios de Autocompletar se proporcionan mediante las apps que administran datos de usuario, como los administradores de contraseñas.

Android facilita el relleno de formularios con el marco de trabajo de Autocompletar disponible en Android 8.0 (API nivel 26) y versiones posteriores. Los usuarios pueden aprovechar las funciones de Autocompletar solo si existe una app que brinde servicios de Autocompletar en su dispositivo.

En esta página, se muestra la forma de implementar un servicio de Autocompletar en la app. Si buscas un ejemplo de código donde se muestre la forma de implementar un servicio, consulta el Ejemplo de marco de trabajo de Autocompletar de Android. Para obtener más detalles sobre el funcionamiento de los servicios de Autocompletar, consulta la documentación de referencia de las clases AutofillService y AutofillManager.

Permisos y declaraciones de manifiesto

Las apps que brindan servicios de Autocompletar deben incluir una declaración donde se describa la implementación del servicio. Para especificar una declaración, incluye un elemento <service> en el manifiesto de la app. El elemento <service> debe incluir los siguientes atributos y elementos:

En el ejemplo siguiente, se muestra una declaración del servicio de Autocompletar:

<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>

El elemento <meta-data> incluye un atributo android:resource que apunta a un recurso XML con más detalles sobre el servicio. El recurso service_configuration del ejemplo anterior especifica una actividad que permite que los usuarios configuren el servicio. En el ejemplo siguiente, se muestra el recurso XML service_configuration:

<autofill-service
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:settingsActivity="com.example.android.SettingsActivity" />

Para obtener más información sobre los recursos XML, consulta Provisión de recursos.

Solicitud para habilitar el servicio

Una app se utiliza como el servicio de Autocompletar después de que declara el permiso BIND_AUTOFILL_SERVICE y el usuario lo habilita en la configuración del dispositivo. Para verificar si es el servicio habilitado actualmente, la app puede llamar al método hasEnabledAutofillServices() de la clase AutofillManager.

Si la app no es el servicio de Autocompletar actual, puede solicitar al usuario que modifique la configuración de Autocompletar mediante el intent ACTION_REQUEST_SET_AUTOFILL_SERVICE. El intent devuelve un valor RESULT_OK si el usuario seleccionó un servicio de Autocompletar que coincide con el paquete del emisor.

Cómo rellenar vistas de cliente

El servicio de Autocompletar recibe solicitudes para rellenar vistas de cliente cuando el usuario interactúa con otras apps. Si el servicio de Autocompletar tiene datos de usuario que satisfacen la solicitud, envía los datos en la respuesta. El sistema Android muestra una interfaz de usuario de Autocompletar con los datos disponibles, como se muestra en la figura 1.

Interfaz de usuario de Autocompletar

Figura 1. Interfaz de usuario de Autocompletar con un conjunto de datos

El marco de trabajo de Autocompletar define un flujo de trabajo para rellenar vistas diseñado para minimizar el tiempo que el sistema Android queda vinculado al servicio de Autocompletar. En cada solicitud, el sistema Android envía un objeto AssistStructure al servicio mediante una llamada al método onFillRequest(). El servicio de Autocompletar comprueba si puede satisfacer la solicitud con los datos de usuario que almacenó previamente. Si puede satisfacer la solicitud, el servicio empaqueta los datos en objetos Dataset. El servicio llama al método onSuccess() mediante la transferencia de un objeto FillResponse, que contiene los objetos Dataset. Si el servicio no tiene datos para satisfacer la solicitud, transfiere null al método onSuccess(). Si se produce un error al procesar la solicitud, el servicio llama al método onFailure() en su lugar. Para obtener una explicación detallada del flujo de trabajo, consulta Uso básico.

El siguiente código muestra un ejemplo del 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;
}

Un servicio puede tener más de un conjunto de datos que satisfaga la solicitud. En este caso, el sistema Android muestra varias opciones (una para cada conjunto de datos) en la interfaz de usuario de Autocompletar. El siguiente ejemplo de código muestra la forma de proporcionar varios conjuntos de datos en una respuesta:

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();

Los servicios de Autocompletar pueden navegar por los objetos ViewNode en AssistStructure a fin de recuperar los datos de Autocompletar requeridos para satisfacer la solicitud. Un servicio puede recuperar datos de Autocompletar mediante los métodos de la clase ViewNode, como getAutofillId(). Un servicio debe poder describir el contenido de una vista para comprobar si puede satisfacer la solicitud. El uso del atributo autofillHints es el primer criterio que un servicio debe utilizar para describir el contenido de una vista. Sin embargo, las apps de cliente deben proporcionar de forma explícita el atributo en sus vistas de modo que esté disponible para el servicio. Si una app de cliente no proporciona el atributo autofillHints, un servicio debe utilizar su propia heurística para describir el contenido. El servicio puede utilizar métodos de otras clases para obtener información sobre el contenido de la vista, como, por ejemplo, getText() o getHint(). Para obtener más información, consulta Proporcionar sugerencias a Autocompletar. El siguiente ejemplo muestra la forma de recorrer AssistStructure y recuperar datos de Autocompletar de un 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);
    }
}

Cómo guardar datos de usuario

Un servicio de Autocompletar necesita datos de usuario para rellenar las vistas de las apps. Cuando los usuarios rellenan manualmente una vista, se les solicita guardar los datos en el servicio de Autocompletar actual, como se muestra en la figura 2.

Interfaz de usuario de guardado de Autocompletar

Figura 2. Interfaz de usuario de guardado de Autocompletar

Para guardar los datos, el servicio debe indicar su interés por almacenar los datos para usarlos en el futuro. Antes de que el sistema Android envíe una solicitud para guardar los datos, se muestra una solicitud de relleno en la que el servicio tiene la oportunidad de rellenar las vistas. Para indicar que le interesa guardar los datos, el servicio incluye un objeto SaveInfo en la respuesta de la solicitud de relleno anterior. El objeto SaveInfo contiene como mínimo los siguientes datos:

  • El tipo de datos de usuario que se guardarán. Para obtener una lista de los valores SAVE_DATA disponibles, consulta SaveInfo.
  • El conjunto mínimo de vistas que se deben modificar para activar una solicitud de guardado. Por ejemplo, un formulario de inicio de sesión generalmente requiere que el usuario actualice las vistas username y password para activar una solicitud de guardado.

Se asocia un objeto SaveInfo con un objeto FillResponse, como se muestra en el siguiente ejemplo 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();
    ...
}

El servicio de Autocompletar puede implementar una lógica para conservar los datos de usuario en el método onSaveRequest(), al que normalmente se realiza una llamada cuando se completa la actividad de cliente o una app de cliente llama a commit(). El siguiente código muestra un ejemplo del 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();
}

Los servicios de Autocompletar deben encriptar los datos confidenciales antes de conservarlos. Sin embargo, los datos de usuario incluyen etiquetas o datos que no son confidenciales. Por ejemplo, una cuenta de usuario puede incluir una etiqueta que marque los datos como una cuenta laboral o personal. Los servicios no deben encriptar las etiquetas, ya que pueden usarlas en vistas de presentación si el usuario no se ha autenticado y sustituirlas por datos reales después de que el usuario se autentique.

Cómo posponer la interfaz de usuario de guardado de Autocompletar

A partir de Android 10, si utilizas varias pantallas para implementar un flujo de trabajo de Autocompletar (por ejemplo, una pantalla para el campo de nombre de usuario y otra para el de contraseña), puedes posponer la interfaz de usuario de guardado de Autocompletar mediante el indicador SaveInfo.FLAG_DELAY_SAVE.

Si se establece este indicador, no se activa la interfaz de usuario de guardado de Autocompletar cuando se confirma el contexto de Autocompletar asociado con la respuesta SaveInfo. En lugar de eso, se puede usar una actividad independiente dentro de la misma tarea para entregar las solicitudes de relleno futuras y, posteriormente, mostrar la interfaz de usuario a través de una solicitud de guardado. Para obtener más información, consulta SaveInfo.FLAG_DELAY_SAVE.

Cómo requerir la autenticación de usuario

Los servicios de Autocompletar pueden requerir que el usuario se autentique para poder rellenar vistas a fin de brindar un nivel adicional de seguridad. Los siguientes casos son buenos candidatos para implementar la autenticación de usuario:

  • Es necesario desbloquear los datos de usuario en la app mediante una contraseña maestra o un escaneo de huella digital.
  • Es necesario desbloquear un conjunto de datos específico, por ejemplo, los detalles de una tarjeta de crédito mediante un código de verificación de tarjeta (CVC).

En un caso donde el servicio requiere la autenticación del usuario para desbloquear los datos, el servicio puede presentar datos predeterminados o una etiqueta y especificar el Intent que se ocupa de la autenticación. Si necesitas datos adicionales para procesar la solicitud una vez completado el flujo de autenticación, puedes agregar esos datos al intent. A continuación, la actividad de autenticación puede devolver los datos a la clase AutofillService de la app. El siguiente ejemplo de código muestra la forma de especificar que la solicitud requiere autenticación:

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();

Una vez que la actividad completa el flujo de autenticación, esta debe llamar al método setResult() mediante la transferencia de un valor RESULT_OK y establecer el elemento extra EXTRA_AUTHENTICATION_RESULT en el objeto FillResponse que incluye el conjunto de datos propagado. El siguiente código muestra un ejemplo de la forma de mostrar el resultado cuando se completa el flujo de autenticación:

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);

En el caso donde se debe desbloquear un conjunto de datos de tarjeta de crédito, el servicio puede mostrar la interfaz de usuario para solicitar el CVC. Es posible presentar datos predeterminados, como el nombre del banco y los últimos cuatro dígitos del número de tarjeta de crédito, para ocultar los datos hasta que se desbloquee el conjunto de datos. El siguiente ejemplo muestra la forma de requerir la autenticación para un conjunto de datos y ocultar los datos hasta que el usuario proporcione el 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();

Una vez que la actividad valida el CVC, esta debe llamar al método setResult() mediante la transferencia de un valor RESULT_OK y establecer el elemento extra EXTRA_AUTHENTICATION_RESULT en un objeto Dataset que contiene el número y la fecha de vencimiento de la tarjeta de crédito. El nuevo conjunto de datos reemplaza el conjunto de datos que requiere autenticación y las vistas se rellenan de inmediato. El siguiente código muestra un ejemplo de la forma de mostrar el conjunto de datos cuando el usuario proporciona el 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);

Cómo organizar los datos en grupos lógicos

Los servicios de Autocompletar deben organizar los datos en grupos lógicos para aislar los conceptos de dominios diferentes. Estos grupos lógicos se denominan particiones en esta página. En la siguiente lista, se muestran ejemplos típicos de particiones y campos:

  • Credenciales, incluidos los campos de nombre de usuario y contraseña.
  • Dirección, incluidos los campos de calle, ciudad, estado y código postal.
  • Información de pago, incluidos los campos de número de tarjeta de crédito, fecha de vencimiento y código de verificación.

Un servicio de Autocompletar que divide correctamente los datos en particiones puede evitar la exposición de los datos de más de una partición en un conjunto de datos y así, proteger mejor los datos de sus usuarios. Por ejemplo, un conjunto de datos que contiene credenciales no necesariamente debe incluir información de pago. Al organizar los datos en particiones, el servicio puede exponer la cantidad mínima de información necesaria para satisfacer una solicitud.

La organización de datos en particiones permite que los servicios llenen actividades que contienen vistas de varias particiones y, al mismo tiempo, envíen una cantidad mínima de datos a la app cliente. Por ejemplo, analiza una actividad que contiene vistas para nombre de usuario, contraseña, calle y ciudad, y un servicio de Autocompletar que tiene los siguientes datos:

Partición Campo 1 Campo 2
Credenciales laboral_nombredeusuario laboral_contraseña
personal_nombredeusuario personal_contraseña
Dirección laboral_calle laboral_ciudad
personal_calle personal_ciudad

El servicio puede preparar un conjunto de datos que incluya la partición de credenciales para las cuentas laboral y personal. Cuando el usuario elige una, una respuesta de Autocompletar subsiguiente puede proporcionar la dirección laboral o personal, según la primera elección del usuario.

Para identificar el campo que originó la solicitud, el servicio puede llamar al método isFocused() mientras recorre el objeto AssistStructure. Esto permite que los servicios preparen un elemento FillResponse con los datos de la partición adecuada.

Casos de Autocompletar avanzados

Cómo paginar conjuntos de datos
Una respuesta de Autocompletar larga puede exceder el tamaño de transacción permitido del objeto Binder que representa el objeto de uso remoto requerido para procesar la solicitud. Para evitar que el sistema Android genere una excepción en estos casos, puedes mantener el elemento FillResponse pequeño si agregas no más de 20 objetos Dataset a la vez. Si la respuesta requiere más conjuntos de datos, puedes agregar un conjunto de datos que haga saber a los usuarios que existe más información y, cuando se seleccione, recupere el siguiente grupo de conjunto de datos. Para obtener más información, consulta addDataset(Dataset).
Cómo guardar divisiones de datos en varias pantallas

A menudo, las apps dividen los datos de usuario entre varias pantallas de la misma actividad, especialmente en las actividades que se utilizan para crear una nueva cuenta de usuario. Por ejemplo, la primera pantalla solicita un nombre de usuario y, si el nombre de usuario está disponible, avanza a la segunda pantalla, que solicita una contraseña. En estas situaciones, el servicio de Autocompletar debe esperar a que el usuario ingrese ambos campos para poder mostrar la interfaz de usuario de guardado de Autocompletar. Un servicio puede seguir estos pasos para manejar esas situaciones:

  1. En la primera solicitud de relleno, el servicio agrega un conjunto de estado de cliente a la respuesta, donde se incluyen los ID de Autocompletar de los campos parciales presentes en la pantalla.
  2. En la segunda solicitud de relleno, el servicio recupera el conjunto de estado de cliente, obtiene los ID de Autocompletar establecidos en la solicitud anterior del estado de cliente y agrega estos ID y el indicador FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE al objeto SaveInfo utilizado en la segunda respuesta.
  3. En la solicitud de guardado, el servicio utiliza los objetos FillContext apropiados para obtener el valor de cada campo. Existe un contexto de relleno por cada solicitud de relleno.

Para obtener más información, consulta Cómo guardar cuando los datos se dividen en varias pantallas.

Cómo proporcionar una lógica de inicialización y desmontaje para cada solicitud

Cada vez que se realiza una solicitud de Autocompletar, el sistema Android se enlaza con el servicio y llama al método onConnected(). Una vez que el servicio procesa la solicitud, el sistema Android llama al método onDisconnected() y se desenlaza del servicio. Puedes implementar onConnected() para proporcionar un código que se ejecute antes de procesar una solicitud y onDisconnected() para proporcionar código que se ejecute después de procesar una solicitud.

Personalización de la interfaz de usuario de guardado de Autocompletar

Los servicios de Autocompletar pueden personalizar la interfaz de usuario de guardado de Autocompletar para que los usuarios puedan decidir si desean permitir que el servicio guarde sus datos. Los servicios pueden proporcionar información adicional sobre lo que se guardaría a través de un texto simple o una vista personalizada. Además, los servicios pueden modificar el aspecto del botón que cancela la solicitud de guardado y recibir una notificación cuando el usuario presiona ese botón. Para obtener más información, consulta la documentación de referencia de SaveInfo.

Modo de compatibilidad

El modo de compatibilidad permite que los servicios de Autocompletar usen la estructura virtual de accesibilidad con fines de autorrelleno. Es especialmente útil para brindar la función de Autocompletar en navegadores que no implementaron de forma explícita las API de Autocompletar.

Para probar el servicio de Autocompletar mediante el modo de compatibilidad, debes incluir explícitamente en la lista blanca el navegador o la app que requieren el modo de compatibilidad. Para revisar los paquetes ya incluidos en la lista blanca, ejecuta el siguiente comando:

$ adb shell settings get global autofill_compat_mode_allowed_packages

Si el paquete que deseas probar no está en la lista blanca, puedes agregarlo mediante este comando:

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

... donde pkgX es el paquete de la app. Si la app es un navegador, utiliza resIdx para especificar el ID de recurso del campo de entrada que contiene la URL de la página representada.

El modo de compatibilidad tiene las siguientes limitaciones:

Para obtener más información sobre el modo de compatibilidad, incluidas las limitaciones asociadas, consulta la referencia de la clase AutofillService.