自動入力サービスの作成

自動入力サービスは、他のアプリのビューの中にデータを挿入することによりユーザーによるフォーム入力を簡単にするアプリです。また自動入力サービスにより、アプリのビューからユーザーデータを取り出し、後で使用できるように保存しておくこともできます。通常、自動入力サービスは、パスワード管理機能など、ユーザーデータを管理するアプリによって提供されます。

Android の場合は、Android 8.0(API レベル 26)以降で使用可能な自動入力フレームワークによりフォームに容易に入力できます。ユーザーは、自分の端末に自動入力サービスを提供するアプリが存在する場合のみ、自動入力機能を利用できます。

このページでは、アプリに自動入力サービスを実装する方法について説明します。サービスを実装する方法を示すコードサンプルについては、Android AutofillFramework サンプルをご覧ください。自動入力サービスの仕組みについて詳しくは、AutofillServiceクラスとAutofillManagerクラスのリファレンス資料をご覧ください。

マニフェストの宣言とパーミッション

自動入力サービスを提供するアプリには、サービスの実装について記述する宣言が含まれていなければなりません。宣言を指定するには、アプリのマニフェストの中に <service> 要素を含めます。<service> 要素には、次の属性および要素が含まれていなければなりません:

次の例は、自動入力サービスの宣言を示しています:

<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> 要素には、サービスについての詳細が含まれる XML リソースを参照する android:resource 属性が含まれています。前の例の service_configuration リソースは、ユーザーがサービスを設定するためのアクティビティを指定します。次の例は、service_configuration XML リソースを示しています:

<autofill-service
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:settingsActivity="com.example.android.SettingsActivity" />

XML リソースについて詳しくは、リソースの指定をご覧ください。

サービスを有効にするためのプロンプト

アプリが BIND_AUTOFILL_SERVICE パーミッションを宣言している場合、ユーザーが端末の設定でそれを有効にすると、自動入力サービスとして使用できるようになります。アプリのサービスが現在有効になっているかどうかを確認するには、AutofillManager クラスの hasEnabledAutofillServices() メソッドを呼び出します。

アプリが現在の自動入力サービスではない場合、ACTION_REQUEST_SET_AUTOFILL_SERVICE インテントを使用することにより、自動入力設定を変更するようにユーザーにリクエストすることができます。このインテントは、呼び出し元パッケージに一致する自動入力サービスをユーザーが選択した場合に RESULT_OK 値を返します。

クライアントビューに記入する

自動入力サービスは、ユーザーが他のアプリとやり取りする際に、クライアントビューへの記入のリクエストを受信します。そのリクエストを満たすユーザーデータが自動入力サービスにある場合、応答としてそのデータを送信します。Android システムにより、利用可能なデータを示す自動入力 UI が表示されます。図 1 をご覧ください:

自動入力 UI

図1.自動入力 UI によるデータセットの表示。

自動入力フレームワークにより、ビューへの記入のワークフローが定義されます。それは、Android システムが自動入力サービスに関わる時間が最小限に抑えられるように設計されています。リクエストごとに Android システムは、onFillRequest() メソッドを呼び出すことにより、サービスに対して AssistStructure オブジェクトを送信します。自動入力サービスでは、それまでに保存したユーザーデータでそのリクエストを満たすものがあるかどうかを調べます。リクエストを満たすことができるなら、サービスはそのデータを Dataset オブジェクトの中にパッケージングします。サービスは、onSuccess() メソッドを呼び出し、Dataset オブジェクトを含む FillResponse オブジェクトをそれに渡します。リクエストを満たすデータがない場合は、onSuccess() メソッドに null を渡します。リクエストの処理中にエラーが発生した場合は、onFailure() メソッドを呼び出します。このワークフローについて詳しくは、基本的な使用方法をご覧ください。

次のコードは、onFillRequest() メソッドの例を示しています:

Kotlin

