Tworzenie usług autouzupełniania

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

Android ułatwia wypełnianie formularzy dzięki platformie autouzupełniania dostępnej na Androidzie 8.0 (poziom interfejsu API 26) i nowszych. Użytkownicy mogą korzystać z funkcji autouzupełniania tylko wtedy, gdy na urządzeniu jest dostępna aplikacja oferująca usługi autouzupełniania.

Na tej stronie dowiesz się, jak wdrożyć usługę autouzupełniania w aplikacji. Jeśli szukasz przykładowego kodu, który pokazuje, jak wdrożyć usługę, zapoznaj się z przykładem AutofillFramework w języku Java lub Kotlin. Więcej informacji o sposobie działania usług autouzupełniania znajdziesz na stronach z informacjami o klasach AutofillService i AutofillManager.

Deklaracje i uprawnienia w pliku manifestu

Aplikacje zapewniające usługi autouzupełniania muszą zawierać deklarację opisującą ich implementację. Aby określić deklarację, umieść w pliku manifestu aplikacji element <service>. Element <service> musi zawierać te atrybuty i elementy:

Ten przykład przedstawia 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 zasób XML z dodatkowymi informacjami o usłudze. Zasób service_configuration z poprzedniego przykładu określa działanie, które umożliwia użytkownikom konfigurowanie usługi. Oto przykład zasobu 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.

Monit o włączenie usługi

Aplikacja jest używana jako usługa autouzupełniania po zadeklarowaniu uprawnienia BIND_AUTOFILL_SERVICE, a użytkownik włączy je 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 aktualną usługą autouzupełniania, może poprosić użytkownika o zmianę ustawień autouzupełniania za pomocą intencji ACTION_REQUEST_SET_AUTOFILL_SERVICE. Intencja zwraca wartość RESULT_OK, jeśli użytkownik wybierze usługę autouzupełniania pasującą do pakietu elementu wywołującego.

Wypełnianie widoków klienta

Gdy użytkownik wchodzi w interakcję z innymi aplikacjami, usługa autouzupełniania otrzymuje żądania wypełnienia widoków klienta. Jeśli usługa autouzupełniania ma dane użytkownika, które spełniają żądanie, wysyła je w odpowiedzi. System Android pokazuje interfejs autouzupełniania z dostępnymi danymi, jak widać na ilustracji 1:

Interfejs autouzupełniania

Rysunek 1. Interfejs autouzupełniania wyświetlający zbiór danych.

Platforma autouzupełniania określa przepływ pracy do wypełniania widoków, który ma na celu skrócenie czasu, który system Android jest powiązany 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 zapisanych wcześniej danych użytkownika. Jeśli może spełnić żądanie, usługa pakuje dane w obiektach 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 spełnienia żądania, przekazuje null do metody onSuccess().

Jeśli podczas przetwarzania żądania wystąpi błąd, usługa wywołuje metodę onFailure(). Szczegółowy opis przepływu pracy znajdziesz w opisie na stronie z informacjami o AutofillService.

Oto 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 mieć więcej niż 1 zbiór danych, który spełnia to żądanie. W takim przypadku system Android pokazuje w interfejsie autouzupełniania wiele opcji – po jednej na każdy zbiór danych. Poniższy przykładowy kod pokazuje, jak udostępnić w odpowiedzi wiele zbiorów danych:

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ą poruszać się po obiektach ViewNode w AssistStructure, aby pobrać dane autouzupełniania wymagane do realizacji żądania. Usługa może pobierać dane autouzupełniania przy użyciu metod klasy ViewNode, takich jak getAutofillId().

Usługa musi mieć możliwość opisania zawartości widoku, aby sprawdzić, czy może spełnić żądanie. Użycie atrybutu autofillHints to pierwsza metoda, której usługa musi używać do opisania zawartości widoku. Aplikacje klienckie muszą jednak jawnie podać ten atrybut w swoich widokach, zanim stanie się on dostępny dla usługi.

Jeśli aplikacja kliencka nie zawiera atrybutu autofillHints, usługa musi opisywać zawartość za pomocą własnej heurystyki. Aby uzyskać informacje o zawartości widoku, usługa może używać metod z innych klas, takich jak getText() czy getHint(). Więcej informacji znajdziesz w sekcji Podawanie wskazówek dotyczących autouzupełniania.

