Créer des services de saisie automatique

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 à la fonctionnalité Autofill Framework 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 montrant comment implémenter un service, consultez l'exemple AutofillFramework Java ou Kotlin. Pour en savoir plus sur le fonctionnement des services de saisie automatique, consultez les pages 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 :

  • L'attribut android:name, qui pointe vers la sous-classe de AutofillService dans l'application qui implémente le service.
  • L'attribut android:permission, qui déclare l'autorisation BIND_AUTOFILL_SERVICE.
  • L'élément <intent-filter>, dont l'élément enfant <action> obligatoire spécifie l'action android.service.autofill.AutofillService.
  • L'élément <meta-data> facultatif, que vous pouvez utiliser pour fournir des paramètres de configuration supplémentaires pour le service.

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 Présentation des ressources de l'application.

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 la 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 de saisie automatique avec les données disponibles, comme illustré à la figure 1 :

UI de saisie automatique

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

Autofill Framework 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. Si oui, il 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 en savoir plus sur le workflow, consultez la description sur la page de référence AutofillService.

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 l'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, par exemple getText() ou getHint(), pour obtenir des informations sur le contenu de l'affichage. Pour en savoir plus, consultez Fournir des indications 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.

UI de sauvegarde de saisie automatique

Figure 2. UI de sauvegarde de saisie automatique.

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 dans la réponse à la requête de saisie. L'objet SaveInfo contient au moins les données suivantes :

  • Type de données utilisateur enregistrées. Pour 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 généralement 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 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 peuvent inclure des libellés ou des données non sensibles. Par exemple, un compte utilisateur peut inclure un libellé qui identifie les données comme compte professionnel ou personnel. Les services ne doivent pas chiffrer les libellés. Cela leur permet de les utiliser dans les affichages de présentation si l'utilisateur ne s'est pas authentifié, puis de les remplacer par les données réelles après authentification de l'utilisateur.

Reporter l'activation de l'UI de sauvegarde de saisie automatique

À 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 l'activation de l'UI de sauvegarde de saisie automatique à l'aide de l'indicateur SaveInfo.FLAG_DELAY_SAVE.

Si cet indicateur est défini, l'UI de sauvegarde de saisie automatique n'est pas activée 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 en utilisant un code CVC pour déverrouiller des informations de carte de crédit.

Si le service requiert l'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, such as 'Bank-1234'
val maskedPresentation: String = "${paymentData.bank}-" +
        paymentData.creditCardNumber.substring(paymentData.creditCardNumber.length - 4)
val authPresentation = RemoteViews(packageName, android.R.layout.simple_list_item_1).apply {
    setTextViewText(android.R.id.text1, maskedPresentation)
}

// Prepare an intent that displays the UI that asks for the CVC
val cvcIntent = Intent(this, CvcActivity::class.java)
val cvcIntentSender: IntentSender = PendingIntent.getActivity(
        this,
        1001,
        cvcIntent,
        PendingIntent.FLAG_CANCEL_CURRENT
).intentSender

// Build a FillResponse object that includes a Dataset that requires authentication
val fillResponse: FillResponse = FillResponse.Builder()
        .addDataset(
                Dataset.Builder()
                        // The values in the dataset are replaced by the actual
                        // data once the user provides the CVC
                        .setValue(parsedStructure.creditCardId, null, authPresentation)
                        .setValue(parsedStructure.expDateId, null, authPresentation)
                        .setAuthentication(cvcIntentSender)
                        .build()
        ).build()

Java

// Parse the structure and fetch payment data
ParsedStructure parsedStructure = parseStructure(structure);
Payment paymentData = fetchPaymentData(parsedStructure);

// Build the presentation that shows the bank and the last four digits of the
// credit card number, such as 'Bank-1234'
String maskedPresentation = paymentData.bank + "-" +
    paymentData.creditCardNumber.subString(paymentData.creditCardNumber.length - 4);
RemoteViews authPresentation = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1);
authPresentation.setTextViewText(android.R.id.text1, maskedPresentation);

// Prepare an intent that displays the UI that asks for the CVC
Intent cvcIntent = new Intent(this, CvcActivity.class);
IntentSender cvcIntentSender = PendingIntent.getActivity(
        this,
        1001,
        cvcIntent,
        PendingIntent.FLAG_CANCEL_CURRENT
).getIntentSender();

// Build a FillResponse object that includes a Dataset that requires authentication
FillResponse fillResponse = new FillResponse.Builder()
        .addDataset(new Dataset.Builder()
                // The values in the dataset are replaced by the actual
                // data once the user provides the CVC
                .setValue(parsedStructure.creditCardId, null, authPresentation)
                .setValue(parsedStructure.expDateId, null, authPresentation)
                .setAuthentication(cvcIntentSender)
                .build())
        .build();

Une fois que l'activité a validé le code CVC, 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 un objet Dataset contenant le numéro et la date d'expiration de la carte de crédit. 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 to use as the presentation when
// creating the Dataset object. This presentation isn't actually used, but the
// Builder object requires a non-null presentation.
val notUsed = RemoteViews(packageName, android.R.layout.simple_list_item_1)

