Создание служб автозаполнения,Создание служб автозаполнения

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

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
Реквизиты для входа имя_рабочего_пользователя рабочий_пароль
личное_имя_пользователя личный_пароль
Адрес рабочая_улица рабочий_город
персональная_улица личный_город

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

Служба может идентифицировать поле, вызвавшее запрос, вызвав метод isFocused() при обходе объекта AssistStructure . Это позволяет службе подготовить FillResponse с соответствующими данными раздела.

Автозаполнение одноразового кода по СМС

Ваша служба автозаполнения может помочь пользователю заполнить одноразовые коды, отправленные по SMS, с помощью API SMS Retriever.

Для использования этой функции должны быть выполнены следующие требования:

  • Служба автозаполнения работает на Android 9 (уровень API 28) или выше.
  • Пользователь дает согласие вашему сервису автозаполнения на чтение одноразовых кодов из СМС.
  • Приложение, для которого вы предоставляете автозаполнение, еще не использует API SMS Retriever для чтения одноразовых кодов.

Ваша служба автозаполнения может использовать SmsCodeAutofillClient , доступный путем вызова SmsCodeRetriever.getAutofillClient() из Служб Google Play 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 в качестве ответа автозаполнения.

Расширенные сценарии автозаполнения

Интеграция с клавиатурой
Начиная с 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 .

,

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

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
Реквизиты для входа имя_рабочего_пользователя рабочий_пароль
личное_имя_пользователя личный_пароль
Адрес рабочая_улица рабочий_город
персональная_улица личный_город

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

Служба может идентифицировать поле, вызвавшее запрос, вызвав метод isFocused() при обходе объекта AssistStructure . Это позволяет службе подготовить FillResponse с соответствующими данными раздела.

Автозаполнение одноразового кода по СМС

Ваша служба автозаполнения может помочь пользователю заполнить одноразовые коды, отправленные по SMS, с помощью API SMS Retriever.

Для использования этой функции должны быть выполнены следующие требования:

  • Служба автозаполнения работает на Android 9 (уровень API 28) или выше.
  • Пользователь дает согласие вашему сервису автозаполнения на чтение одноразовых кодов из СМС.
  • Приложение, для которого вы предоставляете автозаполнение, еще не использует API SMS Retriever для чтения одноразовых кодов.

Ваша служба автозаполнения может использовать SmsCodeAutofillClient , доступный путем вызова SmsCodeRetriever.getAutofillClient() из Служб Google Play 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 в качестве ответа автозаполнения.

Расширенные сценарии автозаполнения

Интеграция с клавиатурой
Начиная с 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 .