リッチ コンテンツを受信する

図 1.Unified API は、特定の UI メカニズムに縛られず、長押しメニューからの貼り付けやドラッグ&ドロップなどで受信コンテンツを処理できる単一の場所を提供します。

ユーザーは画像、動画、その他の表現力豊かなコンテンツを好みますが、アプリでこのコンテンツを挿入したり移動したりするのは必ずしも簡単ではありません。Android 12(API レベル 31)には、アプリがリッチ コンテンツをより簡単に受信できるよう、任意のソース(クリップボード、キーボード、ドラッグ)からコンテンツを受け取れる Unified API が導入されました。

`OnReceiveContentListener` などのインターフェースを UI コンポーネントにアタッチすると、任意のメカニズムでコンテンツが挿入されたときにコールバックを受け取ることができます。OnReceiveContentListenerコードでは、このコールバック 1 か所で、プレーン テキストや書式付きテキストからマークアップ、画像、動画、音声ファイルまで、あらゆるコンテンツの受信を処理できるようになります。

以前の Android バージョンとの下位互換性を維持するため、この API は AndroidX(Core 1.7 およびAppcompat 1.4 以降)でも使用できます。この機能を実装する場合は、AndroidX を使用することをおすすめします。

概要

他の既存の API では、長押しメニューやドラッグなどの UI メカニズムごとに、それに対応する固有の API があります。これは、コンテンツを挿入するメカニズムごとに類似のコードを追加して、各 API を個別に統合する必要があることを意味します。

さまざまなアクションと実装する相対 API を示す画像
図 2.以前は、コンテンツを挿入するための UI メカニズムごとに、異なる API をアプリに実装していました。

OnReceiveContentListener API は、実装する API を 1 つにすることで、これらのさまざまなコードパスを統合します。これにより、アプリ固有のロジックに集中し、それ以外の処理はプラットフォームに委ねることができます。

簡略化された統合 API を示す画像
図 3.この Unified API を使用すると、1 つの API の実装ですべての UI メカニズムをサポートできます。

このアプローチでは、コンテンツを挿入する新しい方法をプラットフォームに追加する際に、アプリでサポートを有効にするためにコードを変更する必要がありません。特定のユースケース用のフル カスタマイズをアプリに実装する必要がある場合も、同様に動作する既存の API を引き続き使用できます。

実装

この API は、単一メソッド OnReceiveContentListenerとのリスナーインターフェースです。 古いバージョンの Android プラットフォームをサポートするには、それに対応する matching OnReceiveContentListener interface in the AndroidX Core library.

この API を使用するには、アプリが処理できるコンテンツのタイプを指定してリスナーを実装します。

Kotlin

object MyReceiver : OnReceiveContentListener {
    val MIME_TYPES = arrayOf("image/*", "video/*")
    
    // ...
    
    override fun onReceiveContent(view: View, payload: ContentInfoCompat): ContentInfoCompat? {
        TODO("Not yet implemented")
    }
}

Java

public class MyReceiver implements OnReceiveContentListener {
     public static final String[] MIME_TYPES = new String[] {"image/*", "video/*"};
     // ...
}

アプリがサポートするすべてのコンテンツ MIME タイプを指定した後、リスナーの残りの部分を実装します。

Kotlin

class MyReceiver : OnReceiveContentListener {
    override fun onReceiveContent(view: View, contentInfo: ContentInfoCompat): ContentInfoCompat {
        val split = contentInfo.partition { item: ClipData.Item -> item.uri != null }
        val uriContent = split.first
        val remaining = split.second
        if (uriContent != null) {
            // App-specific logic to handle the URI(s) in uriContent.
        }
        // Return anything that your app didn't handle. This preserves the
        // default platform behavior for text and anything else that you aren't
        // implementing custom handling for.
        return remaining
    }

    companion object {
        val MIME_TYPES = arrayOf("image/*", "video/*")
    }
}