// Create a dataset with the credit card number and expiration date.
val responseDataset: Dataset = Dataset.Builder()
        .setValue(
                parsedStructure.creditCardId,
                AutofillValue.forText(paymentData.creditCardNumber),
                notUsed
        )
        .setValue(
                parsedStructure.expDateId,
                AutofillValue.forText(paymentData.expirationDate),
                notUsed
        )
        .build()

val replyIntent = Intent().apply {
    putExtra(EXTRA_AUTHENTICATION_RESULT, responseDataset)
}

Java

// Parse the structure and fetch payment data.
ParsedStructure parsedStructure = parseStructure(structure);
Payment paymentData = fetchPaymentData(parsedStructure);

// Build a non-null RemoteViews object to use as the presentation when
// creating the Dataset object. This presentation isn't actually used, but the
// Builder object requires a non-null presentation.
RemoteViews notUsed = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1);

// Create a dataset with the credit card number and expiration date.
Dataset responseDataset = new Dataset.Builder()
        .setValue(parsedStructure.creditCardId,
                AutofillValue.forText(paymentData.creditCardNumber), notUsed)
        .setValue(parsedStructure.expDateId,
                AutofillValue.forText(paymentData.expirationDate), notUsed)
        .build();

Intent replyIntent = new Intent();
replyIntent.putExtra(EXTRA_AUTHENTICATION_RESULT, responseDataset);

Organiser les données en groupes logiques

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

  • Les identifiants, qui comprennent 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, qui comprennent les champs "Numéro de carte de crédit", "Date d'expiration" et "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 n'a pas besoin d'inclure d'informations de paiement. Organiser les données en partitions permet à votre service d'exposer le minimum d'informations pertinentes requis pour satisfaire une requête.

Cela permet également aux services de remplir des activités dont les affichages proviennent de plusieurs partitions tout en envoyant le minimum de données pertinentes à l'application cliente. Prenons pour exemple le cas d'une activité qui inclut des affichages pour le nom d'utilisateur, le mot de passe, la rue et la ville, et d'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 choisit un ensemble de données, la réponse de saisie automatique suivante peut fournir soit l'adresse professionnelle, soit l'adresse personnelle, selon le choix initial 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. Il peut ainsi préparer une FillResponse avec les données de partition appropriées.

Saisie automatique de code à usage unique par SMS

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

Pour utiliser cette fonctionnalité, les exigences suivantes doivent être satisfaites :

  • Le service de saisie automatique s'exécute sur Android 9 (niveau d'API 28) ou version ultérieure.
  • L'utilisateur autorise votre service de saisie automatique à lire des codes à usage unique à partir de SMS.
  • L'application pour laquelle vous fournissez la 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.

Principales étapes pour utiliser cette API dans un service de saisie automatique :

  1. Dans le service de saisie automatique, utilisez hasOngoingSmsRequest depuis 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 depuis SmsCodeAutofillClient pour vérifier si le service de saisie automatique est autorisé à saisir automatiquement des codes à usage unique. 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 à usage unique envoyés par SMS. Si l'utilisateur autorise votre service de saisie automatique à récupérer des codes à usage unique à partir d'un SMS, les SMS reçus au cours des cinq dernières minutes seront recherchés.

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

  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 existe des informations supplémentaires 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 les données réparties 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, un deuxième écran demande le mot de passe. Dans ces cas-là, le service de saisie automatique doit attendre que l'utilisateur ait rempli les deux champs pour que l'UI de sauvegarde de saisie automatique puisse s'afficher. Suivez ces étapes pour gérer de tels scénarios :

  1. Dans la première requête de saisie, ajoutez un groupe d'états client dans la réponse, contenant les ID de saisie automatique des champs partiels affichés à l'écran.
  2. Dans la deuxième requête de saisie, récupérez le groupe d'états client ainsi que les ID de saisie automatique définis dans la requête précédente à partir de l'état du client, puis ajoutez 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, utilisez les objets FillContext appropriés pour obtenir la valeur de chaque champ. Chaque requête de saisie possède un seul contexte 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 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 un code qui s'exécute avant le traitement d'une requête et onDisconnected() pour fournir un code qui s'exécute après le traitement d'une requête.

Personnaliser l'UI de sauvegarde de saisie automatique

Les services de saisie automatique peuvent personnaliser l'UI de sauvegarde de saisie automatique 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 les données qui sont enregistrées, via un texte simple ou un affichage personnalisé. Les services peuvent également modifier l'apparence du bouton qui annule la requête d'enregistrement et recevoir une notification lorsque l'utilisateur appuie dessus. Pour en savoir plus, consultez la page de référence sur 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'implémentent pas explicitement les API de saisie automatique.

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

$ adb shell settings get global autofill_compat_mode_allowed_packages

Si le package que vous testez n'y figure pas, ajoutez-le en exécutant la commande suivante, où pkgX représente le package de l'application :

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

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 :

  • Une requête d'enregistrement est déclenchée lorsque le service utilise l'indicateur FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE ou que la méthode setTrigger() est appelée. FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE est défini par défaut lorsque vous utilisez le mode de compatibilité.
  • La valeur textuelle des nœuds peut ne pas être disponible avec la méthode onSaveRequest(SaveRequest, SaveCallback).

Pour en savoir plus sur le mode de compatibilité, y compris sur ses limites, consultez la documentation de référence de la classe AutofillService.