override fun onFillRequest(
    request: FillRequest,
    cancellationSignal: CancellationSignal,
    callback: FillCallback
) {
    // Get the structure from the request
    val context: List<FillContext> = request.fillContexts
    val structure: AssistStructure = context[context.size - 1].structure

    // Traverse the structure looking for nodes to fill out.
    val parsedStructure: ParsedStructure = parseStructure(structure)

    // Fetch user data that matches the fields.
    val (username: String, password: String) = fetchUserData(parsedStructure)

    // Build the presentation of the datasets
    val usernamePresentation = RemoteViews(packageName, android.R.layout.simple_list_item_1)
    usernamePresentation.setTextViewText(android.R.id.text1, "my_username")
    val passwordPresentation = RemoteViews(packageName, android.R.layout.simple_list_item_1)
    passwordPresentation.setTextViewText(android.R.id.text1, "Password for my_username")

    // Add a dataset to the response
    val fillResponse: FillResponse = FillResponse.Builder()
            .addDataset(Dataset.Builder()
                    .setValue(
                            parsedStructure.usernameId,
                            AutofillValue.forText(username),
                            usernamePresentation
                    )
                    .setValue(
                            parsedStructure.passwordId,
                            AutofillValue.forText(password),
                            passwordPresentation
                    )
                    .build())
            .build()

    // If there are no errors, call onSuccess() and pass the response
    callback.onSuccess(fillResponse)
}

data class ParsedStructure(var usernameId: AutofillId, var passwordId: AutofillId)

data class UserData(var username: String, var password: String)

Java

@Override
public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal, FillCallback callback) {
    // Get the structure from the request
    List<FillContext> context = request.getFillContexts();
    AssistStructure structure = context.get(context.size() - 1).getStructure();

    // Traverse the structure looking for nodes to fill out.
    ParsedStructure parsedStructure = parseStructure(structure);

    // Fetch user data that matches the fields.
    UserData userData = fetchUserData(parsedStructure);

    // Build the presentation of the datasets
    RemoteViews usernamePresentation = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1);
    usernamePresentation.setTextViewText(android.R.id.text1, "my_username");
    RemoteViews passwordPresentation = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1);
    passwordPresentation.setTextViewText(android.R.id.text1, "Password for my_username");

    // Add a dataset to the response
    FillResponse fillResponse = new FillResponse.Builder()
            .addDataset(new Dataset.Builder()
                    .setValue(parsedStructure.usernameId,
                            AutofillValue.forText(userData.username), usernamePresentation)
                    .setValue(parsedStructure.passwordId,
                            AutofillValue.forText(userData.password), passwordPresentation)
                    .build())
            .build();

    // If there are no errors, call onSuccess() and pass the response
    callback.onSuccess(fillResponse);
}

class ParsedStructure {
    AutofillId usernameId;
    AutofillId passwordId;
}

class UserData {
    String username;
    String password;
}

サービスにはリクエストを満たすデータセットが複数ある場合があります。その場合、Android システムは、自動入力 UI に複数のオプション — データセットごとに 1 つ — を表示します。次のコード例は、応答に複数のデータセットを提供する方法を示しています:

Kotlin

// Add multiple datasets to the response
val fillResponse: FillResponse = FillResponse.Builder()
        .addDataset(Dataset.Builder()
                .setValue(parsedStructure.usernameId,
                        AutofillValue.forText(user1Data.username), username1Presentation)
                .setValue(parsedStructure.passwordId,
                        AutofillValue.forText(user1Data.password), password1Presentation)
                .build())
        .addDataset(Dataset.Builder()
                .setValue(parsedStructure.usernameId,
                        AutofillValue.forText(user2Data.username), username2Presentation)
                .setValue(parsedStructure.passwordId,
                        AutofillValue.forText(user2Data.password), password2Presentation)
                .build())
        .build()

Java

// Add multiple datasets to the response
FillResponse fillResponse = new FillResponse.Builder()
        .addDataset(new Dataset.Builder()
                .setValue(parsedStructure.usernameId,
                        AutofillValue.forText(user1Data.username), username1Presentation)
                .setValue(parsedStructure.passwordId,
                        AutofillValue.forText(user1Data.password), password1Presentation)
                .build())
        .addDataset(new Dataset.Builder()
                .setValue(parsedStructure.usernameId,
                        AutofillValue.forText(user2Data.username), username2Presentation)
                .setValue(parsedStructure.passwordId,
                        AutofillValue.forText(user2Data.password), password2Presentation)
                .build())
        .build();

