Tworzenie usług autouzupełniania

Usługa autouzupełniania to aplikacja, która ułatwia użytkownikom wypełnianie formularzy poprzez wstrzykiwanie danych do widoków innych aplikacji. Usługi autouzupełniania mogą też pobierać dane użytkownika z widoków w aplikacji i przechowywać je na potrzeby późniejszego użycia. Usługi autouzupełniania są zwykle dostarczane przez aplikacje, które zarządzają danymi użytkownika, takie jak menedżery haseł.

Android ułatwia wypełnianie formularzy dzięki systemowi automatycznego wypełniania, który jest dostępny w Androidzie 8.0 (poziom interfejsu API 26) i nowszych wersjach. Użytkownicy mogą korzystać z funkcji autouzupełniania tylko wtedy, gdy na ich urządzeniu jest aplikacja oferująca usługi autouzupełniania.

Na tej stronie znajdziesz informacje o wdrażaniu usługi autouzupełniania w aplikacji. Jeśli szukasz przykładowego kodu, który pokazuje, jak zaimplementować usługę, zapoznaj się z przykładem AutofillFramework w Javie lub Kotlinie. Więcej informacji o tym, jak działają usługi autouzupełniania, znajdziesz na stronach referencyjnych dotyczących klas AutofillServiceAutofillManager.

Deklaracje i uprawnienia w pliku manifestu

Aplikacje, które udostępniają usługi autouzupełniania, muszą zawierać deklarację opisującą implementację usługi. Aby określić deklarację, dodaj element <service> do pliku manifestu aplikacji. Element <service> musi zawierać te atrybuty i elementy:

  • Atrybut android:name, który wskazuje na podklasę AutofillService w aplikacji, która wdraża usługę.
  • atrybut android:permission, który deklaruje uprawnienie BIND_AUTOFILL_SERVICE.
  • <intent-filter> element, którego wymagany element podrzędny <action> określa działanie android.service.autofill.AutofillService.
  • Opcjonalny element <meta-data>, który możesz wykorzystać do podania dodatkowych parametrów konfiguracji usługi.

Poniższy przykład pokazuje deklarację usługi autouzupełniania:

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

Element <meta-data> zawiera atrybut android:resource, który wskazuje na zasób XML zawierający dodatkowe informacje o usłudze. Zasób service_configuration w poprzednim przykładzie określa aktywność, która umożliwia użytkownikom skonfigurowanie usługi. Poniższy przykład pokazuje zasób XML service_configuration:

<autofill-service
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:settingsActivity="com.example.android.SettingsActivity" />

Więcej informacji o zasobach XML znajdziesz w artykule Omówienie zasobów aplikacji.

Prośba o włączenie usługi

Aplikacja jest używana jako usługa autouzupełniania po zadeklarowaniu uprawnienia BIND_AUTOFILL_SERVICE i włączeniu go przez użytkownika w ustawieniach urządzenia. Aplikacja może sprawdzić, czy jest to obecnie włączona usługa, wywołując metodę hasEnabledAutofillServices() klasy AutofillManager.

Jeśli aplikacja nie jest bieżącą usługą autouzupełniania, może poprosić użytkownika o zmianę ustawień autouzupełniania za pomocą intencji ACTION_REQUEST_SET_AUTOFILL_SERVICE. Jeśli użytkownik wybierze usługę autouzupełniania, która pasuje do pakietu wywołującego, intencja zwraca wartość RESULT_OK.

Wypełnianie widoku z perspektywy klienta

Usługa autouzupełniania otrzymuje żądania dotyczące wypełniania widoków klienta, gdy użytkownik wchodzi w interakcję z innymi aplikacjami. Jeśli usługa autouzupełniania ma dane użytkownika, które odpowiadają żądaniu, wysyła je w odpowiedzi. System Androida wyświetla interfejs automatycznego wypełniania z dostępnymi danymi, jak pokazano na rysunku 1:

Interfejs autouzupełniania

Rysunek 1. Interfejs autouzupełniania z wyświetlonym zbiorem danych

