Créer des services de saisie automatique

Stay organized with collections Save and categorize content based on your preferences.

Un service de saisie automatique est une application qui permet aux utilisateurs de remplir des formulaires plus facilement en injectant des données dans les affichages d'autres applications. Les services de saisie automatique peuvent également récupérer des données utilisateur à partir des affichages d'une application et les stocker pour une utilisation ultérieure. Ces services sont généralement fournis par des applications qui gèrent les données utilisateur telles que les gestionnaires de mots de passe.

Android facilite le remplissage des formulaires grâce au framework de saisie automatique disponible dans Android 8.0 (niveau d'API 26) ou version ultérieure. Les utilisateurs ne peuvent bénéficier des fonctionnalités de saisie automatique que s'ils disposent d'une application qui permet la saisie automatique sur leur appareil.

Cette page explique comment implémenter un service de saisie automatique dans votre application. Si vous recherchez un exemple de code qui montre comment implémenter un service, consultez l'exemple AutofillFramework Java | Kotlin. Pour en savoir plus sur le fonctionnement des services de saisie automatique, consultez la documentation de référence des classes AutofillService et AutofillManager.

Déclarations et autorisations du fichier manifeste

Les applications qui fournissent des services de saisie automatique doivent inclure une déclaration décrivant la mise en œuvre du service. Pour spécifier la déclaration, incluez un élément <service> dans le fichier manifeste de l'application. L'élément <service> doit inclure les attributs et éléments suivants :

Voici un exemple de déclaration de service de saisie automatique :

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

L'élément <meta-data> inclut un attribut android:resource qui pointe vers une ressource XML avec plus de détails sur le service. Dans l'exemple précédent, la ressource service_configuration spécifie une activité permettant aux utilisateurs de configurer le service. L'exemple suivant montre la ressource XML service_configuration :

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

Pour en savoir plus sur les ressources XML, consultez la page Fournir des ressources.

Invite pour activer le service

Une application est utilisée comme service de saisie automatique après avoir déclaré l'autorisation BIND_AUTOFILL_SERVICE et après avoir été activée par l'utilisateur dans les paramètres de l'appareil. Une application peut vérifier si elle est bien sélectionnée comme service actif en appelant la méthode hasEnabledAutofillServices() de la classe AutofillManager.

Si l'application n'est pas le service de saisie automatique actuel, elle peut demander à l'utilisateur de modifier les paramètres de saisie automatique à l'aide de l'intent ACTION_REQUEST_SET_AUTOFILL_SERVICE. L'intent renvoie une valeur RESULT_OK si l'utilisateur a sélectionné un service de saisie automatique correspondant au package de l'appelant.

Remplir les affichages du client

Le service de saisie automatique reçoit des requêtes pour remplir les affichages du client lorsque l'utilisateur interagit avec d'autres applications. Si le service de saisie automatique dispose de données utilisateur qui répondent à la requête, il envoie les données dans la réponse. Le système Android affiche une UI (interface utilisateur) de saisie automatique avec les données disponibles, comme illustré dans la figure 1 :

UI de saisie automatique

Figure 1. UI de saisie automatique affichant un ensemble de données.

Le framework de saisie automatique définit un workflow pour remplir les affichages, afin de réduire la durée pendant laquelle le système Android est lié au service de saisie automatique. Dans chaque requête, le système Android envoie un objet AssistStructure au service en appelant la méthode onFillRequest(). Le service de saisie automatique vérifie s'il peut satisfaire la requête avec les données utilisateur qu'il a précédemment stockées. S'il peut satisfaire la requête, le service empaquette les données dans des objets Dataset. Le service appelle la méthode onSuccess() en transmettant un objet FillResponse, qui contient les objets Dataset. Si le service ne dispose pas de données pour satisfaire la requête, il transmet null à la méthode onSuccess(). En cas d'erreur lors du traitement de la requête, le service appelle la méthode onFailure(). Pour obtenir des explications détaillées sur le workflow, consultez la page Utilisation de base.

Le code suivant montre un exemple de la méthode 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 service peut disposer de plusieurs ensembles de données répondant à la requête. Dans ce cas, le système Android affiche plusieurs options (une pour chaque ensemble de données) dans l'UI de saisie automatique. L'exemple de code suivant montre comment fournir plusieurs ensembles de données dans une réponse :

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

Les services de saisie automatique peuvent parcourir les objets ViewNode de la AssistStructure pour récupérer les données de saisie automatique requises pour traiter la requête. Un service peut récupérer des données de saisie automatique à l'aide de méthodes de la classe ViewNode telles que getAutofillId(). Un service doit pouvoir décrire le contenu d'un affichage pour vérifier s'il peut répondre à la requête. L'utilisation de l'attribut autofillHints est la première approche qu'un service doit utiliser pour décrire le contenu d'un affichage. Toutefois, les applications clientes doivent fournir explicitement l'attribut dans leurs affichages avant qu'il soit disponible pour le service. Si une application cliente ne fournit pas l'attribut autofillHints, le service doit utiliser ses propres modèles heuristiques pour décrire le contenu. Le service peut utiliser des méthodes d'autres classes pour obtenir des informations sur le contenu de l'affichage telles que getText() ou getHint(). Pour en savoir plus, consultez la section Fournir des conseils pour la saisie automatique. L'exemple suivant montre comment balayer AssistStructure et récupérer les données de saisie automatique à partir d'un objet 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);
    }
}