Poniższy przykład pokazuje, jak poruszać się po AssistStructure i pobierać 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łnią widok, zostaną poproszeni o zapisanie danych w bieżącej usłudze autouzupełniania, jak pokazano na rysunku 2.

Interfejs zapisywania autouzupełniania

Rysunek 2. Interfejs zapisu autouzupełniania.

Aby zapisać dane, usługa musi poinformować, że jest zainteresowana przechowywaniem danych do wykorzystania w przyszłości. Zanim system Android wyśle żądanie zapisania danych, pojawia się żądanie wypełnienia, które umożliwia wypełnienie widoków danych. Aby wskazać, że interesuje Cię zapisywanie danych, usługa umieszcza w odpowiedzi na żądanie wypełnienia obiekt SaveInfo. Obiekt SaveInfo zawiera co najmniej te dane:

  • Typ zapisanych danych użytkownika. Listę dostępnych wartości SAVE_DATA znajdziesz w sekcji SaveInfo.
  • Minimalny zestaw widoków, które muszą zostać zmienione, aby aktywować żądanie zapisania. Na przykład w celu wywołania żądania zapisu formularz logowania zwykle wymaga od użytkownika zaktualizowania widoków username i password.

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ę utrwalającą dane użytkownika w metodzie onSaveRequest(), która jest zwykle wywoływana po zakończeniu działania klienta lub po wywołaniu aplikacji klienckiej commit(). Oto 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, zanim zostaną zachowane. Dane użytkownika mogą jednak zawierać etykiety i dane, które nie są poufne. Na przykład konto użytkownika może zawierać etykietę oznaczającą dane jako konto służbowe lub osobiste. Usługi nie mogą szyfrować etykiet. Gdy nie szyfrują etykiet, usługi mogą ich używać w widokach prezentacji, jeśli użytkownik nie był uwierzytelniony. Następnie po uwierzytelnieniu użytkownika usługi mogą zastępować etykiety rzeczywistymi danymi.

Przełóż interfejs zapisu autouzupełniania

Począwszy od Androida 10, jeśli wdrażasz przepływ pracy autouzupełniania za pomocą kilku ekranów – na przykład jednego ekranu dla pola nazwy użytkownika, a drugi z hasłem – możesz opóźnić działanie interfejsu zapisywania autouzupełniania, używając flagi SaveInfo.FLAG_DELAY_SAVE.

Jeśli jest ustawiona ta flaga, interfejs zapisu autouzupełniania nie jest wywoływany po zatwierdzeniu kontekstu autouzupełniania powiązanego z odpowiedzią SaveInfo. Zamiast tego możesz użyć osobnego działania w ramach tego samego zadania, aby w przyszłości wysyłać żądania wypełnienia, a następnie wyświetlić interfejs za pomocą żądania zapisu. Więcej informacji: SaveInfo.FLAG_DELAY_SAVE.

Wymagaj uwierzytelnienia użytkowników

Usługi autouzupełniania mogą zapewnić dodatkowy poziom bezpieczeństwa, ponieważ wymagają od użytkownika uwierzytelnienia przed rozpoczęciem wypełniania widoków. Uwierzytelnianie użytkowników warto wdrożyć w tych sytuacjach:

  • Dane użytkownika w aplikacji trzeba odblokować za pomocą hasła podstawowego lub skanu odcisku palca.
  • Określony zbiór danych, na przykład dane karty kredytowej, trzeba odblokować za pomocą kodu weryfikacyjnego karty (CVC).

W sytuacji, gdy usługa przed odblokowaniem danych wymaga uwierzytelnienia użytkownika, może przedstawiać dane stałe lub etykietę i określić Intent, który obsługuje uwierzytelnianie. Jeśli potrzebujesz dodatkowych danych do przetworzenia żądania po zakończeniu procesu uwierzytelniania, możesz dodać je do intencji. Twoja aktywność związana z uwierzytelnianiem może wówczas zwracać dane do klasy AutofillService w Twojej aplikacji.

