Ein Autofill-Dienst ist eine App, die es Nutzern erleichtert, Formulare auszufüllen, indem Daten in die Ansichten anderer Apps eingefügt werden. Autofill-Dienste können auch Nutzerdaten aus den Ansichten in einer App abrufen und zur späteren Verwendung speichern. Autofill-Dienste werden in der Regel von Apps bereitgestellt, die Nutzerdaten wie Passwortmanager verwalten.
Android erleichtert das Ausfüllen von Formularen mit dem Autofill-Framework, das in Android 8.0 (API-Level 26) und höher verfügbar ist. Nutzer können die Autofill-Funktionen nur nutzen, wenn auf ihrem Gerät eine App vorhanden ist, die Autofill-Dienste bereitstellt.
Auf dieser Seite wird beschrieben, wie Sie einen Autofill-Dienst in Ihrer App implementieren. Wenn Sie ein Codebeispiel für die Implementierung eines Dienstes suchen, sehen Sie sich das AutofillFramework-Beispiel in Java oder Kotlin an.
Weitere Informationen zur Funktionsweise von Autofill-Diensten finden Sie auf den Referenzseiten für die Klassen AutofillService
und AutofillManager
.
Manifestdeklarationen und Berechtigungen
Apps, die Autofill-Dienste anbieten, müssen eine Erklärung enthalten, in der die Implementierung des Dienstes beschrieben wird. Fügen Sie zum Angeben der Deklaration ein <service>
-Element in das App-Manifest ein. Das <service>
-Element muss die folgenden Attribute und Elemente enthalten:
- Das Attribut
android:name
verweist auf die Unterklasse vonAutofillService
in der App, die den Dienst implementiert. android:permission
-Attribut, mit dem die BerechtigungBIND_AUTOFILL_SERVICE
deklariert wird.<intent-filter>
-Element, dessen obligatorisches untergeordnetes Element<action>
dieandroid.service.autofill.AutofillService
-Aktion angibt.- Optionales
<meta-data>
-Element, mit dem Sie zusätzliche Konfigurationsparameter für den Dienst angeben können.
Das folgende Beispiel zeigt die Deklaration eines Autofill-Dienstes:
<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>
Das <meta-data>
-Element enthält ein android:resource
-Attribut, das auf eine XML-Ressource mit weiteren Details zum Dienst verweist.
Die service_configuration
-Ressource im vorherigen Beispiel gibt eine Aktivität an, mit der Nutzer den Dienst konfigurieren können. Das folgende Beispiel zeigt die XML-Ressource service_configuration
:
<autofill-service
xmlns:android="http://schemas.android.com/apk/res/android"
android:settingsActivity="com.example.android.SettingsActivity" />
Weitere Informationen zu XML-Ressourcen finden Sie unter App-Ressourcen.
Aufforderung zum Aktivieren des Dienstes
Eine App wird als Autofill-Dienst verwendet, nachdem sie die Berechtigung BIND_AUTOFILL_SERVICE
deklariert hat und der Nutzer sie in den Geräteeinstellungen aktiviert hat. Eine App kann prüfen, ob sie der derzeit aktivierte Dienst ist, indem sie die Methode hasEnabledAutofillServices()
der Klasse AutofillManager
aufruft.
Wenn die App nicht der aktuelle Autofill-Dienst ist, kann sie den Nutzer mit dem Intent ACTION_REQUEST_SET_AUTOFILL_SERVICE
auffordern, die Autofill-Einstellungen zu ändern. Der Intent gibt den Wert RESULT_OK
zurück, wenn der Nutzer einen Autofill-Dienst auswählt, der dem Paket des Aufrufers entspricht.
Kundenansichten ausfüllen
Der Autofill-Dienst empfängt Anfragen zum Ausfüllen von Clientansichten, wenn der Nutzer mit anderen Apps interagiert. Wenn der Autofill-Dienst Nutzerdaten hat, die die Anfrage erfüllen, werden die Daten in der Antwort gesendet. Das Android-System zeigt eine Benutzeroberfläche für die automatische Vervollständigung mit den verfügbaren Daten an, wie in Abbildung 1 dargestellt:
Abbildung 1: Autofill-Benutzeroberfläche mit einem Dataset
Das Framework für das automatische Ausfüllen definiert einen Workflow zum Ausfüllen von Ansichten, der darauf ausgelegt ist, die Zeit zu minimieren, in der das Android-System an den Dienst für das automatische Ausfüllen gebunden ist. Bei jeder Anfrage sendet das Android-System ein AssistStructure
-Objekt an den Dienst, indem es die Methode onFillRequest()
aufruft.
Der Autofill-Dienst prüft, ob er die Anfrage mit Nutzerdaten erfüllen kann, die er zuvor gespeichert hat. Wenn der Dienst die Anfrage erfüllen kann, werden die Daten in Dataset
-Objekte verpackt. Der Dienst ruft die Methode onSuccess()
auf und übergibt ein FillResponse
-Objekt, das die Dataset
-Objekte enthält. Wenn der Dienst keine Daten hat, um die Anfrage zu erfüllen, wird null
an die Methode onSuccess()
übergeben.
Der Dienst ruft stattdessen die Methode onFailure()
auf, wenn bei der Verarbeitung der Anfrage ein Fehler auftritt. Eine detaillierte Beschreibung des Workflows finden Sie auf der Referenzseite zu AutofillService
.
Der folgende Code zeigt ein Beispiel für die Methode 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; }
Ein Dienst kann mehrere Datasets haben, die die Anfrage erfüllen. In diesem Fall zeigt das Android-System in der Autofill-Benutzeroberfläche mehrere Optionen an, eine für jeden Datensatz. Das folgende Codebeispiel zeigt, wie mehrere Datasets in einer Antwort bereitgestellt werden:
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();
Autofill-Dienste können die ViewNode
-Objekte in der AssistStructure
durchsuchen, um die für die Erfüllung der Anfrage erforderlichen Autofill-Daten abzurufen. Ein Dienst kann Autofill-Daten mit Methoden der Klasse ViewNode
wie getAutofillId()
abrufen.
Ein Dienst muss in der Lage sein, den Inhalt einer Ansicht zu beschreiben, um zu prüfen, ob er die Anfrage erfüllen kann. Die Verwendung des Attributs autofillHints
ist der erste Ansatz, den ein Dienst verwenden muss, um den Inhalt einer Ansicht zu beschreiben. Client-Apps müssen das Attribut jedoch explizit in ihren Ansichten angeben, bevor es für den Dienst verfügbar ist.
Wenn eine Client-App das Attribut autofillHints
nicht bereitstellt, muss ein Dienst eigene Heuristiken verwenden, um den Inhalt zu beschreiben.
Der Dienst kann Methoden aus anderen Klassen verwenden, z. B. getText()
oder getHint()
, um Informationen zum Inhalt der Ansicht abzurufen.
Weitere Informationen finden Sie unter Hinweise für die automatische Vervollständigung bereitstellen.
Im folgenden Beispiel wird gezeigt, wie Sie das AssistStructure
durchlaufen und Autofill-Daten aus einem ViewNode
-Objekt abrufen:
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); } }
Nutzerdaten speichern
Ein Autofill-Service benötigt Nutzerdaten, um Ansichten in Apps auszufüllen. Wenn Nutzer eine Ansicht manuell ausfüllen, werden sie aufgefordert, die Daten im aktuellen Autofill-Dienst zu speichern (siehe Abbildung 2).
Abbildung 2: Benutzeroberfläche zum Speichern von Autofill-Daten.
Damit die Daten gespeichert werden können, muss der Dienst angeben, dass er die Daten für die zukünftige Verwendung speichern möchte. Bevor das Android-System eine Anfrage zum Speichern der Daten sendet, wird eine Anfrage zum Ausfüllen gesendet, bei der der Dienst die Möglichkeit hat, die Ansichten auszufüllen. Um anzugeben, dass der Dienst die Daten speichern möchte, enthält er in der Antwort auf die Fill-Anfrage ein SaveInfo
-Objekt. Das SaveInfo
-Objekt enthält mindestens die folgenden Daten:
- Die Art der gespeicherten Nutzerdaten. Eine Liste der verfügbaren
SAVE_DATA
-Werte finden Sie unterSaveInfo
. - Die Mindestanzahl an Ansichten, die geändert werden müssen, um eine Speicheranfrage auszulösen.
Bei einem Anmeldeformular muss der Nutzer beispielsweise die Ansichten
username
undpassword
aktualisieren, um eine Speicheranfrage auszulösen.
Ein SaveInfo
-Objekt ist einem FillResponse
-Objekt zugeordnet, wie im folgenden Codebeispiel gezeigt:
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(); ... }
Der Autofill-Dienst kann Logik implementieren, um die Nutzerdaten in der Methode onSaveRequest()
zu speichern. Diese wird in der Regel aufgerufen, nachdem die Clientaktivität abgeschlossen ist oder wenn die Client-App commit()
aufruft.
Der folgende Code zeigt ein Beispiel für die Methode 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(); }
Autofill-Dienste müssen vertrauliche Daten verschlüsseln, bevor sie gespeichert werden. Nutzerdaten können jedoch auch Labels oder Daten enthalten, die nicht vertraulich sind. Ein Nutzerkonto kann beispielsweise ein Label enthalten, das die Daten als Arbeitskonto oder privates Konto kennzeichnet. Dienste dürfen Labels nicht verschlüsseln. Wenn Labels nicht verschlüsselt werden, können Dienste die Labels in Präsentationsansichten verwenden, wenn der Nutzer sich nicht authentifiziert hat. Nach der Authentifizierung des Nutzers können die Labels dann durch die tatsächlichen Daten ersetzt werden.
Autofill-Speicher-UI verschieben
Wenn Sie ab Android 10 mehrere Bildschirme verwenden, um einen Autofill-Ablauf zu implementieren, z. B. einen Bildschirm für das Feld „Nutzername“ und einen anderen für das Passwort, können Sie die Benutzeroberfläche zum Speichern von Autofill-Daten mit dem Flag SaveInfo.FLAG_DELAY_SAVE
aufschieben.
Wenn dieses Flag festgelegt ist, wird die Benutzeroberfläche zum Speichern von Autofill-Daten nicht ausgelöst, wenn der mit der SaveInfo
-Antwort verknüpfte Autofill-Kontext übernommen wird. Stattdessen können Sie eine separate Aktivität innerhalb derselben Aufgabe verwenden, um zukünftige Fill-Anfragen zu stellen und die Benutzeroberfläche dann über eine Speicheranfrage anzuzeigen. Weitere Informationen finden Sie unter SaveInfo.FLAG_DELAY_SAVE
.
Nutzerauthentifizierung erforderlich
Autofill-Dienste können für zusätzliche Sicherheit sorgen, indem sie eine Authentifizierung des Nutzers erfordern, bevor Ansichten ausgefüllt werden können. In den folgenden Szenarien ist es sinnvoll, die Nutzerauthentifizierung zu implementieren:
- Die Nutzerdaten in der App müssen mit einem primären Passwort oder einem Fingerabdruckscan entsperrt werden.
- Ein bestimmter Datensatz muss entsperrt werden, z. B. Kreditkartendetails durch Eingabe eines Kartenprüfcodes (CVC).
Wenn für den Dienst eine Nutzerauthentifizierung erforderlich ist, bevor die Daten entsperrt werden, kann er Platzhalterdaten oder ein Label präsentieren und die Intent
angeben, die sich um die Authentifizierung kümmert. Wenn Sie nach dem Authentifizierungsablauf zusätzliche Daten zum Verarbeiten der Anfrage benötigen, können Sie diese dem Intent hinzufügen. Ihre Authentifizierungsaktivität kann die Daten dann an die Klasse AutofillService
in Ihrer App zurückgeben.
Das folgende Codebeispiel zeigt, wie angegeben wird, dass für die Anfrage eine Authentifizierung erforderlich ist:
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();
Sobald die Aktivität den Authentifizierungsablauf abgeschlossen hat, muss sie die Methode setResult()
aufrufen, einen RESULT_OK
-Wert übergeben und das Extra EXTRA_AUTHENTICATION_RESULT
auf das FillResponse
-Objekt festlegen, das das ausgefüllte Dataset enthält. Der folgende Code zeigt ein Beispiel dafür, wie das Ergebnis zurückgegeben wird, sobald die Authentifizierungsabläufe abgeschlossen sind:
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);
Wenn ein Kreditkartendatensatz entsperrt werden muss, kann im Dienst eine Benutzeroberfläche angezeigt werden, in der nach der CVC gefragt wird. Sie können die Daten ausblenden, bis das Dataset durch die Angabe von Platzhalterdaten wie dem Namen der Bank und den letzten vier Ziffern der Kreditkartennummer entsperrt wird. Im folgenden Beispiel wird gezeigt, wie die Authentifizierung für ein Dataset erforderlich gemacht und die Daten ausgeblendet werden, bis der Nutzer den CVC angibt:
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();
Sobald die Aktivität den CVC validiert hat, sollte sie die Methode setResult()
aufrufen, einen RESULT_OK
-Wert übergeben und das Extra EXTRA_AUTHENTICATION_RESULT
auf ein Dataset
-Objekt festlegen, das die Kreditkartennummer und das Ablaufdatum enthält. Der neue Datensatz ersetzt den Datensatz, für den eine Authentifizierung erforderlich ist, und die Ansichten werden sofort ausgefüllt. Im folgenden Codebeispiel wird gezeigt, wie das Dataset zurückgegeben wird, sobald der Nutzer den CVC angegeben hat:
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);
Daten in logischen Gruppen organisieren
Autofill-Dienste müssen die Daten in logischen Gruppen organisieren, die Konzepte aus verschiedenen Bereichen isolieren. Auf dieser Seite werden diese logischen Gruppen als Partitionen bezeichnet. Die folgende Liste enthält typische Beispiele für Partitionen und Felder:
- Anmeldedaten, einschließlich der Felder für Nutzername und Passwort.
- Adresse, die die Felder „Straße“, „Ort“, „Bundesland“ und „Postleitzahl“ enthält.
- Zahlungsinformationen, einschließlich der Felder für Kreditkartennummer, Ablaufdatum und Sicherheitscode.
Ein AutoFill-Dienst, der Daten korrekt partitioniert, kann die Daten seiner Nutzer besser schützen, da er keine Daten aus mehr als einer Partition in einem Datensatz offenlegt. Ein Dataset, das Anmeldedaten enthält, muss beispielsweise keine Zahlungsinformationen enthalten. Wenn Sie Daten in Partitionen organisieren, kann Ihr Dienst die Mindestmenge an relevanten Informationen bereitstellen, die zum Erfüllen einer Anfrage erforderlich ist.
Durch das Organisieren von Daten in Partitionen können Dienste Aktivitäten mit Ansichten aus mehreren Partitionen ausfüllen und gleichzeitig die Mindestmenge an relevanten Daten an die Client-App senden. Nehmen wir beispielsweise eine Aktivität mit Ansichten für Nutzername, Passwort, Straße und Stadt und einen Autofill-Dienst mit den folgenden Daten an:
Partition | Feld 1 | Feld 2 |
---|---|---|
Anmeldedaten | work_username | work_password |
personal_username | personal_password | |
Adresse | work_street | work_city |
personal_street | personal_city |
Der Dienst kann ein Dataset vorbereiten, das die Anmeldedatenpartition für das Arbeitskonto und das private Konto enthält. Wenn der Nutzer ein Dataset auswählt, kann eine nachfolgende Autofill-Antwort je nach erster Auswahl des Nutzers entweder die geschäftliche oder die private Adresse enthalten.
Ein Dienst kann das Feld ermitteln, aus dem die Anfrage stammt, indem er beim Durchlaufen des AssistStructure
-Objekts die Methode isFocused()
aufruft. So kann der Dienst eine FillResponse
mit den entsprechenden Partitionsdaten vorbereiten.
Automatisches Ausfüllen von einmaligen SMS-Codes
Ihr AutoFill-Dienst kann den Nutzer beim Ausfüllen von Einmalcodes unterstützen, die über SMS gesendet werden. Dazu wird die SMS Retriever API verwendet.
Damit Sie diese Funktion nutzen können, müssen die folgenden Voraussetzungen erfüllt sein:
- Der Autofill-Dienst wird unter Android 9 (API-Level 28) oder höher ausgeführt.
- Der Nutzer erteilt die Einwilligung, dass Ihr Autofill-Dienst Einmalcodes aus SMS lesen darf.
- Die Anwendung, für die Sie die Autofill-Funktion bereitstellen, verwendet noch nicht die SMS Retriever API zum Lesen von Einmalcodes.
Ihr Dienst für automatisches Ausfüllen kann SmsCodeAutofillClient
verwenden, das durch Aufrufen von SmsCodeRetriever.getAutofillClient()
aus Google Play-Diensten 19.0.56 oder höher verfügbar ist.
Die wichtigsten Schritte für die Verwendung dieser API in einem Autofill-Service sind:
- Verwenden Sie im Autofill-Dienst
hasOngoingSmsRequest
ausSmsCodeAutofillClient
, um festzustellen, ob für den Paketnamen der Anwendung, die Sie automatisch ausfüllen, aktive Anfragen vorhanden sind. Ihr Autofill-Dienst darf nur dann einen Vorschlag-Prompt anzeigen, wennfalse
zurückgegeben wird. - Verwenden Sie im Autofill-Service
checkPermissionState
ausSmsCodeAutofillClient
, um zu prüfen, ob der Autofill-Service die Berechtigung hat, Einmalcodes automatisch auszufüllen. Dieser Berechtigungsstatus kannNONE
,GRANTED
oderDENIED
sein. Der Autofill-Dienst muss für die StatusNONE
undGRANTED
eine Aufforderung zur Vervollständigung anzeigen. - Verwenden Sie in der Aktivität zur Autofill-Authentifizierung die Berechtigung
SmsRetriever.SEND_PERMISSION
, um einenBroadcastReceiver
zu registrieren, der aufSmsCodeRetriever.SMS_CODE_RETRIEVED_ACTION
wartet, um das SMS-Code-Ergebnis zu empfangen, sobald es verfügbar ist. Rufen Sie
startSmsCodeRetriever
aufSmsCodeAutofillClient
auf, um auf einmalige Codes zu warten, die per SMS gesendet werden. Wenn der Nutzer Berechtigungen für Ihren AutoFill-Dienst erteilt, um Einmalcodes aus SMS abzurufen, wird nach SMS gesucht, die in den letzten ein bis fünf Minuten eingegangen sind.Wenn Ihr Autofill-Dienst die Nutzerberechtigung zum Lesen von Einmalcodes anfordern muss, schlägt die von
startSmsCodeRetriever
zurückgegebeneTask
möglicherweise mit dem FehlerResolvableApiException
fehl. In diesem Fall müssen Sie die MethodeResolvableApiException.startResolutionForResult()
aufrufen, um ein Einwilligungsdialogfeld für die Berechtigungsanfrage zu präsentieren.Empfangen Sie das SMS-Code-Ergebnis aus dem Intent und geben Sie den SMS-Code dann als Autofill-Antwort zurück.
Autofill in Chrome aktivieren
In Chrome können externe Autofill-Dienste Formulare automatisch ausfüllen. Das sorgt für eine reibungslose und einfache Nutzererfahrung. Wenn Nutzer Autofill-Dienste von Drittanbietern verwenden möchten, um Passwörter, Passkeys und andere Informationen wie Adressen und Zahlungsdaten automatisch ausfüllen zu lassen, müssen sie in den Chrome-Einstellungen Autofill mit einem anderen Dienst auswählen.
Damit Nutzer mit Ihrem Dienst und Chrome unter Android das bestmögliche Autofill-Erlebnis haben, sollten Autofill-Dienstanbieter ihre Nutzer dazu anhalten, ihren bevorzugten Dienstanbieter in den Chrome-Einstellungen anzugeben.
So können Entwickler Nutzern helfen, den Schalter zu aktivieren:
- Chrome-Einstellungen abfragen und ermitteln, ob der Nutzer einen Drittanbieter-Autofill-Dienst verwenden möchte.
- Deeplink zur Seite mit den Chrome-Einstellungen, auf der Nutzer Autofill-Dienste von Drittanbietern aktivieren können.
Chrome-Einstellungen lesen
Jede App kann lesen, ob Chrome den Autofill-Modus von Drittanbietern verwendet, der die Verwendung von Android Autofill ermöglicht. Chrome verwendet die ContentProvider
von Android, um diese Informationen zu kommunizieren. Deklarieren Sie in Ihrem Android-Manifest, von welchen Kanälen Sie Einstellungen lesen möchten:
<uses-permission android:name="android.permission.READ_USER_DICTIONARY"/>
<queries>
<!-- To Query Chrome Beta: -->
<package android:name="com.chrome.beta" />
<!-- To Query Chrome Stable: -->
<package android:name="com.android.chrome" />
</queries>
Verwenden Sie dann die ContentResolver
von Android, um diese Informationen anzufordern. Dazu müssen Sie den Content-URI erstellen:
Kotlin
val CHROME_CHANNEL_PACKAGE = "com.android.chrome" // Chrome Stable. val CONTENT_PROVIDER_NAME = ".AutofillThirdPartyModeContentProvider" val THIRD_PARTY_MODE_COLUMN = "autofill_third_party_state" val THIRD_PARTY_MODE_ACTIONS_URI_PATH = "autofill_third_party_mode" val uri = Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(CHROME_CHANNEL_PACKAGE + CONTENT_PROVIDER_NAME) .path(THIRD_PARTY_MODE_ACTIONS_URI_PATH) .build() val cursor = contentResolver.query( uri, arrayOf(THIRD_PARTY_MODE_COLUMN), // projection null, // selection null, // selectionArgs null // sortOrder ) if (cursor == null) { // Terminate now! Older versions of Chromium don't provide this information. } cursor?.use { // Use the safe call operator and the use function for auto-closing if (it.moveToFirst()) { // Check if the cursor has any rows val index = it.getColumnIndex(THIRD_PARTY_MODE_COLUMN) if (index != -1) { // Check if the column exists val value = it.getInt(index) if (0 == value) { // 0 means that the third party mode is turned off. Chrome uses its built-in // password manager. This is the default for new users. } else { // 1 means that the third party mode is turned on. Chrome uses forwards all // autofill requests to Android Autofill. Users have to opt-in for this. } } else { // Handle the case where the column doesn't exist. Log a warning, perhaps. Log.w("Autofill", "Column $THIRD_PARTY_MODE_COLUMN not found in cursor") } } } // The cursor is automatically closed here
Java
final String CHROME_CHANNEL_PACKAGE = "com.android.chrome"; // Chrome Stable. final String CONTENT_PROVIDER_NAME = ".AutofillThirdPartyModeContentProvider"; final String THIRD_PARTY_MODE_COLUMN = "autofill_third_party_state"; final String THIRD_PARTY_MODE_ACTIONS_URI_PATH = "autofill_third_party_mode"; final Uri uri = new Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(CHROME_CHANNEL_PACKAGE + CONTENT_PROVIDER_NAME) .path(THIRD_PARTY_MODE_ACTIONS_URI_PATH) .build(); final Cursor cursor = getContentResolver().query( uri, /*projection=*/new String[] {THIRD_PARTY_MODE_COLUMN}, /*selection=*/ null, /*selectionArgs=*/ null, /*sortOrder=*/ null); if (cursor == null) { // Terminate now! Older versions of Chromium don't provide this information. } cursor.moveToFirst(); // Retrieve the result; int index = cursor.getColumnIndex(THIRD_PARTY_MODE_COLUMN); if (0 == cursor.getInt(index)) { // 0 means that the third party mode is turned off. Chrome uses its built-in // password manager. This is the default for new users. } else { // 1 means that the third party mode is turned on. Chrome uses forwards all // autofill requests to Android Autofill. Users have to opt-in for this. }
Deeplink zu den Chrome-Einstellungen
Wenn Sie einen Deeplink zur Chrome-Einstellungsseite erstellen möchten, auf der Nutzer Autofill-Dienste von Drittanbietern aktivieren können, verwenden Sie einen Android-Intent
.
Konfigurieren Sie die Aktion und die Kategorien wie in diesem Beispiel:
Kotlin
val autofillSettingsIntent = Intent(Intent.ACTION_APPLICATION_PREFERENCES) autofillSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT) autofillSettingsIntent.addCategory(Intent.CATEGORY_APP_BROWSER) autofillSettingsIntent.addCategory(Intent.CATEGORY_PREFERENCE) // Invoking the intent with a chooser allows users to select the channel they // want to configure. If only one browser reacts to the intent, the chooser is // skipped. val chooser = Intent.createChooser(autofillSettingsIntent, "Pick Chrome Channel") startActivity(chooser) // If the caller knows which Chrome channel they want to configure, // they can instead add a package hint to the intent, e.g. val specificChromeIntent = Intent(Intent.ACTION_APPLICATION_PREFERENCES) // Create a *new* intent specificChromeIntent.addCategory(Intent.CATEGORY_DEFAULT) specificChromeIntent.addCategory(Intent.CATEGORY_APP_BROWSER) specificChromeIntent.addCategory(Intent.CATEGORY_PREFERENCE) specificChromeIntent.setPackage("com.android.chrome") // Set the package on the *new* intent startActivity(specificChromeIntent) // Start the *new* intent
Java
Intent autofillSettingsIntent = new Intent(Intent.ACTION_APPLICATION_PREFERENCES); autofillSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT); autofillSettingsIntent.addCategory(Intent.CATEGORY_APP_BROWSER); autofillSettingsIntent.addCategory(Intent.CATEGORY_PREFERENCE); // Invoking the intent with a chooser allows users to select the channel they // want to configure. If only one browser reacts to the intent, the chooser is // skipped. Intent chooser = Intent.createChooser(autofillSettingsIntent, "Pick Chrome Channel"); startActivity(chooser); // If the caller knows which Chrome channel they want to configure, // they can instead add a package hint to the intent, e.g. autofillSettingsIntent.setPackage("com.android.chrome"); startActivity(autofillSettingsInstent);
Erweiterte Autofill-Szenarien
- Mit Tastatur integrieren
- Ab Android 11 können Tastaturen und andere Eingabemethoden-Editoren (IMEs) Vorschläge für die automatische Vervollständigung inline anzeigen, anstatt ein Drop-down-Menü zu verwenden. Weitere Informationen dazu, wie Ihr Dienst zum automatischen Ausfüllen diese Funktion unterstützen kann, finden Sie unter Autofill in Tastaturen einbinden.
- Datasets paginieren
- Eine große Autofill-Antwort kann die zulässige Transaktionsgröße des
Binder
-Objekts überschreiten, das das für die Verarbeitung der Anfrage erforderliche Remote-Objekt darstellt. Damit das Android-System in diesen Szenarien keine Ausnahme auslöst, können Sie dieFillResponse
klein halten, indem Sie jeweils nicht mehr als 20Dataset
-Objekte hinzufügen. Wenn für Ihre Antwort weitere Datasets erforderlich sind, können Sie ein Dataset hinzufügen, das Nutzer darüber informiert, dass weitere Informationen verfügbar sind, und die nächste Gruppe von Datasets abruft, wenn es ausgewählt wird. Weitere Informationen finden Sie unteraddDataset(Dataset)
. - Auf mehreren Bildschirmen aufgeteilte Daten speichern
In Apps werden Nutzerdaten häufig auf mehrere Bildschirme in derselben Aktivität aufgeteilt, insbesondere in Aktivitäten, die zum Erstellen eines neuen Nutzerkontos verwendet werden. Auf dem ersten Bildschirm wird beispielsweise nach einem Nutzernamen gefragt. Wenn der Nutzername verfügbar ist, wird auf dem zweiten Bildschirm nach einem Passwort gefragt. In diesen Fällen muss der Autofill-Dienst warten, bis der Nutzer beide Felder ausgefüllt hat, bevor die Benutzeroberfläche zum Speichern von Autofill-Daten angezeigt werden kann. So gehen Sie in solchen Fällen vor:
- Fügen Sie in der ersten Anfrage zum Ausfüllen in die Antwort ein Clientstatus-Bundle ein, das die IDs für das automatische Ausfüllen der auf dem Bildschirm vorhandenen Teilfelder enthält.
- Rufen Sie in der zweiten Fill-Anfrage das Clientstatus-Bundle ab, rufen Sie die in der vorherigen Anfrage festgelegte Autofill-ID-Gruppe aus dem Clientstatus ab und fügen Sie diese IDs und das Flag
FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE
demSaveInfo
-Objekt hinzu, das in der zweiten Antwort verwendet wird. - Verwenden Sie in der Speicheranfrage die entsprechenden
FillContext
-Objekte, um den Wert jedes Felds abzurufen. Es gibt einen Füllkontext pro Füllanfrage.
Weitere Informationen finden Sie unter Speichern, wenn Daten auf mehrere Bildschirme aufgeteilt werden.
- Initialisierungs- und Bereinigungslogik für jede Anfrage bereitstellen
Bei jeder Autofill-Anfrage bindet das Android-System an den Dienst und ruft seine
onConnected()
-Methode auf. Sobald der Dienst die Anfrage verarbeitet hat, ruft das Android-System die MethodeonDisconnected()
auf und löst die Bindung an den Dienst auf. Sie könnenonConnected()
implementieren, um Code bereitzustellen, der vor der Verarbeitung einer Anfrage ausgeführt wird, undonDisconnected()
, um Code bereitzustellen, der nach der Verarbeitung einer Anfrage ausgeführt wird.- Benutzeroberfläche zum Speichern von Autofill-Daten anpassen
Autofill-Dienste können die Benutzeroberfläche zum Speichern von Autofill-Daten anpassen, damit Nutzer entscheiden können, ob der Dienst ihre Daten speichern darf. Dienste können zusätzliche Informationen dazu liefern, was gespeichert wird, entweder in Form von einfachem Text oder in einer benutzerdefinierten Ansicht. Dienste können auch die Darstellung der Schaltfläche ändern, mit der die Speicheranfrage abgebrochen wird, und eine Benachrichtigung erhalten, wenn der Nutzer auf diese Schaltfläche tippt. Weitere Informationen finden Sie auf der Referenzseite zu
SaveInfo
.- Kompatibilitätsmodus
Im Kompatibilitätsmodus können Autofill-Dienste die virtuelle Struktur für Bedienungshilfen für Autofill-Zwecke verwenden. Das ist besonders nützlich, um die Autofill-Funktion in Browsern bereitzustellen, die die Autofill-APIs nicht explizit implementieren.
Wenn Sie Ihren Autofill-Service im Kompatibilitätsmodus testen möchten, müssen Sie den Browser oder die App, für die der Kompatibilitätsmodus erforderlich ist, explizit auf die Zulassungsliste setzen. Mit dem folgenden Befehl können Sie prüfen, welche Pakete bereits auf der Zulassungsliste stehen:
$ adb shell settings get global autofill_compat_mode_allowed_packages
Wenn das Paket, das Sie testen, nicht aufgeführt ist, fügen Sie es hinzu, indem Sie den folgenden Befehl ausführen. Dabei ist
pkgX
das Paket der App:$ adb shell settings put global autofill_compat_mode_allowed_packages pkg1[resId1]:pkg2[resId1,resId2]
Wenn es sich bei der App um einen Browser handelt, verwenden Sie
resIdx
, um die Ressourcen-ID des Eingabefelds anzugeben, das die URL der gerenderten Seite enthält.
Der Kompatibilitätsmodus unterliegt den folgenden Einschränkungen:
- Eine Speicheranfrage wird ausgelöst, wenn der Dienst das Flag
FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE
verwendet oder die MethodesetTrigger()
aufgerufen wird.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE
ist standardmäßig festgelegt, wenn der Kompatibilitätsmodus verwendet wird. - Der Textwert der Knoten ist möglicherweise nicht in der Methode
onSaveRequest(SaveRequest, SaveCallback)
verfügbar.
Weitere Informationen zum Kompatibilitätsmodus, einschließlich der damit verbundenen Einschränkungen, finden Sie in der Klassenreferenz zu AutofillService
.