自動入力サービスは、AssistStructure の中の ViewNode オブジェクトを調べて、リクエストを満たすために必要な自動入力データを取り出すことができます。サービスで自動入力データを取り出すには、ViewNode クラスのメソッド(getAutofillId() など)を使用することができます。サービスでリクエストを満たせるかどうかを確認するには、ビューの内容を記述することができなければなりません。ビューの内容を記述するためにサービスで使用するべき最初のアプローチは、autofillHints 属性を使用することです。しかし、この属性がサービスから利用可能になるためには、その前にクライアント アプリがそのビューの中に明示的にこの属性を提供していなければなりません。クライアント アプリが autofillHints 属性を提供していない場合、サービスは独自のヒューリスティックな手法を使用して内容を記述する必要があります。サービスでは、getText()getHint() など、他のクラスのメソッドを使用することにより、ビューの内容についての情報を取得できます。詳しくは、自動入力のヒントの提供をご覧ください。次の例は、AssistStructure を調べて、ViewNode オブジェクトから自動入力データを取り出す方法を示しています:

Kotlin

fun traverseStructure(structure: AssistStructure) {
    val windowNodes: List<AssistStructure.WindowNode> =
            structure.run {
                (0 until windowNodeCount).map { getWindowNodeAt(it) }
            }

    windowNodes.forEach { windowNode: AssistStructure.WindowNode ->
        val viewNode: ViewNode? = windowNode.rootViewNode
        traverseNode(viewNode)
    }
}

fun traverseNode(viewNode: ViewNode?) {
    if (viewNode?.autofillHints?.isNotEmpty() == true) {
        // If the client app provides autofill hints, you can obtain them using:
        // viewNode.getAutofillHints();
    } else {
        // Or use your own heuristics to describe the contents of a view
        // using methods such as getText() or getHint().
    }

    val children: List<ViewNode>? =
            viewNode?.run {
                (0 until childCount).map { getChildAt(it) }
            }

    children?.forEach { childNode: ViewNode ->
        traverseNode(childNode)
    }
}

Java

public void traverseStructure(AssistStructure structure) {
    int nodes = structure.getWindowNodeCount();

    for (int i = 0; i < nodes; i++) {
        WindowNode windowNode = structure.getWindowNodeAt(i);
        ViewNode viewNode = windowNode.getRootViewNode();
        traverseNode(viewNode);
    }
}

public void traverseNode(ViewNode viewNode) {
    if(viewNode.getAutofillHints() != null && viewNode.getAutofillHints().length > 0) {
        // If the client app provides autofill hints, you can obtain them using:
        // viewNode.getAutofillHints();
    } else {
        // Or use your own heuristics to describe the contents of a view
        // using methods such as getText() or getHint().
    }

    for(int i = 0; i < viewNode.getChildCount(); i++) {
        ViewNode childNode = viewNode.getChildAt(i);
        traverseNode(childNode);
    }
}

ユーザーデータを保存する

自動入力サービスでアプリのビューに記入するには、ユーザーデータが必要です。ユーザーが手動でビューに記入すると、図 2 に示すようにデータを現在の自動入力サービスに保存するように促すプロンプトが表示されます。

自動入力保存 UI

図 2.自動入力保存 UI

サービスでデータを保存するには、将来使用するためにデータを保存しておきたいことをサービスが指示する必要があります。Android システムでデータを保存するリクエストを送信する前に、サービスがビューに記入することのできる記入リクエストがあります。データを保存することを指示するため、サービスは、先行する記入リクエストの応答に SaveInfo オブジェクトを含めます。SaveInfo オブジェクトには、少なくとも次のデータが含まれます:

  • 保存するユーザーデータの型。使用可能な SAVE_DATA 値のリストについては、SaveInfoをご覧ください。
  • 保存リクエストをトリガーするために変更する必要のある最小限のビューセット。たとえば、多くのログイン フォームの場合、保存リクエストのトリガーとして、ユーザーが usernamepassword のビューを更新する必要があります。

SaveInfo オブジェクトは、FillResponse オブジェクトに関連しています。次のコード例をご覧ください:

Kotlin