Poniższy przykładowy kod 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 działanie zakończy proces uwierzytelniania, musi wywołać metodę setResult(), przekazać wartość RESULT_OK i ustawić dodatkowy obiekt EXTRA_AUTHENTICATION_RESULT na obiekt FillResponse, który zawiera wypełniony zbiór danych. Poniższy kod zawiera przykład zwracania wyniku po zakończeniu procesu 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 zbiór danych karty kredytowej musi być odblokowany, usługa może wyświetlić interfejs z prośbą o podanie kodu CVC. Aby ukryć dane, dopóki zbiór danych nie zostanie odblokowany, możesz podać dane stałe, takie jak nazwa banku i 4 ostatnie cyfry numeru karty kredytowej. Z przykładu poniżej dowiesz się, jak wymagać uwierzytelniania zbioru danych i ukrywać dane do momentu podania przez użytkownika kodu CVC:

Kotlin

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

// Build the presentation that shows the bank and the last four digits of the
// credit card number, such as 'Bank-1234'
val maskedPresentation: String = "${paymentData.bank}-" +
        paymentData.creditCardNumber.substring(paymentData.creditCardNumber.length - 4)
val authPresentation = RemoteViews(packageName, android.R.layout.simple_list_item_1).apply {
    setTextViewText(android.R.id.text1, maskedPresentation)
}

// Prepare an intent that displays the UI that asks for the CVC
val cvcIntent = Intent(this, CvcActivity::class.java)
val cvcIntentSender: IntentSender = PendingIntent.getActivity(
        this,
        1001,
        cvcIntent,
        PendingIntent.FLAG_CANCEL_CURRENT
).intentSender

// Build a FillResponse object that includes a Dataset that requires authentication
val fillResponse: FillResponse = FillResponse.Builder()
        .addDataset(
                Dataset.Builder()
                        // The values in the dataset are replaced by the actual
                        // data once the user provides the CVC
                        .setValue(parsedStructure.creditCardId, null, authPresentation)
                        .setValue(parsedStructure.expDateId, null, authPresentation)
                        .setAuthentication(cvcIntentSender)
                        .build()
        ).build()

Java

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

// Build the presentation that shows the bank and the last four digits of the
// credit card number, such as 'Bank-1234'
String maskedPresentation = paymentData.bank + "-" +
    paymentData.creditCardNumber.subString(paymentData.creditCardNumber.length - 4);
RemoteViews authPresentation = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1);
authPresentation.setTextViewText(android.R.id.text1, maskedPresentation);

// Prepare an intent that displays the UI that asks for the CVC
Intent cvcIntent = new Intent(this, CvcActivity.class);
IntentSender cvcIntentSender = PendingIntent.getActivity(
        this,
        1001,
        cvcIntent,
        PendingIntent.FLAG_CANCEL_CURRENT
).getIntentSender();

// Build a FillResponse object that includes a Dataset that requires authentication
FillResponse fillResponse = new FillResponse.Builder()
        .addDataset(new Dataset.Builder()
                // The values in the dataset are replaced by the actual
                // data once the user provides the CVC
                .setValue(parsedStructure.creditCardId, null, authPresentation)
                .setValue(parsedStructure.expDateId, null, authPresentation)
                .setAuthentication(cvcIntentSender)
                .build())
        .build();

Po sprawdzeniu kodu CVC w ramach aktywności należy wywołać metodę setResult(), przekazując wartość RESULT_OK, a także ustawić dodatkowy obiekt 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, który wymaga uwierzytelnienia, a widoki danych są wypełniane od razu. Poniższy kod pokazuje przykład zwracania zbioru danych, gdy użytkownik poda kod 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ą uporządkować dane w grupy logiczne, które izolują koncepcje od różnych domen. Na tej stronie te grupy logiczne są nazywane partycjami. Poniższa lista zawiera typowe przykłady partycji i pól:

  • Dane logowania, w tym pola nazwy użytkownika i hasła.
  • Adres zawierający ulicę, miasto, stan/region i kod pocztowy.
  • Informacje o płatności, w tym numer karty kredytowej, data ważności i pola kodu weryfikacyjnego.

Usługa autouzupełniania, która prawidłowo partycjonuje dane, jest w stanie lepiej chronić dane użytkowników, nie ujawniając ich danych z więcej niż jednej partycji w zbiorze danych. Na przykład zbiór danych zawierający dane uwierzytelniające nie musi zawierać danych karty. Uporządkowanie danych na partycjach umożliwia usłudze udostępnienie minimalnej ilości odpowiednich informacji wymaganych do spełnienia żądania.

