自動入力用にアプリを最適化する

標準ビューを使用しているアプリは、特別な設定をしなくても自動入力フレームワークと連携できます。アプリとフレームワークの連携を最適化することも可能です。

自動入力環境をセットアップする

このセクションでは、アプリに対して基本的な自動入力機能をセットアップする方法について説明します。

自動入力サービスを設定する

アプリが自動入力フレームワークを使用するには、デバイス上で自動入力サービスを設定する必要があります。Android 8.0(API レベル 26)以降を搭載したスマートフォンやタブレットであれば自動入力サービスが付属していますが、アプリをテストする際はテストサービス(たとえば Android の自動入力フレームワーク サンプルの自動入力サービス)を使用することをおすすめします。エミュレータを使用する場合、エミュレータにはデフォルトの自動入力サービスが付属していないことがあるため、自動入力サービスを明示的に設定します。

サンプルアプリからテスト自動入力サービスをインストールしたら、[設定] > [システム] > [言語と入力] > [詳細設定] > [入力アシスタント] > [自動入力サービス] に移動して、自動入力サービスを有効にします。

自動入力テスト用にエミュレータを設定する手順については、自動入力とアプリの連携をテストするをご覧ください。

自動入力用のヒントを指定する

自動入力サービスは、ヒューリスティックに基づいて各ビューのタイプを判別します。ただし、アプリがヒューリスティックに依存している場合、アプリを更新したときに、自動入力の動作が予期せず変更される場合があります。自動入力サービスがアプリのフォーム ファクタを正しく識別するようにするには、自動入力のヒントを設定します。

自動入力のヒントは、android:autofillHints 属性を使用して設定できます。次の例では、EditText"password" のヒントを設定しています。

<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:autofillHints="password" />

プログラムで setAutofillHints() メソッドを使用してヒントを設定することもできます。次の例をご覧ください。

Kotlin

val password = findViewById<EditText>(R.id.password)
password.setAutofillHints(View.AUTOFILL_HINT_PASSWORD)

Java

EditText password = findViewById(R.id.password);
password.setAutofillHints(View.AUTOFILL_HINT_PASSWORD);

事前定義済みヒント定数を含める

自動入力フレームワークはヒントを検証しません。ヒントは、変更や検証を行うことなく自動入力サービスに渡されます。任意の値を使用できますが、View クラスと AndroidX HintConstants クラスには、公式にサポートされているヒント定数のリストが用意されています。

リスト内の定数を組み合わせることで、一般的な自動入力シナリオに適したレイアウトを作成できます。

アカウント認証情報

ログイン フォームには、アカウント認証情報のヒント(AUTOFILL_HINT_USERNAMEAUTOFILL_HINT_PASSWORD など)を含めることができます。

新しいアカウントを作成する場合や、ユーザー名やパスワードを変更する場合は、AUTOFILL_HINT_NEW_USERNAMEAUTOFILL_HINT_NEW_PASSWORD を使用します。

クレジット カード情報

クレジット カード情報をリクエストする場合は、AUTOFILL_HINT_CREDIT_CARD_NUMBERAUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE などのヒントを使用します。

クレジット カードの有効期限に関しては、次のいずれかを行います。

住所

住所フォームのフィールドでは、以下のようなヒントを使用できます。

人名

人名をリクエストする場合は、以下のようなヒントを使用できます。

電話番号

電話番号の場合は、以下を使用します。

ワンタイム パスワード(OTP)

単一のビュー内にワンタイム パスワードを入力する場合は、AUTOFILL_HINT_SMS_OTP を使用します。

複数のビューを使用して、各ビューを OTP の 1 つの桁にマッピングする場合は、generateSmsOtpHintForCharacterPosition() メソッドを使用して、文字ごとのヒントを生成します。

自動入力の対象とするフィールドをマーキングする

自動入力を目的として、個別のフィールドをビュー構造でアプリに含めることができます。デフォルトでは、ビューは IMPORTANT_FOR_AUTOFILL_AUTO モードを使用します。このモードの場合、Android はヒューリスティックを使用して、そのビューを自動入力の対象にするかどうかを判断します。