Enregistrer les données utilisateur

Un service de saisie automatique a besoin de données utilisateur pour remplir les affichages dans les applications. Lorsque les utilisateurs remplissent manuellement un affichage, ils sont invités à enregistrer les données dans le service de saisie automatique actuel, comme illustré à la figure 2.

Sauvegarde de saisie automatique dans l'UI

Figure 2. Sauvegarde de saisie automatique dans l'UI

Pour enregistrer les données, le service doit indiquer qu'il souhaite les stocker pour une utilisation ultérieure. Avant que le système Android n'envoie une requête pour enregistrer les données, le service a la possibilité de remplir les affichages. Pour indiquer qu'il souhaite enregistrer les données, le service inclut un objet SaveInfo en réponse à la requête de saisie précédente. L'objet SaveInfo contient au moins les données suivantes :

  • Type de données utilisateur qui seraient enregistrées. Pour obtenir la liste des valeurs SAVE_DATA disponibles, consultez SaveInfo.
  • Nombre minimal d'affichages devant être modifiés pour déclencher une requête d'enregistrement. Par exemple, pour déclencher une requête d'enregistrement, un formulaire de connexion a besoin que l'utilisateur mette à jour les affichages username et password.

Un objet SaveInfo est associé à un objet FillResponse, comme illustré dans l'exemple de code suivant :

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

Le service de saisie automatique peut implémenter une logique pour conserver les données utilisateur dans la méthode onSaveRequest(), qui est généralement appelée une fois l'activité du client terminée ou lorsque l'application cliente appelle commit(). Le code suivant montre un exemple de la méthode 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();
}

Les services de saisie automatique doivent chiffrer les données sensibles avant de les conserver. Toutefois, les données utilisateur incluent des libellés ou des données non sensibles. Par exemple, un compte utilisateur peut inclure un libellé qui marque les données en tant que compte professionnel ou personnel. Les services ne doivent pas chiffrer les libellés, ce qui leur permet de les utiliser dans les affichages de présentation si l'utilisateur ne s'est pas authentifié, et de les remplacer par les données réelles après l'authentification de l'utilisateur.

Reporter la sauvegarde de saisie automatique dans l'UI

À partir d'Android 10, si vous utilisez plusieurs écrans pour implémenter un workflow de saisie automatique (par exemple, un écran pour le champ de nom d'utilisateur et un autre pour le mot de passe), vous pouvez reporter la sauvegarde de saisie automatique dans l'UI à l'aide de l'indicateur SaveInfo.FLAG_DELAY_SAVE.

