Usługa autouzupełniania to aplikacja, która ułatwia użytkownikom wypełnianie formularzy poprzez wstawianie danych do widoków innych aplikacji. Usługi autouzupełniania mogą też pobierać dane użytkownika z widoków w aplikacji i przechowywać je do późniejszego wykorzystania. Usługi autouzupełniania są zwykle udostępniane przez aplikacje, które zarządzają danymi użytkowników, takie jak menedżery haseł.
Android ułatwia wypełnianie formularzy dzięki funkcji autouzupełniania dostępnej 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 zainstalowana aplikacja, która udostępnia usługi autouzupełniania.
Na tej stronie dowiesz się, jak wdrożyć w aplikacji usługę autouzupełniania. 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 działaniu usług autouzupełniania znajdziesz na stronach referencyjnych klas AutofillService
i AutofillManager
.
Deklaracje i uprawnienia w pliku manifestu
Aplikacje, które udostępniają usługi autouzupełniania, muszą zawierać deklarację opisującą sposób wdrożenia usługi. Aby określić deklarację, umieść element <service>
w pliku manifestu aplikacji. Element <service>
musi zawierać te atrybuty i elementy:
- atrybut
android:name
, który wskazuje podklasęAutofillService
w aplikacji, która implementuje usługę. android:permission
atrybut, który deklaruje uprawnienieBIND_AUTOFILL_SERVICE
.- Element
<intent-filter>
, którego obowiązkowy element podrzędny<action>
określa działanieandroid.service.autofill.AutofillService
. - Opcjonalny element
<meta-data>
, którego możesz użyć, aby podać dodatkowe parametry 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 zasób XML z dodatkowymi informacjami o usłudze.
service_configuration
w poprzednim przykładzie określa zasób, który umożliwia użytkownikom skonfigurowanie usługi. Poniższy przykład przedstawia 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 obecnie włączoną usługą, 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
. Intencja zwraca wartość RESULT_OK
, jeśli użytkownik wybierze usługę autouzupełniania, która pasuje do pakietu wywołującego.
Wypełnianie widoków klienta
Usługa autouzupełniania otrzymuje żądania wypełnienia widoków klienta, gdy użytkownik wchodzi w interakcję z innymi aplikacjami. Jeśli usługa autouzupełniania ma dane użytkownika, które spełniają wymagania żądania, wysyła je w odpowiedzi. System Android wyświetla interfejs autouzupełniania z dostępnymi danymi, jak pokazano na ilustracji 1:
Rysunek 1. Interfejs autouzupełniania wyświetlający zbiór 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 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 za pomocą danych użytkownika, które wcześniej zapisała. Jeśli usługa może spełnić żądanie, pakuje dane w obiekty Dataset
. Usługa wywołuje metodę onSuccess()
, przekazując obiekt FillResponse
zawierający obiekty Dataset
. Jeśli usługa nie ma danych, które spełniają żądanie, przekazuje wartość null
do metody onSuccess()
.
Jeśli podczas przetwarzania żądania wystąpi błąd, usługa wywoła metodę onFailure()
. Szczegółowe wyjaśnienie przepływu pracy znajdziesz w opisie na AutofillService
stronie referencyjnej.
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 mieć więcej niż jeden zbiór danych, który spełnia wymagania żądania. W takim przypadku system Android wyświetla w interfejsie autouzupełniania kilka opcji – po jednej dla każdego zbioru danych. Poniższy przykład kodu pokazuje, jak podać 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 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
to pierwsze podejście, którego usługa musi użyć do opisania zawartości widoku. Jednak aplikacje klienckie muszą jawnie podać atrybut w swoich widokach, zanim będzie on dostępny dla usługi.
Jeśli aplikacja kliencka nie udostępnia atrybutu autofillHints
, usługa musi używać własnych heurystyk do opisywania treści.
Usługa może używać metod z innych klas, np. getText()
lub getHint()
, aby uzyskać informacje o zawartości widoku.
Więcej informacji znajdziesz w artykule Podawanie wskazówek dotyczących automatycznego wypełniania.
Poniższy przykład pokazuje, jak przejść przez 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, są proszeni o zapisanie danych w bieżącej usłudze autouzupełniania, jak pokazano na rysunku 2.
Rysunek 2. Interfejs zapisywania autouzupełniania.
Aby zapisać dane, usługa musi wskazać, że jest zainteresowana przechowywaniem danych do wykorzystania w przyszłości. Zanim system Android wyśle prośbę o zapisanie danych, następuje prośba o wypełnienie, w której usługa ma możliwość wypełnienia widoków. Aby wskazać, że jest zainteresowana zapisaniem danych, usługa w odpowiedzi na żądanie wypełnienia zawiera obiekt SaveInfo
. Obiekt SaveInfo
zawiera co najmniej te dane:
- Typ zapisywanych danych użytkownika. Listę dostępnych wartości
SAVE_DATA
znajdziesz w sekcjiSaveInfo
. - Minimalny zestaw widoków, które muszą zostać zmienione, aby wywołać żądanie zapisania.
Na przykład formularz logowania zwykle wymaga od użytkownika zaktualizowania widoków
username
ipassword
, aby wywołać żądanie zapisu.
Obiekt SaveInfo
jest powiązany z obiektem FillResponse
, jak pokazano 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 zaimplementować logikę, która będzie utrwalać dane użytkownika w metodzie onSaveRequest()
, która jest zwykle wywoływana po zakończeniu działania klienta lub gdy aplikacja klienta wywoła metodę 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ą szyfrować dane wrażliwe przed ich zapisaniem. Dane użytkowników mogą jednak zawierać etykiety lub dane, które nie są wrażliwe. Na przykład konto użytkownika może zawierać etykietę, która oznacza dane jako konto służbowe lub osobiste. Usługi nie mogą szyfrować etykiet. Jeśli etykiety nie są szyfrowane, usługi mogą ich używać w widokach prezentacji, jeśli użytkownik nie uwierzytelnił się. Następnie usługi mogą zastąpić etykiety rzeczywistymi danymi po uwierzytelnieniu użytkownika.
Odroczenie interfejsu zapisywania autouzupełniania
Od Androida 10, jeśli używasz wielu ekranów do implementacji przepływu pracy autouzupełniania – na przykład jednego ekranu dla pola nazwy użytkownika, a drugiego dla hasła – możesz odłożyć interfejs zapisywania autouzupełniania, używając flagi SaveInfo.FLAG_DELAY_SAVE
.
Jeśli ten flag jest ustawiony, interfejs zapisywania autouzupełniania nie jest wywoływany, gdy zatwierdzony zostanie kontekst autouzupełniania powiązany z odpowiedzią SaveInfo
. Zamiast tego możesz użyć oddzielnego działania w ramach tego samego zadania, aby dostarczać przyszłe żądania wypełnienia, a następnie wyświetlać interfejs za pomocą żądania zapisu. Więcej informacji znajdziesz w sekcji SaveInfo.FLAG_DELAY_SAVE
.
Wymagaj uwierzytelnienia użytkownika
Usługi autouzupełniania mogą zapewniać dodatkowy poziom bezpieczeństwa, wymagając od użytkownika uwierzytelnienia przed wypełnieniem widoków. W tych scenariuszach warto wdrożyć uwierzytelnianie użytkowników:
- Dane użytkownika w aplikacji muszą być odblokowywane za pomocą hasła głównego lub skanu odcisku palca.
- Określony zbiór danych musi zostać odblokowany, np. dane karty kredytowej, za pomocą kodu weryfikacyjnego karty (CVC).
W sytuacji, gdy usługa wymaga uwierzytelnienia użytkownika przed odblokowaniem danych, może wyświetlać dane standardowe lub etykietę i określać Intent
, które zajmuje się uwierzytelnianiem. Jeśli po zakończeniu procesu uwierzytelniania potrzebujesz dodatkowych danych do przetworzenia żądania, możesz dodać je 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();
Po zakończeniu procesu uwierzytelniania aktywność musi wywołać metodę setResult()
, przekazując wartość RESULT_OK
, i ustawić dodatkowy parametr EXTRA_AUTHENTICATION_RESULT
na obiekt FillResponse
, który zawiera wypełniony zbiór danych. Poniższy kod pokazuje 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 scenariuszu, w którym należy odblokować zbiór danych karty kredytowej, usługa może wyświetlić interfejs z prośbą o podanie kodu CVC. Możesz ukryć dane do momentu odblokowania zbioru danych, wyświetlając dane standardowe, takie jak nazwa banku i 4 ostatnie cyfry numeru karty kredytowej. Poniższy przykład pokazuje, jak wymagać uwierzytelniania w przypadku zbioru danych i ukrywać dane do momentu, aż użytkownik poda kod 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();
Gdy aktywność zweryfikuje kod CVC, powinna wywołać metodę setResult()
, przekazując wartość RESULT_OK
i ustawiając dodatkowy element 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ą od razu wypełniane. Poniższy kod pokazuje przykład zwracania zbioru danych po podaniu przez użytkownika kodu 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 oddzielają koncepcje z różnych domen. Na tej stronie te grupy logiczne są nazywane partycjami. Poniżej znajdziesz typowe przykłady partycji i pól:
- dane logowania, w tym pola nazwy użytkownika i hasła;
- Adres, który zawiera pola ulica, miasto, województwo i kod pocztowy.
- dane do płatności, w tym numer karty kredytowej, datę ważności i pola kodu weryfikacyjnego;
Usługa autouzupełniania, która prawidłowo dzieli dane na partycje, może lepiej chronić dane użytkowników, ponieważ nie udostępnia w zbiorze danych informacji z więcej niż jednej partycji. Na przykład zbiór danych zawierający dane logowania nie musi zawierać informacji o płatnościach. Organizowanie danych w partycjach umożliwia usłudze udostępnianie minimalnej ilości odpowiednich informacji wymaganych do spełnienia żą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 zawiera widoki nazwy użytkownika, hasła, ulicy i miasta, oraz usługę autouzupełniania, która ma 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 dla konta służbowego, jak i osobistego. Gdy użytkownik wybierze zbiór danych, kolejna odpowiedź autouzupełniania może zawierać adres służbowy lub osobisty, 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 przechodzenia przez obiekt 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 SMS Retriever API.
Aby korzystać z tej funkcji, musisz spełniać te wymagania:
- Usługa autouzupełniania działa na Androidzie 9 (API na poziomie 28) lub nowszym.
- Użytkownik wyraża zgodę na odczytywanie przez usługę autouzupełniania jednorazowych kodów z SMS-ów.
- Aplikacja, w której chcesz włączyć autouzupełnianie, nie korzysta jeszcze z interfejsu SMS Retriever API do odczytywania kodów jednorazowych.
Usługa autouzupełniania może korzystać z SmsCodeAutofillClient
, która jest dostępna po wywołaniu SmsCodeRetriever.getAutofillClient()
z Usług Google Play w wersji 19.0.56 lub nowszej.
Główne etapy korzystania z tego interfejsu API w usłudze autouzupełniania to:
- W usłudze autouzupełniania użyj
hasOngoingSmsRequest
zSmsCodeAutofillClient
, aby sprawdzić, czy dla nazwy pakietu aplikacji, w której chcesz użyć autouzupełniania, są aktywne jakieś żądania. Usługa autouzupełniania musi wyświetlać sugestię tylko wtedy, gdy ta funkcja zwraca wartośćfalse
. - W usłudze autouzupełniania użyj
checkPermissionState
zSmsCodeAutofillClient
, aby sprawdzić, czy usługa autouzupełniania ma uprawnienia do autouzupełniania jednorazowych kodów. Ten stan uprawnień może mieć wartośćNONE
,GRANTED
lubDENIED
. Usługa autouzupełniania musi wyświetlać prośbę o sugestię w przypadku stanówNONE
iGRANTED
. - W aktywności uwierzytelniania autouzupełniania użyj uprawnienia
SmsRetriever.SEND_PERMISSION
, aby zarejestrowaćBroadcastReceiver
nasłuchiwanieSmsCodeRetriever.SMS_CODE_RETRIEVED_ACTION
w celu otrzymania wyniku kodu SMS gdy będzie dostępny. Zadzwoń pod numer
startSmsCodeRetriever
naSmsCodeAutofillClient
, aby zacząć nasłuchiwać jednorazowe kody wysyłane SMS-em. Jeśli użytkownik przyzna usłudze autouzupełniania uprawnienia do pobierania jednorazowych kodów z SMS-ów, usługa będzie wyszukiwać SMS-y otrzymane w ciągu ostatnich 1–5 minut.Jeśli usługa autouzupełniania musi poprosić użytkownika o zezwolenie na odczytanie kodów jednorazowych, wartość
Task
zwrócona przezstartSmsCodeRetriever
może być nieprawidłowa i zwrócić wartośćResolvableApiException
. W takim przypadku musisz wywołać metodęResolvableApiException.startResolutionForResult()
, aby wyświetlić okno z prośbą o zgodę na dostęp do uprawnień.Otrzymaj kod SMS z intencji, a następnie zwróć go jako odpowiedź autouzupełniania.
Włączanie autouzupełniania w Chrome
Chrome umożliwia usługom automatycznego wypełniania innych firm natywne wypełnianie formularzy, co zapewnia użytkownikom prostsze i wygodniejsze korzystanie z internetu. Aby używać usług autouzupełniania innych firm do automatycznego wypełniania haseł, kluczy dostępu i innych informacji, takich jak adresy i dane do płatności, użytkownicy muszą w ustawieniach Chrome wybrać Autouzupełnianie przy użyciu innej usługi.
Aby zapewnić użytkownikom jak najlepsze działanie autouzupełniania w Chrome na Androidzie, dostawcy usług autouzupełniania powinni zachęcać użytkowników do określania preferowanego dostawcy usług w ustawieniach Chrome.
Aby pomóc użytkownikom w przełączeniu suwaka, deweloperzy mogą:
- Wysyłanie zapytań do ustawień Chrome i sprawdzanie, czy użytkownik chce korzystać z usługi automatycznego wypełniania formularzy innej firmy.
- Link bezpośredni do strony ustawień Chrome, na której użytkownicy mogą włączyć usługi automatycznego wypełniania innych firm.
Odczytywanie ustawień Chrome
Każda aplikacja może odczytać, czy Chrome używa trybu autouzupełniania innej firmy, który umożliwia korzystanie z autouzupełniania na Androidzie. Chrome używa ContentProvider
Androida do przekazywania tych informacji. W pliku manifestu Androida zadeklaruj, z których kanałów chcesz odczytywać ustawienia:
<uses-permission android:name="android.permission.READ_USER_DICTIONARY"/>
<queries>
<!-- To Query Chrome Beta: -->
<package android:name="com.chrome.beta" />
<!-- To Query Chrome Stable: -->
<package android:name="com.android.chrome" />
</queries>
Następnie użyj funkcji ContentResolver
Androida, aby poprosić o te informacje, tworząc identyfikator URI treści:
Kotlin
val CHROME_CHANNEL_PACKAGE = "com.android.chrome" // Chrome Stable. val CONTENT_PROVIDER_NAME = ".AutofillThirdPartyModeContentProvider" val THIRD_PARTY_MODE_COLUMN = "autofill_third_party_state" val THIRD_PARTY_MODE_ACTIONS_URI_PATH = "autofill_third_party_mode" val uri = Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(CHROME_CHANNEL_PACKAGE + CONTENT_PROVIDER_NAME) .path(THIRD_PARTY_MODE_ACTIONS_URI_PATH) .build() val cursor = contentResolver.query( uri, arrayOf(THIRD_PARTY_MODE_COLUMN), // projection null, // selection null, // selectionArgs null // sortOrder ) if (cursor == null) { // Terminate now! Older versions of Chromium don't provide this information. } cursor?.use { // Use the safe call operator and the use function for auto-closing if (it.moveToFirst()) { // Check if the cursor has any rows val index = it.getColumnIndex(THIRD_PARTY_MODE_COLUMN) if (index != -1) { // Check if the column exists val value = it.getInt(index) if (0 == value) { // 0 means that the third party mode is turned off. Chrome uses its built-in // password manager. This is the default for new users. } else { // 1 means that the third party mode is turned on. Chrome uses forwards all // autofill requests to Android Autofill. Users have to opt-in for this. } } else { // Handle the case where the column doesn't exist. Log a warning, perhaps. Log.w("Autofill", "Column $THIRD_PARTY_MODE_COLUMN not found in cursor") } } } // The cursor is automatically closed here
Java
final String CHROME_CHANNEL_PACKAGE = "com.android.chrome"; // Chrome Stable. final String CONTENT_PROVIDER_NAME = ".AutofillThirdPartyModeContentProvider"; final String THIRD_PARTY_MODE_COLUMN = "autofill_third_party_state"; final String THIRD_PARTY_MODE_ACTIONS_URI_PATH = "autofill_third_party_mode"; final Uri uri = new Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(CHROME_CHANNEL_PACKAGE + CONTENT_PROVIDER_NAME) .path(THIRD_PARTY_MODE_ACTIONS_URI_PATH) .build(); final Cursor cursor = getContentResolver().query( uri, /*projection=*/new String[] {THIRD_PARTY_MODE_COLUMN}, /*selection=*/ null, /*selectionArgs=*/ null, /*sortOrder=*/ null); if (cursor == null) { // Terminate now! Older versions of Chromium don't provide this information. } cursor.moveToFirst(); // Retrieve the result; int index = cursor.getColumnIndex(THIRD_PARTY_MODE_COLUMN); if (0 == cursor.getInt(index)) { // 0 means that the third party mode is turned off. Chrome uses its built-in // password manager. This is the default for new users. } else { // 1 means that the third party mode is turned on. Chrome uses forwards all // autofill requests to Android Autofill. Users have to opt-in for this. }
Precyzyjny link do ustawień Chrome
Aby utworzyć link bezpośredni do strony ustawień Chrome, na której użytkownicy mogą włączyć usługi autouzupełniania innych firm, użyj intencji Androida Intent
.
Skonfiguruj działanie i kategorie zgodnie z tym przykładem:
Kotlin
val autofillSettingsIntent = Intent(Intent.ACTION_APPLICATION_PREFERENCES) autofillSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT) autofillSettingsIntent.addCategory(Intent.CATEGORY_APP_BROWSER) autofillSettingsIntent.addCategory(Intent.CATEGORY_PREFERENCE) // Invoking the intent with a chooser allows users to select the channel they // want to configure. If only one browser reacts to the intent, the chooser is // skipped. val chooser = Intent.createChooser(autofillSettingsIntent, "Pick Chrome Channel") startActivity(chooser) // If the caller knows which Chrome channel they want to configure, // they can instead add a package hint to the intent, e.g. val specificChromeIntent = Intent(Intent.ACTION_APPLICATION_PREFERENCES) // Create a *new* intent specificChromeIntent.addCategory(Intent.CATEGORY_DEFAULT) specificChromeIntent.addCategory(Intent.CATEGORY_APP_BROWSER) specificChromeIntent.addCategory(Intent.CATEGORY_PREFERENCE) specificChromeIntent.setPackage("com.android.chrome") // Set the package on the *new* intent startActivity(specificChromeIntent) // Start the *new* intent
Java
Intent autofillSettingsIntent = new Intent(Intent.ACTION_APPLICATION_PREFERENCES); autofillSettingsIntent.addCategory(Intent.CATEGORY_DEFAULT); autofillSettingsIntent.addCategory(Intent.CATEGORY_APP_BROWSER); autofillSettingsIntent.addCategory(Intent.CATEGORY_PREFERENCE); // Invoking the intent with a chooser allows users to select the channel they // want to configure. If only one browser reacts to the intent, the chooser is // skipped. Intent chooser = Intent.createChooser(autofillSettingsIntent, "Pick Chrome Channel"); startActivity(chooser); // If the caller knows which Chrome channel they want to configure, // they can instead add a package hint to the intent, e.g. autofillSettingsIntent.setPackage("com.android.chrome"); startActivity(autofillSettingsInstent);
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 tekście, a nie w menu rozwijanym. 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ź autouzupełniania może przekroczyć dopuszczalny rozmiar transakcji obiektu
Binder
, który reprezentuje obiekt zdalny wymagany do przetworzenia żądania. Aby zapobiec zgłaszaniu przez system Android wyjątku w tych scenariuszach, możesz utrzymywać mały rozmiarFillResponse
, dodając nie więcej niż 20 obiektówDataset
naraz. Jeśli odpowiedź wymaga większej liczby zbiorów danych, możesz dodać zbiór, który informuje użytkowników, że jest więcej informacji, i pobiera następną grupę zbiorów danych po wybraniu. Więcej informacji znajdziesz w sekcjiaddDataset(Dataset)
. - Zapisywanie danych podzielonych na kilka ekranów
Aplikacje często dzielą dane użytkownika na kilka ekranów w ramach tej samej aktywności, szczególnie w przypadku aktywności służących do tworzenia nowego konta użytkownika. Na przykład na pierwszym ekranie pojawia się prośba o podanie nazwy użytkownika, a jeśli jest ona dostępna, na drugim ekranie pojawia się prośba o podanie hasła. W takich sytuacjach usługa autouzupełniania musi poczekać, aż użytkownik wpisze dane w obu polach, zanim wyświetli interfejs zapisywania autouzupełniania. Aby sobie z nimi poradzić, wykonaj te czynności:
- W pierwszym żądaniu wypełnienia dodaj w odpowiedzi pakiet stanu klienta, który zawiera identyfikatory automatycznego wypełniania częściowych pól widocznych na ekranie.
- W drugim żądaniu wypełnienia pobierz pakiet stanu klienta, uzyskaj identyfikatory autouzupełniania ustawione w poprzednim żądaniu ze stanu klienta i dodaj te identyfikatory oraz flagę
FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE
do obiektuSaveInfo
użytego w drugiej odpowiedzi. - W save request użyj odpowiednich obiektów
FillContext
, aby uzyskać wartość każdego pola. Na każde żądanie wypełnienia przypada jeden kontekst wypełnienia.
Więcej informacji znajdziesz w artykule Zapisywanie danych podzielonych na kilka ekranów.
- Dla każdego żądania podaj logikę inicjowania i zamykania.
Za każdym razem, gdy pojawia się żądanie autouzupeł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 odłączy się od usługi. Możesz zaimplementowaćonConnected()
, aby udostępnić kod, który jest uruchamiany przed przetworzeniem żądania, ionDisconnected()
, aby udostępnić kod, który jest uruchamiany 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ą zezwolić usłudze na zapisywanie ich danych. Usługi mogą podawać dodatkowe informacje o tym, co jest zapisywane, w formie zwykłego tekstu lub w widoku dostosowanym do potrzeb użytkownika. Usługi mogą też zmieniać wygląd przycisku, który anuluje żądanie zapisania, i otrzymywać powiadomienie, gdy użytkownik go naciśnie. 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 udostępniania 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, musisz wyraźnie dodać do listy dozwolonych przeglądarkę lub aplikację, która wymaga trybu zgodności. Możesz sprawdzić, które pakiety są już na liście dozwolonych, uruchamiając to polecenie:
$ adb shell settings get global autofill_compat_mode_allowed_packages
Jeśli testowanego pakietu 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 parametru
resIdx
, aby określić identyfikator zasobu pola wejściowego, które zawiera adres URL renderowanej strony.
Tryb zgodności ma te ograniczenia:
- Żądanie zapisu jest wywoływane, gdy usługa używa flagi
FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE
lub wywoływana jest metodasetTrigger()
.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE
jest ustawiony domyślnie, gdy używasz 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 związanych z nim ograniczeniach, znajdziesz w dokumentacji klasy AutofillService
.