イメージ キーボードのサポート

多くの場合、ユーザーは絵文字、ステッカー、その他の種類のリッチ コンテンツを使用してコミュニケーションします。以前のバージョンの Android では、ソフト キーボード(インプット メソッド エディタ(IME)ともいいます)は、Unicode 絵文字のみをアプリに送信できました。リッチ コンテンツの場合、他のアプリでは使用できないアプリ固有の API や、単純な共有アクションやクリップボードによる画像の送信などの回避策を使用した、アプリ固有の API がアプリで作成されました。

画像検索をサポートするキーボードの画像
図 1. イメージ キーボードのサポートの例。

Android 7.1(API レベル 25)以降、Android SDK に Commit Content API が含まれています。この API は、IME が画像やその他のリッチ コンテンツをアプリ内のテキスト エディタに直接送信するための汎用的な方法を提供します。この API は、リビジョン 25.0.0 の v13 サポート ライブラリでも利用できます。サポート ライブラリには、実装を簡素化するヘルパー メソッドが含まれているため、サポート ライブラリを使用することをおすすめします。

この API を使用すると、任意のキーボードからリッチ コンテンツを受け入れるメッセージ アプリを作成できます。また、リッチ コンテンツをあらゆるアプリに送信できます。Google キーボードGoogle のメッセージ アプリなどのアプリは、図 1 の Commit Content API をサポートしています。

このドキュメントでは、Commit Content API を IME とアプリの両方に実装する方法について説明します。

仕組み

キーボードの画像挿入には、IME とアプリからの参加が必要です。画像挿入プロセスの各ステップは次のとおりです。

  1. ユーザーが EditText をタップすると、エディタは、EditorInfo.contentMimeTypes で受け入れ可能な MIME コンテンツ タイプのリストを送信します。

  2. IME は、サポートされているタイプのリストを読み取り、エディタが受け入れ可能なコンテンツをソフト キーボード内に表示します。

  3. ユーザーが画像を選択すると、IME は commitContent() を呼び出し、InputContentInfo をエディタに送信します。commitContent() 呼び出しは commitText() 呼び出しに似ていますが、リッチ コンテンツ用です。InputContentInfo には、コンテンツ プロバイダ内のコンテンツを識別する URI が格納されます。

このプロセスを図 2 に示します。

アプリから IME へ、またアプリに戻るシーケンスを示す画像
図 2. アプリから IME へのアプリのフロー。

アプリに画像サポートを追加する

IME のリッチ コンテンツを受け入れるには、アプリで受け入れ可能なコンテンツ タイプを IME に伝え、コンテンツの受信時に実行されるコールバック メソッドを指定する必要があります。次の例は、PNG 画像を受け入れる EditText を作成する方法を示しています。

Kotlin

var editText: EditText = object : EditText(this) {
    override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection {
        var ic = super.onCreateInputConnection(outAttrs)
        EditorInfoCompat.setContentMimeTypes(outAttrs, arrayOf("image/png"))
        val mimeTypes = ViewCompat.getOnReceiveContentMimeTypes(this)
        if (mimeTypes != null) {
            EditorInfoCompat.setContentMimeTypes(outAttrs, mimeTypes)
            ic = InputConnectionCompat.createWrapper(this, ic, outAttrs)
        }
        return ic
    }
}

Java

EditText editText = new EditText(this) {
    @Override
    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
        InputConnection ic = super.onCreateInputConnection(outAttrs);
        EditorInfoCompat.setContentMimeTypes(outAttrs, new String[]{"image/png"});
        String[] mimeTypes = ViewCompat.getOnReceiveContentMimeTypes(this);
        if (mimeTypes != null) {
            EditorInfoCompat.setContentMimeTypes(outAttrs, mimeTypes);
            ic = InputConnectionCompat.createWrapper(this, ic, outAttrs);
        }
        return ic;
    }
};

以下に詳細を説明します。

  • この例ではサポート ライブラリを使用しているため、android.view.inputmethod ではなく android.support.v13.view.inputmethod への参照があります。

  • この例では、EditText を作成し、その onCreateInputConnection(EditorInfo) メソッドをオーバーライドして、InputConnection を変更します。InputConnection は、IME と、その入力を受け取るアプリとの間の通信チャネルです。

  • super.onCreateInputConnection() 呼び出しは、組み込みの動作(テキストの送受信)を保持し、InputConnection への参照を提供します。

  • setContentMimeTypes() は、サポートされている MIME タイプのリストを EditorInfo に追加します。setContentMimeTypes() の前に super.onCreateInputConnection() を呼び出します。

  • IME がコンテンツをコミットするたびに、callback が実行されます。メソッド onCommitContent() には、コンテンツ URI を含む InputContentInfoCompat への参照があります。

    • API レベル 25 以降でアプリが動作していて、IME によって INPUT_CONTENT_GRANT_READ_URI_PERMISSION フラグが設定されている場合は、権限をリクエストして解放する。それ以外の場合は、IME によってコンテンツ URI が付与されているか、コンテンツ プロバイダがアクセスを制限していないため、すでにコンテンツ URI にアクセスできる。詳細については、IME に画像サポートを追加するをご覧ください。
  • createWrapper() は、InputConnection、変更された EditorInfo、コールバックを新しい InputConnection にラップして返します。

