Android は、コピー&ペースト用の強力なクリップボード ベース フレームワークを備えています。このフレームワークは、テキスト文字列や、複雑なデータ構造、テキストとバイナリのストリーム データ、さらにはアプリアセットなど、シンプルなデータ型と複雑なデータ型の両方をサポートします。シンプルなテキストデータはクリップボード内に直接保存されます。複雑なデータは参照として保存され、貼り付け側アプリがコンテンツ プロバイダを使用して解決します。コピー&ペーストは、単一のアプリ内でも、フレームワークを実装している複数のアプリ間でも機能します。
フレームワークの一部でコンテンツ プロバイダが使用されるため、このトピックは、ある程度 Android Content Provider API に精通していることを前提としています。詳細については、コンテンツ プロバイダをご覧ください。
クリップボード フレームワーク
クリップボード フレームワークを使用する場合は、データをクリップ オブジェクトに配置して、そのクリップ オブジェクトをシステムレベルのクリップボードに配置します。クリップ オブジェクトには、次の 3 つの形式があります。
- テキスト
- テキスト文字列。文字列をコピーするには、文字列を直接クリップ オブジェクトに配置して、そのクリップ オブジェクトをクリップボードに配置します。文字列を貼り付けるには、クリップボードからクリップ オブジェクトを取得して、その文字列をアプリのストレージにコピーします。
- URI
- 任意の形式の URI を示す
Uri
オブジェクト。主に、コンテンツ プロバイダから複雑なデータをコピーする場合に使用します。データをコピーするには、Uri
オブジェクトをクリップ オブジェクトに配置し、そのクリップ オブジェクトをクリップボードに配置します。データを貼り付けるには、クリップ オブジェクトを取得して、Uri
オブジェクトを取得し、それをコンテンツ プロバイダなどのデータソースに変換して、そのソースからアプリのストレージにデータをコピーします。 - インテント
Intent
。アプリ ショートカットのコピーをサポートします。データをコピーするには、インテントを作成してクリップ オブジェクトに配置し、そのクリップ オブジェクトをクリップボードに配置します。データを貼り付けるには、クリップ オブジェクトを取得して、そのインテント オブジェクトをアプリのメモリ領域にコピーします。
クリップボードは、一度に 1 つのクリップ オブジェクトしか保持できません。アプリがクリップ オブジェクトをクリップボードに配置すると、以前のクリップ オブジェクトは消去されます。
ユーザーがアプリにデータを貼り付けられるようにする場合でも、すべてのタイプのデータを処理する必要はありません。ユーザーに貼り付けのオプションを付与する前に、クリップボード上のデータを調べることができます。クリップ オブジェクト内には、特定のデータ形式のほかに、利用可能な MIME タイプを伝えるメタデータも含まれています。このメタデータは、アプリがクリップボード データを使用して何か有用なことができるかどうかを判断する際に役立ちます。たとえば、主にテキストを処理するアプリの場合は、URI やインテントを含むクリップ オブジェクトを無視することができます。
また、クリップボード上のデータの形式に関係なく、ユーザーがテキストを貼り付けられるようにすることもできます。この場合、クリップボード データをテキスト表現に強制変換したうえで、テキストを貼り付けます。詳細については、クリップボードをテキストに強制変換するをご覧ください。
クリップボード クラス
このセクションでは、クリップボード フレームワークが使用するクラスについて説明します。
ClipboardManager
Android システムで、システム クリップボードはグローバルな ClipboardManager
クラスで表されます。このクラスを直接インスタンス化しないでください。代わりに、getSystemService(CLIPBOARD_SERVICE)
を呼び出して、その参照を取得します。
ClipData、ClipData.Item、ClipDescription
クリップボードにデータを追加するには、データの説明とデータ自体の両方を含む ClipData
オブジェクトを作成します。クリップボードは、一度に 1 つの ClipData
しか保持できません。ClipData
には、1 つの ClipDescription
オブジェクトと、1 つ以上の ClipData.Item
オブジェクトが含まれます。
ClipDescription
オブジェクトには、クリップに関するメタデータが含まれます。特に、クリップのデータに対して利用可能な MIME タイプの配列が含まれています。クリップをクリップボード上に配置すると、貼り付け側アプリがこの配列を利用できるようになります。貼り付け側アプリは、この配列を調べることで、利用可能な MIME タイプの中で処理可能なものがあるか確認します。
ClipData.Item
オブジェクトには、テキスト、URI、インテントのいずれかのデータが含まれます。
- テキスト
CharSequence
。- URI
Uri
。通常はコンテンツ プロバイダ URI が格納されますが、どのような URI でも可能です。データ提供側のアプリは、URI をクリップボード上に配置します。データ貼り付け側のアプリは、クリップボードから URI を取得し、それを使用してコンテンツ プロバイダ(または他のデータソース)にアクセスし、データを取得します。- インテント
Intent
。このデータ型を使用すると、アプリのショートカットをクリップボードにコピーできます。ユーザーは、後でそのショートカットを別のアプリに貼り付けて使用できます。
1 つのクリップに複数の ClipData.Item
オブジェクトを追加できます。これにより、複数の選択内容を単一のクリップとしてコピー&ペーストすることができます。たとえば、一度に複数のアイテムを選択できるリスト ウィジェットがある場合に、すべてのアイテムを一度にクリップボードにコピーすることができます。この機能を実現するには、リストアイテムごとに個別の ClipData.Item
を作成して、すべての ClipData.Item
オブジェクトを ClipData
オブジェクトに追加します。
ClipData コンビニエンス メソッド
ClipData
クラスには、単一の ClipData.Item
オブジェクトとシンプルな ClipDescription
オブジェクトを使用して ClipData
オブジェクトを作成するための静的コンビニエンス メソッドが用意されています。
-
newPlainText(label, text)
- 単一の
ClipData.Item
オブジェクトがテキスト文字列を格納しているClipData
オブジェクトを返します。ClipDescription
オブジェクトのラベルはlabel
に設定されています。ClipDescription
内の単一の MIME タイプはMIMETYPE_TEXT_PLAIN
です。newPlainText()
を使用すると、テキスト文字列からクリップを作成できます。 -
newUri(resolver, label, URI)
- 単一の
ClipData.Item
オブジェクトが URI を格納しているClipData
オブジェクトを返します。ClipDescription
オブジェクトのラベルはlabel
に設定されています。URI がコンテンツ URI の場合(Uri.getScheme()
がcontent:
を返す場合)、このメソッドは、resolver
内で提供されるContentResolver
オブジェクトを使用して、コンテンツ プロバイダから利用可能な MIME タイプを取得し、ClipDescription
内に保存します。URI がcontent:
URI ではない場合、このメソッドは、MIME タイプをMIMETYPE_TEXT_URILIST
に設定します。newUri()
を使用すると、URI(特にcontent:
URI)からクリップを作成できます。 -
newIntent(label, intent)
- 単一の
ClipData.Item
オブジェクトがIntent
を格納しているClipData
オブジェクトを返します。ClipDescription
オブジェクトのラベルはlabel
に設定されています。MIME タイプはMIMETYPE_TEXT_INTENT
に設定されています。newIntent()
を使用すると、インテント オブジェクトからクリップを作成できます。
クリップボード データをテキストに強制変換する
テキストだけを処理するアプリの場合でも、ClipData.Item.coerceToText()
メソッドを使用して変換することで、クリップボードから非テキストデータをコピーすることができます。
このメソッドは、ClipData.Item
内のデータをテキストに変換して、CharSequence
を返します。ClipData.Item.coerceToText()
が返す値は、ClipData.Item
内のデータの形式に基づきます。
- テキスト
ClipData.Item
がテキストの場合(getText()
が null でない場合)、coerceToText()
はそのテキストを返します。- URI
ClipData.Item
が URI の場合(getUri()
が null でない場合)、coerceToText()
はそれをコンテンツ URI として使用するように試みます。-
URI がコンテンツ URI で、プロバイダがテキスト ストリームを返すことができる場合、
coerceToText()
はテキスト ストリームを返します。 -
URI がコンテンツ URI で、プロバイダがテキスト ストリームを提供しない場合、
coerceToText()
は URI のテキスト表現を返します。このテキスト表現は、Uri.toString()
によって返されるものと同じです。 -
URI がコンテンツ URI でない場合、
coerceToText()
は URI のテキスト表現を返します。このテキスト表現は、Uri.toString()
によって返されるものと同じです。
-
URI がコンテンツ URI で、プロバイダがテキスト ストリームを返すことができる場合、
- インテント
ClipData.Item
がインテントの場合(getIntent()
が null でない場合)、coerceToText()
はそれをインテント URI に変換して返します。このテキスト表現は、Intent.toUri(URI_INTENT_SCHEME)
によって返されるものと同じです。
クリップボード フレームワークの概要を図 1 に示します。データをコピーする場合、アプリは、ClipData
オブジェクトを ClipboardManager
グローバル クリップボードに配置します。ClipData
には、1 つ以上の ClipData.Item
オブジェクトと、1 つの ClipDescription
オブジェクトが含まれます。データを貼り付ける場合、アプリは、ClipData
を取得して、ClipDescription
から MIME タイプを取得し、ClipData.Item
から、あるいは ClipData.Item
が参照しているコンテンツ プロバイダから、データを取得します。

