AutoFill-Dienste erstellen

Ein Autofill-Dienst ist eine App, mit der Nutzer Formulare ausfüllen können, 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 ab Android 8.0 (API-Level 26) verfügbaren Autofill-Framework. Nutzer können die Autofill-Funktionen nur verwenden, wenn es eine App gibt, die Autofill-Dienste auf ihrem Gerät bereitstellt.

Auf dieser Seite wird beschrieben, wie Sie einen Autofill-Dienst in Ihrer App implementieren. Ein Codebeispiel, das zeigt, wie ein Dienst implementiert wird, finden Sie im AutofillFramework-Beispiel in Java oder Kotlin. Weitere Informationen zur Funktionsweise von Autofill-Diensten finden Sie auf den Referenzseiten der 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 dazu ein <service>-Element in das App-Manifest ein. Das Element <service> muss die folgenden Attribute und Elemente enthalten:

  • android:name-Attribut, das auf die abgeleitete Klasse von AutofillService in der Anwendung verweist, die den Dienst implementiert.
  • android:permission-Attribut, das die Berechtigung BIND_AUTOFILL_SERVICE angibt.
  • <intent-filter>-Element, dessen obligatorisches untergeordnetes <action>-Element die Aktion android.service.autofill.AutofillService angibt.
  • Optionales Element <meta-data>, mit dem Sie zusätzliche Konfigurationsparameter für den Dienst angeben können

Das folgende Beispiel zeigt eine Erklärung für einen Autofill-Service:

<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 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 in der Übersicht über Anwendungsressourcen.

Aufforderung zum Aktivieren des Dienstes

Eine App wird als Autofill-Dienst verwendet, nachdem sie die Berechtigung BIND_AUTOFILL_SERVICE deklariert und der Nutzer sie in den Geräteeinstellungen aktiviert hat. Eine Anwendung kann durch Aufrufen der Methode hasEnabledAutofillServices() der Klasse AutofillManager prüfen, ob es der derzeit aktivierte Dienst ist.

Wenn die App nicht der aktuelle Autofill-Service ist, kann sie den Nutzer auffordern, die Autofill-Einstellungen mithilfe des Intents ACTION_REQUEST_SET_AUTOFILL_SERVICE zu ändern. Der Intent gibt den Wert RESULT_OK zurück, wenn der Nutzer einen Autofill-Dienst auswählt, der mit dem Paket des Aufrufers übereinstimmt.

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 der Anfrage entsprechen, sendet er die Daten in der Antwort. Das Android-System zeigt eine Autofill-UI mit den verfügbaren Daten an, wie in Abbildung 1 dargestellt:

Autofill-Benutzeroberfläche

Abbildung 1: Autofill-Benutzeroberfläche, auf der ein Datensatz angezeigt wird

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

Der Autofill-Dienst prüft, ob die Anfrage mit zuvor gespeicherten Nutzerdaten erfüllt werden kann. Wenn die Anfrage erfüllt werden kann, erstellt der Dienst ein Paket für die Daten in den 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 hat, um die Anfrage zu erfüllen, übergibt er null an die Methode onSuccess().

Wenn bei der Verarbeitung der Anfrage ein Fehler auftritt, ruft der Dienst stattdessen die Methode onFailure() auf. Eine detaillierte Erläuterung des Workflows finden Sie in der Beschreibung auf der AutofillService-Referenzseite.

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 der Anfrage entsprechen. In diesem Fall zeigt das Android-System mehrere Optionen – eine für jedes Dataset – in der Autofill-UI an. Das folgende Codebeispiel zeigt, wie Sie mehrere Datasets in einer Antwort bereitstellen:

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 zur Ausführung der Anfrage erforderlich sind. Ein Dienst kann Autofill-Daten mit Methoden der ViewNode-Klasse wie getAutofillId() abrufen.