おすすめの方法は次のとおりです。

  • リッチ コンテンツをサポートしていないエディタは、setContentMimeTypes() を呼び出さず、EditorInfo.contentMimeTypesnull に設定したままにします。

  • InputContentInfo で指定された MIME タイプが受け入れられるタイプと一致しない場合、エディタはその内容を無視します。

  • リッチ コンテンツはテキスト カーソルの位置に影響せず、テキスト カーソルの位置の影響も受けません。コンテンツを操作する際、エディタはカーソルの位置を無視できます。

  • エディタの OnCommitContentListener.onCommitContent() メソッドでは、コンテンツを読み込む前であっても、非同期で true を返すことができます。

  • commit 前に IME で編集できるテキストとは異なり、リッチ コンテンツは直ちに commit されます。ユーザーがコンテンツを編集または削除できるようにしたい場合は、自分でロジックを実装してください。

アプリをテストするには、リッチ コンテンツを送信できるキーボードがデバイスまたはエミュレータにあることを確認してください。Android 7.1 以降の場合は、Google キーボードを使用できます。

IME に画像サポートを追加する

リッチ コンテンツをアプリに送信する IME は、次の例に示すように Commit Content API を実装する必要があります。

  • onStartInput() または onStartInputView() をオーバーライドして、ターゲット エディタからサポートされているコンテンツ タイプのリストを読み取ります。ターゲット エディタが GIF 画像を受け入れるかどうかをチェックするコード スニペットを以下に示します。

Kotlin

override fun onStartInputView(editorInfo: EditorInfo, restarting: Boolean) {
    val mimeTypes: Array<String> = EditorInfoCompat.getContentMimeTypes(editorInfo)

    val gifSupported: Boolean = mimeTypes.any {
        ClipDescription.compareMimeTypes(it, "image/gif")
    }

    if (gifSupported) {
        // The target editor supports GIFs. Enable the corresponding content.
    } else {
        // The target editor doesn't support GIFs. Disable the corresponding
        // content.
    }
}

Java

@Override
public void onStartInputView(EditorInfo info, boolean restarting) {
    String[] mimeTypes = EditorInfoCompat.getContentMimeTypes(editorInfo);

    boolean gifSupported = false;
    for (String mimeType : mimeTypes) {
        if (ClipDescription.compareMimeTypes(mimeType, "image/gif")) {
            gifSupported = true;
        }
    }

    if (gifSupported) {
        // The target editor supports GIFs. Enable the corresponding content.
    } else {
        // The target editor doesn't support GIFs. Disable the corresponding
        // content.
    }
}

  • ユーザーが画像を選択したときに、アプリにコンテンツを commit します。コンポーズ中のテキストがある場合は commitContent() を呼び出さないでください。エディタがフォーカスを喪失する可能性があります。次のコード スニペットは、GIF 画像を commit する方法を示しています。

Kotlin

// Commits a GIF image.

// @param contentUri = Content URI of the GIF image to be sent.
// @param imageDescription = Description of the GIF image to be sent.

fun commitGifImage(contentUri: Uri, imageDescription: String) {
    val inputContentInfo = InputContentInfoCompat(
            contentUri,
            ClipDescription(imageDescription, arrayOf("image/gif")),
            null
    )
    val inputConnection = currentInputConnection
    val editorInfo = currentInputEditorInfo
    var flags = 0
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
        flags = flags or InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION
    }
    InputConnectionCompat.commitContent(inputConnection, editorInfo, inputContentInfo, flags, null)
}

Java

// Commits a GIF image.

// @param contentUri = Content URI of the GIF image to be sent.
// @param imageDescription = Description of the GIF image to be sent.

public static void commitGifImage(Uri contentUri, String imageDescription) {
    InputContentInfoCompat inputContentInfo = new InputContentInfoCompat(
            contentUri,
            new ClipDescription(imageDescription, new String[]{"image/gif"}),
            null
    );
    InputConnection inputConnection = getCurrentInputConnection();
    EditorInfo editorInfo = getCurrentInputEditorInfo();
    Int flags = 0;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
        flags |= InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION;
    }
    InputConnectionCompat.commitContent(
            inputConnection, editorInfo, inputContentInfo, flags, null);
}

IME の作成者は、ほとんどの場合、コンテンツ URI リクエストに応答するために独自のコンテンツ プロバイダを実装する必要があります。ただし、IME が MediaStore などの既存のコンテンツ プロバイダのコンテンツをサポートしている場合は例外です。コンテンツ プロバイダの構築については、コンテンツ プロバイダファイル プロバイダのドキュメントをご覧ください。

独自のコンテンツ プロバイダを作成する場合は、android:exportedfalse に設定してエクスポートしないことをおすすめします。代わりに、android:grantUriPermissiontrue に設定して、プロバイダで権限の付与を有効にします。IME は、コンテンツがコミットされると、コンテンツ URI にアクセスするパーミッションを付与できるようになります。変換には次の 2 つの方法があります。

  • Android 7.1(API レベル 25)以降では、commitContent() を呼び出すときに、フラグ パラメータを INPUT_CONTENT_GRANT_READ_URI_PERMISSION に設定します。その後、アプリが受け取る InputContentInfo オブジェクトは、requestPermission()releasePermission() を呼び出して、一時的な読み取り権限のリクエストとリリースを行うことができます。

  • Android 7.0(API レベル 24)以前では INPUT_CONTENT_GRANT_READ_URI_PERMISSION が無視されるため、コンテンツに手動で権限を付与します。これを行う方法の 1 つとして grantUriPermission() を使用できますが、独自の要件を満たす独自のメカニズムを実装することもできます。

IME をテストするには、リッチ コンテンツを受信できるアプリがデバイスまたはエミュレータにあることを確認してください。Google Messenger アプリは Android 7.1 以降で使用できます。