ただし以下のように、ビューやビュー構造、アクティビティ全体を自動入力の対象にしないケースもあります。

  • ログイン アクティビティ内の CAPTCHA フィールド
  • テキスト エディタやスプレッドシート エディタなど、ユーザーがコンテンツを作成するビュー
  • ゲームプレイを表示するアクティビティなど、ゲーム内の一部のアクティビティ

各ビューを自動入力の対象とするかどうかは、android:importantForAutofill 属性を使用して設定できます。

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:importantForAutofill="no" />

importantForAutofill には次のいずれかの値を指定できます。

auto
Android システムは、ヒューリスティックを使用して、そのビューを自動入力の対象にするかどうかを判断します。
no
このビューは、自動入力の対象にはなりません。
noExcludeDescendants
このビューもこのビューの子も、自動入力の対象にはなりません。
yes
このビューは、自動入力の対象になります。
yesExcludeDescendants
このビューは自動入力の対象になりますが、その子ビューは自動入力の対象にはなりません。

また、setImportantForAutofill() メソッドを使用することもできます。

Kotlin

val captcha = findViewById<TextView>(R.id.captcha)
captcha.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO)

Java

TextView captcha = findViewById(R.id.captcha);
captcha.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO);

たとえば上の例で、自動入力の対象から除外するには以下のように宣言します。

  • ログイン アクティビティの CAPTCHA フィールド: android:importantForAutofill="no" または IMPORTANT_FOR_AUTOFILL_NO を使用して、このビューを自動入力の対象から除外するようにマーキングします。
  • ユーザーがコンテンツを作成するビュー: android:importantForAutofill="noExcludeDescendants" または IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS を使用して、ビュー構造全体を自動入力の対象から除外するようにマーキングします。
  • ゲーム内の一部のアクティビティのビュー: android:importantForAutofill="noExcludeDescendants" または IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS を使用して、ビュー構造全体を自動入力の対象から除外するようにマーキングします。

ウェブサイトとモバイルアプリのデータを関連付ける

アプリとウェブサイトを関連付けると、Google 自動入力などの自動入力サービスが、ブラウザと Android デバイス間でユーザーのログインデータを共有できるようになります。ユーザーが両方のプラットフォーム上で同じ自動入力サービスを選択している場合、ウェブアプリにログインすると、そのログイン認証情報は、対応する Android アプリにログインする際に自動入力されるようになります。

Android アプリとウェブサイトを関連付けるには、delegate_permission/common.get_login_creds リレーションを持つ Digital Asset Links をサイト内でホストします。そして、アプリの AndroidManifest.xml ファイル内で関連付けを宣言します。ウェブサイトと Android アプリを関連付ける手順については、アプリとウェブサイト間の自動ログインを有効にするをご覧ください。

自動入力ワークフローを完成する

このセクションでは、アプリのユーザー向けに自動入力機能を改善するための具体的な手順について説明します。

自動入力が有効になっているか判定する

ユーザーは、[設定] > [システム] > [言語と入力] > [詳細設定] > [入力アシスタント] > [自動入力サービス] に移動することで、自動入力の有効 / 無効の切り替えや、自動入力サービスの変更を行うことができます。ユーザーの自動入力設定をアプリでオーバーライドすることはできませんが、ユーザーが自動入力を有効にしていれば、アプリ内やアプリの特定のビュー内に追加の自動入力機能を実装することは可能です。

たとえば、ユーザーが自動入力を有効にしている場合に、TextView は、オーバーフロー メニュー内に自動入力エントリを表示します。ユーザーが自動入力を有効にしているかどうかをチェックするには、AutofillManager オブジェクトの isEnabled() メソッドを呼び出します。

自動入力を有効にしていないユーザー向けに登録機能やログイン機能を最適化するには、One Tap サインインを実装します。

自動入力リクエストを自動的にトリガーする

場合によっては、ユーザーのアクションに応じて自動入力リクエストを自動的にトリガーする必要があります。たとえば TextView は、ユーザーがビューを長押しすると、自動入力メニュー項目を表示します。自動入力リクエストを自動トリガーするサンプルコードを以下に示します。

Kotlin

