
図 1. イメージ キーボード サポートの例
多くのユーザーは、絵文字やステッカーなど、さまざまなリッチ コンテンツを使用してコミュニケーションができることを望んでいます。以前のバージョンの Android の場合、ソフト キーボード(インプット メソッド エディタ、IME)でアプリに送信できるのは、Unicode の絵文字だけに限られていました。リッチ コンテンツに関しては、別のアプリでは使用できない各アプリ固有の API を作成するか、Easy Share Action やクリップボードを通じて画像を送信するなどの回避策を使用する必要がありました。
Android 7.1(API レベル 25)の Android SDK には、Commit Content API が含まれています。この API により、IME は、アプリ内のテキスト エディタに画像や各種リッチ コンテンツを直接送信できるようになります。この API は、リビジョン 25.0.0 以降の v13 サポート ライブラリでも利用できます。このサポート ライブラリは Android 3.2(API レベル 13)以降のデバイス上で実行可能で、実装を簡素化するヘルパー メソッドが含まれているため、サポート ライブラリを使用することをおすすめします。
この API を使用すると、任意のキーボードからリッチ コンテンツを受け入れるメッセージ アプリや、リッチ コンテンツを任意のアプリに送信できるキーボードを作成できます。Google キーボードや Google Messenger などのアプリは、Android 7.1 の Commit Content API をサポートしています(図 1 を参照)。
このページでは、IME とアプリの両方で Commit Content API を実装する方法について説明します。
仕組み
キーボードによる画像挿入は、IME とアプリの両方が対応している必要があります。画像挿入プロセスの各ステップは次のとおりです。
ユーザーが
EditText
をタップすると、エディタは、EditorInfo.contentMimeTypes
を使用して、受け入れ可能な MIME コンテンツ タイプのリストを送信します。IME は、サポートされているタイプのリストを読み取り、エディタが受け入れ可能なコンテンツをソフト キーボード内に表示します。
ユーザーが画像を選択すると、IME は
commitContent()
を呼び出し、InputContentInfo
をエディタに送信します。commitContent()
呼び出しは、commitText()
呼び出しと似ていますが、リッチ コンテンツ用です。InputContentInfo
には、コンテンツ プロバイダ内のコンテンツを識別する URI が格納されます。
アプリに画像サポートを追加する
アプリが IME からリッチ コンテンツを受け入れるには、受け入れ可能なコンテンツ タイプを IME に伝えて、コンテンツの受信時に実行されるコールバック メソッドを指定する必要があります。以下の例は、PNG 画像を受け入れる EditText
を作成する方法を示しています。
Kotlin
val editText = object : EditText(this) { override fun onCreateInputConnection(editorInfo: EditorInfo): InputConnection { val ic: InputConnection = super.onCreateInputConnection(editorInfo) EditorInfoCompat.setContentMimeTypes(editorInfo, arrayOf("image/png")) val callback = InputConnectionCompat.OnCommitContentListener { inputContentInfo, flags, opts -> val lacksPermission = (flags and InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0 // read and display inputContentInfo asynchronously if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1 && lacksPermission) { try { inputContentInfo.requestPermission() } catch (e: Exception) { return@OnCommitContentListener false // return false if failed } } // read and display inputContentInfo asynchronously. // call inputContentInfo.releasePermission() as needed. true // return true if succeeded } return InputConnectionCompat.createWrapper(ic, editorInfo, callback) } }
Java
EditText editText = new EditText(this) { @Override public InputConnection onCreateInputConnection(EditorInfo editorInfo) { final InputConnection ic = super.onCreateInputConnection(editorInfo); EditorInfoCompat.setContentMimeTypes(editorInfo, new String [] {"image/png"}); final InputConnectionCompat.OnCommitContentListener callback = new InputConnectionCompat.OnCommitContentListener() { @Override public boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags, Bundle opts) { // read and display inputContentInfo asynchronously if (BuildCompat.isAtLeastNMR1() && (flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) { try { inputContentInfo.requestPermission(); } catch (Exception e) { return false; // return false if failed } } // read and display inputContentInfo asynchronously. // call inputContentInfo.releasePermission() as needed. return true; // return true if succeeded } }; return InputConnectionCompat.createWrapper(ic, editorInfo, callback); } };
多くのことが行われているので、何が起こっているのかを説明しましょう。
この例ではサポート ライブラリを使用しているため、
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 にアクセスできるはずです。詳細については、IME に画像サポートを追加するをご覧ください。
- API レベル 25 以降のデバイス上でアプリが稼働していて、IME によって
createWrapper()
は、InputConnection
、変更後のEditorInfo
、新しいInputConnection
に対するコールバックをラップして返します。
推奨される方法は以下のとおりです。
リッチ コンテンツをサポートしていないエディタは、
setContentMimeTypes()
を呼び出さずに、EditorInfo.contentMimeTypes
をnull
に設定したままにします。InputContentInfo
内で指定されている MIME タイプが、受け入れ可能なタイプのいずれとも一致しない場合、エディタは、コンテンツを無視する必要があります。リッチ コンテンツは、テキスト カーソルの位置には影響せず、テキスト カーソルの位置から影響を受けることもありません。コンテンツを操作する際、エディタはカーソルの位置を無視できます。
エディタの
OnCommitContentListener.onCommitContent()
メソッドを使用すると、コンテンツをロードする前であっても、非同期でtrue
を返すことができます。コミットする前に IME 内で編集できるテキストとは異なり、リッチ コンテンツはすぐにコミットされます。ユーザーがコンテンツを編集、削除できるようにする場合は、デベロッパー自身でロジックを実装する必要があります。
アプリをテストする際は、デバイスまたはエミュレータに、リッチ コンテンツを送信できるキーボードがあるか確認してください。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 corresponding content } else { // the target editor does not support GIFs. disable 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 corresponding content } else { // the target editor does not support GIFs. disable corresponding content } }
- ユーザーが画像を選択したときに、アプリにコンテンツをコミットします。エディタがフォーカスを失う可能性があるため、テキストを入力している間は
commitContent()
を呼び出さないでください。GIF 画像をコミットするコード スニペットを以下に示します。
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 リクエストに応答する必要があります。
MediaStore
のような既存のコンテンツ プロバイダからのコンテンツを IME がサポートすることもありますが、例外的です。コンテンツ プロバイダの作成方法については、コンテンツ プロバイダとファイル プロバイダをご覧ください。独自のコンテンツ プロバイダを作成している場合は、エクスポートしないことをおすすめします(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
は無視されるため、手動でコンテンツにパーミッションを付与する必要があります。grantUriPermission()
を使用する方法もありますが、独自の要件を満たす独自のメカニズムを実装することをおすすめします。
IME をテストする際は、デバイスまたはエミュレータに、リッチ コンテンツを受信できるアプリがあるか確認してください。Android 7.1 以降の場合は、Google Messenger アプリを使用できます。