Platforma autouzupełniania definiuje przepływ pracy służący do wypełniania widoków, który ma na celu zminimalizowanie czasu, przez jaki system Android jest połączony z usługą autouzupełniania. W każdym żądaniu system Android wysyła do usługi obiekt AssistStructure, wywołując metodę onFillRequest().

Usługa autouzupełniania sprawdza, czy może spełnić żądanie, korzystając z danych użytkownika, które zostały wcześniej zapisane. Jeśli może spełnić żądanie, usługa pakuje dane w obiekty Dataset. Usługa wywołuje metodę onSuccess(), przekazując obiekt FillResponse, który zawiera obiekty Dataset. Jeśli usługa nie ma danych potrzebnych do wykonania żądania, przekazuje null do metody onSuccess().

Jeśli podczas przetwarzania żądania wystąpi błąd, usługa wywoła metodę onFailure(). Szczegółowe omówienie przepływu pracy znajdziesz na stronie referencyjnej AutofillService.

Poniższy kod pokazuje przykład metody 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;
}

Usługa może zawierać więcej niż 1 zbiór danych, który spełnia kryteria zapytania. W takim przypadku system Android wyświetla w interfejsie automatycznego wypełniania kilka opcji – po jednej dla każdego zbioru danych. Poniższy przykładowy kod pokazuje, jak przesyłać wiele zbiorów danych w odpowiedzi:

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

Usługi autouzupełniania mogą przeszukiwać obiekty ViewNodeAssistStructure, aby pobrać dane autouzupełniania wymagane do wykonania żądania. Usługa może pobierać dane autouzupełniania za pomocą metod klasy ViewNode, takich jak getAutofillId().

Usługa musi być w stanie opisać zawartość widoku, aby sprawdzić, czy może spełnić żądanie. Użycie atrybutu autofillHints jest pierwszym podejściem, którego usługa musi użyć do opisania zawartości widoku. Jednak aplikacje klienckie muszą wyraźnie podawać ten atrybut w swoich widokach, zanim będzie on dostępny dla usługi.

Jeśli aplikacja klienta nie udostępnia atrybutu autofillHints, usługa musi użyć własnej heurystyki do opisu treści. Usługa może używać metod z innych klas, takich jak getText() lub getHint(), aby uzyskać informacje o treści widoku. Więcej informacji znajdziesz w artykule Podawanie podpowiedzi w automatycznym wypełnianiu.

Z tego przykładu dowiesz się, jak przejść przez element AssistStructure i pobrać dane autouzupełniania z obiektu ViewNode:

Kotlin

fun traverseStructure(structure: AssistStructure) {
    val windowNodes: List<AssistStructure.WindowNode> =
            structure.run {
                (0 until windowNodeCount).map { getWindowNodeAt(it) }
            }

    windowNodes.forEach { windowNode: AssistStructure.WindowNode ->
        val viewNode: ViewNode? = windowNode.rootViewNode
        traverseNode(viewNode)
    }
}

fun traverseNode(viewNode: ViewNode?) {
    if (viewNode?.autofillHints?.isNotEmpty() == true) {
        // If the client app provides autofill hints, you can obtain them using
        // viewNode.getAutofillHints();
    } else {
        // Or use your own heuristics to describe the contents of a view
        // using methods such as getText() or getHint()
    }

    val children: List<ViewNode>? =
            viewNode?.run {
                (0 until childCount).map { getChildAt(it) }
            }

    children?.forEach { childNode: ViewNode ->
        traverseNode(childNode)
    }
}

Java

public void traverseStructure(AssistStructure structure) {
    int nodes = structure.getWindowNodeCount();

    for (int i = 0; i < nodes; i++) {
        WindowNode windowNode = structure.getWindowNodeAt(i);
        ViewNode viewNode = windowNode.getRootViewNode();
        traverseNode(viewNode);
    }
}

public void traverseNode(ViewNode viewNode) {
    if(viewNode.getAutofillHints() != null && viewNode.getAutofillHints().length > 0) {
        // If the client app provides autofill hints, you can obtain them using
        // viewNode.getAutofillHints();
    } else {
        // Or use your own heuristics to describe the contents of a view
        // using methods such as getText() or getHint()
    }

    for(int i = 0; i < viewNode.getChildCount(); i++) {
        ViewNode childNode = viewNode.getChildAt(i);
        traverseNode(childNode);
    }
}