override fun onFillRequest(
    request: FillRequest,
    cancellationSignal: CancellationSignal,
    callback: FillCallback
) {
    ...
    // Builder object requires a non-null presentation.
    val notUsed = RemoteViews(packageName, android.R.layout.simple_list_item_1)

    val fillResponse: FillResponse = FillResponse.Builder()
            .addDataset(
                    Dataset.Builder()
                            .setValue(parsedStructure.usernameId, null, notUsed)
                            .setValue(parsedStructure.passwordId, null, notUsed)
                            .build()
            )
            .setSaveInfo(
                    SaveInfo.Builder(
                            SaveInfo.SAVE_DATA_TYPE_USERNAME or SaveInfo.SAVE_DATA_TYPE_PASSWORD,
                            arrayOf(parsedStructure.usernameId, parsedStructure.passwordId)
                    ).build()
            )
            .build()
    ...
}

Java

@Override
public void onFillRequest(FillRequest request, CancellationSignal cancellationSignal, FillCallback callback) {
    ...
    // Builder object requires a non-null presentation.
    RemoteViews notUsed = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1);

    FillResponse fillResponse = new FillResponse.Builder()
            .addDataset(new Dataset.Builder()
                    .setValue(parsedStructure.usernameId, null, notUsed)
                    .setValue(parsedStructure.passwordId, null, notUsed)
                    .build())
            .setSaveInfo(new SaveInfo.Builder(
                    SaveInfo.SAVE_DATA_TYPE_USERNAME | SaveInfo.SAVE_DATA_TYPE_PASSWORD,
                    new AutofillId[] {parsedStructure.usernameId, parsedStructure.passwordId})
                    .build())
            .build();
    ...
}

自動入力サービスは、onSaveRequest() メソッドの中でユーザーデータを保持するためのロジックを実装できます。それが呼び出されるのは、通常、クライアント アクティビティが終了した後、またはクライアント アプリが commit() を呼び出した時点です。次のコードは、onSaveRequest() メソッドの例を示しています:

Kotlin

override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) {
    // Get the structure from the request
    val context: List<FillContext> = request.fillContexts
    val structure: AssistStructure = context[context.size - 1].structure

    // Traverse the structure looking for data to save
    traverseStructure(structure)

    // Persist the data, if there are no errors, call onSuccess()
    callback.onSuccess()
}

Java

@Override
public void onSaveRequest(SaveRequest request, SaveCallback callback) {
    // Get the structure from the request
    List<FillContext> context = request.getFillContexts();
    AssistStructure structure = context.get(context.size() - 1).getStructure();

    // Traverse the structure looking for data to save
    traverseStructure(structure);

    // Persist the data, if there are no errors, call onSuccess()
    callback.onSuccess();
}

自動入力サービスが機密データを保持する場合、その前にそれを暗号化しなければなりません。しかし、ユーザーデータには、機密ではないラベルやデータが含まれています。たとえば、ユーザー アカウントには、仕事用アカウントなのか個人用アカウントなのかを示すデータのラベルが含まれていることがあります。サービスでラベルを暗号化しないでください。それにより、ユーザーが認証されていない場合にプレゼンテーション ビューの中でラベルを使用することができ、ユーザー認証後に実際のデータでラベルを置き換えることができます。

自動入力保存 UI を延期する

Android 10 以降において、自動入力ワークフロー実装のために複数画面を使用する場合(ユーザー名フィールドに 1 つの画面、パスワード用にもう 1 つの画面など)、SaveInfo.FLAG_DELAY_SAVE フラグを使用することによって自動入力保存 UI を延期することができます。

このフラグがセットされている場合、SaveInfo 応答に関連する自動入力コンテキストがコミットされても、自動入力保存 UI はトリガーされません。むしろ、将来の記入リクエスト発行には同じタスク内の別のアクティビティを使用し、保存リクエストにより UI を表示することができます。詳細については、SaveInfo.FLAG_DELAY_SAVE をご覧ください。

ユーザー認証を求める

自動入力サービスでは、ビューに記入する前にユーザーが認証するように求めることにより、付加的なセキュリティ レベルを提供することができます。ユーザー認証を実装する上で、以下のシナリオを参考にしてください:

  • アプリのユーザーデータを、マスター パスワードまたは指紋スキャンによりロック解除する必要がある。
  • クレジットカードの詳細など、特定のデータセットを、カード認証コード(CVC)を使用してロック解除する必要がある。

