AutoFill-Dienste erstellen

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 verwalten, z. B. Passwortmanager.

Android erleichtert das Ausfüllen von Formularen mit dem Autofill-Framework, das in Android 8.0 (API-Ebene 26) und höher verfügbar ist. Nutzer können die Autofill-Funktionen nur dann nutzen, wenn auf ihrem Gerät eine App installiert ist, die Autofill-Dienste bereitstellt.

Auf dieser Seite erfahren Sie, wie Sie einen Autofill-Dienst in Ihrer App implementieren. Ein Codebeispiel zur Implementierung eines Dienstes finden Sie im AutofillFramework-Beispiel in Java oder Kotlin. 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 bereitstellen, 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:

  • android:name-Attribut, das auf die Unterklasse von AutofillService in der App verweist, die den Dienst implementiert.
  • android:permission-Attribut, das die Berechtigung BIND_AUTOFILL_SERVICE deklariert.
  • <intent-filter>-Element, dessen obligatorisches untergeordnetes Element <action> die android.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 eine Deklaration für einen Autofill-Dienst:

<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 das Attribut android:resource, das auf eine XML-Ressource mit weiteren Details zum Dienst verweist. Die Ressource service_configuration im vorherigen Beispiel gibt eine Aktivität an, mit der Nutzer den Dienst konfigurieren können. Das folgende Beispiel zeigt die service_configuration-XML-Ressource:

<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 – Übersicht.

Aufforderung zum Aktivieren des Dienstes

Eine App wird als Autofill-Dienst verwendet, nachdem sie die BIND_AUTOFILL_SERVICE-Berechtigung erklärt 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 bitten, die Autofill-Einstellungen mithilfe der ACTION_REQUEST_SET_AUTOFILL_SERVICE-Intent 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 erhält Anfragen zum Ausfüllen von Kundenansichten, wenn der Nutzer mit anderen Apps interagiert. Wenn der Autofill-Dienst Nutzerdaten hat, die der Anfrage entsprechen, werden die Daten in der Antwort gesendet. Das Android-System zeigt eine Autofill-Benutzeroberfläche mit den verfügbaren Daten an, wie in Abbildung 1 dargestellt:

Autofill-UI

Abbildung 1: Benutzeroberfläche für die automatische Vervollständigung mit einem Datensatz

Das Autofill-Framework definiert einen Workflow zum Ausfüllen von Ansichten, der die Zeit minimiert, in der das Android-System an den Autofill-Dienst gebunden ist. Bei jeder Anfrage sendet das Android-System ein AssistStructure-Objekt an den Dienst, indem die Methode onFillRequest() aufgerufen wird.

Der Autofill-Dienst prüft, ob er die Anfrage mit zuvor gespeicherten Nutzerdaten erfüllen kann. Wenn die Anfrage erfüllt werden kann, verpackt der Dienst die Daten in Dataset-Objekten. Der Dienst ruft die Methode onSuccess() auf und übergibt ein FillResponse-Objekt, das die Dataset-Objekte enthält. Wenn der Dienst keine Daten für die Anfrage hat, wird null an die onSuccess()-Methode übergeben.

Wenn bei der Verarbeitung der Anfrage ein Fehler auftritt, ruft der Dienst stattdessen die Methode onFailure() auf. Eine ausführliche Erklärung des Workflows finden Sie in der Beschreibung auf der Referenzseite für 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 Datensätze haben, die der Anfrage entsprechen. In diesem Fall zeigt das Android-System in der Benutzeroberfläche für die automatische Vervollständigung mehrere Optionen an, eine für jeden Datensatz. Im folgenden Codebeispiel wird gezeigt, wie mehrere Datensätze 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 aufrufen, um die Autofill-Daten abzurufen, die für die Ausführung der Anfrage erforderlich sind. Ein Dienst kann Autofill-Daten mithilfe von Methoden der Klasse ViewNode abrufen, z. B. getAutofillId().

Ein Dienst muss den Inhalt einer Ansicht beschreiben können, um zu prüfen, ob er die Anfrage erfüllen kann. Die Verwendung des Attributs autofillHints ist der erste Ansatz, den ein Dienst zur Beschreibung des Inhalts einer Ansicht verwenden muss. Client-Apps müssen das Attribut jedoch explizit in ihren Ansichten angeben, damit es für den Dienst verfügbar ist.

