Служба автозаполнения — это приложение, которое упрощает пользователям заполнение форм, добавляя данные в представления других приложений. Службы автозаполнения также могут извлекать пользовательские данные из представлений приложения и сохранять их для использования в будущем. Службы автозаполнения обычно предоставляются приложениями, управляющими пользовательскими данными, например, менеджерами паролей.
Android упрощает заполнение форм благодаря фреймворку автозаполнения, доступному в Android 8.0 (API уровня 26) и выше. Пользователи могут воспользоваться функциями автозаполнения только при наличии на их устройстве приложения, предоставляющего такую услугу.
На этой странице показано, как реализовать службу автозаполнения в вашем приложении. Если вы ищете пример кода, демонстрирующий реализацию службы, см. пример AutofillFramework на Java или Kotlin . Подробнее о работе служб автозаполнения см. на справочных страницах по классам AutofillService
и AutofillManager
.
Манифест деклараций и разрешений
Приложения, предоставляющие услуги автозаполнения, должны включать декларацию, описывающую реализацию службы. Чтобы указать декларацию, включите элемент <service>
в манифест приложения . Элемент <service>
должен включать следующие атрибуты и элементы:
- Атрибут
android:name
, который указывает на подклассAutofillService
в приложении, реализующем службу. - Атрибут
android:permission
, который объявляет разрешениеBIND_AUTOFILL_SERVICE
. - Элемент
<intent-filter>
, обязательный дочерний элемент<action>
которого определяет действиеandroid.service.autofill.AutofillService
. - Дополнительный элемент
<meta-data>
, который можно использовать для предоставления дополнительных параметров конфигурации для службы.
В следующем примере показано объявление службы автозаполнения:
<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>
Элемент <meta-data>
включает атрибут android:resource
, который указывает на XML-ресурс с дополнительной информацией о сервисе. Ресурс service_configuration
в предыдущем примере определяет действие, позволяющее пользователям настраивать сервис. В следующем примере показан XML-ресурс service_configuration
:
<autofill-service
xmlns:android="http://schemas.android.com/apk/res/android"
android:settingsActivity="com.example.android.SettingsActivity" />
Дополнительную информацию о ресурсах XML см. в разделе Обзор ресурсов приложения .
Запрос на включение услуги
Приложение используется в качестве службы автозаполнения после того, как оно объявляет разрешение BIND_AUTOFILL_SERVICE
, а пользователь включает его в настройках устройства. Приложение может проверить, включена ли эта служба в данный момент, вызвав метод hasEnabledAutofillServices()
класса AutofillManager
.
Если приложение не является текущей службой автозаполнения, оно может запросить у пользователя изменение настроек автозаполнения, используя намерение ACTION_REQUEST_SET_AUTOFILL_SERVICE
. Намерение возвращает значение RESULT_OK
, если пользователь выбирает службу автозаполнения, соответствующую пакету вызывающего объекта.
Заполнить клиентские представления
Служба автозаполнения получает запросы на заполнение клиентских представлений при взаимодействии пользователя с другими приложениями. Если у службы автозаполнения есть пользовательские данные, удовлетворяющие запросу, она отправляет их в ответе. Система Android отображает интерфейс автозаполнения с доступными данными, как показано на рисунке 1:
Рисунок 1. Интерфейс автозаполнения, отображающий набор данных.
Фреймворк автозаполнения определяет рабочий процесс заполнения представлений, призванный минимизировать время, которое система Android использует для работы со службой автозаполнения. В каждом запросе система Android отправляет службе объект AssistStructure
, вызывая метод onFillRequest()
.
Служба автозаполнения проверяет, может ли она удовлетворить запрос пользовательскими данными, которые она ранее сохранила. Если запрос выполнен, служба упаковывает данные в объекты Dataset
. Служба вызывает метод onSuccess()
, передавая объект FillResponse
, содержащий объекты Dataset
. Если у службы нет данных для удовлетворения запроса, она передаёт null
методу onSuccess()
.
Вместо этого сервис вызывает метод onFailure()
, если при обработке запроса возникает ошибка. Подробное описание рабочего процесса см. на странице справки AutofillService
.
В следующем коде показан пример метода onFillRequest()
:
Котлин
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)
Ява
@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; }
Сервис может иметь более одного набора данных, удовлетворяющего запросу. В этом случае система Android отображает несколько вариантов — по одному для каждого набора данных — в интерфейсе автозаполнения. В следующем примере кода показано, как предоставить несколько наборов данных в ответе:
Котлин
// 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()
Ява
// 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();
Службы автозаполнения могут перемещаться по объектам ViewNode
в AssistStructure
для получения данных автозаполнения, необходимых для выполнения запроса. Служба может получать данные автозаполнения, используя методы класса ViewNode
, например, getAutofillId()
.
Сервис должен иметь возможность описать содержимое представления, чтобы проверить, может ли он удовлетворить запрос. Использование атрибута autofillHints
— это первый подход, который сервис должен использовать для описания содержимого представления. Однако клиентские приложения должны явно указать этот атрибут в своих представлениях, прежде чем он станет доступен сервису.
Если клиентское приложение не предоставляет атрибут autofillHints
, служба должна использовать собственную эвристику для описания содержимого. Служба может использовать методы других классов, например getText()
или getHint()
, для получения информации о содержимом представления. Подробнее см. в разделе Предоставление подсказок для автозаполнения .
В следующем примере показано, как обойти AssistStructure
и извлечь данные автозаполнения из объекта ViewNode
:
Котлин
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) } }
Ява
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); } }
Сохранить данные пользователя
Сервису автозаполнения требуются пользовательские данные для заполнения представлений в приложениях. Когда пользователи вручную заполняют представление, им предлагается сохранить данные в текущем сервисе автозаполнения, как показано на рисунке 2.
Рисунок 2. Интерфейс сохранения автозаполнения.
Чтобы сохранить данные, сервис должен указать, что он заинтересован в их сохранении для будущего использования. Прежде чем система Android отправит запрос на сохранение данных, сервису предоставляется возможность заполнить представления. Чтобы указать, что он заинтересован в сохранении данных, сервис включает объект SaveInfo
в ответ на запрос на заполнение. Объект SaveInfo
содержит как минимум следующие данные:
- Тип сохраняемых пользовательских данных. Список доступных значений
SAVE_DATA
см. вSaveInfo
. - Минимальный набор представлений, которые необходимо изменить для запуска запроса на сохранение. Например, форма входа обычно требует от пользователя обновить
username
иpassword
, чтобы запустить запрос на сохранение.
Объект SaveInfo
связан с объектом FillResponse
, как показано в следующем примере кода:
Котлин
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() ... }
Ява
@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(); ... }
Служба автозаполнения может реализовать логику для сохранения пользовательских данных в методе onSaveRequest()
, который обычно вызывается после завершения клиентской активности или когда клиентское приложение вызывает commit()
. Следующий код демонстрирует пример метода onSaveRequest()
:
Котлин
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() }
Ява
@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(); }
Сервисы автозаполнения должны шифровать конфиденциальные данные перед их сохранением. Однако пользовательские данные могут включать метки или данные, которые не являются конфиденциальными. Например, учётная запись пользователя может включать метку, которая отмечает данные как рабочие или личные . Сервисы не должны шифровать метки. Не шифруя метки, сервисы могут использовать их в представлениях, если пользователь не прошёл аутентификацию. Затем сервисы могут заменить метки фактическими данными после аутентификации пользователя.
Отложить сохранение автозаполнения пользовательского интерфейса
Начиная с Android 10, если вы используете несколько экранов для реализации рабочего процесса автозаполнения (например, один экран для поля имени пользователя и другой для пароля), вы можете отложить сохранение пользовательского интерфейса автозаполнения с помощью флага SaveInfo.FLAG_DELAY_SAVE
.
Если этот флаг установлен, интерфейс сохранения автозаполнения не активируется при фиксации контекста автозаполнения, связанного с ответом SaveInfo
. Вместо этого можно использовать отдельное действие в той же задаче для отправки будущих запросов на заполнение, а затем отобразить интерфейс через запрос на сохранение. Подробнее см. SaveInfo.FLAG_DELAY_SAVE
.
Требовать аутентификацию пользователя
Службы автозаполнения могут обеспечить дополнительный уровень безопасности, требуя от пользователя аутентификации перед заполнением полей. В следующих сценариях рекомендуется реализовать аутентификацию пользователя:
- Данные пользователя в приложении необходимо разблокировать с помощью основного пароля или сканирования отпечатка пальца.
- Необходимо разблокировать определенный набор данных, например данные кредитной карты, используя код проверки карты (CVC).
В сценарии, когда сервис требует аутентификации пользователя перед разблокировкой данных, он может предоставить шаблонные данные или метку и указать Intent
, которое отвечает за аутентификацию. Если вам требуются дополнительные данные для обработки запроса после завершения аутентификации, вы можете добавить их в намерение. Действие аутентификации затем может вернуть эти данные классу AutofillService
в вашем приложении.
В следующем примере кода показано, как указать, что запрос требует аутентификации:
Котлин
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()
Ява
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();
После завершения процесса аутентификации действие должно вызвать метод setResult()
, передав значение RESULT_OK
, и установить дополнительный параметр EXTRA_AUTHENTICATION_RESULT
для объекта FillResponse
, содержащего заполненный набор данных. Следующий код показывает пример возврата результата после завершения процесса аутентификации:
Котлин
// 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)
Ява
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);
В случае необходимости разблокировки набора данных кредитной карты сервис может отобразить пользовательский интерфейс с запросом CVC-кода. Вы можете скрыть данные до разблокировки набора данных, указав шаблонные данные, например, название банка и последние четыре цифры номера кредитной карты. В следующем примере показано, как потребовать аутентификацию для набора данных и скрыть данные до тех пор, пока пользователь не предоставит CVC-код:
Котлин
// 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()
Ява
// 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();
После проверки CVC-кода действие должно вызвать метод setResult()
, передав значение RESULT_OK
, и установить дополнительный параметр EXTRA_AUTHENTICATION_RESULT
для объекта Dataset
, содержащего номер кредитной карты и срок действия. Новый набор данных заменяет набор данных, требующий аутентификации, и представления заполняются немедленно. В следующем коде показан пример возврата набора данных после ввода CVC-кода пользователем:
Котлин
// 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) }
Ява
// 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);
Организуйте данные в логические группы
Службы автозаполнения должны организовывать данные в логические группы, изолирующие понятия из разных предметных областей. На этой странице эти логические группы называются разделами . Ниже приведены типичные примеры разделов и полей:
- Учетные данные, включающие поля имени пользователя и пароля.
- Адрес, включающий поля улицы, города, штата и почтового индекса.
- Платежная информация, включающая номер кредитной карты, срок действия и поля проверочного кода.
Сервис автозаполнения, который правильно разделяет данные, способен лучше защищать данные своих пользователей, не раскрывая данные из нескольких разделов набора данных. Например, набор данных, содержащий учётные данные, не обязан включать платёжную информацию. Организация данных по разделам позволяет вашему сервису предоставлять минимальное количество релевантной информации, необходимое для выполнения запроса.
Организация данных в разделы позволяет сервисам заполнять действия, содержащие представления из нескольких разделов, отправляя при этом минимальное количество релевантных данных в клиентское приложение. Например, рассмотрим действие, включающее представления имени пользователя, пароля, улицы и города, а также сервис автозаполнения со следующими данными:
Раздел | Поле 1 | Поле 2 |
---|---|---|
Реквизиты для входа | work_username | рабочий_пароль |
персональное_имя_пользователя | личный_пароль | |
Адрес | рабочая_улица | work_city |
персональная_улица | личный_город |
Сервис может подготовить набор данных, включающий раздел с учётными данными как для рабочих, так и для личных учётных записей. При выборе набора данных последующий ответ автозаполнения может содержать рабочий или личный адрес, в зависимости от первоначального выбора пользователя.
Служба может определить поле, из которого был отправлен запрос, вызвав метод isFocused()
при обходе объекта AssistStructure
. Это позволяет службе подготовить FillResponse
с соответствующими данными раздела.
Автозаполнение одноразового кода SMS
Ваша служба автозаполнения может помочь пользователю заполнять одноразовые коды, отправленные по SMS, с помощью API SMS Retriever.
Для использования этой функции необходимо выполнить следующие требования:
- Служба автозаполнения работает на базе Android 9 (уровень API 28) или выше.
- Пользователь дает согласие на то, чтобы ваш сервис автозаполнения считывал одноразовые коды из СМС.
- Приложение, для которого вы обеспечиваете автозаполнение, еще не использует API SMS Retriever для считывания одноразовых кодов.
Ваша служба автозаполнения может использовать SmsCodeAutofillClient
, доступный путем вызова SmsCodeRetriever.getAutofillClient()
из Google Play Services 19.0.56 или выше.
Основные шаги по использованию этого API в сервисе автозаполнения:
- В службе автозаполнения используйте
hasOngoingSmsRequest
изSmsCodeAutofillClient
, чтобы определить, есть ли активные запросы на имя пакета приложения, которое вы заполняете. Служба автозаполнения должна отображать подсказку только в том случае, если функция возвращаетfalse
. - В службе автозаполнения используйте
checkPermissionState
изSmsCodeAutofillClient
, чтобы проверить, есть ли у службы автозаполнения разрешение на автоматическое заполнение одноразовых кодов. Это может бытьNONE
,GRANTED
илиDENIED
. Служба автозаполнения должна отображать подсказку для состоянийNONE
иGRANTED
. - В действии аутентификации автозаполнения используйте разрешение
SmsRetriever.SEND_PERMISSION
для регистрацииBroadcastReceiver
, прослушивающегоSmsCodeRetriever.SMS_CODE_RETRIEVED_ACTION
, чтобы получать результат SMS-кода, когда он доступен. Вызовите
startSmsCodeRetriever
вSmsCodeAutofillClient
, чтобы начать прослушивание одноразовых кодов, отправленных по SMS. Если пользователь предоставляет вашей службе автозаполнения разрешение на извлечение одноразовых кодов из SMS, будет выполнен поиск SMS-сообщений, полученных в течение последних одной-пяти минут.Если ваш сервис автозаполнения должен запрашивать у пользователя разрешение на чтение одноразовых кодов, то
Task
, возвращаемаяstartSmsCodeRetriever
, может завершиться ошибкой с исключениемResolvableApiException
. В этом случае необходимо вызвать методResolvableApiException.startResolutionForResult()
чтобы отобразить диалоговое окно с запросом согласия.Получите результат SMS-кода из намерения, а затем верните SMS-код в качестве ответа автозаполнения.
Включить автозаполнение в Chrome
Chrome позволяет сторонним сервисам автозаполнения автоматически заполнять формы, что делает работу пользователей более удобной и простой. Чтобы использовать сторонние сервисы автозаполнения для автоматического заполнения паролей, ключей доступа и другой информации, такой как адреса и платежные данные, необходимо выбрать опцию «Автозаполнение с помощью другого сервиса» в настройках Chrome.
Чтобы обеспечить пользователям наилучший опыт автозаполнения с вашим сервисом и Chrome на Android, поставщики услуг автозаполнения должны рекомендовать своим пользователям указывать предпочитаемого поставщика услуг в настройках Chrome.
Чтобы помочь пользователям включить переключатель, разработчики могут:
- Запросите настройки Chrome и узнайте, хочет ли пользователь использовать сторонний сервис автозаполнения.
- Глубокая ссылка на страницу настроек Chrome, где пользователи могут включить сторонние службы автозаполнения.
Прочитать настройки Chrome
Любое приложение может узнать, использует ли Chrome режим автозаполнения 3P, позволяющий использовать Android Autofill. Chrome использует ContentProvider
Android для передачи этой информации. Укажите в манифесте Android, из каких каналов вы хотите считывать настройки:
<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>
Затем используйте ContentResolver
Android для запроса этой информации, построив URI контента:
Котлин
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
Ява
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. }
Глубокая ссылка на настройки Chrome
Чтобы создать глубокую ссылку на страницу настроек Chrome, где пользователи могут включить сторонние сервисы автозаполнения, используйте Intent
Android. Обязательно настройте действие и категории, как показано в этом примере:
Котлин
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
Ява
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);
Расширенные сценарии автозаполнения
- Интеграция с клавиатурой
- Начиная с Android 11, платформа позволяет клавиатурам и другим редакторам методов ввода ( IME ) отображать предложения автозаполнения в строке, а не в раскрывающемся меню. Подробнее о том, как ваша служба автозаполнения может поддерживать эту функцию, см. в статье Интеграция автозаполнения с клавиатурами .
- Разбить наборы данных на страницы
- Большой ответ автозаполнения может превысить допустимый размер транзакции объекта
Binder
, представляющего удалённый объект, необходимый для обработки запроса. Чтобы предотвратить возникновение исключения в таких ситуациях в системе Android, можно поддерживать небольшой размерFillResponse
, добавляя не более 20 объектовDataset
за раз. Если вашему ответу требуется больше наборов данных, можно добавить набор данных, который сообщит пользователям о наличии дополнительной информации и извлечёт следующую группу наборов данных при выборе. Подробнее см.addDataset(Dataset)
. - Сохранение данных, разделенных на несколько экранов
Приложения часто разделяют пользовательские данные на несколько экранов в рамках одного действия, особенно при создании новой учётной записи. Например, на первом экране запрашивается имя пользователя, и, если имя пользователя доступно, на втором экране запрашивается пароль. В таких ситуациях служба автозаполнения должна дождаться, пока пользователь введёт оба поля, прежде чем будет отображен интерфейс сохранения автозаполнения. Для обработки таких ситуаций выполните следующие действия:
- В первом запросе на заполнение добавьте в ответ пакет состояния клиента , содержащий идентификаторы автозаполнения частичных полей, представленных на экране.
- Во втором запросе на заполнение извлеките пакет состояния клиента, получите идентификаторы автозаполнения, установленные в предыдущем запросе из состояния клиента, и добавьте эти идентификаторы и флаг
FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE
к объектуSaveInfo
, используемому во втором ответе. - В запросе на сохранение используйте соответствующие объекты
FillContext
для получения значения каждого поля. На каждый запрос на заполнение приходится один контекст заполнения.
Дополнительную информацию см. в разделе Сохранение при разделении данных на несколько экранов .
- Предоставить логику инициализации и демонтажа для каждого запроса
При каждом запросе на автозаполнение система Android привязывается к сервису и вызывает его метод
onConnected()
. После обработки запроса сервис вызывает методonDisconnected()
и отключается от сервиса. Вы можете реализовать методonConnected()
для предоставления кода, выполняемого до обработки запроса, и методonDisconnected()
для предоставления кода, выполняемого после обработки запроса.- Настройте интерфейс сохранения автозаполнения
Сервисы автозаполнения могут настраивать пользовательский интерфейс сохранения данных, чтобы помочь пользователям решить, сохранять ли данные. Сервисы могут предоставлять дополнительную информацию о сохраняемых данных в виде простого текста или в виде настраиваемого представления. Сервисы также могут изменять внешний вид кнопки отмены запроса на сохранение и получать уведомление при нажатии этой кнопки. Подробнее см. на странице справки
SaveInfo
.- Режим совместимости
Режим совместимости позволяет службам автозаполнения использовать виртуальную структуру доступности для целей автозаполнения. Он особенно полезен для реализации функции автозаполнения в браузерах, которые явно не реализуют API автозаполнения.
Чтобы протестировать службу автозаполнения в режиме совместимости, явно добавьте браузер или приложение, требующее режима совместимости, в разрешённый список. Вы можете проверить, какие пакеты уже добавлены в разрешённый список, выполнив следующую команду:
$ adb shell settings get global autofill_compat_mode_allowed_packages
Если тестируемый вами пакет отсутствует в списке, добавьте его, выполнив следующую команду, где
pkgX
— пакет приложения:$ adb shell settings put global autofill_compat_mode_allowed_packages pkg1[resId1]:pkg2[resId1,resId2]
Если приложение является браузером, то используйте
resIdx
для указания идентификатора ресурса поля ввода, содержащего URL отображаемой страницы.
Режим совместимости имеет следующие ограничения:
- Запрос на сохранение инициируется, когда служба использует флаг
FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE
или вызывается методsetTrigger()
.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE
устанавливается по умолчанию при использовании режима совместимости. - Текстовое значение узлов может быть недоступно в методе
onSaveRequest(SaveRequest, SaveCallback)
.
Дополнительную информацию о режиме совместимости, включая связанные с ним ограничения, см. в справочнике по классу AutofillService
.