サービスでデータのロック解除をする前にユーザーの認証が必要なシナリオでは、サービスはボイラープレート データまたはラベルを表示し、認証を処理する Intent を指定することができます。認証フローの完了後、リクエスト処理のために付加的なデータが必要な場合、そのようなデータをインテントに追加することができます。その後、認証アクティビティからアプリの AutofillService クラスにデータを返すことができます。次のコード例は、リクエストに認証が必要であることを指定する方法を示しています:

Kotlin

val authPresentation = RemoteViews(packageName, android.R.layout.simple_list_item_1).apply {
    setTextViewText(android.R.id.text1, "requires authentication")
}
val authIntent = Intent(this, AuthActivity::class.java).apply {
    // Send any additional data required to complete the request.
    putExtra(MY_EXTRA_DATASET_NAME, "my_dataset")
}

val intentSender: IntentSender = PendingIntent.getActivity(
        this,
        1001,
        authIntent,
        PendingIntent.FLAG_CANCEL_CURRENT
).intentSender

// Build a FillResponse object that requires authentication.
val fillResponse: FillResponse = FillResponse.Builder()
        .setAuthentication(autofillIds, intentSender, authPresentation)
        .build()

Java

RemoteViews authPresentation = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1);
authPresentation.setTextViewText(android.R.id.text1, "requires authentication");
Intent authIntent = new Intent(this, AuthActivity.class);

// Send any additional data required to complete the request.
authIntent.putExtra(MY_EXTRA_DATASET_NAME, "my_dataset");
IntentSender intentSender = PendingIntent.getActivity(
                this,
                1001,
                authIntent,
                PendingIntent.FLAG_CANCEL_CURRENT
        ).getIntentSender();

// Build a FillResponse object that requires authentication.
FillResponse fillResponse = new FillResponse.Builder()
        .setAuthentication(autofillIds, intentSender, authPresentation)
        .build();

アクティビティの認証フローが完了したら、setResult() メソッドに RESULT_OK 値を渡して呼び出し、さらに EXTRA_AUTHENTICATION_RESULT エクストラをデータ設定済みのデータセットを含む FillResponse オブジェクトに設定しなければなりません。次のコードは、認証フロー完了後に結果を返す方法を示すサンプルです:

Kotlin

// The data sent by the service and the structure are included in the intent.
val datasetName: String? = intent.getStringExtra(MY_EXTRA_DATASET_NAME)
val structure: AssistStructure = intent.getParcelableExtra(EXTRA_ASSIST_STRUCTURE)
val parsedStructure: ParsedStructure = parseStructure(structure)
val (username, password) = fetchUserData(parsedStructure)

// Build the presentation of the datasets.
val usernamePresentation =
        RemoteViews(packageName, android.R.layout.simple_list_item_1).apply {
            setTextViewText(android.R.id.text1, "my_username")
        }
val passwordPresentation =
        RemoteViews(packageName, android.R.layout.simple_list_item_1).apply {
            setTextViewText(android.R.id.text1, "Password for my_username")
        }

// Add the dataset to the response.
val fillResponse: FillResponse = FillResponse.Builder()
        .addDataset(Dataset.Builder()
                .setValue(
                        parsedStructure.usernameId,
                        AutofillValue.forText(username),
                        usernamePresentation
                )
                .setValue(
                        parsedStructure.passwordId,
                        AutofillValue.forText(password),
                        passwordPresentation
                )
                .build()
        ).build()

val replyIntent = Intent().apply {
    // Send the data back to the service.
    putExtra(MY_EXTRA_DATASET_NAME, datasetName)
    putExtra(EXTRA_AUTHENTICATION_RESULT, fillResponse)
}

setResult(Activity.RESULT_OK, replyIntent)

Java

Intent intent = getIntent();

// The data sent by the service and the structure are included in the intent.
String datasetName = intent.getStringExtra(MY_EXTRA_DATASET_NAME);
AssistStructure structure = intent.getParcelableExtra(EXTRA_ASSIST_STRUCTURE);
ParsedStructure parsedStructure = parseStructure(structure);
UserData userData = fetchUserData(parsedStructure);