Ein Dienst muss den Inhalt einer Ansicht beschreiben können, um zu prüfen, ob die Anfrage erfüllt werden kann. Die Verwendung des Attributs autofillHints ist der erste Ansatz, mit dem ein Dienst den Inhalt einer Ansicht beschreiben muss. Clientanwendungen müssen das Attribut jedoch explizit in ihren Ansichten angeben, bevor es für den Dienst verfügbar ist.

Wenn eine Clientanwendung das Attribut autofillHints nicht bereitstellt, muss ein Dienst seine eigene Heuristik zur Beschreibung der Inhalte verwenden. 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 Autofill bereitstellen.

Das folgende Beispiel zeigt, wie AssistStructure durchlaufen und AutoFill-Daten aus einem ViewNode-Objekt abgerufen werden:

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 Datenansichten 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).

Benutzeroberfläche zum Speichern von Autofill

Abbildung 2: Benutzeroberfläche zum Speichern von Autofill.

Zum Speichern der Daten muss der Dienst angeben, dass er die Daten für eine zukünftige Verwendung speichern möchte. Bevor das Android-System eine Anfrage zum Speichern der Daten sendet, gibt es eine Ausführungsanfrage, bei der der Dienst die Möglichkeit hat, die Ansichten auszufüllen. Um anzugeben, dass er die Daten speichern möchte, fügt der Dienst ein SaveInfo-Objekt in die Antwort auf die Ausführungsanfrage ein. Das SaveInfo-Objekt enthält mindestens die folgenden Daten:

  • Der Typ der gespeicherten Nutzerdaten. Eine Liste der verfügbaren SAVE_DATA-Werte finden Sie unter SaveInfo.
  • Die Mindestanzahl von Ansichten, die geändert werden müssen, um eine Speicheranfrage auszulösen. Beispielsweise muss der Nutzer in einem Anmeldeformular normalerweise die Ansichten username und password 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 eine Logik zur Beibehaltung der Nutzerdaten in der onSaveRequest()-Methode implementieren. Diese wird normalerweise nach Abschluss der Clientaktivität oder wenn die Client-App commit() aufruft, aufgerufen. 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 sensible Daten verschlüsseln, bevor sie gespeichert werden. Nutzerdaten können jedoch auch Labels oder nicht vertrauliche Daten enthalten. Beispielsweise kann ein Nutzerkonto ein Label enthalten, das die Daten als Arbeitskonto oder als privates Konto kennzeichnet. Dienste dürfen keine Labels verschlüsseln. Wenn Labels nicht verschlüsselt werden, können Dienste die Labels in Präsentationsansichten verwenden, 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.

UI zum Speichern von Autofill verschieben

Wenn Sie ab Android 10 mehrere Bildschirme zum Implementieren eines Autofill-Workflows verwenden – z. B. einen Bildschirm für das Feld für den Nutzernamen 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 verschieben.

Wenn dieses Flag gesetzt ist, wird die UI zum Speichern von Autofill-Daten nicht ausgelöst, wenn der mit der SaveInfo-Antwort verknüpfte Autofill-Kontext festgeschrieben wird. Stattdessen können Sie eine separate Aktivität innerhalb derselben Aufgabe verwenden, um zukünftige Ausführungsanfragen zu senden, und dann die UI über eine Speicheranfrage anzeigen. Weitere Informationen findest du unter SaveInfo.FLAG_DELAY_SAVE.

Nutzerauthentifizierung verlangen

Autofill-Dienste können eine zusätzliche Sicherheitsstufe bieten, da der Nutzer sich authentifizieren muss, bevor Ansichten ausgefüllt werden können. 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 entsperrt werden.
  • Ein bestimmtes Dataset, z. B. Kreditkartendetails, muss mit einer Kartenprüfnummer (CVC) entsperrt werden.