Zapisywanie danych użytkowników

Usługa autouzupełniania potrzebuje danych użytkownika, aby wypełniać widoki w aplikacjach. Gdy użytkownicy ręcznie wypełniają widok, wyświetla się im prośba o zapisanie danych do bieżącej usługi autouzupełniania (patrz rys. 2).

Interfejs zapisywania autouzupełniania

Rysunek 2. Interfejs zapisywania danych do autouzupełniania

Aby zapisać dane, usługa musi wskazać, że chce je przechowywać na przyszłość. Zanim system Android wyśle prośbę o zapisanie danych, usługa otrzymuje prośbę o wypełnienie, aby mogła wypełnić widoki. Aby wskazać, że jest zainteresowana zapisaniem danych, usługa zawiera obiekt SaveInfo w odpowiedzi na żądanie wypełnienia. Obiekt SaveInfo zawiera co najmniej te dane:

  • Typ zapisywanych danych użytkownika. Listę dostępnych wartości SAVE_DATA znajdziesz w sekcji SaveInfo.
  • Minimalny zestaw widoków, który musi zostać zmieniony, aby wywołać żądanie zapisania. Na przykład formularz logowania zwykle wymaga od użytkownika zaktualizowania widoków usernamepassword, aby wywołać żądanie zapisu.

Obiekt SaveInfo jest powiązany z obiektem FillResponse, jak w tym przykładzie kodu:

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();
    ...
}

Usługa autouzupełniania może stosować logikę, która przechowuje dane użytkownika w metodzie onSaveRequest(), która jest zwykle wywoływana po zakończeniu aktywności klienta lub gdy aplikacja klienta wywołuje funkcję commit(). Poniższy kod pokazuje przykład metody 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();
}

Usługi autouzupełniania muszą zaszyfrować dane wrażliwe przed ich zapisaniem. Dane użytkownika mogą jednak zawierać etykiety lub dane, które nie są poufne. Na przykład konto użytkownika może zawierać etykietę, która oznacza dane jako należące do konta służbowego lub osobistego. Usługi nie mogą szyfrować etykiet. Brak szyfrowania etykiet umożliwia usługom korzystanie z nich w widokach prezentacji, nawet jeśli użytkownik nie zalogował się. Następnie usługi mogą zastąpić etykiety rzeczywistymi danymi po uwierzytelnieniu użytkownika.

Opóźnianie interfejsu zapisywania autouzupełniania

Począwszy od Androida 10, jeśli do implementacji przepływu pracy autouzupełniania używasz wielu ekranów (np. jednego ekranu dla pola nazwy użytkownika i drugiego dla hasła), możesz opóźnić wyświetlenie interfejsu zapisywania autouzupełniania, używając flagi SaveInfo.FLAG_DELAY_SAVE.

Jeśli ten parametr jest ustawiony, interfejs zapisywania danych autouzupełniania nie jest uruchamiany, gdy kontekst autouzupełniania powiązany z odpowiedzią SaveInfo zostanie zapisany. Zamiast tego możesz użyć osobnego działania w ramach tego samego zadania, aby przesyłać przyszłe żądania wypełniania, a potem wyświetlać interfejs za pomocą żądania zapisywania. Więcej informacji znajdziesz w sekcji SaveInfo.FLAG_DELAY_SAVE.

Wymagaj uwierzytelniania użytkowników

Usługi autouzupełniania mogą zapewnić dodatkowy poziom bezpieczeństwa, wymagając od użytkownika uwierzytelnienia przed wypełnieniem widoków. W tych scenariuszach warto zastosować uwierzytelnianie użytkownika:

  • Dane użytkownika w aplikacji muszą być odblokowywane za pomocą hasła głównego lub skanu linii papilarnych.
  • Należy odblokować określony zbiór danych, na przykład dane karty kredytowej, za pomocą kodu weryfikacyjnego karty (CVC).

Jeśli usługa wymaga uwierzytelnienia użytkownika przed odblokowaniem danych, może wyświetlić dane szablonowe lub etykietę i wskazywać Intent, który odpowiada za uwierzytelnianie. Jeśli do przetworzenia żądania potrzebujesz dodatkowych danych po zakończeniu procesu uwierzytelniania, możesz je dodać do intencji. Aktywność uwierzytelniania może następnie zwrócić dane do klasy AutofillService w aplikacji.