// Build the presentation of the datasets.
RemoteViews usernamePresentation = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1);
usernamePresentation.setTextViewText(android.R.id.text1, "my_username");
RemoteViews passwordPresentation = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1);
passwordPresentation.setTextViewText(android.R.id.text1, "Password for my_username");

// Add the dataset to the response.
FillResponse fillResponse = new FillResponse.Builder()
        .addDataset(new Dataset.Builder()
                .setValue(parsedStructure.usernameId,
                        AutofillValue.forText(userData.username), usernamePresentation)
                .setValue(parsedStructure.passwordId,
                        AutofillValue.forText(userData.password), passwordPresentation)
                .build())
        .build();

Intent replyIntent = new Intent();

// Send the data back to the service.
replyIntent.putExtra(MY_EXTRA_DATASET_NAME, datasetName);
replyIntent.putExtra(EXTRA_AUTHENTICATION_RESULT, fillResponse);

setResult(RESULT_OK, replyIntent);

クレジットカードをロック解除する必要があるシナリオの場合、サービスでは CVC を尋ねる UI を表示できます。銀行名やクレジットカードの末尾 4 桁など、ボイラープレート データを表示することにより、データセットがロック解除されるまでデータを非表示にすることができます。以下のサンプルは、データセットの認証を求め、ユーザーが CVC を入力するまでデータを非表示にする方法を示しています:

Kotlin

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

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

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

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

Java

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

// Build the presentation that shows the bank and the last four digits of the credit card number.
// For example '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 を入力した後、データセットを返す方法を示す例です:

Kotlin

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

// Build a non-null RemoteViews object that we can use as the presentation when
// creating the Dataset object. This presentation isn't actually used, but the
// Builder object requires a non-null presentation.
val notUsed = RemoteViews(packageName, android.R.layout.simple_list_item_1)

// Create a dataset with the credit card number and expiration date.
val responseDataset: Dataset = Dataset.Builder()
        .setValue(
                parsedStructure.creditCardId,
                AutofillValue.forText(paymentData.creditCardNumber),
                notUsed
        )
        .setValue(
                parsedStructure.expDateId,
                AutofillValue.forText(paymentData.expirationDate),
                notUsed
        )
        .build()

val replyIntent = Intent().apply {
    putExtra(EXTRA_AUTHENTICATION_RESULT, responseDataset)
}

Java

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

// Build a non-null RemoteViews object that we can 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 つのデータセットに複数のパーティションのデータを露出させることがないため、ユーザーのデータの保護を強化できます。たとえば、認証情報を含むデータセットに、支払い情報が含まれるとは限りません。データを複数パーティションに編成することにより、サービスがリクエストを満たすために露出させることの必要な情報の量を最小限に抑えることができます。

データを複数パーティションに編成するなら、複数パーティションからのビューのあるアクティビティにデータを設定しつつ、クライアント アプリに送るデータ量を最小限にすることができます。たとえば、ユーザー名、パスワード、番地、および市町村の各ビューを含むアクティビティがあり、自動入力サービスには次のデータがあるとします:

パーティション フィールド 1 フィールド 2
認証情報 work_username work_password
personal_username personal_password
住所 work_street work_city
personal_street personal_city

サービスでは、仕事用アカウントと個人用アカウントの両方について認証情報パーティションを含むデータセットを準備できます。ユーザーが 1 つを選ぶと、自動入力のその後の応答では、ユーザーの最初の選択内容に応じて、仕事の住所か個人の住所のいずれかを提示できます。

サービスでは、AssistStructure オブジェクトを走査しながら、isFocused() メソッドを呼び出すことにより、リクエスト発行元のフィールドを特定することができます。これによりサービスは、該当するパーティション データにより FillResponse を準備することができます。

自動入力の高度なシナリオ

データセットをページ分割する
自動入力応答のサイズが大きい場合、リクエストの処理に必要なリモータブル オブジェクトを表す Binder オブジェクトのトランザクションとして可能なサイズを超えることがあります。そのようなシナリオで Android システムが例外を送出するのを防ぐため、一度に追加する Dataset オブジェクトが 20 個以下になるようにすることにより、FillResponse を小さく保つことができます。応答にそれより多くのデータセットが必要な場合、さらに多くの情報があることをユーザーに通知するデータセットを追加し、ユーザーがそれを選択するとデータセットの次のグループを取り出すようにすることができます。詳細については、addDataset(Dataset) をご覧ください。
複数画面に分割されたデータの保存