fun eventHandler(view: View) {
    val afm = requireContext().getSystemService(AutofillManager::class.java)
    afm?.requestAutofill(view)
}

Java

public void eventHandler(View view) {
    AutofillManager afm = context.getSystemService(AutofillManager.class);
    if (afm != null) {
        afm.requestAutofill(view);
    }
}

また、cancel() メソッドを使用すると、現在の自動入力コンテキストをキャンセルできます。ログインページ内に、フィールドをクリアするボタンを設置する場合に便利です。

選択ツール コントロール内のデータに対して正しい自動入力タイプを使用する

自動入力内で選択ツールを使用すると、日付データや時刻データを格納するフィールドの値をユーザーが変更できる UI を提供できます。たとえば、クレジット カード フォームの場合、日付選択ツールを用意しておくと、ユーザーは簡単にクレジット カードの有効期限の入力や変更ができるようになります。ただし、選択ツールが表示されていないときにデータを表示するためには、別のビュー(EditText など)を使用する必要があります。

EditText オブジェクトは、AUTOFILL_TYPE_TEXT タイプの自動入力データをネイティブに想定しています。別のタイプのデータを使用する場合は、EditText から継承したカスタムビューを作成して、対象データタイプの処理に必要となるメソッドを実装します。たとえば、日付フィールドがある場合は、AUTOFILL_TYPE_DATE タイプの値を正しく処理できるロジックを持つメソッドを実装してください。

自動入力データタイプを指定すると、自動入力サービスが、ビュー内に表示されるデータを適切に作成できるようになります。詳細については、自動入力内で選択ツールを使用するをご覧ください。

自動入力コンテキストを終了する

自動入力コンテキストを終了する際、自動入力フレームワークは、「保存して自動入力で使用」ダイアログを表示することで、ユーザー入力を保存して今後使用できるようにします。通常は、アクティビティが終了すると、自動入力コンテキストも終了します。ただし、場合によっては、フレームワークに明示的に通知する必要があります。たとえば、ログイン画面とコンテンツ画面の両方で、同じアクティビティを使用しているが、異なるフラグメントを使用している場合などが該当します。このような場合、AutofillManager.commit() を呼び出すことで明示的にコンテキストを終了できます。

カスタムビューのサポート

カスタムビューの場合、autofill API を使用することで、自動入力フレームワークに公開するメタデータを指定できます。ビューによっては、仮想の子のコンテナとして機能します(OpenGL レンダリング UI を含むビューなど)。このようなビューの場合、自動入力フレームワークと連携するには、API を使用して、アプリ内で使用する情報の構造を指定する必要があります。

アプリでカスタムビューを使用する場合は、以下のシナリオを考慮に入れてください。

  • カスタムビューが標準ビュー構造(デフォルト ビュー構造)を持っている場合。
  • カスタムビューが仮想構造(自動入力フレームワークでは利用できないビュー構造)を持っている場合。

標準ビュー構造のカスタムビュー

カスタムビューは、自動入力が機能するうえで必要となるメタデータを定義できます。 カスタムビューを使用する場合は、自動入力フレームワークと連携できるように、メタデータを適切に管理してください。カスタムビューは以下の処理を行う必要があります。

  • フレームワークからアプリに送信される自動入力値を処理します。
  • 自動入力のタイプと値をフレームワークに提供します。

自動入力がトリガーされると、自動入力フレームワークは、ビューに対して autofill() を呼び出し、ビューに必要な値を送信します。デベロッパーは、autofill() を実装して、カスタムビューが自動入力値を処理する方法を指定します。

カスタムビューは、getAutofillType() メソッドと getAutofillValue() メソッドをオーバーライドして、自動入力のタイプと値を指定する必要があります。

また、ユーザーがビューの値を指定できない場合(たとえば、ビューが無効になっている場合)は、自動入力でビューに入力しないようにしてください。このような場合、getAutofillType()AUTOFILL_TYPE_NONE を返し、getAutofillValue()null を返し、autofill() は何もしないようにします。

次のような場合、フレームワーク内で正常に動作するには、追加の手順が必要です。

  • カスタムビューが編集可能な場合。
  • カスタムビューに機密データが含まれている場合。