In einem Szenario, in dem der Dienst vor dem Entsperren der Daten eine Nutzerauthentifizierung erfordert, kann der Dienst Boilerplate-Daten oder ein Label angeben und den Intent angeben, der die Authentifizierung übernimmt. Wenn Sie zusätzliche Daten benötigen, um die Anfrage nach Abschluss des Authentifizierungsvorgangs zu verarbeiten, können Sie diese Daten dem Intent hinzufügen. Über Ihre Authentifizierungsaktivität können die Daten dann an die AutofillService-Klasse in Ihrer Anwendung zurückgegeben werden.

Das folgende Codebeispiel zeigt, wie Sie angeben, dass die Anfrage eine Authentifizierung erfordert:

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 Authentifizierungsvorgang abgeschlossen hat, muss sie die Methode setResult() aufrufen, einen RESULT_OK-Wert übergeben und das zusätzliche EXTRA_AUTHENTICATION_RESULT auf das FillResponse-Objekt festlegen, das das ausgefüllte Dataset enthält. Der folgende Code zeigt ein Beispiel für die Rückgabe des Ergebnisses nach Abschluss der Authentifizierung:

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

In dem Szenario, in dem ein Kreditkarten-Dataset entsperrt werden muss, kann der Dienst eine UI anzeigen, in der die CVC-Nummer angefordert wird. Sie können die Daten ausblenden, bis der Datensatz entriegelt ist. Dazu geben Sie Textbausteindaten wie den Namen der Bank und die letzten vier Ziffern der Kreditkartennummer ein. Das folgende Beispiel zeigt, wie eine Authentifizierung für ein Dataset erforderlich ist 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_AUTHENTICATION_RESULT-Extra auf ein Dataset-Objekt setzen, das die Kreditkartennummer und das Ablaufdatum enthält. Das neue Dataset ersetzt das Dataset, das eine Authentifizierung erfordert, und die Ansichten werden sofort ausgefüllt. Der folgende Code zeigt ein Beispiel für die Rückgabe des Datasets, nachdem 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);

Die Daten in logischen Gruppen organisieren

Autofill-Dienste müssen die Daten in logische Gruppen organisieren, die Konzepte von verschiedenen Domains isolieren. Auf dieser Seite werden diese logischen Gruppen als Partitionen bezeichnet. Die folgende Liste enthält typische Beispiele für Partitionen und Felder:

  • Anmeldedaten, die Felder für Nutzername und Passwort enthalten.
  • Adresse, einschließlich der Felder für Straße, Stadt, Bundesland und Postleitzahl
  • Zahlungsinformationen, z. B. die Kreditkartennummer, das Ablaufdatum und die Felder mit dem Bestätigungscode

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 Dataset verfügbar macht. Beispielsweise muss ein Dataset mit Anmeldedaten keine Zahlungsinformationen enthalten. Durch das Organisieren von Daten in Partitionen kann Ihr Dienst nur die für die Erfüllung einer Anfrage erforderliche Mindestmenge an relevanten Informationen bereitstellen.

Durch das Organisieren von Daten in Partitionen können Dienste Aktivitäten ausführen, die Ansichten aus mehreren Partitionen enthalten, und gleichzeitig die minimale Menge relevanter Daten an die Client-App senden. Angenommen, eine Aktivität umfasst Ansichten für Nutzername, Passwort, Straße und Stadt sowie einen Autofill-Service mit den folgenden Daten:

Partition Feld 1 Feld 2
Anmeldedaten Arbeitsnutzername Arbeitspasswort
persönlicher_Nutzername privates_Passwort
Adresse Arbeitsstraße Arbeitsort
Privatstraße persönlicher_Stadt

Der Dienst kann ein Dataset vorbereiten, das die Partition mit Anmeldedaten für die Arbeits- und persönlichen Konten enthält. Wenn der Nutzer einen Datensatz auswählt, kann eine nachfolgende Autofill-Antwort entweder die Arbeits- oder die private Adresse enthalten, je nach erster Wahl des Nutzers.