Wenn eine Client-App das autofillHints-Attribut nicht angibt, muss ein Dienst seine eigenen Heuristiken verwenden, um den Inhalt zu beschreiben. Der Dienst kann Methoden aus anderen Klassen wie getText() oder getHint() verwenden, um Informationen zum Inhalt der Ansicht abzurufen. Weitere Informationen finden Sie unter Hinweise für das automatische Ausfüllen bereitstellen.

Im folgenden Beispiel wird gezeigt, wie Sie 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-Dienst 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, wie in Abbildung 2 dargestellt.

Benutzeroberfläche für das Speichern von Autofill-Informationen

Abbildung 2: Benutzeroberfläche für das Speichern von Autofill-Informationen

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 Ansichten ausfüllen kann. Um anzugeben, dass er an der Speicherung der Daten interessiert ist, fügt der Dienst in der Antwort auf die Anfrage zum Ausfüllen ein SaveInfo-Objekt ein. Das SaveInfo-Objekt muss mindestens die folgenden Daten enthalten:

  • Die Art der gespeicherten Nutzerdaten. Eine Liste der verfügbaren SAVE_DATA-Werte finden Sie unter SaveInfo.
  • Die Mindestanzahl an Ansichten, die geändert werden müssen, um eine Speicheranfrage auszulösen. Bei einem Anmeldeformular muss der Nutzer beispielsweise in der Regel die Ansichten username und password aktualisieren, um eine Speicheranfrage auszulösen.

Ein SaveInfo-Objekt ist mit einem FillResponse-Objekt verknüpft, 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 Methode wird normalerweise aufgerufen, nachdem die Clientaktivität beendet wurde 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 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, auch wenn sich der Nutzer nicht authentifiziert hat. Anschließend können Dienste die Labels nach der Authentifizierung des Nutzers durch die tatsächlichen Daten ersetzen.

Benutzeroberfläche für das Speichern von Autofill-Informationen verschieben

Wenn Sie ab Android 10 mehrere Bildschirme für die Implementierung eines Autofill-Workflows verwenden, 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 mit dem Flag SaveInfo.FLAG_DELAY_SAVE verschieben.

Wenn dieses Flag gesetzt ist, wird die Benutzeroberfläche zum Speichern von Autofill-Daten nicht ausgelöst, wenn der Autofill-Kontext, der mit der SaveInfo-Antwort verknüpft ist, verbindlich gemacht wird. Stattdessen können Sie eine separate Aktivität innerhalb derselben Aufgabe verwenden, um zukünftige Ausfüllanfragen zu senden und dann die Benutzeroberfläche über eine Speicheranfrage anzuzeigen. Weitere Informationen finden Sie unter SaveInfo.FLAG_DELAY_SAVE.

Nutzerauthentifizierung erforderlich machen

Autofill-Dienste können für zusätzliche Sicherheit sorgen, indem der Nutzer authentifiziert werden muss, bevor er Ansichten ausfüllen kann. Die folgenden Szenarien eignen sich gut für die Implementierung der Nutzerauthentifizierung:

  • Die Nutzerdaten in der App müssen mit einem primären Passwort oder einem Fingerabdruck gescannt werden, um entsperrt zu werden.
  • Ein bestimmter Datensatz muss entsperrt werden, z. B. Kreditkartendetails mithilfe eines Kartenprüfcodes (CVC).

Wenn für den Dienst eine Nutzerauthentifizierung erforderlich ist, bevor die Daten entsperrt werden, kann der Dienst Standarddaten oder ein Label anzeigen und die Intent angeben, die für die Authentifizierung zuständig ist. Wenn Sie nach Abschluss des Authentifizierungsvorgangs zusätzliche Daten zur Verarbeitung der Anfrage benötigen, können Sie diese dem Intent hinzufügen. Ihre Authentifizierungsaktivitäten können die Daten dann an die AutofillService-Klasse in Ihrer App zurückgeben.

Im folgenden Codebeispiel wird angegeben, 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 FillResponse-Objekt, das den ausgefüllten Datensatz enthält, um das EXTRA_AUTHENTICATION_RESULT-Extra ergänzen. Im folgenden Code wird ein Beispiel dafür gezeigt, wie das Ergebnis zurückgegeben wird, sobald die Authentifizierung abgeschlossen ist:

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 der Dienst eine Benutzeroberfläche anzeigen, auf der nach der CVC gefragt wird. Sie können die Daten ausblenden, bis der Datensatz entsperrt wird, indem Sie Standarddaten wie den Namen der Bank und die letzten vier Ziffern der Kreditkartennummer präsentieren. Im folgenden Beispiel wird gezeigt, wie eine Authentifizierung für einen Datensatz erforderlich gemacht und die Daten ausgeblendet werden, bis der Nutzer die CVC angegeben hat:

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_AUTHENTICATION_RESULT-Extra 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 Code wird gezeigt, wie der Datensatz zurückgegeben wird, sobald der Nutzer die 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 Domains voneinander abgrenzen. 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, einschließlich der Felder „Straße“, „Ort“, „Bundesland“ und „Postleitzahl“.
  • Zahlungsinformationen, einschließlich Kreditkartennummer, Ablaufdatum und Sicherheitscode

