多くの場合、ユーザーは絵文字、ステッカー、その他の種類のリッチ コンテンツを使用してコミュニケーションします。以前のバージョンの Android では、ソフト キーボード(インプット メソッド エディタ(IME)ともいいます)は、Unicode 絵文字のみをアプリに送信できました。リッチ コンテンツの場合、他のアプリでは使用できないアプリ固有の API や、単純な共有アクションやクリップボードによる画像の送信などの回避策を使用した、アプリ固有の API がアプリで作成されました。
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 とアプリからの参加が必要です。画像挿入プロセスの各ステップは次のとおりです。
ユーザーが
EditText
をタップすると、エディタは、EditorInfo.contentMimeTypes
で受け入れ可能な MIME コンテンツ タイプのリストを送信します。IME は、サポートされているタイプのリストを読み取り、エディタが受け入れ可能なコンテンツをソフト キーボード内に表示します。
ユーザーが画像を選択すると、IME は
commitContent()
を呼び出し、InputContentInfo
をエディタに送信します。commitContent()
呼び出しはcommitText()
呼び出しに似ていますが、リッチ コンテンツ用です。InputContentInfo
には、コンテンツ プロバイダ内のコンテンツを識別する URI が格納されます。
このプロセスを図 2 に示します。
アプリに画像サポートを追加する
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 に画像サポートを追加するをご覧ください。
- API レベル 25 以降でアプリが動作していて、IME によって
createWrapper()
は、InputConnection
、変更されたEditorInfo
、コールバックを新しいInputConnection
にラップして返します。
おすすめの方法は次のとおりです。
リッチ コンテンツをサポートしていないエディタは、
setContentMimeTypes()
を呼び出さず、EditorInfo.contentMimeTypes
をnull
に設定したままにします。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:exported
を false
に設定してエクスポートしないことをおすすめします。代わりに、android:grantUriPermission
を true
に設定して、プロバイダで権限の付与を有効にします。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 以降で使用できます。