Ein Dienst kann das Feld identifizieren, von dem die Anfrage stammt. Dazu ruft die Methode isFocused() beim Durchlaufen des AssistStructure-Objekts auf. Dadurch kann der Dienst ein FillResponse mit den entsprechenden Partitionsdaten vorbereiten.

SMS mit einmaligem Code automatisch ausfüllen

Ihr Autofill-Dienst kann den Nutzer beim Ausfüllen von einmaligen Codes, die per SMS gesendet werden, mithilfe der SMS Retriever API unterstützen.

Für die Verwendung dieser Funktion müssen die folgenden Voraussetzungen erfüllt sein:

  • Der Autofill-Service läuft unter Android 9 (API-Level 28) oder höher.
  • Der Nutzer erteilt die Einwilligung, dass dein Autofill-Dienst Einmalcodes von SMS liest.
  • Die Anwendung, für die du Autofill bereitstellt, verwendet nicht bereits die SMS Retriever API zum Lesen von Einmalcodes.

Ihr Autofill-Dienst kann SmsCodeAutofillClient verwenden, das über SmsCodeRetriever.getAutofillClient() über die Google Play-Dienste ab Version 19.0.56 verfügbar ist.

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

  1. Verwenden Sie im Autofill-Dienst hasOngoingSmsRequest aus SmsCodeAutofillClient, um festzustellen, ob für den Paketnamen der App, die Sie automatisch ausfüllen möchten, aktive Anfragen vorhanden sind. Der Autofill-Dienst darf nur dann eine Vorschlagsaufforderung anzeigen, wenn die Meldung false zurückgegeben wird.
  2. Verwenden Sie im Autofill-Dienst checkPermissionState aus SmsCodeAutofillClient, um zu prüfen, ob der Autofill-Dienst die Berechtigung zum automatischen Ausfüllen einmaliger Codes hat. Dieser Berechtigungsstatus kann NONE, GRANTED oder DENIED sein. Der Autofill-Dienst muss eine Eingabeaufforderung für Vorschläge für die Status NONE und GRANTED anzeigen.
  3. Verwenden Sie in der Autofill-Authentifizierungsaktivität die Berechtigung SmsRetriever.SEND_PERMISSION, um ein BroadcastReceiver zu registrieren, das auf SmsCodeRetriever.SMS_CODE_RETRIEVED_ACTION wartet, um das Ergebnis des SMS-Codes zu erhalten, wenn es verfügbar ist.
  4. Rufen Sie startSmsCodeRetriever unter SmsCodeAutofillClient auf, um auf einmalige Codes zu warten, die per SMS gesendet werden. Wenn der Nutzer Ihrem Autofill-Dienst die Berechtigung erteilt, einmalige Codes aus SMS abzurufen, wird nach SMS gesucht, die in den letzten ein bis fünf Minuten empfangen wurden.

    Wenn Ihr Autofill-Dienst die Nutzerberechtigung zum Lesen von Einmalcodes anfordern muss, schlägt das von startSmsCodeRetriever zurückgegebene Task möglicherweise fehl und wird ResolvableApiException zurückgegeben. In diesem Fall müssen Sie die Methode ResolvableApiException.startResolutionForResult() aufrufen, um ein Einwilligungsdialogfeld für die Berechtigungsanfrage einzublenden.

  5. Sie erhalten das SMS-Codeergebnis vom Intent und geben dann den SMS-Code als Autofill-Antwort zurück.

Erweiterte Autofill-Szenarien