カスタムビューが編集可能な場合

ビューが編集可能な場合は、AutofillManager オブジェクトに対して notifyValueChanged() を呼び出すことによって、変更に関して自動入力フレームワークに通知します。

カスタムビューに機密データが含まれている場合

メールアドレス、クレジット カード番号、パスワードなどの個人情報(PII)がビュー内に含まれている場合は、機密データを含むビューとしてマーキングする必要があります。

通常、静的リソースからコンテンツを取得するビューの場合は機密データが含まれることはありませんが、動的にコンテンツが設定されるビューの場合は機密データが含まれることがあります。たとえば、「ユーザー名を入力」というラベルに機密データは含まれませんが、「こんにちは、ジョン」というラベルには機密データが含まれます。

注: デフォルトでは、自動入力フレームワークは、すべてのデータが機密データであると想定します。機密ではないデータとしてマーキングすることもできます。

ビューに機密データが含まれているかどうかをマーキングするには、onProvideAutofillStructure() を実装し、ViewStructure オブジェクトに対して setDataIsSensitive() を呼び出します。

ビュー構造内のデータを、機密ではないとマーキングするサンプルコードを以下に示します。

Kotlin

override fun onProvideAutofillStructure(structure: ViewStructure, flags: Int) {
    super.onProvideAutofillStructure(structure, flags)

    structure.setDataIsSensitive(false)
}

Java

@Override
public void onProvideAutofillStructure(ViewStructure structure, int flags) {
    super.onProvideAutofillStructure(structure, flags);

    structure.setDataIsSensitive(false);
}

ビュー内で指定できる値を、事前定義済みの値だけに限定する場合は、setAutofillOptions() メソッドを使用して、ビューの自動入力時に利用できるオプションを設定します。特に、自動入力タイプが AUTOFILL_TYPE_LIST のビューの場合、このメソッドを使用することをおすすめします。これにより、ビューの入力に使用できるオプションを自動入力サービスが認識できるようになり、ジョブの実行が最適化されます。

アダプターを使用するビュー(Spinner など)の場合も同様です。たとえば、クレジット カードの有効期限フィールドで使用するために、現在の年に基づいて動的に年の値を作成するスピナーの場合、getAutofillOptions() インターフェースの Adapter メソッドを実装することで、年のリストを提供できます。

ArrayAdapter を使用するビューでも、値のリストを提供できます。ArrayAdapter では、静的リソース用の自動入力オプションが自動設定されます。動的に値を提供する場合は getAutofillOptions() をオーバーライドします。

仮想構造のカスタムビュー

自動入力フレームワークでアプリの UI 内の情報を編集、保存するには、ビュー構造が必要です。ただし、以下のような場合は、自動入力フレームワークでビュー構造を使用することはできません。

  • アプリがローレベルのレンダリング エンジン(OpenGL など)を使用して UI をレンダリングしている場合。
  • アプリが Canvas のインスタンスを使用して UI を描画している場合。

このような場合、onProvideAutofillVirtualStructure() を実装し、以下の手順を行うことで、ビュー構造を指定できます。

  1. ビュー構造の子の数を増やす場合は、addChildCount() を呼び出します。
  2. 子を追加する場合は、newChild() を呼び出します。
  3. 子に対して自動入力 ID を設定するには、setAutofillId() を呼び出します。
  4. 自動入力の値やタイプなど、関連プロパティを設定します。
  5. 仮想の子に含まれるデータが機密データの場合は setDataIsSensitive()true を渡し、機密データでない場合は false を渡します。

仮想構造内に新しい子を作成するコード スニペットを以下に示します。

Kotlin

override fun onProvideAutofillVirtualStructure(structure: ViewStructure, flags: Int) {

    super.onProvideAutofillVirtualStructure(structure, flags)

    // Create a new child in the virtual structure.
    structure.addChildCount(1)
    val child = structure.newChild(childIndex)

    // Set the autofill ID for the child.
    child.setAutofillId(structure.autofillId!!, childVirtualId)

    // Populate the child by providing properties such as value and type.
    child.setAutofillValue(childAutofillValue)
    child.setAutofillType(childAutofillType)

    // Some children can provide a list of values, such as when the child is
    // a spinner.
    val childAutofillOptions = arrayOf<CharSequence>("option1", "option2")
    child.setAutofillOptions(childAutofillOptions)

    // Just like other types of views, mark the data as sensitive when
    // appropriate.
    val sensitive = !contentIsSetFromResources()
    child.setDataIsSensitive(sensitive)
}