アプリではしばしば同じアクティビティの中でユーザーデータを複数の画面に分割することがあります。特に、新しいユーザー アカウントを作成するために使用されるアクティビティでは、この方法がよく使用されます。たとえば、最初の画面ではユーザー名を、そしてそのユーザー名が有効であれば、パスワードの入力を求める第 2 の画面に移ります。そのような状況で自動入力サービスは、自動入力保存 UI を表示する前に、ユーザーが 2 つのフィールドの両方に入力するまで待機する必要があります。サービスでこのようなシナリオを処理するには、以下の手順を実行します:

  1. 最初の入力リクエストでサービスは、応答の中にクライアント状態バンドルを追加します。そこには、フィールド群のうちその画面に存在するものに対応する自動入力 ID を含めます。
  2. 第 2 の入力リクエストでサービスは、クライアント状態バンドルを取り出し、前のリクエストでクライアントの状態に基づいて設定された自動入力 ID を取得し、それらの ID および FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE フラグを第 2 の応答で使用される SaveInfo オブジェクトに追加します。
  3. 保存リクエストでサービスは、適切な FillContext オブジェクトを使用することにより、各フィールドの値を取得します。1 つの入力リクエストごとに 1 つの入力コンテキストがあります。

詳しくは、データが複数画面に分割されている場合の保存をご覧ください。

各リクエストに初期化ロジックと解体ロジックを提供する

自動入力リクエストがあるたびに、Android システムはサービスにバインドし、その onConnected() メソッドを呼び出します。サービスがリクエストを処理すると、Android システムにより onDisconnected() メソッドが呼び出され、サービスからアンバインドされます。リクエストの処理の前に実行するコードを提供する onConnected()、およびリクエストの処理の後に実行するコードを提供する onDisconnected() を実装することができます。

自動入力保存 UI をカスタマイズする

自動入力サービスでは、自動入力保存 UI をカスタマイズすることにより、サービスが自分のデータを保存するようにするかどうかをユーザーが決定するのを助けることができます。サービスでは、シンプルなテキストか、カスタマイズ済みのビューのいずれかにより、何が保存されるのかについての付加的な情報を提供できます。またサービスでは、保存リクエストをキャンセルするボタンの外観を変更したり、ユーザーがそのボタンをタップした時点で通知を受けるようにしたりできます。詳細については、SaveInfo のリファレンス ドキュメントをご覧ください。

互換性モード

互換性モードでは、自動入力サービスでユーザー補助機能の仮想構造を使用して、自動入力プロセスを実施することができます。これは、明示的には自動入力 API がまだ実装されていないブラウザで自動入力機能を提供する上で特に便利です。

互換性モードを使用して自動入力サービスをテストするには、互換性モードを必要とするブラウザまたはアプリを明示的にホワイトリストに含める必要があります。どのパッケージがすでにホワイトリストに含められているかを調べるには、以下のコマンドを実行します:

$ adb shell settings get global autofill_compat_mode_allowed_packages

テスト対象のパッケージがリストにない場合、以下のコマンドを実行することによりそれをホワイトリストに含めることができます:

$ adb shell settings put global autofill_compat_mode_allowed_packages pkg1[resId1]:pkg2[resId1,resId2]

pkgX はアプリのパッケージです。アプリがブラウザの場合、resIdx を使用することにより、表示するページの URL を含む入力フィールドのリソース ID を指定します。

互換性モードには次の制限事項があります。

  • サービスで FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE フラグが使用されている場合、または setTrigger() メソッドが呼び出された時点で、保存リクエストがトリガーされます。デフォルトでは、互換性モードを使用すると FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE がセットされます。
  • onSaveRequest(SaveRequest, SaveCallback) メソッドでは、ノードのテキスト値が利用可能でない可能性があります。

互換性モードやそれに関連するさまざまな制限事項について詳しくは、AutofillService クラスのリファレンスをご覧ください。