In Tastatur einbinden
Ab Android 11 können Tastaturen und andere IMEs für Eingabemethoden Autofill-Vorschläge inline anzeigen lassen, anstatt ein Drop-down-Menü zu verwenden. Weitere Informationen dazu, wie der Autofill-Dienst diese Funktion unterstützt, finden Sie unter Autofill in Tastaturen einbinden.
Datasets paginieren
Eine große Autofill-Antwort kann die zulässige Transaktionsgröße des Objekts Binder überschreiten, das das für die Verarbeitung der Anfrage erforderliche entfernbare Objekt darstellt. Um zu verhindern, dass das Android-System in diesen Szenarien eine Ausnahme ausgibt, kannst du die FillResponse klein halten. Füge dazu nicht mehr als 20 Dataset-Objekte gleichzeitig hinzu. Falls für Ihre Antwort mehr Datasets erforderlich sind, können Sie ein Dataset hinzufügen, das die Nutzer über weitere Informationen informiert und die nächste Gruppe von Datasets bei Auswahl abruft. Weitere Informationen finden Sie unter addDataset(Dataset).
Auf mehrere Bildschirme aufgeteilte Daten speichern

Apps teilen Nutzerdaten oft in einer Aktivität auf mehreren Bildschirmen auf, insbesondere bei 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 einem 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 UI zum Speichern von AutoFill angezeigt werden kann. Gehen Sie in solchen Fällen so vor:

  1. Fügen Sie der Antwort in der ersten Ausfüllanfrage ein Clientstatus-Bundle hinzu, das die Autofill-IDs der Teilfelder auf dem Bildschirm enthält.
  2. Rufen Sie in der zweiten Ausführungsanfrage das Clientstatus-Bundle und die in der vorherigen Anfrage festgelegten Autofill-IDs aus dem Clientstatus ab. Fügen Sie diese IDs sowie das Flag FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE dem in der zweiten Antwort verwendeten SaveInfo-Objekt hinzu.
  3. Verwenden Sie in der Speicheranfrage die richtigen FillContext-Objekte, um den Wert der einzelnen Felder zu erhalten. Pro Ausführungsanfrage gibt es einen Füllkontext.

Weitere Informationen finden Sie unter Speichern, wenn Daten auf mehrere Bildschirme aufgeteilt werden.

Initialisierungs- und Teardown-Logik für jede Anfrage bereitstellen

Jedes Mal, wenn eine Autofill-Anfrage eingeht, stellt das Android-System eine Verbindung zum Dienst her und ruft die Methode onConnected() auf. Sobald der Dienst die Anfrage verarbeitet, ruft das Android-System die Methode onDisconnected() auf und hebt die Bindung zum Dienst auf. 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 zum Speichern von Autofill-Daten anpassen

Autofill-Dienste können die Benutzeroberfläche zum Speichern von AutoFill anpassen, damit Nutzer entscheiden können, ob der Dienst ihre Daten speichern darf. Dienste können zusätzliche Informationen darüber liefern, was gespeichert wird, entweder in einem einfachen Text oder über eine benutzerdefinierte Ansicht. Dienste können auch das Aussehen der Schaltfläche ändern, mit der die Speicheranfrage abgebrochen wird. Sie erhalten dann eine Benachrichtigung, 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 der Bedienungshilfen für Autofill-Zwecke verwenden. Sie ist besonders nützlich, um die Autofill-Funktion in Browsern bereitzustellen, in denen die Autofill-APIs nicht explizit implementiert sind.

Wenn Sie den Autofill-Dienst im Kompatibilitätsmodus testen möchten, setzen Sie den Browser oder die Anwendung, für die der Kompatibilitätsmodus erforderlich ist, explizit 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 zu testende Paket nicht aufgeführt ist, fügen Sie es mit dem folgenden Befehl hinzu, wobei pkgX das Paket der Anwendung ist:

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

Wenn die Anwendung ein Browser ist, geben Sie mit resIdx die Ressourcen-ID des Eingabefelds an, das die URL der gerenderten Seite enthält.

Für den Kompatibilitätsmodus gelten 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 bei Verwendung des Kompatibilitätsmodus standardmäßig festgelegt.
  • Der Textwert der Knoten ist in der Methode onSaveRequest(SaveRequest, SaveCallback) möglicherweise nicht verfügbar.

Weitere Informationen zum Kompatibilitätsmodus, einschließlich der damit verbundenen Einschränkungen, finden Sie in der Referenz zur Klasse AutofillService.