Poniższy przykład kodu pokazuje, jak określić, że żądanie wymaga uwierzytelnienia:

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

Gdy aktywność zakończy proces uwierzytelniania, musi wywołać metodę setResult(), przekazując wartość RESULT_OK, i ustawić dodatkowy parametr EXTRA_AUTHENTICATION_RESULT w obiekcie FillResponse, który zawiera wypełniony zbiór danych. Poniższy kod pokazuje, jak zwrócić wynik po zakończeniu przepływu uwierzytelniania:

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

W sytuacji, gdy trzeba odblokować zbiór danych karty kredytowej, usługa może wyświetlić interfejs z prośbą o podanie kodu CVC. Możesz ukryć dane, dopóki nie zostaną odblokowane, prezentując dane szablonowe, takie jak nazwa banku i ostatnie 4 cyfry numeru karty kredytowej. Ten przykład pokazuje, jak wymagać uwierzytelnienia zbioru danych i ukrywać dane, dopóki użytkownik nie poda kodu zabezpieczającego:

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

Po zweryfikowaniu kodu CVC aktywność powinna wywołać metodę setResult(), podając wartość RESULT_OK i ustawiając dodatkowy parametr EXTRA_AUTHENTICATION_RESULT na obiekt Dataset, który zawiera numer karty kredytowej i datę ważności. Nowy zbiór danych zastępuje zbiór danych, który wymaga uwierzytelniania, a widoki są wypełniane natychmiast. Poniższy kod pokazuje, jak zwrócić zbiór danych po podaniu przez użytkownika kodu weryfikacyjnego CVC:

Kotlin

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

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

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

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

Java

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

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

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

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

Porządkowanie danych w grupy logiczne

Usługi autouzupełniania muszą porządkować dane w logiczne grupy, które izolują pojęcia z różnych domen. Na tej stronie te grupy logiczne są nazywane partycjami. Poniżej znajdziesz typowe przykłady partycji i pol:

  • Dane logowania, które obejmują pola nazwy użytkownika i hasła.
  • Adres, który zawiera pola ulicy, miasta, stanu i kodu pocztowego.
  • Dane do płatności, w tym numer karty kredytowej, data ważności i pola kodu weryfikacyjnego.

Usługa autouzupełniania, która prawidłowo dzieli dane, może lepiej chronić dane użytkowników, nie ujawniając danych z więcej niż 1 partycji w danym zbiorze danych. Na przykład zbiór danych zawierający dane logowania nie musi zawierać informacji o płatnościach. Organizowanie danych w partycjach pozwala usłudze udostępniać minimalną ilość odpowiednich informacji wymaganych do zaspokojenia żądania.

Organizowanie danych w partycjach umożliwia usługom wypełnianie działań, które mają widoki z wielu partycji, przy jednoczesnym wysyłaniu do aplikacji klienckiej minimalnej ilości odpowiednich danych. Rozważmy na przykład działanie, które obejmuje widoki nazwy użytkownika, hasła, ulicy i miasta, oraz usługę autouzupełniania, która zawiera te dane:

Partycja Pole 1 Pole 2
Dane logowania work_username work_password
personal_username personal_password
Adres work_street work_city
personal_street personal_city

Usługa może przygotować zbiór danych, który zawiera partycję danych logowania zarówno na potrzeby kont służbowych, jak i osobistych. Gdy użytkownik wybierze zbiór danych, odpowiedź autouzupełniania może zawierać adres służbowy lub osobisty, w zależności od tego, który adres użytkownik wybrał jako pierwszy.

Usługa może zidentyfikować pole, z którego pochodzi żądanie, wywołując metodę isFocused() podczas przechodzenia obiektu AssistStructure. Dzięki temu usługa może przygotować FillResponse z odpowiednimi danymi partycji.

Autouzupełnianie jednorazowych kodów SMS

Usługa autouzupełniania może pomagać użytkownikowi w wypełnianiu jednorazowych kodów wysyłanych SMS-em za pomocą interfejsu API SMS Retriever.

