Создание сервисов автозаполнения

Служба автозаполнения — это приложение, которое упрощает пользователям заполнение форм путем внедрения данных в представления других приложений. Службы автозаполнения также могут извлекать пользовательские данные из представлений в приложении и сохранять их для использования в более позднее время. Службы автозаполнения обычно предоставляются приложениями, которые управляют пользовательскими данными, например, менеджерами паролей.

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 , который позаботится об аутентификации. Если вам нужны дополнительные данные для обработки запроса после завершения потока аутентификации, вы можете добавить такие данные в 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 рабочий_пароль
персональное_имя_пользователя личный_пароль
Адрес рабочая_улица 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 в сервисе автозаполнения:

  1. В службе автозаполнения используйте hasOngoingSmsRequest из SmsCodeAutofillClient , чтобы определить, есть ли какие-либо активные запросы для имени пакета приложения, которое вы заполняете автоматически. Ваша служба автозаполнения должна отображать подсказку только в том случае, если она возвращает false .
  2. В службе автозаполнения используйте checkPermissionState из SmsCodeAutofillClient , чтобы проверить, имеет ли служба автозаполнения разрешение на автозаполнение одноразовых кодов. Это состояние разрешения может быть NONE , GRANTED или DENIED . Служба автозаполнения должна отображать подсказку для состояний NONE и GRANTED .
  3. В действии аутентификации автозаполнения используйте разрешение SmsRetriever.SEND_PERMISSION для регистрации BroadcastReceiver , прослушивающего SmsCodeRetriever.SMS_CODE_RETRIEVED_ACTION , чтобы получать результат SMS-кода, когда он доступен.
  4. Вызовите startSmsCodeRetriever на SmsCodeAutofillClient , чтобы начать прослушивание одноразовых кодов, отправленных через SMS. Если пользователь предоставляет разрешения вашей службе автозаполнения на извлечение одноразовых кодов из SMS, она ищет SMS-сообщения, полученные в течение последних одной-пяти минут с текущего момента.

    Если вашей службе автозаполнения необходимо запросить разрешение пользователя на чтение одноразовых кодов, то Task , возвращаемая startSmsCodeRetriever , может завершиться ошибкой с возвращением ResolvableApiException . Если это произойдет, вам необходимо вызвать метод ResolvableApiException.startResolutionForResult() , чтобы отобразить диалоговое окно согласия для запроса на разрешение.

  5. Получите результат отправки SMS-кода из намерения, а затем верните SMS-код в качестве ответа автозаполнения.

Включить автозаполнение в Chrome

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, где пользователи могут включить сторонние службы автозаполнения, используйте Android Intent . Обязательно настройте действие и категории, как показано в этом примере:

Котлин

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) .
Сохранение данных, разделенных на несколько экранов

Приложения часто разделяют пользовательские данные на несколько экранов в одном действии, особенно в действиях, используемых для создания новой учетной записи пользователя. Например, первый экран запрашивает имя пользователя, и если имя пользователя доступно, второй экран запрашивает пароль. В таких ситуациях служба автозаполнения должна ждать, пока пользователь не введет оба поля, прежде чем можно будет отобразить пользовательский интерфейс сохранения автозаполнения. Выполните следующие действия для обработки таких сценариев:

  1. В первом запросе на заполнение добавьте в ответ пакет состояния клиента , содержащий идентификаторы автозаполнения частичных полей, представленных на экране.
  2. Во втором запросе на заполнение извлеките пакет состояния клиента, получите идентификаторы автозаполнения, установленные в предыдущем запросе из состояния клиента, и добавьте эти идентификаторы и флаг FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE к объекту SaveInfo , используемому во втором ответе.
  3. В запросе сохранения используйте соответствующие объекты FillContext для получения значения каждого поля. На каждый запрос заполнения приходится один контекст заполнения.

Для получения дополнительной информации см. раздел Сохранение при разделении данных на несколько экранов .

Предоставить логику инициализации и демонтажа для каждого запроса

Каждый раз, когда есть запрос на автозаполнение, система Android привязывается к службе и вызывает ее метод onConnected() . Как только служба обрабатывает запрос, система Android вызывает метод 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 .