Si cet indicateur est défini, la sauvegarde de saisie automatique ne se déclenche pas dans l'UI lorsque le contexte de saisie automatique associé à la réponse SaveInfo est validé. À la place, vous pouvez utiliser une activité distincte dans la même tâche pour envoyer de futures requêtes de saisie, puis afficher l'UI via une requête d'enregistrement. Pour en savoir plus, consultez SaveInfo.FLAG_DELAY_SAVE.

Exiger l'authentification des utilisateurs

Les services de saisie automatique peuvent fournir un niveau de sécurité supplémentaire en obligeant l'utilisateur à s'authentifier avant de pouvoir remplir des affichages. Les scénarios suivants conviennent à l'implémentation de l'authentification des utilisateurs :

  • Les données utilisateur de l'application doivent être déverrouillées à l'aide d'un mot de passe principal ou d'une empreinte digitale.
  • Un ensemble de données spécifique doit être déverrouillé, par exemple les informations de carte de crédit, à l'aide d'un code CVC.

Si le service requiert une authentification de l'utilisateur avant de déverrouiller les données, il peut présenter des données standardisées ou un libellé, et spécifier l'Intent en charge de l'authentification. Si vous avez besoin de données supplémentaires pour traiter la requête une fois le flux d'authentification terminé, vous pouvez ajouter ces données à l'intent. Votre activité d'authentification peut ensuite renvoyer les données à la classe AutofillService de votre application. L'exemple de code suivant montre comment spécifier que la requête nécessite une authentification :

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

Une fois que l'activité a terminé le flux d'authentification, elle doit appeler la méthode setResult() en transmettant une valeur RESULT_OK et définir l'élément EXTRA_AUTHENTICATION_RESULT supplémentaire sur l'objet FillResponse qui inclut l'ensemble de données renseigné. Le code suivant montre comment renvoyer le résultat une fois le flux d'authentification terminé :

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

Dans le cas où un ensemble de données de carte de crédit doit être déverrouillé, le service peut afficher l'UI pour demander le code CVC. Vous pouvez masquer les données jusqu'à ce que l'ensemble de données soit déverrouillé en présentant des données standardisées telles que le nom de la banque et les quatre derniers chiffres du numéro de carte de crédit. L'exemple suivant montre comment exiger l'authentification pour un ensemble de données et masquer les données jusqu'à ce que l'utilisateur fournisse le code 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();

Une fois que le code CVC de l'activité a été validé, il doit appeler la méthode setResult() en transmettant une valeur RESULT_OK et définir l'élément EXTRA_AUTHENTICATION_RESULT supplémentaire sur un objet Dataset contenant le numéro de carte de crédit et sa date d'expiration. Le nouvel ensemble de données remplace celui qui nécessite une authentification et les affichages sont immédiatement remplis. Le code suivant montre comment renvoyer l'ensemble de données une fois que l'utilisateur a fourni le code 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);

Organiser les données en groupes logiques

Les services de saisie automatique doivent organiser les données en groupes logiques qui isolent les concepts de différents domaines. Ces groupes logiques sont appelés partitions sur cette page. La liste suivante présente des exemples typiques de partitions et de champs :

  • Les identifiants, y compris les champs "Nom d'utilisateur" et "Mot de passe".
  • L'adresse, qui comprend les champs "Rue", "Ville", "État" et "Code postal".
  • Les informations de paiement, y compris les champs concernant le numéro de carte de crédit, la date d'expiration et le code CVC.

Un service de saisie automatique qui partitionne correctement les données peut mieux protéger les données de ses utilisateurs en ne les exposant pas à plusieurs partitions d'un ensemble de données. Par exemple, un ensemble de données qui inclut des identifiants ne doit pas nécessairement inclure des informations de paiement. L'organisation de vos données en partitions permet à votre service d'exposer la quantité minimale d'informations requise pour satisfaire une requête.

L'organisation des données dans des partitions permet aux services de remplir des activités ayant des affichages à partir de plusieurs partitions tout en envoyant la quantité minimale de données à l'application cliente. Prenons l'exemple d'une activité qui comprend le nom d'utilisateur, le mot de passe, la rue et la ville, et un service de saisie automatique contenant les données suivantes :