Java

@Override
public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) {

    super.onProvideAutofillVirtualStructure(structure, flags);

    // Create a new child in the virtual structure.
    structure.addChildCount(1);
    ViewStructure child =
            structure.newChild(childIndex);

    // Set the autofill ID for the child.
    child.setAutofillId(structure.getAutofillId(), childVirtualId);

    // Populate the child by providing properties such as value and type.
    child.setAutofillValue(childAutofillValue);
    child.setAutofillType(childAutofillType);

    // Some children can provide a list of values, such as when the child is
    // a spinner.
    CharSequence childAutofillOptions[] = { "option1", "option2" };
    child.setAutofillOptions(childAutofillOptions);

    // Just like other types of views, mark the data as sensitive when
    // appropriate.
    boolean sensitive = !contentIsSetFromResources();
    child.setDataIsSensitive(sensitive);
}

仮想構造内の要素が変化する場合は、以下のタスクを実行して自動入力フレームワークに通知します。

  • 子の内部のフォーカスが変化する場合は、AutofillManager オブジェクトに対して notifyViewEntered()notifyViewExited() を呼び出します。
  • 子の値が変化する場合は、AutofillManager オブジェクトに対して notifyValueChanged() を呼び出します。
  • ユーザーがワークフローのステップを完了したためにビュー階層が利用できなくなった場合(たとえば、ユーザーがログイン フォームを使用してログインした場合)は、AutofillManager オブジェクトに対して commit() を呼び出します。
  • ユーザーがワークフローのステップをキャンセルしたためにビュー階層が無効になった場合(たとえば、ユーザーがログイン フォームをクリアするボタンをタップした場合)は、AutofillManager オブジェクトに対して cancel() を呼び出します。

自動入力イベントに対するコールバックを使用する

独自のオートコンプリート ビューを備えているアプリの場合、UI 自動入力機能の変化に応じてビューの有効 / 無効を切り替えるようアプリに指示するメカニズムが必要です。自動入力フレームワークは、このメカニズムを AutofillCallback の形式で提供します。

このクラスには、onAutofillEvent(View, int) メソッドが用意されています。ビューに関連付けられた自動入力状態が変化すると、アプリでこのメソッドが呼び出されます。このメソッドのオーバーロード バージョンもあります。このバージョンには、仮想ビューでアプリが使用できる childId パラメータが含まれています。利用可能な状態は、コールバック内の定数として定義されます。

コールバックを登録するには、registerCallback() クラスの AutofillManager メソッドを使用します。自動入力イベントのコールバックを宣言するサンプルコードを以下に示します。

Kotlin

val afm = context.getSystemService(AutofillManager::class.java)

afm?.registerCallback(object : AutofillManager.AutofillCallback() {
    // For virtual structures, override
    // onAutofillEvent(View view, int childId, int event) instead.
    override fun onAutofillEvent(view: View, event: Int) {
        super.onAutofillEvent(view, event)
        when (event) {
            EVENT_INPUT_HIDDEN -> {
                // The autofill affordance associated with the view was hidden.
            }
            EVENT_INPUT_SHOWN -> {
                // The autofill affordance associated with the view was shown.
            }
            EVENT_INPUT_UNAVAILABLE -> {
                // Autofill isn't available.
            }
        }

    }
})

Java

AutofillManager afm = getContext().getSystemService(AutofillManager.class);

afm.registerCallback(new AutofillManager.AutofillCallback() {
    // For virtual structures, override
    // onAutofillEvent(View view, int childId, int event) instead.
    @Override
    public void onAutofillEvent(@NonNull View view, int event) {
        super.onAutofillEvent(view, event);
        switch (event) {
            case EVENT_INPUT_HIDDEN:
                // The autofill affordance associated with the view was hidden.
                break;
            case EVENT_INPUT_SHOWN:
                // The autofill affordance associated with the view was shown.
                break;
            case EVENT_INPUT_UNAVAILABLE:
                // Autofill isn't available.
                break;
        }
    }
});