図 1. Android クリップボード フレームワーク
クリップボードにコピーする
上記で説明したとおり、クリップボードにデータをコピーするには、グローバル ClipboardManager
オブジェクトのハンドルを取得して、ClipData
オブジェクトを作成し、1 つの ClipDescription
オブジェクトと 1 つまたは複数の ClipData.Item
オブジェクトを追加して、完成した ClipData
オブジェクトを ClipboardManager
オブジェクトに追加します。詳細な手順について、以下で説明します。
-
コンテンツ URI を使用してデータをコピーする場合は、コンテンツ プロバイダをセットアップします。
コンテンツ プロバイダを使用してコピー&ペーストを行う例として、Note Pad サンプルアプリをご覧ください。NotePadProvider クラスが、コンテンツ プロバイダを実装しています。NotePad クラスが、サポート対象となる MIME タイプを含め、プロバイダと他のアプリとの間のコントラクトを定義しています。
-
システム クリップボードを取得します。
Kotlin
when(menuItem.itemId) { ... R.id.menu_copy -> { // if the user selects copy // Gets a handle to the clipboard service. val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager } }
Java
... // if the user selects copy case R.id.menu_copy: // Gets a handle to the clipboard service. ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
-
新しい
ClipData
オブジェクトにデータをコピーします。-
テキストの場合
Kotlin
// Creates a new text clip to put on the clipboard val clip: ClipData = ClipData.newPlainText("simple text", "Hello, World!")
Java
// Creates a new text clip to put on the clipboard ClipData clip = ClipData.newPlainText("simple text", "Hello, World!");
-
URI の場合
下記のスニペットは、プロバイダのコンテンツ URI に対してレコード ID をエンコードすることによって URI を作成しています。この手法の詳細については、URI に対して識別子をエンコードするをご覧ください。
Kotlin
// Creates a Uri based on a base Uri and a record ID based on the contact's last name // Declares the base URI string const val CONTACTS = "content://com.example.contacts" // Declares a path string for URIs that you use to copy data const val COPY_PATH = "/copy" // Declares the Uri to paste to the clipboard val copyUri: Uri = Uri.parse("$CONTACTS$COPY_PATH/$lastName") ... // Creates a new URI clip object. The system uses the anonymous getContentResolver() object to // get MIME types from provider. The clip object's label is "URI", and its data is // the Uri previously created. val clip: ClipData = ClipData.newUri(contentResolver, "URI", copyUri)
Java
// Creates a Uri based on a base Uri and a record ID based on the contact's last name // Declares the base URI string private static final String CONTACTS = "content://com.example.contacts"; // Declares a path string for URIs that you use to copy data private static final String COPY_PATH = "/copy"; // Declares the Uri to paste to the clipboard Uri copyUri = Uri.parse(CONTACTS + COPY_PATH + "/" + lastName); ... // Creates a new URI clip object. The system uses the anonymous getContentResolver() object to // get MIME types from provider. The clip object's label is "URI", and its data is // the Uri previously created. ClipData clip = ClipData.newUri(getContentResolver(), "URI", copyUri);
-
インテントの場合
下記のスニペットは、アプリのインテントを作成して、クリップ オブジェクト内に配置しています。
Kotlin
// Creates the Intent val appIntent = Intent(this, com.example.demo.myapplication::class.java) ... // Creates a clip object with the Intent in it. Its label is "Intent" and its data is // the Intent object created previously val clip: ClipData = ClipData.newIntent("Intent", appIntent)
Java
// Creates the Intent Intent appIntent = new Intent(this, com.example.demo.myapplication.class); ... // Creates a clip object with the Intent in it. Its label is "Intent" and its data is // the Intent object created previously ClipData clip = ClipData.newIntent("Intent", appIntent);
-
-
新しいクリップ オブジェクトをクリップボード上に配置します。
Kotlin
// Set the clipboard's primary clip. clipboard.setPrimaryClip(clip)
Java
// Set the clipboard's primary clip. clipboard.setPrimaryClip(clip);
クリップボードから貼り付ける
上記で説明したとおり、クリップボードからデータを貼り付けるには、グローバル クリップボード オブジェクトを取得して、クリップ オブジェクトを取得し、データを参照して、可能であればクリップ オブジェクトから独自のストレージにデータをコピーします。このセクションでは、3 つの形式のクリップボード データを貼り付ける手順について説明します。
プレーン テキストを貼り付ける
プレーン テキストを貼り付けるには、まず、グローバル クリップボードを取得して、プレーン テキストを返すことができるか検証します。次に、getText()
を使用して、クリップ オブジェクトを取得し、そのテキストを独自のストレージにコピーします。手順は次のとおりです。
-
getSystemService(CLIPBOARD_SERVICE)
を使用して、グローバルClipboardManager
オブジェクトを取得します。また、貼り付けたテキストを格納するグローバル変数を宣言します。Kotlin
var clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager var pasteData: String = ""
Java
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); String pasteData = "";
-
次に、現在のアクティビティ内で「貼り付け」オプションを有効にするか無効にするかを決定します。クリップボードがクリップを含んでいるかどうか、クリップのデータタイプを処理できるかどうかを検証します。
Kotlin
// Gets the ID of the "paste" menu item val pasteItem: MenuItem = menu.findItem(R.id.menu_paste) // If the clipboard doesn't contain data, disable the paste menu item. // If it does contain data, decide if you can handle the data. pasteItem.isEnabled = when { !clipboard.hasPrimaryClip() -> { false } !(clipboard.primaryClipDescription.hasMimeType(MIMETYPE_TEXT_PLAIN)) -> { // This disables the paste menu item, since the clipboard has data but it is not plain text false } else -> { // This enables the paste menu item, since the clipboard contains plain text. true } }
Java
// Gets the ID of the "paste" menu item MenuItem pasteItem = menu.findItem(R.id.menu_paste); // If the clipboard doesn't contain data, disable the paste menu item. // If it does contain data, decide if you can handle the data. if (!(clipboard.hasPrimaryClip())) { pasteItem.setEnabled(false); } else if (!(clipboard.getPrimaryClipDescription().hasMimeType(MIMETYPE_TEXT_PLAIN))) { // This disables the paste menu item, since the clipboard has data but it is not plain text pasteItem.setEnabled(false); } else { // This enables the paste menu item, since the clipboard contains plain text. pasteItem.setEnabled(true); }
-
クリップボードからデータをコピーします。プログラム内のこのポイントに到達できるのは、「貼り付け」メニュー項目が有効な場合に限られます。そのため、クリップボード内にプレーン テキストが格納されていると想定できます。ただし、格納されているプレーン テキストがテキスト文字列であるのか URI であるのかは、まだわかりません。これをテストするスニペットを以下に示します。ただし、プレーン テキストを処理するコードだけを表示しています。
Kotlin
when (menuItem.itemId) { ... R.id.menu_paste -> { // Responds to the user selecting "paste" // Examines the item on the clipboard. If getText() does not return null, the clip item // contains the text. Assumes that this application can only handle one item at a time. val item = clipboard.primaryClip.getItemAt(0) // Gets the clipboard as text. pasteData = item.text return if (pasteData != null) { // If the string contains data, then the paste operation is done true } else { // The clipboard does not contain text. // If it contains a URI, attempts to get data from it val pasteUri: Uri? = item.uri if (pasteUri != null) { // If the URI contains something, try to get text from it // calls a routine to resolve the URI and get data from it. This routine is not // presented here. pasteData = resolveUri(pasteUri) true } else { // Something is wrong. The MIME type was plain text, but the clipboard does not // contain either text or a Uri. Report an error. Log.e(TAG,"Clipboard contains an invalid data type") false } } } }
Java
// Responds to the user selecting "paste" case R.id.menu_paste: // Examines the item on the clipboard. If getText() does not return null, the clip item contains the // text. Assumes that this application can only handle one item at a time. ClipData.Item item = clipboard.getPrimaryClip().getItemAt(0); // Gets the clipboard as text. pasteData = item.getText(); // If the string contains data, then the paste operation is done if (pasteData != null) { return true; // The clipboard does not contain text. If it contains a URI, attempts to get data from it } else { Uri pasteUri = item.getUri(); // If the URI contains something, try to get text from it if (pasteUri != null) { // calls a routine to resolve the URI and get data from it. This routine is not // presented here. pasteData = resolveUri(Uri); return true; } else { // Something is wrong. The MIME type was plain text, but the clipboard does not contain either // text or a Uri. Report an error. Log.e(TAG, "Clipboard contains an invalid data type"); return false; } }
コンテンツ URI からデータを貼り付ける
ClipData.Item
オブジェクトがコンテンツ URI を格納していて、その MIME タイプの 1 つを処理できると判断した場合は、ContentResolver
を作成し、適切なコンテンツ プロバイダ メソッドを呼び出してデータを取得します。
以下では、クリップボード上のコンテンツ URI に基づいてコンテンツ プロバイダからデータを取得する手順について説明します。アプリが使用できる MIME タイプがプロバイダから利用可能であるかチェックします。
-
MIME タイプを格納するグローバル変数を宣言します。
Kotlin
// Declares a MIME type constant to match against the MIME types offered by the provider const val MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact"
Java
// Declares a MIME type constant to match against the MIME types offered by the provider public static final String MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact";
-
グローバル クリップボードを取得します。また、コンテンツ リゾルバも取得します。これにより、コンテンツ プロバイダにアクセスできるようになります。
Kotlin
// Gets a handle to the Clipboard Manager val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager // Gets a content resolver instance val cr = contentResolver
Java
// Gets a handle to the Clipboard Manager ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); // Gets a content resolver instance ContentResolver cr = getContentResolver();
-
クリップボードからメインクリップを取得し、そのコンテンツを URI として取得します。
Kotlin
// Gets the clipboard data from the clipboard val clip: ClipData? = clipboard.primaryClip clip?.run { // Gets the first item from the clipboard data val item: ClipData.Item = getItemAt(0) // Tries to get the item's contents as a URI val pasteUri: Uri? = item.uri
Java
// Gets the clipboard data from the clipboard ClipData clip = clipboard.getPrimaryClip(); if (clip != null) { // Gets the first item from the clipboard data ClipData.Item item = clip.getItemAt(0); // Tries to get the item's contents as a URI Uri pasteUri = item.getUri();
-
getType(Uri)
を呼び出して、URI がコンテンツ URI かどうかをテストします。Uri
が有効なコンテンツ プロバイダをポイントしていない場合、このメソッドは null を返します。Kotlin
// If the clipboard contains a URI reference pasteUri?.let { // Is this a content URI? val uriMimeType: String? = cr.getType(it)
Java
// If the clipboard contains a URI reference if (pasteUri != null) { // Is this a content URI? String uriMimeType = cr.getType(pasteUri);
-
現在のアプリが認識できる MIME タイプをコンテンツ プロバイダがサポートしているかどうかをテストします。サポートしている場合は、
ContentResolver.query()
を呼び出してデータを取得します。戻り値はCursor
です。Kotlin
// If the return value is not null, the Uri is a content Uri uriMimeType?.takeIf { // Does the content provider offer a MIME type that the current application can use? it == MIME_TYPE_CONTACT }?.apply { // Get the data from the content provider. cr.query(pasteUri, null, null, null, null)?.use { pasteCursor -> // If the Cursor contains data, move to the first record if (pasteCursor.moveToFirst()) { // get the data from the Cursor here. The code will vary according to the // format of the data model. } // Kotlin `use` will automatically close the Cursor } } } }
Java
// If the return value is not null, the Uri is a content Uri if (uriMimeType != null) { // Does the content provider offer a MIME type that the current application can use? if (uriMimeType.equals(MIME_TYPE_CONTACT)) { // Get the data from the content provider. Cursor pasteCursor = cr.query(uri, null, null, null, null); // If the Cursor contains data, move to the first record if (pasteCursor != null) { if (pasteCursor.moveToFirst()) { // get the data from the Cursor here. The code will vary according to the // format of the data model. } } // close the Cursor pasteCursor.close(); } } } }
インテントを貼り付ける
インテントを貼り付けるには、まず、グローバル クリップボードを取得します。ClipData.Item
オブジェクトを調べて、インテントを格納しているかどうかを確認します。次に、getIntent()
を呼び出して、インテントを独自のストレージにコピーします。この手順を行うスニペットは以下のとおりです。
Kotlin
// Gets a handle to the Clipboard Manager val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager // Checks to see if the clip item contains an Intent, by testing to see if getIntent() returns null val pasteIntent: Intent? = clipboard.primaryClip?.getItemAt(0)?.intent if (pasteIntent != null) { // handle the Intent } else { // ignore the clipboard, or issue an error if your application was expecting an Intent to be // on the clipboard }
Java
// Gets a handle to the Clipboard Manager ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); // Checks to see if the clip item contains an Intent, by testing to see if getIntent() returns null Intent pasteIntent = clipboard.getPrimaryClip().getItemAt(0).getIntent(); if (pasteIntent != null) { // handle the Intent } else { // ignore the clipboard, or issue an error if your application was expecting an Intent to be // on the clipboard }
コンテンツ プロバイダを使用して複雑なデータをコピーする
コンテンツ プロバイダは、データベース レコードやファイル ストリームなど、複雑なデータのコピーをサポートします。データをコピーするには、コンテンツ URI をクリップボードに配置します。次に、貼り付け側アプリが、クリップボードからこの URI を取得し、それを使用してデータベース データ記述子やファイル ストリーム記述子を取得します。
貼り付け側アプリが取得するのはデータのコンテンツ URI だけであるため、取得するデータについて認識する必要があります。そのためには、URI 自体に対してデータの識別子をエンコードしてこの情報を提供するか、コピー対象のデータを返す一意の URI を指定します。どちらの手法を選択するのかは、データの整理方法によって決まります。
以下のセクションでは、URI のセットアップ方法、複雑なデータの提供方法、ファイル ストリームの提供方法について説明します。以下の説明は、コンテンツ プロバイダ設計の一般原則に精通していることを前提としています。
URI に対して識別子をエンコードする
URI を使用してクリップボードにデータをコピーする場合、URI 自体に対してデータの識別子をエンコードすると便利です。これにより、コンテンツ プロバイダが URI から識別子を取得し、それを使用してデータを取得できるようになります。貼り付け側アプリは、識別子が存在していることを認識する必要はありません。クリップボードから「参照」(URI と識別子)を取得して、コンテンツ プロバイダに渡すだけで、データを取得できます。
コンテンツ URI に対して識別子をエンコードする場合は通常、URI の末尾に識別子を連結します。たとえば、プロバイダ URI を次の文字列として定義したとします。
"content://com.example.contacts"
この URI に対して名前をエンコードする場合は、次のようなスニペットを使用します。
Kotlin
val uriString = "content://com.example.contacts/Smith" // uriString now contains content://com.example.contacts/Smith. // Generates a uri object from the string representation val copyUri = Uri.parse(uriString)
Java
String uriString = "content://com.example.contacts" + "/" + "Smith"; // uriString now contains content://com.example.contacts/Smith. // Generates a uri object from the string representation Uri copyUri = Uri.parse(uriString);
すでにコンテンツ プロバイダを使用している場合は、URI がコピー用であることを示す新しい URI パスを追加することをおすすめします。たとえば、次の URI パスをすでに使用しているとします。
"content://com.example.contacts"/people "content://com.example.contacts"/people/detail "content://com.example.contacts"/people/images
このような場合は、URI をコピーするための専用のパスを新たに追加してください。
"content://com.example.contacts/copying"
これにより、パターン マッチングによって「コピー版」の URI を検出し、コピー&ペースト専用のコードで処理できるようになります。
すでにコンテンツ プロバイダ、内部データベース、内部テーブルを使用してデータを整理している場合は通常、このエンコード方式を使用します。このようなケースでは、コピーするデータが複数あり、データごとに一意の識別子が設定されていると考えられます。貼り付け側アプリからのクエリに応じて、識別子に基づいてデータを検索し、返すことができます。
データが複数ない場合は通常、識別子をエンコードする必要はありません。プロバイダ固有の URI を使用するだけで済みます。クエリに応じて、プロバイダは、現在格納しているデータを返します。
ID に基づいて単一のレコードを取得する手法は、Note Pad サンプルアプリでも、メモリストからメモを開く際に使用されています。このサンプルでは SQL データベースの _id
フィールドを使用していますが、任意の数値識別子や文字識別子を使用できます。
データ構造をコピーする
複雑なデータのコピー&ペーストを行うコンテンツ プロバイダは、ContentProvider
コンポーネントのサブクラスとしてセットアップします。また、提供するレコードを正確にポイントするように、クリップボード上に配置した URI をエンコードする必要があります。さらに、アプリの既存の状態についても考慮する必要があります。
-
すでにコンテンツ プロバイダを使用している場合は、その機能に追加できます。
query()
メソッドを編集して、データ貼り付け側アプリから取得する URI を処理できるようにするだけで済みます。「コピー版」の URI パターンを処理できるようにメソッドを編集してください。 - アプリが内部データベースを保持している場合は、このデータベースをコンテンツ プロバイダに移動することで、データベースからのコピーを促進できます。
- 現在データベースを使用していない場合は、クリップボードから貼り付けを行うアプリにデータを提供することを唯一の目的とするシンプルなコンテンツ プロバイダを実装してください。
コンテンツ プロバイダでは、少なくとも次のメソッドをオーバーライドします。
-
query()
- 貼り付け側アプリは、このメソッドを使用し、クリップボード上に配置されている URI を指定することで、データを取得できるものと想定しています。コピーをサポートするには、このメソッドによって、特別な「コピー版」のパスを含む URI を検出できる必要があります。これにより、アプリは「コピー版」の URI を作成して、クリップボード上に配置できるようになります。「コピー版」の URI には、コピーパスと、コピーするレコードを正確にポイントするポインタが含まれます。
-
getType()
- このメソッドは、コピーするデータの MIME タイプを返します。
newUri()
メソッドは、getType()
を呼び出して、MIME タイプを新しいClipData
オブジェクトに配置します。複雑なデータの MIME タイプについては、コンテンツ プロバイダをご覧ください。
insert()
や update()
など、他のコンテンツ プロバイダ メソッドは必要ありません。貼り付け側アプリは、サポート対象の MIME タイプを取得して、プロバイダからデータをコピーするだけで済みます。上記のような別のメソッドをすでに使用していたとしても、コピー処理を妨げることはありません。
複雑なデータをコピーするようにアプリをセットアップする方法を次のスニペットに示します。
-
アプリのグローバル定数内で、ベース URI 文字列と、データをコピーする際に使用する URI 文字列を識別するパスを宣言します。また、コピーしたデータの MIME タイプも宣言します。
Kotlin
// Declares the base URI string private const val CONTACTS = "content://com.example.contacts" // Declares a path string for URIs that you use to copy data private const val COPY_PATH = "/copy" // Declares a MIME type for the copied data const val MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact"
Java
// Declares the base URI string private static final String CONTACTS = "content://com.example.contacts"; // Declares a path string for URIs that you use to copy data private static final String COPY_PATH = "/copy"; // Declares a MIME type for the copied data public static final String MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact";
-
ユーザーがデータをコピーするアクティビティ内で、データをクリップボードにコピーするコードをセットアップします。コピー リクエストに応じて、URI をクリップボード上に配置します。
Kotlin
class MyCopyActivity : Activity() { ... when(item.itemId) { R.id.menu_copy -> { // The user has selected a name and is requesting a copy. // Appends the last name to the base URI // The name is stored in "lastName" uriString = "$CONTACTS$COPY_PATH/$lastName" // Parses the string into a URI val copyUri: Uri? = Uri.parse(uriString) // Gets a handle to the clipboard service. val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clip: ClipData = ClipData.newUri(contentResolver, "URI", copyUri) // Set the clipboard's primary clip. clipboard.setPrimaryClip(clip) } }
Java
public class MyCopyActivity extends Activity { ... // The user has selected a name and is requesting a copy. case R.id.menu_copy: // Appends the last name to the base URI // The name is stored in "lastName" uriString = CONTACTS + COPY_PATH + "/" + lastName; // Parses the string into a URI Uri copyUri = Uri.parse(uriString); // Gets a handle to the clipboard service. ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); ClipData clip = ClipData.newUri(getContentResolver(), "URI", copyUri); // Set the clipboard's primary clip. clipboard.setPrimaryClip(clip);
-
コンテンツ プロバイダのグローバル スコープで、URI マッチャーを作成して、クリップボード上に配置した URI にマッチする URI パターンを追加します。
Kotlin
// A Uri Match object that simplifies matching content URIs to patterns. private val sUriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply { // Adds a matcher for the content URI. It matches // "content://com.example.contacts/copy/*" addURI(CONTACTS, "names/*", GET_SINGLE_CONTACT) } // An integer to use in switching based on the incoming URI pattern private const val GET_SINGLE_CONTACT = 0 ... class MyCopyProvider : ContentProvider() { ... }
Java
public class MyCopyProvider extends ContentProvider { ... // A Uri Match object that simplifies matching content URIs to patterns. private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH); // An integer to use in switching based on the incoming URI pattern private static final int GET_SINGLE_CONTACT = 0; ... // Adds a matcher for the content URI. It matches // "content://com.example.contacts/copy/*" sUriMatcher.addURI(CONTACTS, "names/*", GET_SINGLE_CONTACT);
-
query()
メソッドをセットアップします。このメソッドは、コーディング方法に応じて、さまざまな URI パターンを処理できます。ただし、以下では、クリップボード コピー処理用のパターンだけを表示しています。Kotlin
// Sets up your provider's query() method. override fun query( uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String? ): Cursor? { ... // when based on the incoming content URI when(sUriMatcher.match(uri)) { GET_SINGLE_CONTACT -> { // query and return the contact for the requested name. Here you would decode // the incoming URI, query the data model based on the last name, and return the result // as a Cursor. } } ... }
Java
// Sets up your provider's query() method. public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { ... // Switch based on the incoming content URI switch (sUriMatcher.match(uri)) { case GET_SINGLE_CONTACT: // query and return the contact for the requested name. Here you would decode // the incoming URI, query the data model based on the last name, and return the result // as a Cursor. ... }
-
コピーされたデータの適切な MIME タイプを返すように
getType()
メソッドをセットアップします。Kotlin
// Sets up your provider's getType() method. override fun getType(uri: Uri): String? { ... return when(sUriMatcher.match(uri)) { GET_SINGLE_CONTACT -> MIME_TYPE_CONTACT ... } }
Java
// Sets up your provider's getType() method. public String getType(Uri uri) { ... switch (sUriMatcher.match(uri)) { case GET_SINGLE_CONTACT: return (MIME_TYPE_CONTACT); ... } }
クリップボードからコンテンツ URI を取得し、それを使用してデータを取得し貼り付ける方法については、コンテンツ URI からデータを貼り付けるをご覧ください。
データ ストリームをコピーする
大量のテキストデータやバイナリデータをストリームとしてコピー&ペーストすることができます。次のような形式のデータを使用できます。
- 実際のデバイス上に保存されているファイル。
- ソケットからのストリーム。
- プロバイダの基盤データベース システム内に保存されている大量のデータ。
データ ストリーム用のコンテンツ プロバイダは、Cursor
オブジェクトではなく、AssetFileDescriptor
などのファイル記述子オブジェクトを使用して、データへのアクセスを提供します。貼り付け側アプリは、このファイル記述子を使用してデータ ストリームを読み取ります。
プロバイダを使用してデータ ストリームをコピーするようにアプリをセットアップする手順は次のとおりです。
-
クリップボード上に配置するデータ ストリームのコンテンツ URI をセットアップします。次のような方法があります。
- URI に対してデータ ストリームの識別子をエンコードして(URI に対して識別子をエンコードするを参照)、識別子とそれに対応するストリーム名を格納するテーブルをプロバイダ内で保持します。
- 直接 URI に対してストリーム名をエンコードします。
- プロバイダから現在のストリームを常に返す一意の URI を使用します。この方法を使用する場合は、URI を通じて別のストリームをクリップボードにコピーするたびに、そのストリームをポイントするようにプロバイダを更新する必要があります。
- 提供する予定のデータ ストリームのタイプごとに MIME タイプを指定します。貼り付け側アプリがクリップボード上のデータを貼り付けられるかどうかを判断する際、この情報が必要になります。
-
ストリームのファイル記述子を返す
ContentProvider
メソッドの 1 つを実装します。コンテンツ URI に対して識別子をエンコードする場合、このメソッドを使用して、開くストリームを判断します。 - データ ストリームをクリップボードにコピーするには、コンテンツ URI を作成して、クリップボード上に配置します。
データ ストリームを貼り付ける場合、アプリは、クリップボードからクリップを取得して、URI を取得し、それを使用して、ストリームを開く ContentResolver
ファイル記述子メソッドを呼び出します。ContentResolver
メソッドは、対応する ContentProvider
メソッドを呼び出して、コンテンツ URI を渡します。プロバイダが、ファイル記述子を ContentResolver
メソッドに返します。その後、貼り付け側アプリがストリームからデータを読み取ります。
コンテンツ プロバイダにとって最も重要なファイル記述子メソッドのリストを以下に示します。それぞれに、対応する ContentResolver
メソッドが用意されており、メソッド名に文字列「Descriptor」が付加されます。たとえば、openAssetFile()
の ContentResolver
バージョンは、openAssetFileDescriptor()
になります。
-
openTypedAssetFile()
- このメソッドは、指定された MIME タイプがプロバイダによってサポートされている場合に限り、アセット ファイル記述子を返します。呼び出し元(貼り付け側アプリ)が、MIME タイプパターンを指定します。URI をクリップボードにコピーしたアプリのコンテンツ プロバイダは、その MIME タイプを提供できる場合は
AssetFileDescriptor
ファイル ハンドルを返し、提供できない場合は例外をスローします。このメソッドは、ファイルのサブセクションを処理します。このメソッドを使用することで、コンテンツ プロバイダがクリップボードにコピーしたアセットを読み取ることができます。
-
openAssetFile()
- このメソッドは、
openTypedAssetFile()
の汎用的な形式です。可能な MIME タイプのフィルタリングは行いませんが、ファイルのサブセクションを読み取ることができます。 -
openFile()
- このメソッドは、
openAssetFile()
の汎用的な形式です。ファイルのサブセクションを読み取ることはできません。
必要に応じて、ファイル記述子メソッドと一緒に openPipeHelper()
メソッドを使用できます。これにより、貼り付け側アプリは、パイプを使用してバックグラウンド スレッド内でストリーム データを読み取ることができるようになります。このメソッドを使用するには、ContentProvider.PipeDataWriter
インターフェースを実装する必要があります。この手法は、Note Pad サンプルアプリでも使用されています。NotePadProvider.java
の openTypedAssetFile()
メソッドをご覧ください。
効果的なコピー&ペースト機能を設計する
アプリにとって効果的なコピー&ペースト機能を設計するうえで、次の点に注意する必要があります。
- クリップボード上には常に 1 つのクリップしか存在できません。システム内のいずれかのアプリによって新しいコピー処理が行われると、以前のクリップは上書きされます。ユーザーは、アプリから離れた後、コピー操作を行ってから戻ってくることがあります。そのため、アプリ内でユーザーが以前コピーしたクリップをそのままクリップボードが保持していると想定することはできません。
-
クリップごとに複数の
ClipData.Item
オブジェクトを使用しているのは、単一の選択内容に対して複数の参照形式を使用するためではなく、複数の選択内容のコピー&ペーストをサポートするためです。通常は、クリップ内のClipData.Item
オブジェクトはすべて同じ形式にします。つまり、すべてのオブジェクトの形式をシンプル テキストか、コンテンツ URI か、Intent
で統一し、混合しないようにしてください。 -
データを提供するときに、さまざまな MIME 表現を指定できます。サポートする MIME タイプを
ClipDescription
に追加して、コンテンツ プロバイダ内でその MIME タイプを実装します。 -
クリップボードからデータを取得した後、アプリは、利用可能な MIME タイプをチェックし、使用できる MIME タイプがあった場合はどの MIME タイプを使用するのかを決定します。クリップボード上にクリップがあり、ユーザーが貼り付けをリクエストした場合でも、必ずしもアプリは貼り付けを行う必要はありません。対応可能な MIME タイプの場合は、貼り付けを行います。必要に応じて、
coerceToText()
を使用して、クリップボード上のデータをテキストに自動変換することもできます。利用可能な MIME タイプのうち、複数の MIME タイプをアプリがサポートしている場合、使用する MIME タイプをユーザーが選択できるように設定することもできます。