Partition Champ 1 Champ 2
Identifiants work_username work_password
personal_username personal_password
Adresse work_street work_city
personal_street personal_city

Le service peut préparer un ensemble de données comprenant la partition d'identifiants pour les comptes professionnel et personnel. Lorsque l'utilisateur en choisit une, la réponse de saisie automatique suivante peut indiquer l'adresse professionnelle ou personnelle, en fonction du premier choix de l'utilisateur.

Un service peut identifier le champ à l'origine de la requête en appelant la méthode isFocused() lors du balayage de l'objet AssistStructure. Cela permet aux services de préparer une FillResponse avec les données de partition appropriées.

Saisie automatique de code unique par SMS

Votre service de saisie automatique peut aider l'utilisateur à saisir des codes uniques envoyés par SMS à l'aide de l'API SMS Retriever.

Pour utiliser cette fonctionnalité, vous devez remplir les conditions suivantes :

  • Le service de saisie automatique s'exécute sur Android 9 (niveau d'API 28) ou version ultérieure.
  • L'utilisateur a autorisé votre service de saisie automatique à lire des codes uniques à partir de SMS.
  • L'application pour laquelle vous fournissez des informations de saisie automatique n'utilise pas déjà l'API SMS Retriever pour lire les codes à usage unique.

Votre service de saisie automatique peut utiliser SmsCodeAutofillClient, disponible en appelant SmsCodeRetriever.getAutofillClient() depuis les services Google Play 19.0.56 ou version ultérieure.

Voici les principales étapes à suivre pour utiliser cette API dans un service de saisie automatique :

  1. Dans le service de saisie automatique, utilisez hasOngoingSmsRequest à partir de SmsCodeAutofillClient pour déterminer si des requêtes sont déjà actives pour le nom de package de l'application que vous remplissez automatiquement. Votre service de saisie automatique ne doit afficher une invite de suggestion que s'il renvoie false.
  2. Dans le service de saisie automatique, utilisez checkPermissionState à partir de SmsCodeAutofillClient pour vérifier si le service de saisie automatique est autorisé à saisir automatiquement des codes uniques. Cet état d'autorisation peut être NONE, GRANTED ou DENIED. Le service de saisie automatique doit afficher une invite de suggestion pour les états NONE et GRANTED.
  3. Dans l'activité d'authentification de saisie automatique, utilisez l'autorisation SmsRetriever.SEND_PERMISSION pour enregistrer un BroadcastReceiver qui écoute SmsCodeRetriever.SMS_CODE_RETRIEVED_ACTION afin de recevoir le résultat du code SMS lorsqu'il est disponible.
  4. Appelez startSmsCodeRetriever sur SmsCodeAutofillClient pour commencer à écouter des codes uniques envoyés par SMS. Si l'utilisateur a autorisé votre service de saisie automatique à récupérer des codes uniques à partir d'un SMS, il recherchera les SMS reçus au cours des cinq dernières minutes.

    Si votre service de saisie automatique doit demander l'autorisation de l'utilisateur pour lire les codes uniques, la Task renvoyée par startSmsCodeRetriever peut échouer et renvoyer un ResolvableApiException. Dans ce cas, vous devez appeler la méthode startResolutionForResult() de ResolvableApiException pour afficher une boîte de dialogue de collecte du consentement.

  5. Vous recevez le résultat du code SMS envoyé par l'intent, puis renvoyez le code SMS sous forme de réponse de saisie automatique.

Scénarios de saisie automatique avancés

Intégrer au clavier
À partir d'Android 11, la plate-forme autorise les claviers et autres éditeurs de mode de saisie (IME) à afficher des suggestions de saisie automatique intégrées au lieu d'utiliser un menu déroulant. Pour en savoir plus sur la compatibilité de votre service de saisie automatique avec cette fonctionnalité, consultez Intégrer la saisie automatique aux claviers.
Paginer des ensembles de données
Une réponse de saisie automatique volumineuse peut dépasser la taille de transaction autorisée de l'objet Binder qui représente l'objet rétractable requis pour traiter la requête. Pour empêcher le système Android de générer une exception dans ces scénarios, vous pouvez limiter la taille de FillResponse en ajoutant un maximum de 20 objets Dataset à la fois. Si votre réponse nécessite des ensembles de données supplémentaires, vous pouvez en ajouter un pour informer les utilisateurs qu'il contient plus d'informations et récupérer le groupe d'ensembles de données suivant lorsqu'il est sélectionné. Pour en savoir plus, consultez addDataset(Dataset).
Enregistrer la répartition des données sur plusieurs écrans