Aby korzystać z tej funkcji, musisz spełniać poniższe wymagania:

  • Usługa autouzupełniania działa na Androidzie 9 (poziom interfejsu API 28) lub nowszym.
  • Użytkownik wyraża zgodę na odczytywanie przez Twoją usługę autouzupełniania jednorazowych kodów z SMS-ów.
  • Aplikacja, której dotyczy autouzupełnianie, nie używa interfejsu SMS Retriever API do odczytywania kodów jednorazowych.

Usługa autouzupełniania może korzystać z usługi SmsCodeAutofillClient, do której można się dostać, wywołując SmsCodeRetriever.getAutofillClient() w Usługach Google Play w wersji 19.0.56 lub nowszej.

Aby używać tego interfejsu API w usłudze autouzupełniania, wykonaj te czynności:

  1. W usłudze autouzupełniania użyj hasOngoingSmsRequestSmsCodeAutofillClient, aby sprawdzić, czy są aktywne jakieś żądania dotyczące nazwy pakietu aplikacji, której dotyczy autouzupełnianie. Usługa autouzupełniania musi wyświetlać prośbę o sugestie tylko wtedy, gdy zwróci wartość false.
  2. W usłudze autouzupełniania użyj opcji checkPermissionStateSmsCodeAutofillClient, aby sprawdzić, czy ma ona uprawnienia do autouzupełniania kodów jednorazowych. Stan uprawnień może być NONE, GRANTED lub DENIED. Usługa autouzupełniania musi wyświetlać prośbę o propozycję w przypadku stanów NONEGRANTED.
  3. W aktywności uwierzytelniania autouzupełniania użyj uprawnienia SmsRetriever.SEND_PERMISSION, aby zarejestrować BroadcastReceiver, które nasłuchuje SmsCodeRetriever.SMS_CODE_RETRIEVED_ACTION, aby otrzymać kod SMS-em, gdy będzie dostępny.
  4. Zadzwoń pod numer startSmsCodeRetriever na urządzeniu SmsCodeAutofillClient, aby zacząć nasłuchiwać kodów jednorazowych wysyłanych SMS-em. Jeśli użytkownik przyzna uprawnienia usłudze autouzupełniania do pobierania jednorazowych kodów z SMS-ów, usługa będzie szukać SMS-ów otrzymanych w ciągu ostatnich 1–5 minut.

    Jeśli usługa autouzupełniania musi poprosić użytkownika o zezwolenie na odczytywanie kodów jednorazowych, Task zwracany przez startSmsCodeRetriever może zakończyć się niepowodzeniem z ResolvableApiException zwróconym jako wartość. W takim przypadku musisz wywołać metodę ResolvableApiException.startResolutionForResult(), aby wyświetlić okno zgody dotyczące prośby o dostęp.

  5. Otrzymaj kod SMS-a z intencji, a potem zwracaj kod SMS-a jako odpowiedź autouzupełniania.

Zaawansowane scenariusze autouzupełniania

Integracja z klawiaturą
Od Androida 11 platforma umożliwia klawiaturom i innym edytorom metod wprowadzania (IME) wyświetlanie sugestii autouzupełniania w polu tekstowym zamiast w menu wysuwanym. Więcej informacji o tym, jak usługa autouzupełniania może obsługiwać tę funkcję, znajdziesz w artykule Integracja autouzupełniania z klawiaturami.
Stronicowanie zbiorów danych
Duża odpowiedź na autouzupełnianie może przekroczyć dozwolony rozmiar transakcji obiektu Binder, który reprezentuje zdalny obiekt wymagany do przetworzenia żądania. Aby zapobiec wyrzucaniu wyjątków przez system Androida w takich sytuacjach, możesz utrzymać FillResponsena niskim poziomie, dodając nie więcej niż 20 obiektów Dataset naraz. Jeśli odpowiedź wymaga dodatkowych zbiorów danych, możesz dodać zbiór danych, który informuje użytkowników, że jest więcej informacji, i pobiera kolejną grupę zbiorów danych po jej wybraniu. Więcej informacji znajdziesz w artykule addDataset(Dataset).
Zapisywanie danych podzielonych na kilka ekranów