Organizowanie danych na partycjach umożliwia usługom wypełnianie działań mających widoki z wielu partycji, a jednocześnie wysyłanie minimalnej ilości odpowiednich danych do aplikacji klienckiej. Weźmy na przykład działanie obejmujące 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 służbowy_nazwa_użytkownika hasło_służbowe
osobista_nazwa_użytkownika osobiste_hasło
Adres ulica_praca miejsce_pracy
ulica_osobista personal_city

Usługa może przygotować zbiór danych zawierający partycję danych logowania zarówno do konta służbowego, jak i osobistego. Gdy użytkownik wybiera zbiór danych, w kolejnej odpowiedzi autouzupełniania może być adres służbowy lub prywatny (w zależności od pierwszego wyboru użytkownika).

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

Autouzupełnianie jednorazowego kodu SMS

Usługa autouzupełniania może pomóc użytkownikowi w wypełnianiu jednorazowych kodów wysyłanych przez SMS za pomocą interfejsu SMS Retriever API.

Aby korzystać z tej funkcji, musisz spełniać te wymagania:

  • Usługa autouzupełniania działa na Androidzie 9 (poziom interfejsu API 28) lub nowszym.
  • Użytkownik wyraża zgodę na odczytywanie jednorazowych kodów z SMS-ów przez usługę autouzupełniania.
  • Aplikacja, dla której udostępniasz autouzupełnianie, nie korzysta jeszcze z interfejsu SMS Retriever API do odczytu kodów jednorazowych.

Usługa autouzupełniania może używać SmsCodeAutofillClient, które jest dostępne, wywołując SmsCodeRetriever.getAutofillClient() z Usług Google Play w wersji 19.0.56 lub nowszej.

Podstawowe czynności, które należy wykonać, aby użyć tego interfejsu API w usłudze autouzupełniania, to:

  1. W usłudze autouzupełniania użyj hasOngoingSmsRequest z SmsCodeAutofillClient, aby określić, czy są jakieś aktywne żądania dla nazwy pakietu aplikacji, którą wypełniasz automatycznie. Twoja usługa autouzupełniania może wyświetlać prośbę o sugestię tylko wtedy, gdy zwraca wartość false.
  2. W usłudze autouzupełniania użyj kodu checkPermissionState ze strony SmsCodeAutofillClient, aby sprawdzić, czy ma ona uprawnienia do autouzupełniania kodów jednorazowych. Może on mieć wartość NONE, GRANTED lub DENIED. Usługa autouzupełniania musi wyświetlać sugestię sugestii dla stanów NONE i GRANTED.
  3. W aktywności uwierzytelniania autouzupełniania użyj uprawnienia SmsRetriever.SEND_PERMISSION, aby zarejestrować nasłuchiwanie elementu BroadcastReceiver przez SmsCodeRetriever.SMS_CODE_RETRIEVED_ACTION, aby otrzymać wynik z kodem SMS, gdy jest dostępny.
  4. Zadzwoń pod numer startSmsCodeRetriever pod numer SmsCodeAutofillClient, aby zacząć nasłuchiwać jednorazowych kodów wysyłanych SMS-em. Jeśli użytkownik przyzna usłudze autouzupełniania uprawnienia do pobierania jednorazowych kodów z SMS-ów, aplikacja będzie szukać SMS-ów odebranych w ciągu ostatnich 1–5 minut.

    Jeśli usługa autouzupełniania musi prosić użytkownika o zgodę na odczytywanie jednorazowych kodów, Task zwracany przez startSmsCodeRetriever może zakończyć się niepowodzeniem i zwrócić ResolvableApiException. W takim przypadku musisz wywołać metodę ResolvableApiException.startResolutionForResult(), by wyświetlić okno dialogowe zgody w przypadku prośby o uprawnienia.

  5. Odbierz wynikowy kod SMS z intencji, a następnie zwróć taki kod w odpowiedzi autouzupełniania.

Zaawansowane scenariusze autouzupełniania