Les applications répartissent souvent les données utilisateur sur plusieurs écrans au cours de la même activité, en particulier lors de l'activité servant à créer un compte utilisateur. Par exemple, le premier écran demande un nom d'utilisateur et, si le nom d'utilisateur est disponible, il passe à un deuxième écran, qui demande un mot de passe. Dans ces situations, le service de saisie automatique doit attendre que l'utilisateur entre les deux champs pour que la sauvegarde de saisie automatique puisse s'afficher dans l'UI. Un service peut suivre ces étapes pour gérer de tels scénarios :

  1. Dans la première requête de saisie, le service ajoute un groupe d'états client dans la réponse, contenant les ID de saisie automatique des champs partiels qui s'affichent à l'écran.
  2. Dans la deuxième requête de saisie, le service récupère le bundle d'états client, obtient les ID de saisie automatique définis dans la requête précédente à partir de l'état client, puis ajoute ces ID et l'indicateur FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE à l'objet SaveInfo utilisé dans la deuxième réponse.
  3. Dans la requête d'enregistrement, le service utilise les objets FillContext appropriés pour obtenir la valeur de chaque champ. Il existe un contexte de remplissage par requête de saisie.

Pour en savoir plus, consultez la section Enregistrer lorsque les données sont réparties sur plusieurs écrans.

Fournir une logique d'initialisation et de suppression pour chaque requête

Chaque fois qu'il y a une requête de saisie automatique, le système Android se lie au service et appelle sa méthode onConnected(). Une fois que le service a traité la requête, le système Android appelle la méthode onDisconnected() et se dissocie du service. Vous pouvez implémenter onConnected() pour fournir du code qui s'exécute avant le traitement d'une requête, et onDisconnected() pour fournir du code qui s'exécute après le traitement d'une requête.

Personnaliser la sauvegarde de saisie automatique dans l'UI

Les services de saisie automatique peuvent personnaliser la sauvegarde de saisie automatique dans l'UI afin d'aider les utilisateurs à décider s'ils souhaitent autoriser le service à enregistrer leurs données. Les services peuvent fournir des informations supplémentaires sur ce qui serait enregistré sous forme de texte simple ou via un affichage personnalisé. Les services peuvent également modifier l'apparence du bouton qui annule la requête d'enregistrement et reçoit une notification lorsque l'utilisateur appuie dessus. Pour plus d'informations, consultez la documentation de référence de SaveInfo.

Mode de compatibilité

Le mode de compatibilité permet aux services de saisie automatique d'utiliser la structure virtuelle d'accessibilité à des fins de saisie automatique. Elle est particulièrement utile pour fournir une fonctionnalité de saisie automatique dans les navigateurs qui n'ont pas encore implémenté explicitement les API de saisie automatique.

Pour tester votre service de saisie automatique à l'aide du mode de compatibilité, vous devez ajouter explicitement le navigateur ou l'application qui nécessite le mode de compatibilité. Vous pouvez vérifier quels packages sont déjà ajoutés à la liste d'autorisation en exécutant la commande suivante :

$ adb shell settings get global autofill_compat_mode_allowed_packages

Si le package que vous testez n'est pas listé, vous pouvez l'ajouter en exécutant la commande suivante :

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

… où pkgX représente le package de l'application. Si l'application est un navigateur, utilisez resIdx pour spécifier l'ID de ressource du champ de saisie contenant l'URL de la page affichée.

Le mode de compatibilité présente les limites suivantes :

Pour en savoir plus sur le mode de compatibilité, y compris sur les limites qui lui sont associées, consultez la documentation de référence de la classe AutofillService.