Android は、コピー&ペースト用の強力なクリップボード ベース フレームワークを備えています。このフレームワークは、テキスト文字列、複雑なデータ構造、テキストおよびバイナリのストリーム データ、アプリアセットなど、シンプルなデータ型と複雑なデータ型の両方をサポートします。シンプルなテキストデータはクリップボード内に直接保存されます。複雑なデータは参照として保存され、貼り付け側アプリがコンテンツ プロバイダを使用して解決します。コピー&ペーストは、単一のアプリ内でも、フレームワークを実装している複数のアプリ間でも機能します。
フレームワークの一部でコンテンツ プロバイダが使用されるため、このドキュメントは、ある程度 Android Content Provider API に精通していることを前提としています。詳細については、コンテンツ プロバイダをご覧ください。
ユーザーがクリップボードにコンテンツをコピーする際にはフィードバックが期待されるため、Android 13(API レベル 33)以降では、コピー&ペーストを強化するフレームワークに加えて、デフォルトの UI がユーザーに表示されます。この機能により、通知が重複するリスクがあります。このエッジケースについて詳しくは、重複する通知を回避するをご覧ください。
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
オブジェクトを追加できます。これにより、複数の選択内容を単一のクリップとしてコピー&ペーストすることができます。たとえば、一度に複数のアイテムを選択できるリスト ウィジェットがある場合に、すべてのアイテムを一度にクリップボードにコピーすることができます。そのためには、リストアイテムごとに個別の 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
が参照しているコンテンツ プロバイダから、データを取得します。
クリップボードにコピーする
クリップボードにデータをコピーするには、グローバル ClipboardManager
オブジェクトのハンドルを取得して、ClipData
オブジェクトを作成し、1 つの 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 の場合
下記のスニペットは、プロバイダのコンテンツ URI に対してレコード ID をエンコードすることによって 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 12L(API レベル 32)以前では、コンテンツが正常にコピーされたかどうか、また何をコピーしたかをユーザーが確認できない場合があります。この機能により、コピー後にアプリに表示されるさまざまな通知が標準化され、ユーザーがクリップボードを細かく制御できるようになります。
通知の重複を避ける
Android 12L(API レベル 32)以前の場合、コピー後に Toast
や Snackbar
などのウィジェットを使用して、視覚的なアプリ内フィードバックを表示して、正常にコピーされたことをユーザーに知らせることをおすすめします。
情報が重複して表示されないように、Android 13 以降では、アプリ内コピーの後に表示されるトーストやスナックバーは削除することを強くおすすめします。
実装例を次に示します。
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 以降で、コピーされたコンテンツの視覚的確認に機密コンテンツが表示されなくなります。
機密コンテンツのフラグを設定するには、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 タイプの 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 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 をエンコードします。また、アプリの既存の状態についても考慮してください。
- すでにコンテンツ プロバイダを使用している場合は、その機能に追加できます。
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) // 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
メソッドの 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
インターフェースを実装します。
効果的なコピー&ペースト機能を設計する
アプリにとって効果的なコピー&ペースト機能を設計するうえで、次の点に注意する必要があります。
- クリップボード上には常に 1 つのクリップしか存在できません。システム内のいずれかのアプリによって新しいコピー処理が行われると、以前のクリップは上書きされます。ユーザーは、アプリから離れた後、コピー操作を行ってから戻ってくることがあります。そのため、アプリ内でユーザーが以前コピーしたクリップをそのままクリップボードが保持していると想定することはできません。
-
クリップごとに複数の
ClipData.Item
オブジェクトを使用しているのは、単一の選択内容に対して複数の参照形式を使用するためではなく、複数の選択内容のコピー&ペーストをサポートするためです。通常は、クリップ内のClipData.Item
オブジェクトはすべて同じ形式にします。つまり、すべてのオブジェクトの形式をシンプル テキストか、コンテンツ URI か、Intent
で統一し、混合しないようにしてください。 -
データを提供するときに、さまざまな MIME 表現を指定できます。サポートする MIME タイプを
ClipDescription
に追加して、コンテンツ プロバイダ内でその MIME タイプを実装します。 -
クリップボードからデータを取得した後、アプリは、利用可能な MIME タイプをチェックし、使用できる MIME タイプがあった場合はどの MIME タイプを使用するのかを決定します。クリップボード上にクリップがあり、ユーザーが貼り付けをリクエストした場合でも、必ずしもアプリは貼り付けを行う必要はありません。MIME タイプが対応している場合は、貼り付けを行います。
coerceToText()
を使用して、クリップボード上のデータをテキストに自動変換することもできます。利用可能な MIME タイプのうち、複数の MIME タイプをアプリがサポートしている場合、使用する MIME タイプをユーザーが選択できるように設定することもできます。