Zintegruj z klawiaturą
Od Androida 11 platforma ta umożliwia klawiaturom i innym edytorom metody wprowadzania (IME) wyświetlanie sugestii autouzupełniania w tekście zamiast korzystania z menu. Więcej informacji o tym, jak usługa autouzupełniania może obsługiwać tę funkcję, znajdziesz w artykule o integrowaniu autouzupełniania z klawiaturami.
Dzielenie zbiorów danych na strony
Duża odpowiedź autouzupełniania może przekraczać dozwolony rozmiar transakcji obiektu Binder, który reprezentuje obiekt zdalny wymagany do przetworzenia żądania. Aby zapobiec wyzwaniu przez system Android w takich sytuacjach, możesz zadbać o to, aby FillResponse był mały, dodając nie więcej niż 20 obiektów Dataset jednocześnie. Jeśli odpowiedź wymaga więcej zbiorów danych, możesz dodać zbiór danych, który poinformuje użytkowników, że istnieją więcej informacji, i po wybraniu będzie pobierać kolejną grupę zbiorów danych. Więcej informacji: addDataset(Dataset).
Zapisuj podział danych na wiele ekranów

Aplikacje często dzielą dane użytkownika na wiele ekranów w ramach tej samej aktywności, zwłaszcza w przypadku działań związanych z tworzeniem nowego konta użytkownika. Na przykład na pierwszym ekranie pojawia się prośba o podanie nazwy użytkownika, a jeśli nazwa użytkownika jest dostępna, na drugim ekranie – o hasło. W takich sytuacjach usługa autouzupełniania musi poczekać, aż użytkownik wpisze oba pola, zanim wyświetli się interfejs zapisywania autouzupełniania. W takiej sytuacji wykonaj te czynności:

  1. W pierwszym żądaniu wypełnienia dodaj pakiet stanu klienta w odpowiedzi, która zawiera identyfikatory autouzupełniania pól częściowych widocznych na ekranie.
  2. W drugim żądaniu wypełnienia pobierz pakiet stanu klienta, pobierz ze stanu klienta identyfikatory autouzupełniania ustawione w poprzednim żądaniu i dodaj te identyfikatory oraz flagę FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE do obiektu SaveInfo użytego w drugiej odpowiedzi.
  3. W zapisywaniu żądania użyj odpowiednich obiektów FillContext, aby uzyskać wartość każdego pola. Na każde żądanie wypełnienia jest 1 kontekst wypełnienia.

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

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

Za każdym razem, gdy pojawia się żądanie autouzupełniania, system Android tworzy powiązanie z usługą i wywołuje jej metodę onConnected(). Gdy usługa przetworzy żądanie, system Android wywołuje metodę onDisconnected() i usuwa powiązanie z usługą. Możesz zaimplementować onConnected(), aby udostępnić kod uruchamiany przed przetworzeniem żądania, a onDisconnected(), by udostępnić kod uruchamiany po przetworzeniu żądania.

Dostosowywanie interfejsu zapisywania autouzupełniania

Usługi autouzupełniania mogą dostosowywać interfejs zapisu autouzupełniania, aby pomóc użytkownikom zdecydować, czy chcą, aby usługa zapisywała ich dane. Usługi mogą udostępniać dodatkowe informacje o zapisywaniu w postaci zwykłego tekstu lub widoku dostosowanego. Usługi mogą też zmieniać wygląd przycisku, który anuluje żądanie zapisu i otrzymywać powiadomienie, gdy użytkownik go kliknie. Więcej informacji znajdziesz na stronie z dokumentacją 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 on szczególnie przydatny w przypadku udostępnienia funkcji autouzupełniania w przeglądarkach, które nie korzystają bezpośrednio z interfejsów API autouzupełniania.

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

$ adb shell settings get global autofill_compat_mode_allowed_packages

Jeśli testowany pakiet nie znajduje się 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 resIdx, aby określić identyfikator zasobu w polu do wprowadzania danych, które zawiera adres URL renderowanej strony.

Tryb zgodności ma następujące ograniczenia:

  • Żądanie zapisania jest aktywowane, gdy usługa używa flagi FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE lub metody setTrigger(). Gdy używasz trybu zgodności, zasada FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE jest ustawiona domyślnie.
  • Wartości tekstowe węzłów mogą być niedostępne w metodzie onSaveRequest(SaveRequest, SaveCallback).

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