Android は、コピー&ペースト用の強力なクリップボード ベース フレームワークを備えています。テキスト文字列、複雑なデータ構造、テキストとバイナリのストリーム データ、アプリアセットなど、単純なデータ型と複雑なデータ型をサポートします。単純なテキストデータはクリップボードに直接保存されます。複雑なデータは参照として保存されます。この参照は、貼り付け側アプリがコンテンツ プロバイダによって解決されます。コピーと貼り付けは、アプリ内でも、フレームワークを実装するアプリ間でも機能します。
フレームワークの一部ではコンテンツ プロバイダを使用するため、このドキュメントでは、Android Content Provider API についてある程度の知識があることを前提としています。詳細については、コンテンツ プロバイダをご覧ください。
ユーザーはクリップボードにコンテンツをコピーする際にフィードバックを期待しているため、Android 13(API レベル 33)以降では、コピーと貼り付けの機能を提供するフレームワークに加えて、デフォルトの UI がユーザーに表示されます。この機能により、通知が重複して届く可能性があります。このエッジケースの詳細については、通知の重複を避けるをご覧ください。
![Android 13 のクリップボード通知を示すアニメーション](https://developer.android.google.cn/static/images/about/versions/13/new-copy-paste-UI.gif?authuser=6&hl=ja)
Android 12L(API レベル 32)以下でコピーする場合、ユーザーに手動でフィードバックを提供します。このドキュメントの推奨事項をご覧ください。
クリップボード フレームワーク
クリップボード フレームワークを使用する場合は、データをクリップ オブジェクトに配置してから、そのクリップ オブジェクトをシステムレベルのクリップボードに配置します。クリップ オブジェクトは、次の 3 つの形式のいずれかになります。
- テキスト
- テキスト文字列。文字列をクリップ オブジェクトに直接配置し、それをクリップボードに配置します。文字列を貼り付けるには、クリップボードからクリップ オブジェクトを取得して、その文字列をアプリのストレージにコピーします。
- URI
- 任意の形式の URI を表す
Uri
オブジェクト。主にコンテンツ プロバイダから複雑なデータをコピーする場合に使用します。データをコピーするには、Uri
オブジェクトをクリップ オブジェクト内に配置し、クリップ オブジェクトをクリップボード上に配置します。データを貼り付けるには、クリップ オブジェクトを取得してUri
オブジェクトを取得し、それをコンテンツ プロバイダなどのデータソースに解決して、ソースからアプリのストレージにデータをコピーします。 - インテント
Intent
。アプリケーションのショートカットのコピーがサポートされています。データをコピーするには、Intent
を作成してクリップ オブジェクト内に配置し、クリップ オブジェクトをクリップボード上に配置します。データを貼り付けるには、クリップ オブジェクトを取得して、Intent
オブジェクトをアプリのメモリ領域にコピーします。
クリップボードは、一度に 1 つのクリップ オブジェクトしか保持できません。アプリがクリップ オブジェクトをクリップボードに配置すると、以前のクリップ オブジェクトは表示されなくなります。
ユーザーがアプリケーションにデータを貼り付けられるようにする場合、すべてのタイプのデータを処理する必要はありません。ユーザーに貼り付けのオプションを提供する前に、クリップボードのデータを調べることができます。クリップ オブジェクトには、特定のデータ形式に加えて、使用可能な MIME タイプを示すメタデータも含まれています。このメタデータは、アプリがクリップボード データに対して有用な処理を行えるかどうかを判断するのに役立ちます。たとえば、主にテキストを処理するアプリの場合は、URI やインテントを含むクリップ オブジェクトを無視することができます。
また、クリップボード上のデータの形式に関係なく、ユーザーがテキストを貼り付けられるようにすることもできます。これを行うには、クリップボード データを強制的にテキスト表現に変換してから、そのテキストを貼り付けます。詳しくは、クリップボードをテキストに強制変換するをご覧ください。
クリップボード クラス
このセクションでは、クリップボード フレームワークで使用されるクラスについて説明します。
ClipboardManager
Android システム クリップボードは、グローバル ClipboardManager
クラスで表されます。このクラスを直接インスタンス化しないでください。代わりに、getSystemService(CLIPBOARD_SERVICE)
を呼び出してそのオブジェクトへの参照を取得します。
ClipData、ClipData.Item、ClipDescription
クリップボードにデータを追加するには、データの説明とデータ自体を含む ClipData
オブジェクトを作成します。クリップボードには、一度に 1 つの ClipData
が保持されます。ClipData
には、ClipDescription
オブジェクトと 1 つ以上の ClipData.Item
オブジェクトが含まれます。
ClipDescription
オブジェクトには、クリップに関するメタデータが含まれます。具体的には、クリップのデータに使用できる MIME タイプの配列が含まれます。また、Android 12(API レベル 31)以降では、オブジェクトに定型テキストが含まれているかどうかと、オブジェクトのテキストの種類に関する情報がメタデータに含まれます。クリップボードにクリップを配置すると、貼り付け側アプリがこの情報を利用できます。貼り付け側アプリは、クリップデータを処理できるかどうかを調べることができます。
ClipData.Item
オブジェクトには、テキスト、URI、インテント データのいずれかが含まれます。
- テキスト
CharSequence
。- URI
Uri
。通常はコンテンツ プロバイダの URI が含まれますが、任意の URI も使用できます。データを提供するアプリが、URI をクリップボードに配置します。データを貼り付けるアプリは、クリップボードから URI を取得し、それを使ってコンテンツ プロバイダなどのデータソースにアクセスし、データを取得します。- インテント
Intent
。このデータ型を使用すると、アプリのショートカットをクリップボードにコピーできます。ユーザーは、後でそのショートカットを別のアプリに貼り付けて使用できます。
1 つのクリップに複数の ClipData.Item
オブジェクトを追加できます。これにより、複数の選択内容を 1 つのクリップとしてコピーして貼り付けることができます。たとえば、ユーザーが一度に複数のアイテムを選択できるリスト ウィジェットがある場合、すべてのアイテムを一度にクリップボードにコピーできます。これを行うには、リストアイテムごとに個別の 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()
を使用して、Intent
オブジェクトからクリップを作成します。
クリップボード データをテキストに強制変換する
テキストのみを処理するアプリであっても、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
がIntent
の場合(つまり、getIntent()
が null でない場合)、coerceToText()
はそれをインテント URI に変換して返します。この表現は、Intent.toUri(URI_INTENT_SCHEME)
によって返されるものと同じです。
クリップボード フレームワークの概要を図 2 に示します。データをコピーするには、アプリで ClipData
オブジェクトを ClipboardManager
グローバル クリップボードに配置します。ClipData
には、1 つ以上の ClipData.Item
オブジェクトと 1 つの ClipDescription
オブジェクトが含まれます。データを貼り付ける場合、アプリは ClipData
を取得し、ClipDescription
から MIME タイプを取得し、ClipData.Item
または ClipData.Item
によって参照されるコンテンツ プロバイダからデータを取得します。
![コピー&ペースト フレームワークのブロック図を示す画像](https://developer.android.google.cn/static/images/ui/clipboard/copy_paste_framework.png?authuser=6&hl=ja)
クリップボードにコピー
データをクリップボードにコピーするには、グローバル ClipboardManager
オブジェクトに対するハンドルを取得し、ClipData
オブジェクトを作成して、ClipDescription
と 1 つ以上の ClipData.Item
オブジェクトを追加します。次に、完成した ClipData
オブジェクトを ClipboardManager
オブジェクトに追加します。これについては、次の手順で詳しく説明します。
- コンテンツ URI を使用してデータをコピーする場合は、コンテンツ プロバイダを設定します。
- システム クリップボードを取得します。
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 の場合
このスニペットは、レコード ID をプロバイダのコンテンツ URI にエンコードして URI を作成します。この手法の詳細については、URI に対して識別子をエンコードするをご覧ください。
Kotlin
// Creates a Uri using 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, used 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 using 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, used 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);
-
インテントの場合
次のスニペットは、アプリの
Intent
を作成してクリップ オブジェクトに格納しています。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);
クリップボードにコピーする際のフィードバックの提供
ユーザーは、アプリがコンテンツをクリップボードにコピーするときに、視覚的なフィードバックを期待します。これは、Android 13 以降では自動的に行われますが、以前のバージョンでは手動で実装する必要があります。
Android 13 以降では、クリップボードにコンテンツが追加されると、視覚的な確認メッセージが表示されます。この新しい通知機能により、次のことが可能になります。
- コンテンツが正常にコピーされたことをユーザーに知らせる。
- コピーされたコンテンツのプレビューを表示する。
![Android 13 のクリップボード通知を示すアニメーション](https://developer.android.google.cn/static/images/about/versions/13/new-copy-paste-UI.gif?authuser=6&hl=ja)
Android 12L(API レベル 32)以前では、ユーザーはコンテンツを正常にコピーしたのか、何をコピーしたのか確信が持てない場合があります。この機能により、コピー後にアプリに表示されるさまざまな通知が標準化され、ユーザーがクリップボードをより細かく制御できるようになります。
通知の重複を避ける
Android 12L(API レベル 32)以前では、コピー後に Toast
や Snackbar
などのウィジェットを使用して視覚的なアプリ内フィードバックを発行し、コピーが正常に完了したときにユーザーに通知することをおすすめします。
情報が重複して表示されないように、Android 13 以降では、アプリ内コピーの後に表示されるトーストやスナックバーを削除することを強くおすすめします。
![アプリ内コピーの後にスナックバーを表示する](https://developer.android.google.cn/static/images/about/versions/13/snackbar-overlap.png?authuser=6&hl=ja)
![アプリ内コピーの後にトーストを表示する](https://developer.android.google.cn/static/images/about/versions/13/toast-overlap.png?authuser=6&hl=ja)
実装方法の例を次に示します。
fun textCopyThenPost(textCopied:String) { val clipboardManager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager // When setting the clipboard text. clipboardManager.setPrimaryClip(ClipData.newPlainText ("", textCopied)) // Only show a toast for Android 12 and lower. if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) Toast.makeText(context, “Copied”, Toast.LENGTH_SHORT).show() }
機密コンテンツをクリップボードに追加する
アプリでパスワードやクレジット カード情報などの機密コンテンツをクリップボードにコピーできるようにする場合は、ClipboardManager.setPrimaryClip()
を呼び出す前に ClipData
の ClipDescription
にフラグを追加する必要があります。このフラグを追加すると、Android 13 以降ではコピーされたコンテンツの視覚的な確認に機密コンテンツが表示されなくなります。
![機密コンテンツのフラグを設定せずにコピーされたテキストのプレビュー](https://developer.android.google.cn/static/images/about/versions/13/sensitive-content-before.png?authuser=6&hl=ja)
![機密コンテンツのフラグを設定してコピーされたテキストのプレビュー](https://developer.android.google.cn/static/images/about/versions/13/sensitive-content-after.png?authuser=6&hl=ja)
機密コンテンツのフラグを設定するには、ClipDescription
にブール型のエクストラを追加します。対象 API レベルに関係なく、すべてのアプリでこれを行う必要があります。
// If your app is compiled with the API level 33 SDK or higher. clipData.apply { description.extras = PersistableBundle().apply { putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true) } } // If your app is compiled with a lower SDK. clipData.apply { description.extras = PersistableBundle().apply { putBoolean("android.content.extra.IS_SENSITIVE", true) } }
クリップボードから貼り付け
前述のように、クリップボードからデータを貼り付けるには、グローバル クリップボード オブジェクトを取得し、クリップ オブジェクトを取得して、データを確認し、可能であればクリップ オブジェクトから独自のストレージにデータをコピーします。このセクションでは、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 whether you can handle the data. pasteItem.isEnabled = when { !clipboard.hasPrimaryClip() -> { false } !(clipboard.primaryClipDescription.hasMimeType(MIMETYPE_TEXT_PLAIN)) -> { // Disables the paste menu item, since the clipboard has data but it // isn't plain text. false } else -> { // 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 whether you can handle the data. if (!(clipboard.hasPrimaryClip())) { pasteItem.setEnabled(false); } else if (!(clipboard.getPrimaryClipDescription().hasMimeType(MIMETYPE_TEXT_PLAIN))) { // Disables the paste menu item, since the clipboard has data but // it isn't plain text. pasteItem.setEnabled(false); } else { // 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() doesn't 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 doesn't 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 isn't presented here. pasteData = resolveUri(pasteUri) true } else { // Something is wrong. The MIME type was plain text, but the // clipboard doesn't contain 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 doesn't 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 isn't presented here. pasteData = resolveUri(Uri); return true; } else { // Something is wrong. The MIME type is plain text, but the // clipboard doesn't contain text or a Uri. Report an error. Log.e(TAG, "Clipboard contains an invalid data type"); return false; } }
コンテンツ URI からデータを貼り付ける
ClipData.Item
オブジェクトにコンテンツ URI が含まれていて、その MIME タイプのいずれかを処理できると判断した場合は、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 isn't 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 varies according to the format of the data model. } // Kotlin `use` automatically closes the Cursor. } } } }
Java
// If the return value isn't 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 varies according to the format of the data model. } } // Close the Cursor. pasteCursor.close(); } } } }
インテントを貼り付ける
インテントを貼り付けるには、まずグローバル クリップボードを取得します。ClipData.Item
オブジェクトを調べて、Intent
が含まれているかどうかを確認します。次に、getIntent()
を呼び出して、インテントを独自のストレージにコピーします。この手順を行うスニペットは以下のとおりです。
Kotlin
// Gets a handle to the Clipboard Manager. val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager // Checks whether the clip item contains an Intent by testing whether // 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 // you expect an Intent to be on the clipboard. }
Java
// Gets a handle to the Clipboard Manager. ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); // Checks whether the clip item contains an Intent, by testing whether // 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 // you expect an Intent to be on the clipboard. }
アプリがクリップボード データにアクセスしたときに表示されるシステム通知
Android 12(API レベル 31)以降では、アプリが getPrimaryClip()
を呼び出すと、通常、システムによりトースト メッセージが表示されます。メッセージ内のテキストの形式は次のとおりです。
APP pasted from your clipboard
アプリが次のいずれかを行う場合、トースト メッセージは表示されません。
- 自分のアプリから
ClipData
にアクセスします。 - 特定のアプリから
ClipData
に繰り返しアクセスする。トーストは、アプリがそのアプリのデータに初めてアクセスしたときにのみ表示されます。 getPrimaryClip()
ではなくgetPrimaryClipDescription()
を呼び出すなどして、クリップ オブジェクトのメタデータを取得する。
コンテンツ プロバイダを使用して複雑なデータをコピーする
コンテンツ プロバイダは、データベース レコードやファイル ストリームなどの複雑なデータのコピーをサポートしています。データをコピーするには、クリップボードにコンテンツ 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 を使用できます。クエリに応じて、プロバイダは現在に含まれているデータを返します。
データ構造をコピーする
複雑なデータをコピー&ペーストするためのコンテンツ プロバイダを、ContentProvider
コンポーネントのサブクラスとしてセットアップします。指定するレコードを正確にポイントするように、クリップボード上に配置した URI をエンコードします。また、アプリケーションの既存の状態も考慮してください。
- すでにコンテンツ プロバイダを使用している場合は、その機能に追加できます。データを貼り付けるアプリケーションからの URI を処理するには、
query()
メソッドを変更するだけでよい場合があります。「コピー」の 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) // Sets 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); // Sets 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 -> { // Queries and returns the contact for the requested name. Decodes // the incoming URI, queries the data model based on the last name, // and returns 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: // Queries and returns the contact for the requested name. Decodes the // incoming URI, queries the data model based on the last name, and // returns 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
メソッドのいずれかを実装します。コンテンツ 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
インターフェースを実装します。
効果的なコピー&ペースト機能を設計する
アプリの効果的なコピー&ペースト機能を設計するには、以下の点に留意してください。
- クリップボード上には常に 1 つのクリップしか存在できません。システム内のいずれかのアプリが新しいコピー操作を行うと、以前のクリップが上書きされます。ユーザーがアプリから離れてコピーする前にアプリから移動する可能性があるため、ユーザーがアプリ内で以前にコピーしたクリップがクリップボードに含まれていると想定することはできません。
-
クリップごとに複数の
ClipData.Item
オブジェクトを使用する目的は、1 つの選択内容に対して異なる形式の参照ではなく、複数の選択内容のコピーと貼り付けをサポートすることです。通常は、クリップ内のClipData.Item
オブジェクトはすべて同じ形式にする必要があります。つまり、単純なテキスト、コンテンツ URI、Intent
のいずれかであり、これらを混在させないでください。 -
データを提供するときに、さまざまな MIME 表現を指定できます。サポートする MIME タイプを
ClipDescription
に追加してから、その MIME タイプをコンテンツ プロバイダに実装します。 -
クリップボードからデータを取得した後、アプリは、利用可能な MIME タイプをチェックし、使用できる MIME タイプがあった場合はどの MIME タイプを使用するのかを決定します。クリップボードにクリップがあり、ユーザーが貼り付けをリクエストした場合でも、アプリで貼り付けを行う必要はありません。MIME タイプが対応している場合は貼り付けてください。
coerceToText()
を使用して、クリップボード上のデータをテキストに強制変換することもできます。利用可能な複数の MIME タイプをアプリがサポートしている場合は、ユーザーが使用する MIME タイプを選択できます。