Ein Autofill-Dienst, der Daten korrekt partitioniert, kann die Daten seiner Nutzer besser schützen, da keine Daten aus mehr als einer Partition in einem Datensatz offengelegt werden. Ein Datensatz mit Anmeldedaten muss beispielsweise keine Zahlungsinformationen enthalten. Wenn Sie Daten in Partitionen organisieren, kann Ihr Dienst die minimale Menge an relevanten Informationen offenlegen, die für die Bearbeitung einer Anfrage erforderlich sind.

Durch die Organisation von Daten in Partitionen können Dienste Aktivitäten mit Ansichten aus mehreren Partitionen ausfüllen und gleichzeitig die minimale Menge an relevanten Daten an die Client-App senden. Angenommen, eine Aktivität enthält Ansichten für Nutzername, Passwort, Straße und Ort und einen Autofill-Dienst mit den folgenden Daten:

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 einen Datensatz mit der Anmeldedatenpartition für das geschäftliche und das private Konto vorbereiten. Wenn der Nutzer einen Datensatz auswählt, kann eine nachfolgende Autofill-Antwort je nach der ersten Auswahl des Nutzers entweder die geschäftliche oder die private Adresse enthalten.

Ein Dienst kann das Feld ermitteln, von dem die Anfrage stammt, indem er die Methode isFocused() aufruft, während er das AssistStructure-Objekt durchläuft. So kann der Dienst eine FillResponse mit den entsprechenden Partitionsdaten vorbereiten.

Automatisches Ausfüllen von einmaligen SMS-Codes

Ihr Autofill-Dienst kann Nutzern mithilfe der SMS Retriever API dabei helfen, per SMS gesendete Einmalcodes einzugeben.

Für die Nutzung dieser Funktion 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 Ihrem Autofill-Dienst die Berechtigung, Einmalcodes aus SMS zu lesen.
  • Die Anwendung, für die Sie die Autofill-Funktion bereitstellen, verwendet noch nicht die SMS Retriever API zum Lesen von Einmalcodes.

Ihr Autofill-Dienst kann SmsCodeAutofillClient verwenden. Diese Funktion ist verfügbar, wenn Sie SmsCodeRetriever.getAutofillClient() über Google Play-Dienste 19.0.56 oder höher aufrufen.

Die wichtigsten Schritte zur Verwendung dieser API in einem Autofill-Dienst sind:

  1. Verwenden Sie im Autofill-Dienst hasOngoingSmsRequest von SmsCodeAutofillClient, um festzustellen, ob Anfragen für den Paketnamen der Anwendung aktiv sind, für die Sie das Autofill verwenden. Ihr Autofill-Dienst darf nur dann einen Vorschlag anzeigen, wenn false zurückgegeben wird.
  2. Verwenden Sie im Autofill-Dienst checkPermissionState von SmsCodeAutofillClient, um zu prüfen, ob der Autofill-Dienst berechtigt ist, Einmalcodes automatisch auszufüllen. Dieser Berechtigungsstatus kann NONE, GRANTED oder DENIED sein. Der Autofill-Dienst muss für die Bundesstaaten NONE und GRANTED einen Vorschlag anzeigen.
  3. Verwenden Sie bei der Autofill-Authentifizierungsaktivität die Berechtigung SmsRetriever.SEND_PERMISSION, um einen BroadcastReceiver zu registrieren, der auf SmsCodeRetriever.SMS_CODE_RETRIEVED_ACTION wartet, um das Ergebnis des SMS-Codes zu erhalten, sobald es verfügbar ist.
  4. Rufen Sie startSmsCodeRetriever auf SmsCodeAutofillClient, um sich per SMS gesendete Einmalcodes ansagen zu lassen. Wenn der Nutzer Ihrem AutoFill-Dienst Berechtigungen zum Abrufen von Einmalcodes aus SMS erteilt, wird nach SMS-Nachrichten 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ückgegebene Task möglicherweise fehl und es wird ResolvableApiException zurückgegeben. In diesem Fall müssen Sie die Methode ResolvableApiException.startResolutionForResult() aufrufen, um ein Einwilligungsdialogfeld für die Berechtigungsanfrage anzuzeigen.

  5. Empfangen Sie das SMS-Code-Ergebnis von der Intent-Anfrage und geben Sie den SMS-Code dann als Autofill-Antwort zurück.