Aplikacje często dzielą dane użytkownika na wiele ekranów w ramach tego samego działania, zwłaszcza w przypadku działań służących do tworzenia nowego konta użytkownika. Na przykład na pierwszym ekranie wyświetla się prośba o podanie nazwy użytkownika, a jeśli jest ona dostępna, na drugim ekranie wyświetla się prośba o podanie hasła. W takich sytuacjach usługa autouzupełniania musi poczekać, aż użytkownik wypełni oba pola, zanim wyświetli interfejs zapisywania autouzupełniania. Aby obsłużyć takie sytuacje, wykonaj te czynności:

  1. W pierwszym żądaniu uzupełnienia dodaj w odpowiedzi pakiet stanu klienta, który zawiera identyfikatory automatycznego wypełniania pól częściowych widocznych na ekranie.
  2. W drugim żądaniu wypełniania pobierz pakiet stanu klienta, pobierz identyfikatory automatycznego wypełniania ustawione w poprzednim żądaniu ze stanu klienta i dodaj te identyfikatory oraz flagę FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE do obiektu SaveInfo użytego w drugiej odpowiedzi.
  3. W prośbie o zapisanie użyj odpowiednich obiektów FillContext, aby uzyskać wartość każdego pola. Na każdą prośbę o wypełnienie jest jeden kontekst.

Więcej informacji znajdziesz w artykule Zapisywanie danych podzielonych na kilka ekranów.

Zapewnij logikę inicjowania i zamykania dla każdego żądania.

Za każdym razem, gdy pojawi się żądanie automatycznego wypełniania, system Android łączy się z usługą i wywołuje jej metodę onConnected(). Gdy usługa przetworzy żądanie, system Android wywoła metodę onDisconnected() i rozwiąże powiązanie z usługą. Możesz zaimplementować onConnected(), aby podać kod, który jest wykonywany przed przetworzeniem żądania, oraz onDisconnected(), aby podać kod, który jest wykonywany po przetworzeniu żądania.

Dostosowywanie interfejsu zapisywania autouzupełniania

Usługi autouzupełniania mogą dostosowywać interfejs zapisywania autouzupełniania, aby ułatwić użytkownikom podjęcie decyzji, czy chcą, aby dane były zapisywane. Usługi mogą udostępniać dodatkowe informacje o tym, co zostało zapisane, za pomocą prostego tekstu lub widoku niestandardowego. Usługi mogą też zmienić wygląd przycisku anulowania żądania zapisania i otrzymać powiadomienie, gdy użytkownik go kliknie. Więcej informacji znajdziesz na stronie referencyjnej SaveInfo.

Tryb zgodności

Tryb zgodności umożliwia usługom autouzupełniania korzystanie z wirtualnej struktury ułatwień dostępu na potrzeby autouzupełniania. Jest to szczególnie przydatne w przypadku funkcji autouzupełniania w przeglądarkach, które nie implementują jawnie interfejsów API autouzupełniania.

Aby przetestować usługę autouzupełniania w trybie zgodności, dodaj do białej listy przeglądarkę lub aplikację, która wymaga trybu zgodności. Aby sprawdzić, które pakiety są już na liście dozwolonych, uruchom to polecenie:

$ adb shell settings get global autofill_compat_mode_allowed_packages

Jeśli pakietu, który testujesz, nie ma na liście, dodaj go, uruchamiając to polecenie, gdzie pkgX to pakiet aplikacji:

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

Jeśli aplikacja jest przeglądarką, użyj atrybutu resIdx, aby określić identyfikator zasobu pola wejściowego zawierającego adres URL wyrenderowanej strony.

Tryb zgodności ma te ograniczenia:

  • Prośba o zapisanie jest wywoływana, gdy usługa używa flagi FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE lub wywołuje metodę setTrigger(). FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE jest domyślnie ustawiony podczas korzystania z trybu zgodności.
  • Wartość tekstowa węzłów może być niedostępna w metodzie onSaveRequest(SaveRequest, SaveCallback).

Więcej informacji o trybie zgodności, w tym o powiązanych z nim ograniczeniach, znajdziesz w dokumentacji klasy AutofillService.