コールバックを削除するときは、unregisterCallback() メソッドを使用します。

自動入力ハイライト表示ドローアブルをカスタマイズする

ビューが自動入力されると、プラットフォームはビュー上に Drawable をレンダリングして、ビューのコンテンツが自動入力されたことを示します。デフォルトでは、このドローアブルは、背景の描画に使用されるテーマの色よりもわずかに暗い半透明色の単色長方形になります。ドローアブルは、変更する必要はありませんが、アプリやアクティビティで使用されるテーマandroid:autofilledHighlight アイテムをオーバーライドすることでカスタマイズできます。以下の例をご覧ください。

res/values/styles.xml

<resources>
    <style name="MyAutofilledHighlight" parent="...">
        <item name="android:autofilledHighlight">@drawable/my_drawable</item>
    </style>
</resources>

res/drawable/my_drawable.xml

<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#4DFF0000" />
</shape>

AndroidManifest.xml

<application ...
    android:theme="@style/MyAutofilledHighlight">
<!-- or -->
<activity ...
    android:theme="@style/MyAutofilledHighlight">

自動入力に対する認証を行う

自動入力サービスは、アプリ内のフィールドに自動入力サービスが入力を行う前に、ユーザーに認証を要求できます。その場合、Android システムは、アクティビティのスタックの一部として自動入力サービスの認証アクティビティを起動します。

認証はサービス内で行われるため、認証をサポートするためにアプリを更新する必要はありません。ただし、アクティビティの再起動時にアクティビティのビュー構造が保持されるようにする必要があります(たとえば、onStart()onResume() ではなく、onCreate() でビュー構造を作成するなど)。

AutofillFramework サンプルの HeuristicsService を使用して、入力レスポンス認証を要求するように設定することで、自動入力サービスが認証を必要とする場合のアプリの動作を検証できます。また、BadViewStructureCreationSignInActivity サンプルを使用して、この問題をエミュレートすることもできます。

再利用ビューに自動入力 ID を割り当てる

大規模なデータセットに基づいて要素のスクロール リストを表示する必要があるアプリの場合、RecyclerView クラスなど、ビューを再利用するコンテナが役に立ちます。コンテナがスクロールすると、システムはレイアウト内でビューを再利用しますが、ビューには新しいコンテンツが含まれます。

再利用ビューの初期コンテンツが入力されると、自動入力サービスは、自動入力 ID を使用して、ビューの論理的意味を保持します。システムがレイアウト内でビューを再利用する際、ビューの論理 ID が同じままだと問題が発生し、間違った自動入力ユーザーデータが自動入力 ID に関連付けられることになります。

Android 9(API レベル 28)以降を搭載しているデバイスの場合、この問題を解決するには、以下のメソッドを使用することで、RecyclerView が使用するビューの自動入力 ID を明示的に管理します。

  • getNextAutofillId() メソッドは、アクティビティに固有の新しい自動入力 ID を取得します。
  • setAutofillId() メソッドは、アクティビティ内の対象ビューに対して、一意の論理自動入力 ID を設定します。

既知の問題に対処する

このセクションでは、自動入力フレームワーク内の既知の問題に対する回避策について説明します。

自動入力により、Android 8.0、8.1 でアプリがクラッシュする

Android 8.0(API レベル 26)と 8.1(API レベル 27)では、自動入力によって特定のシナリオでアプリがクラッシュすることがあります。潜在的な問題を回避するには、importantForAutofill=no を使用して、自動入力されないすべてのビューにタグを付けてください。また、importantForAutofill=noExcludeDescendants を使用して、アクティビティ全体にタグを付けることもできます。を

サイズ変更されたダイアログが自動入力の対象にならない

Android 8.1(API レベル 27)以前では、ダイアログ内のビューを表示した後にサイズ変更すると、そのビューは自動入力の対象と見なされなくなります。サイズ変更されたビューは、Android システムが自動入力サービスに送信する AssistStructure オブジェクトには含まれません。そのため、自動入力サービスはビューに入力できなくなります。