Java

 public class MyReceiver implements OnReceiveContentListener {
     public static final String[] MIME_TYPES = new String[] {"image/*", "video/*"};

     @Override
     public ContentInfoCompat onReceiveContent(View view, ContentInfoCompat contentInfo) {
         Pair<ContentInfoCompat, ContentInfoCompat> split = contentInfo.partition(
                 item -> item.getUri() != null);
         ContentInfo uriContent = split.first;
         ContentInfo remaining = split.second;
         if (uriContent != null) {
             // App-specific logic to handle the URI(s) in uriContent.
         }
         // Return anything that your app didn't handle. This preserves the
         // default platform behavior for text and anything else that you aren't
         // implementing custom handling for.
         return remaining;
     }
 }

アプリがすでにインテントとの共有をサポートしている場合は、コンテンツ URI を処理するアプリ固有のロジックを再利用できます。残りすべてのデータを結果から取得し、そのデータの処理をプラットフォームに委ねます。

リスナーを実装したら、アプリの適切な UI 要素でリスナーを設定します。

Kotlin

class MyActivity : Activity() {
    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // ...
        val myInput = findViewById(R.id.my_input)
        ViewCompat.setOnReceiveContentListener(myInput, MyReceiver.MIME_TYPES, MyReceiver())
    }
}

Java

public class MyActivity extends Activity {
     @Override
     public void onCreate(Bundle savedInstanceState) {
         // ...

         AppCompatEditText myInput = findViewById(R.id.my_input);
         ViewCompat.setOnReceiveContentListener(myInput, MyReceiver.MIME_TYPES, new MyReceiver());
     }
}

URI 権限

読み取り権限は、OnReceiveContentListener に渡された ペイロード内の コンテンツ URI に対して、プラットフォームにより自動的に付与、解放されます。

通常、アプリはサービスまたはアクティビティでコンテンツ URI を処理します。長時間実行処理の場合は、 WorkManagerを使用します。これを実装する場合は、 コンテンツを Intent.setClipData を使用して渡し、フラグ FLAG_GRANT_READ_URI_PERMISSIONを設定することで、ターゲット サービスまたはアクティビティに権限を拡張します。

また、現在のコンテキスト内でバックグラウンド スレッドを使用してコンテンツを処理することもできます。この場合、リスナーが受け取った payload オブジェクトへの参照を維持して、プラットフォームにより権限が早期に取り消されないようにします。

カスタムビュー

アプリでカスタムの View サブクラスを使用する場合は、OnReceiveContentListener がバイパスされないように注意する必要があります。

`View` クラスで ` onCreateInputConnection ` メソッドをオーバーライドする場合は、Jetpack API の ` InputConnectionCompat.createWrapper ` を使用して `InputConnection` を構成してください。

View クラスで onTextContextMenuItem メソッドをオーバーライドする場合は、メニュー アイテムが R.id.pasteまたは R.id.pasteAsPlainTextのときはスーパーに委ねてください。

Keyboard Image API との比較

OnReceiveContentListener API は、既存の Keyboard Image API の次期バージョンと考えることができます。この Unified API は、Keyboard Image API の機能に加えて、いくつかの追加機能をサポートします。デバイスと機能の互換性は、Jetpack ライブラリを使用するか、Android SDK のネイティブ API を使用するかによって異なります。

表 1. Jetpack でサポートされる機能と API レベル。
アクションまたは機能 Keyboard Image API によるサポート Unified API によるサポート
キーボードからの挿入 あり(API レベル 13 以上) あり(API レベル 13 以上)
長押しメニューからの貼り付けによる挿入 なし はい
ドラッグ&ドロップによる挿入 なし あり(API レベル 24 以上)
表 2. ネイティブ API でサポートされる機能と API レベル。
アクションまたは機能 Keyboard Image API によるサポート Unified API によるサポート
キーボードからの挿入 あり(API レベル 25 以上) あり(Android 12 以降)
長押しメニューからの貼り付けによる挿入 なし
ドラッグ&ドロップによる挿入 なし