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
オブジェクトを追加できます。これにより、ユーザーは複数の選択項目を 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
で参照されるコンテンツ プロバイダからデータを取得します。
クリップボードにコピー
クリップボードにデータをコピーするには、グローバル 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 の場合
このスニペットは、プロバイダのコンテンツ 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 タイプのいずれかを処理できると判断した場合は、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 から識別子を取得し、それを使用してデータを取得できます。貼り付け側アプリは、この ID の存在を認識する必要はありません。クリップボードから「参照」(URI と ID)を取得し、コンテンツ プロバイダに渡してデータを取得するだけです。
識別子は通常、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 を検出し、コピーして貼り付け専用のコードで処理できます。
すでにコンテンツ プロバイダ、内部データベース、または内部テーブルを使用してデータを整理している場合は、通常このエンコード手法を使用します。この場合、コピーするデータが複数あり、各部分に一意の ID があると考えられます。貼り付け側アプリからのクエリに応じて、その識別子でデータを検索し、返すことができます。
複数のデータがない場合、識別子をエンコードする必要はありません。プロバイダに固有の 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
メソッドの 1 つを実装します。コンテンツ URI で識別子をエンコードする場合は、このメソッドを使用して、開くストリームを決定します。 - データ ストリームをクリップボードにコピーするには、コンテンツ 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 タイプをアプリがサポートしている場合、使用する MIME タイプをユーザーが選択できるようにします。