この問題を回避するには、ダイアログ ウィンドウ パラメータの token プロパティを、ダイアログを作成するアクティビティの token プロパティに置き換えます。自動入力が有効になっていることを確認したら、Dialog を継承するクラスの onWindowAttributesChanged() メソッド内にウィンドウ パラメータを保存します。次に、保存したパラメータの token プロパティを、onAttachedToWindow() メソッドの親アクティビティの token プロパティに置き換えます。

この回避策を実装するクラスを次のコード スニペットに示します。

Kotlin

class MyDialog(context: Context) : Dialog(context) {

    // Used to store the dialog window parameters.
    private var token: IBinder? = null

    private val isDialogResizedWorkaroundRequired: Boolean
        get() {
            if (Build.VERSION.SDK_INT != Build.VERSION_CODES.O || Build.VERSION.SDK_INT != Build.VERSION_CODES.O_MR1) {
                return false
            }
            val autofillManager = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                context.getSystemService(AutofillManager::class.java)
            } else {
                null
            }
            return autofillManager?.isEnabled ?: false
        }

    override fun onWindowAttributesChanged(params: WindowManager.LayoutParams) {
        if (params.token == null && token != null) {
            params.token = token
        }

        super.onWindowAttributesChanged(params)
    }

    override fun onAttachedToWindow() {
        if (isDialogResizedWorkaroundRequired) {
            token = ownerActivity!!.window.attributes.token
        }

        super.onAttachedToWindow()
    }

}

Java

public class MyDialog extends Dialog {

    public MyDialog(Context context) {
        super(context);
    }

    // Used to store the dialog window parameters.
    private IBinder token;

    @Override
    public void onWindowAttributesChanged(WindowManager.LayoutParams params) {
        if (params.token == null && token != null) {
            params.token = token;
        }

        super.onWindowAttributesChanged(params);
    }

    @Override
    public void onAttachedToWindow() {
        if (isDialogResizedWorkaroundRequired()) {
            token = getOwnerActivity().getWindow().getAttributes().token;
        }

        super.onAttachedToWindow();
    }

    private boolean isDialogResizedWorkaroundRequired() {
        if (Build.VERSION.SDK_INT != Build.VERSION_CODES.O
                || Build.VERSION.SDK_INT != Build.VERSION_CODES.O_MR1) {
            return false;
        }
        AutofillManager autofillManager =
                null;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
            autofillManager = getContext().getSystemService(AutofillManager.class);
        }
        return autofillManager != null && autofillManager.isEnabled();
    }

}

不必要な処理を回避するために、デバイス内で自動入力がサポートされているかどうか、現在のユーザーに対して有効になっているかどうか、この回避策が必要かどうかをチェックするコード スニペットに以下に示します。

Kotlin

// AutofillExtensions.kt

fun Context.isDialogResizedWorkaroundRequired(): Boolean {
    // After the issue is resolved on Android, check whether the
    // workaround is still required for the current device.
    return isAutofillAvailable()
}

fun Context.isAutofillAvailable(): Boolean {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
        // The autofill framework is available on Android 8.0
        // or higher.
        return false
    }

    val afm = getSystemService(AutofillManager::class.java)
    // Return true if autofill is supported by the device and enabled
    // for the current user.
    return afm != null && afm.isEnabled
}

Java

public class AutofillHelper {

    public static boolean isDialogResizedWorkaroundRequired(Context context) {
        // After the issue is resolved on Android, check whether the
        // workaround is still required for the current device.
        return isAutofillAvailable(context);
    }

    public static boolean isAutofillAvailable(Context context) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
            // The autofill framework is available on Android 8.0
            // or higher.
            return false;
        }

        AutofillManager afm = context.getSystemService(AutofillManager.class);
        // Return true if autofill is supported by the device and enabled
        // for the current user.
        return afm != null && afm.isEnabled();
    }
}

自動入力とアプリの連携をテストする

自動入力サービスと連携するようにアプリを最適化したら、想定どおり動作するかどうかをテストします。

