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 utilisez
recherchez un exemple de code qui montre comment implémenter un service, consultez le
Exemple AutofillFramework dans 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 deAutofillService
dans l'application qui implémente le service. - L'attribut
android:permission
, qui déclare l'autorisationBIND_AUTOFILL_SERVICE
. - L'élément
<intent-filter>
, dont l'élément enfant<action>
obligatoire spécifie l'actionandroid.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 :
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.
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, consultezSaveInfo
. - 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
etpassword
.
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 :
- Dans le service de saisie automatique, utilisez
hasOngoingSmsRequest
depuisSmsCodeAutofillClient
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 renvoiefalse
. - Dans le service de saisie automatique, utilisez
checkPermissionState
depuisSmsCodeAutofillClient
pour vérifier si le service de saisie automatique est autorisé à saisir automatiquement des codes à usage unique. Cet état d'autorisation peut êtreNONE
,GRANTED
ouDENIED
. Le service de saisie automatique doit afficher une invite de suggestion pour les étatsNONE
etGRANTED
. - Dans l'activité d'authentification de saisie automatique, utilisez l'autorisation
SmsRetriever.SEND_PERMISSION
pour enregistrer unBroadcastReceiver
qui écouteSmsCodeRetriever.SMS_CODE_RETRIEVED_ACTION
afin de recevoir le résultat du code SMS lorsqu'il est disponible. Appelez
startSmsCodeRetriever
surSmsCodeAutofillClient
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 parstartSmsCodeRetriever
peut échouer et renvoyer uneResolvableApiException
. Dans ce cas, vous devez appeler la méthodeResolvableApiException.startResolutionForResult()
afin d'afficher une boîte de dialogue de recueil du consentement pour la demande d'autorisation.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 deFillResponse
en ajoutant un maximum de 20 objetsDataset
à 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, consultezaddDataset(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 :
- 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.
- 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'objetSaveInfo
utilisé dans la deuxième réponse. - 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éthodeonDisconnected()
et se dissocie du service. Vous pouvez implémenteronConnected()
pour fournir un code qui s'exécute avant le traitement d'une requête etonDisconnected()
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éthodesetTrigger()
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
.