Erweiterte Autofill-Szenarien

Integration in die Tastatur
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 Autofill-Dienst diese Funktion unterstützen kann, finden Sie unter Autofill in Tastaturen einbinden.
Datasets paginatieren
Eine große Autofill-Antwort kann die zulässige Transaktionsgröße des Binder-Objekts überschreiten, das das Remote-Objekt darstellt, das zur Verarbeitung der Anfrage erforderlich ist. Um zu verhindern, dass das Android-System in diesen Fällen eine Ausnahme auslöst, können Sie FillResponse klein halten, indem Sie jeweils nicht mehr als 20 Dataset-Objekte hinzufügen. Wenn für Ihre Antwort weitere Datensätze erforderlich sind, können Sie einen Datensatz hinzufügen, der Nutzer darüber informiert, dass es weitere Informationen gibt, und die nächste Gruppe von Datensätzen abruft, wenn er ausgewählt wird. Weitere Informationen finden Sie unter addDataset(Dataset).
Daten auf mehreren Bildschirmen speichern

In Apps werden Nutzerdaten bei derselben Aktivität häufig auf mehrere Bildschirme aufgeteilt, insbesondere bei Aktivitäten, mit denen ein neues Nutzerkonto erstellt wird. 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. Gehen Sie in solchen Fällen so vor:

  1. Fügen Sie in der ersten Füllungsanfrage in der Antwort ein Clientstatus-Bundle mit den Autofill-IDs der teilweise ausgefüllten Felder auf dem Bildschirm hinzu.
  2. Rufen Sie in der zweiten Fill-Anfrage das Client-Status-Bundle ab, rufen Sie die in der vorherigen Anfrage im Clientstatus festgelegten Autofill-IDs ab und fügen Sie diese IDs und das Flag FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE dem SaveInfo-Objekt hinzu, das in der zweiten Antwort verwendet wird.
  3. Verwende 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 verteilt sind.

Initialisierungs- und Deaktivierungslogik für jede Anfrage bereitstellen

Jedes Mal, wenn eine Autofill-Anfrage erfolgt, bindet das Android-System an den Dienst und ruft dessen onConnected()-Methode auf. Sobald der Dienst die Anfrage verarbeitet hat, ruft das Android-System die Methode onDisconnected() auf und trennt die Bindung zum Dienst. Sie können onConnected() implementieren, um Code bereitzustellen, der vor der Verarbeitung einer Anfrage ausgeführt wird, und onDisconnected(), um Code bereitzustellen, der nach der Verarbeitung einer Anfrage ausgeführt wird.

Benutzeroberfläche für das Speichern von Autofill-Daten anpassen

Anbieter von Autofill-Diensten können die Benutzeroberfläche für das Speichern von Autofill-Daten anpassen, damit Nutzer entscheiden können, ob sie dem Dienst erlauben möchten, ihre Daten zu speichern. Dienste können zusätzliche Informationen dazu bereitstellen, was gespeichert wird, entweder durch einfachen Text oder durch eine benutzerdefinierte Ansicht. Dienste können auch das Aussehen der Schaltfläche ändern, mit der der Speichervorgang 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 das automatische Ausfüllen verwenden. Sie ist besonders nützlich, um Autofill-Funktionen in Browsern bereitzustellen, die die Autofill APIs nicht explizit implementieren.

Wenn Sie Ihren Autofill-Dienst im Kompatibilitätsmodus testen möchten, setzen Sie den Browser oder die App, für die der Kompatibilitätsmodus erforderlich ist, ausdrücklich auf die Zulassungsliste. 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 mit dem folgenden Befehl hinzu. Dabei steht pkgX für 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, geben Sie mit resIdx die Ressourcen-ID des Eingabefelds an, das die URL der gerenderten Seite enthält.

Der Kompatibilitätsmodus hat folgende Einschränkungen:

  • Eine Speicheranfrage wird ausgelöst, wenn der Dienst das Flag FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE verwendet oder die Methode setTrigger() aufgerufen wird. FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE ist standardmäßig bei Verwendung des Kompatibilitätsmodus festgelegt.
  • 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 für AutofillService.