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

標準ビューを使用しているアプリは、特別な設定をしなくても自動入力フレームワークと連携することができます。ただし、その場合でも、アプリとフレームワークの連携方法を最適化することができます。ガイド チュートリアルについては、コードラボ「自動入力用にアプリを最適化する」をご覧ください。

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

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

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

アプリが自動入力フレームワークを使用するには、デバイス上で自動入力サービスを設定する必要があります。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 つの桁にマッピングする場合は、generateSmsOptHintForCharacterPosition() メソッドを使用して、文字ごとのヒントを生成します。

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

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

対象にするかどうかを設定するには、android:importantForAutofill 属性を使用します。

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

importantForAutofill の値は、android: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);
    

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

  • ログイン アクティビティ内のキャプチャ フィールドは通常、自動入力の対象にしません。このような場合、ビューを IMPORTANT_FOR_AUTOFILL_NO としてマーキングします。
  • テキスト エディタやスプレッドシート エディタなど、ユーザーがコンテンツを作成するビューの場合、通常、ビュー構造全体を自動入力の対象にしません。このような場合、ビューを IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS としてマーキングします。これにより、このビューのすべての子も、自動入力の対象から除外するようマーキングされます。
  • ゲームプレイを表示するアクティビティなど、ゲーム内の一部のアクティビティでは、そのアクティビティ内のすべてのビューを自動入力の対象にしません。ルートビューを 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() メソッドを呼び出します。

ユーザーは、[設定] > [システム] > [言語と入力] > [詳細設定] > [入力アシスタント] > [自動入力サービス] に移動することで、自動入力の有効 / 無効の切り替えや、自動入力サービスの変更を行うことができます。ユーザーの自動入力設定をアプリレベルでオーバーライドすることはできません。

自動入力を有効にしていないユーザー向けに登録機能やログイン機能を最適化するには、Smart Lock for Passwords を実装することをおすすめします。

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

場合によっては、ユーザーのアクションに応じて自動入力リクエストを自動的にトリガーする必要があります。たとえば、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)

        // Content that comes from static resources generally isn't sensitive.
        val sensitive = !contentIsSetFromResources()
        structure.setDataIsSensitive(sensitive)
    }
    

Java

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

        // Content that comes from static resources generally isn't sensitive.
        boolean sensitive = !contentIsSetFromResources();
        structure.setDataIsSensitive(sensitive);
    }
    

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

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

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. For example, if 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, if
        // 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. For example, if the child is
        // a spinner.
        CharSequence childAutofillOptions[] = { "option1", "option2" };
        child.setAutofillOptions(childAutofillOptions);

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

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

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

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

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

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

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

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.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, you should check if 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 only 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, you should check if 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 only 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)以降を搭載しているエミュレータまたは物理デバイスを使用する必要があります。エミュレータの作成方法については、仮想デバイスを作成、管理するをご覧ください。

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

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

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 以外の値に設定されているか確認します。