アプリをテストする際は、Android 8.0(API レベル 26)以降を搭載しているエミュレータまたは物理デバイスを使用します。エミュレータの作成方法については、仮想デバイスを作成、管理するをご覧ください。

自動入力サービスをインストールする

自動入力とアプリの連携をテストする前に、自動入力サービスを提供する別のアプリをインストールする必要があります。この目的のために、サードパーティ アプリを使用することもできますが、サンプル自動入力サービスを使用する方が簡単で、サードパーティのサービスに登録する必要がありません。

Java の Android 自動入力フレームワーク サンプルを使用して、自動入力サービスとアプリの連携をテストできます。サンプルアプリには、自動入力サービスと自動入力クライアントの Activity クラスが用意されており、アプリと連携する前に自動入力ワークフローをテストできます。このページでは、android-AutofillFramework サンプルアプリを参照しています。

アプリをインストールしたら、エミュレータのシステム設定で自動入力サービスを有効にします。そのためには、[設定] > [システム] > [言語と入力] > [詳細設定] > [入力アシスタント] > [自動入力サービス] に移動します。

データ要件を分析する

自動入力サービスとアプリの連携をテストするには、アプリの入力に使用できるデータが必要です。また、アプリのビュー内にどのタイプのデータが入力されるのかをサービスが認識している必要があります。たとえば、ユーザー名を入力するビューがアプリにある場合、自動入力サービスが機能するには、ユーザー名を含むデータセットと、「ビューに入力するのはユーザー名データである」と認識するメカニズムが必要です。

ビューにどのようなタイプのデータを入力するのかをサービスに通知するには、android:autofillHints 属性を設定します。一部のサービスでは、高度なヒューリスティックを使用してデータのタイプを判定しますが、サンプルアプリなどの場合は、デベロッパーがこの情報を提供する必要があります。自動入力を使用するビュー内に android:autofillHints 属性を設定しておくと、自動入力サービスとアプリが適切に連携できるようになります。

テストを実行する

データ要件を分析したら、テストを実行できます。テストでは、自動入力サービス内にテストデータを保存し、アプリ内で自動入力をトリガーします。

サービス内にデータを保存する

現在アクティブな自動入力サービス内にデータを保存するには、次の手順を行います。

  1. テスト中に使用するデータタイプの入力対象となるビューを含むアプリを開きます。android-AutofillFramework サンプルアプリには、クレジット カード番号やユーザー名など、複数のデータタイプを入力するビューを持つ UI が用意されています。
  2. 必要なデータのタイプを保持するビューをタップします。
  3. ビューに値を入力します。
  4. 確定ボタン([ログイン] や [送信] など)をタップします。通常は、サービスがデータを保存する前に、フォームを送信する必要があります。
  5. システム ダイアログから権限リクエストを確認します。システム ダイアログには現在アクティブなサービスの名前が表示され、それがテストに使用するサービスかどうかを確認できます。表示されたサービスを使用する場合は、[保存] をタップします。

権限ダイアログが表示されない場合や、テストで使用するサービスでない場合は、システム設定内で、目的のサービスが現在アクティブになっているかチェックしてください。

アプリ内で自動入力をトリガーする

アプリ内で自動入力をトリガーするには、次の手順を行います。

  1. アプリを開いて、テストするビューを持つアクティビティに移動します。
  2. 入力対象のビューをタップします。
  3. 自動入力 UI が表示され、ビューに入力できるデータセットが表示されます。図 1 をご覧ください。
  4. 使用するデータが含まれているデータセットをタップします。ビュー内には、以前サービス内に保存したデータが表示されます。
利用可能なデータセットとして「dataset-2」を表示する自動入力 UI
図 1. 利用可能なデータセットを表示する自動入力 UI

自動入力 UI が表示されない場合は、以下の解決方法をお試しください。

  • アプリ内のビューが、android:autofillHints 属性内で正しい値を使用しているか確認します。この属性に関して利用可能な値のリストについては、View クラス内で、AUTOFILL_HINT で始まる定数をご覧ください。
  • android:importantForAutofill 属性が、入力対象ビュー内で no 以外の値に設定されているか、入力対象ビューおよびそのすべての親内で noExcludeDescendants 以外の値に設定されているか確認します。