복사하여 붙여넣기

Android는 복사하여 붙여넣기를 지원하는 강력한 클립보드 기반 프레임워크를 제공합니다. 텍스트 문자열, 복잡한 데이터 구조, 텍스트 및 바이너리 스트림 데이터, 애플리케이션 애셋을 포함하여 단순한 데이터 유형과 복잡한 데이터 유형을 모두 지원합니다. 단순한 텍스트 데이터는 클립보드에 직접 저장되며 복잡한 데이터는 붙여넣기 애플리케이션이 콘텐츠 제공업체를 통해 확인하는 참조로 저장됩니다. 복사 및 붙여넣기는 애플리케이션 내에서뿐 아니라 프레임워크를 구현하는 여러 애플리케이션 사이에서 모두 작동합니다.

프레임워크의 일부에서 콘텐츠 제공자를 사용하므로 이 문서에서는 콘텐츠 제공자에 설명된 Android Content Provider API를 어느 정도 알고 있다고 가정합니다.

사용자는 클립보드에 콘텐츠를 복사할 때 의견을 기대하므로 Android는 복사 및 붙여넣기를 지원하는 프레임워크 외에도 Android 13 (API 수준 33) 이상에서 복사할 때 사용자에게 기본 UI를 표시합니다. 이 기능으로 인해 알림이 중복으로 표시될 수 있습니다. 이 특이 사례에 관한 자세한 내용은 중복 알림 방지 섹션을 참고하세요.

Android 13 클립보드 알림을 보여주는 애니메이션
그림 1. Android 13 이상에서 콘텐츠가 클립보드에 들어갈 때 표시되는 UI

Android 12L (API 수준 32) 이하에서 복사할 때 사용자에게 수동으로 피드백을 제공합니다. 이 문서의 권장사항을 참고하세요.

클립보드 프레임워크

클립보드 프레임워크를 사용할 때 데이터를 클립 객체에 넣은 다음 클립 객체를 시스템 전체 클립보드에 배치합니다. 클립 객체는 다음 세 가지 형식 중 하나일 수 있습니다.

텍스트
텍스트 문자열입니다. 문자열을 클립 객체에 직접 넣은 다음 클립보드에 배치합니다. 문자열을 붙여넣으려면 클립보드에서 클립 객체를 가져와서 문자열을 애플리케이션의 저장소에 복사합니다.
URI
모든 URI 형식을 나타내는 Uri 객체입니다. 주로 콘텐츠 제공업체의 복잡한 데이터를 복사하기 위한 것입니다. 데이터를 복사하려면 Uri 객체를 클립 객체에 넣고 클립 객체를 클립보드에 배치합니다. 데이터를 붙여넣으려면 클립 객체와 Uri 객체를 가져와서 콘텐츠 제공업체와 같은 데이터 소스로 확인하고 소스에서 애플리케이션의 저장소로 데이터를 복사합니다.
인텐트
Intent입니다. 애플리케이션 바로가기 복사를 지원합니다. 데이터를 복사하려면 Intent를 만들고 클립 객체에 넣은 다음 클립 객체를 클립보드에 배치합니다. 데이터를 붙여넣으려면 클립 객체를 가져온 다음 Intent 객체를 애플리케이션의 메모리 영역으로 복사합니다.

클립보드는 한 번에 클립 객체 하나만 보유합니다. 애플리케이션에서 클립 객체를 클립보드에 배치하면 이전 클립 객체는 사라집니다.

사용자가 애플리케이션에 데이터를 붙여넣을 수 있도록 하려면 모든 유형의 데이터를 처리할 필요가 없습니다. 클립보드의 데이터를 검사한 후 사용자에게 붙여넣기 옵션을 제공할 수 있습니다. 클립 객체에는 특정 데이터 형식 외에도 사용 가능한 MIME 유형을 알려주는 메타데이터도 포함됩니다. 이 메타데이터는 애플리케이션이 클립보드 데이터로 유용한 작업을 실행할 수 있는지 판단하는 데 도움이 됩니다. 예를 들어 텍스트를 주로 처리하는 애플리케이션이 있다면 URI 또는 인텐트가 포함된 클립 객체를 무시하는 것이 좋습니다.

사용자가 클립보드의 데이터 형식에 상관없이 텍스트를 붙여넣도록 허용할 수도 있습니다. 이렇게 하려면 클립보드 데이터를 텍스트 표현으로 강제 변환한 다음 이 텍스트를 붙여넣습니다. 자세한 내용은 클립보드를 텍스트로 강제 변환 섹션을 참고하세요.

클립보드 클래스

이 섹션에서는 클립보드 프레임워크에서 사용하는 클래스를 설명합니다.

ClipboardManager

Android 시스템 클립보드는 전역 ClipboardManager 클래스로 표시됩니다. 이 클래스를 직접 인스턴스화하지 마세요. 대신 getSystemService(CLIPBOARD_SERVICE)를 호출하여 참조를 가져옵니다.

ClipData, ClipData.Item, ClipDescription

데이터를 클립보드에 추가하려면 데이터 설명과 데이터 자체를 포함하는 ClipData 객체를 만드세요. 클립보드는 한 번에 ClipData 하나만 보유합니다. ClipData에는 ClipDescription 객체와 하나 이상의 ClipData.Item 객체가 포함됩니다.

ClipDescription 객체에는 클립에 관한 메타데이터가 포함됩니다. 특히 클립 데이터에 사용할 수 있는 MIME 유형의 배열이 포함됩니다. 또한 Android 12 (API 수준 31) 이상에서는 메타데이터에 양식화된 텍스트가 객체에 포함되어 있는지 여부와 객체의 텍스트 유형에 관한 정보가 포함되어 있습니다. 클립보드에 클립을 배치하면 이 정보를 붙여넣기 애플리케이션에서 사용할 수 있습니다. 붙여넣기 애플리케이션은 이 정보를 검사하여 클립 데이터를 처리할 수 있는지 확인할 수 있습니다.

ClipData.Item 객체에는 텍스트, URI 또는 인텐트 데이터가 포함됩니다.

텍스트
CharSequence입니다.
URI
Uri입니다. 모든 URI가 허용되지만 일반적으로 콘텐츠 제공업체 URI가 포함됩니다. 데이터를 제공하는 애플리케이션은 URI를 클립보드에 배치합니다. 데이터를 붙여넣으려는 애플리케이션은 클립보드에서 URI를 가져와서 콘텐츠 제공업체 또는 다른 데이터 소스에 액세스하여 데이터를 검색하는 데 사용합니다.
인텐트
Intent입니다. 이 데이터 유형을 사용하면 애플리케이션 바로가기를 클립보드에 복사할 수 있습니다. 그러면 사용자가 바로가기를 애플리케이션에 붙여넣어 나중에 사용할 수 있습니다.

클립에 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에 저장합니다. content: URI가 아닌 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()에서 반환한 것과 동일합니다.
인텐트
ClipData.ItemIntent인 경우(즉, getIntent()가 null이 아닌 경우) coerceToText()는 이를 인텐트 URI로 변환하여 반환합니다. 표시는 Intent.toUri(URI_INTENT_SCHEME)에서 반환한 것과 동일합니다.

클립보드 프레임워크가 그림 2에 요약되어 있습니다. 데이터를 복사하기 위해 애플리케이션은 ClipData 객체를 ClipboardManager 전역 클립보드에 배치합니다. ClipData에는 ClipData.Item 객체 하나 이상과 ClipDescription 객체 하나가 포함됩니다. 데이터를 붙여넣기 위해 애플리케이션은 ClipData를 가져오고 ClipDescription에서 MIME 유형을 가져오며 ClipData.Item 또는 ClipData.Item에서 참조하는 콘텐츠 제공업체에서 데이터를 가져옵니다.

복사하여 붙여넣기 프레임워크의 블록 다이어그램을 보여주는 이미지
그림 2. Android 클립보드 프레임워크

클립보드에 복사

클립보드에 데이터를 복사하려면 전역 ClipboardManager 객체의 핸들을 가져와서 ClipData 객체를 만들고 ClipDescription 및 하나 이상의 ClipData.Item 객체를 추가하세요. 그런 다음 완성된 ClipData 객체를 ClipboardManager 객체에 추가합니다. 자세한 내용은 다음 절차를 참고하세요.

  1. 콘텐츠 URI를 사용하여 데이터를 복사하려면 콘텐츠 제공자를 설정하세요.
  2. 시스템 클립보드 가져오기

    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
        }
    }

    자바

    ...
    // If the user selects copy.
    case R.id.menu_copy:
    
    // Gets a handle to the clipboard service.
    ClipboardManager clipboard = (ClipboardManager)
            getSystemService(Context.CLIPBOARD_SERVICE);
  3. 새로운 ClipData 객체로 데이터 복사하기

    • 텍스트의 경우

      Kotlin

      // Creates a new text clip to put on the clipboard.
      val clip: ClipData = ClipData.newPlainText("simple text", "Hello, World!")

      자바

      // 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)

      자바

      // 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)

      자바

      // 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);
  4. 새로운 클립 객체를 클립보드에 배치합니다.

    Kotlin

    // Set the clipboard's primary clip.
    clipboard.setPrimaryClip(clip)

    자바

    // Set the clipboard's primary clip.
    clipboard.setPrimaryClip(clip);

클립보드에 복사할 때 의견 제공

사용자는 앱이 콘텐츠를 클립보드에 복사할 때 시각적 피드백을 기대합니다. 이는 Android 13 이상에서는 자동으로 실행되지만 이전 버전에서는 수동으로 구현해야 합니다.

Android 13부터 시스템에서는 콘텐츠가 클립보드에 추가될 때 표준 시각적 확인 메시지를 표시합니다. 새 확인 메시지를 통해 다음 작업이 실행됩니다.

  • 콘텐츠가 복사되었는지 확인합니다.
  • 복사된 콘텐츠의 미리보기를 제공합니다.

Android 13 클립보드 알림을 보여주는 애니메이션
그림 3. Android 13 이상에서 콘텐츠가 클립보드에 들어갈 때 표시되는 UI

Android 12L (API 수준 32) 이하에서는 사용자가 콘텐츠를 성공적으로 복사했는지 또는 복사한 내용을 잘 모르는 경우가 있습니다. 이 기능은 복사 후 앱에서 표시하는 다양한 알림을 표준화하고 사용자가 클립보드를 더 세부적으로 제어할 수 있도록 합니다.

중복 알림 피하기

Android 12L (API 수준 32) 이하에서는 복사 후 Toast 또는 Snackbar와 같은 위젯을 사용하여 시각적 인앱 의견을 표시하여 사용자가 복사한 내용을 알리는 것이 좋습니다.

정보가 중복으로 표시되지 않도록 하려면 Android 13 이상에서는 인앱 사본 뒤에 표시되는 토스트 또는 스낵바를 모두 삭제하는 것이 좋습니다.

인앱 사본 뒤에 스낵바 게시
그림 4. Android 13에서 사본 확인 Snackbar를 표시하면 사용자에게 중복 메시지가 표시됩니다.
인앱 사본 뒤에 토스트 메시지 게시
그림 5. 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()을 호출하기 전에 ClipDataClipDescription에 플래그를 추가해야 합니다. 이 플래그를 추가하면 Android 13 이상에서 복사된 콘텐츠의 시각적 확인 메시지에 민감한 콘텐츠가 표시되지 않습니다.

민감한 콘텐츠를 신고하지 않는 복사된 텍스트 미리보기
그림 6. 민감한 콘텐츠 플래그가 없는 복사된 텍스트 미리보기
민감한 콘텐츠를 신고하는 복사된 텍스트 미리보기
그림 7. 민감한 콘텐츠 플래그가 있는 복사된 텍스트 미리보기

민감한 콘텐츠를 신고하려면 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)
    }
}

클립보드에서 붙여넣기

앞서 설명한 것처럼 전역 클립보드 객체와 클립 객체를 가져와서 데이터를 살펴본 다음 가능하다면 클립 객체의 데이터를 자체 저장소에 복사하여 클립보드에서 데이터를 붙여넣습니다. 이 섹션에서는 세 가지 클립보드 데이터 형식을 붙여넣는 방법을 자세히 설명합니다.

일반 텍스트 붙여넣기

일반 텍스트를 붙여넣으려면 전역 클립보드를 가져와서 일반 텍스트를 반환할 수 있는지 확인합니다. 그런 다음 다음 절차에 설명된 대로 클립 객체를 가져와서 getText()를 사용하여 텍스트를 자체 저장소에 복사합니다.

  1. getSystemService(CLIPBOARD_SERVICE)를 사용하여 전역 ClipboardManager 객체를 가져옵니다. 전역 변수를 선언하여 붙여넣은 텍스트도 포함합니다.

    Kotlin

    var clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
    var pasteData: String = ""

    자바

    ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
    String pasteData = "";
  2. 현재 활동에서 '붙여넣기' 옵션을 사용 설정 또는 사용 중지해야 하는지 확인합니다. 클립보드에 클립이 포함되어 있는지 클립이 표시하는 데이터 유형을 처리할 수 있는지 확인합니다.

    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
        }
    }

    자바

    // 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);
    }
  3. 클립보드에서 데이터를 복사합니다. 코드의 이 지점은 '붙여넣기' 메뉴 항목이 사용 설정된 경우에만 도달할 수 있으므로 클립보드에 일반 텍스트가 포함되어 있다고 가정할 수 있습니다. 텍스트 문자열이나 일반 텍스트를 가리키는 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
                }
            }
        }
    }

    자바

    // 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 유형이 제공업체에서 제공되는지 확인합니다.

  1. 전역 변수를 선언하여 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"

    자바

    // 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";
  2. 전역 클립보드 가져오기 또한 콘텐츠 리졸버를 가져와서 콘텐츠 제공업체에 액세스할 수 있습니다.

    Kotlin

    // Gets a handle to the Clipboard Manager.
    val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
    
    // Gets a content resolver instance.
    val cr = contentResolver

    자바

    // Gets a handle to the Clipboard Manager.
    ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
    
    // Gets a content resolver instance.
    ContentResolver cr = getContentResolver();
  3. 클립보드에서 기본 클립을 가져오고 그 콘텐츠를 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

    자바

    // 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();
  4. 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)

    자바

        // If the clipboard contains a URI reference...
        if (pasteUri != null) {
    
            // ...is this a content URI?
            String uriMimeType = cr.getType(pasteUri);
  5. 콘텐츠 제공업체가 애플리케이션이 이해하는 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.
                }
            }
        }
    }

    자바

            // 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.
}

자바

// 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)

자바

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를 만들어 복사 경로와 복사하려는 정확한 레코드의 포인터를 포함하는 클립보드에 배치할 수 있습니다.
getType()
이 메서드는 복사하려는 데이터의 MIME 유형을 반환해야 합니다. newUri() 메서드는 getType()를 호출하여 MIME 유형을 새 ClipData 객체에 넣습니다.

복잡한 데이터의 MIME 유형은 콘텐츠 제공자에 설명되어 있습니다.

insert() 또는 update()와 같은 다른 콘텐츠 제공업체 메서드를 보유할 필요는 없습니다. 붙여넣기 애플리케이션은 지원되는 MIME 유형만 가져와서 제공업체의 데이터를 복사하면 됩니다. 이러한 메서드가 이미 있다면 복사 작업을 방해하지 않습니다.

다음 스니펫은 애플리케이션을 설정하여 복잡한 데이터를 복사하는 방법을 보여줍니다.

  1. 애플리케이션의 전역 상수에서 데이터를 복사하는 데 사용하는 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"

    자바

    // 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";
  2. 사용자가 데이터를 복사하는 활동에서 코드를 설정하여 데이터를 클립보드에 복사합니다. 복사 요청에 대한 응답으로 클립보드에 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)
        }
    }

    자바

    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);
  3. 콘텐츠 제공업체의 전역 범위에서 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() {
        ...
    }

    자바

    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);
  4. 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.
            }
        }
        ...
    }

    자바

    // 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.
        ...
    }
  5. getType() 메서드를 설정하여 복사된 데이터에 적절한 MIME 유형을 반환합니다.

    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
            ...
        }
    }

    자바

    // 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와 같은 파일 설명자 객체를 사용하여 데이터에 액세스할 수 있도록 합니다. 붙여넣기 애플리케이션은 이 파일 설명자를 사용하여 데이터 스트림을 읽습니다.

애플리케이션을 설정하여 제공업체와 함께 데이터 스트림을 복사하려면 다음 단계를 따르세요.

  1. 클립보드에 배치하는 데이터 스트림의 콘텐츠 URI를 설정합니다. 이 작업 실행의 옵션에는 다음이 포함됩니다.
    • URI에 식별자 인코딩 섹션에 설명된 대로 데이터 스트림의 식별자를 URI에 인코딩한 다음 식별자와 해당하는 스트림 이름이 포함된 테이블을 제공업체에 유지합니다.
    • 스트림 이름을 URI에 직접 인코딩합니다.
    • 항상 제공업체의 현재 스트림을 반환하는 고유한 URI를 사용합니다. 이 옵션을 사용하는 경우 URI를 사용하여 클립보드에 스트림을 복사할 때마다 제공업체를 업데이트하여 다른 스트림을 가리키도록 해야 합니다.
  2. 제공하려는 각 데이터 스트림 유형에 MIME 유형을 제공합니다. 붙여넣기 애플리케이션에는 데이터를 클립보드에 붙여넣을 수 있는지 확인하기 위해 이 정보가 필요합니다.
  3. 스트림의 파일 설명자를 반환하는 ContentProvider 메서드 중 하나를 구현합니다. 콘텐츠 URI에 식별자를 인코딩한다면 이 메서드를 사용하여 어떤 스트림을 열 것인지 확인합니다.
  4. 데이터 스트림을 클립보드에 복사하려면 콘텐츠 URI를 구성하여 클립보드에 배치합니다.

데이터 스트림을 붙여넣으려면 애플리케이션은 클립보드에서 클립을 가져오고 URI를 가져와서 스트림을 여는 ContentResolver 파일 설명자 메서드를 호출하는 데 사용합니다. ContentResolver 메서드는 상응하는 ContentProvider 메서드를 호출하여 콘텐츠 URI를 전달합니다. 제공업체는 파일 설명자를 ContentResolver 메서드에 반환합니다. 그러면 붙여넣기 애플리케이션은 스트림에서 데이터를 읽어야 합니다.

다음 목록은 콘텐츠 제공업체의 가장 중요한 파일 설명자 메서드를 보여줍니다. 각각에는 메서드 이름에 추가된 문자열 '설명어'와 함께 상응하는 ContentResolver 메서드가 있습니다. 예를 들어 openAssetFile()ContentResolver 아날로그는 openAssetFileDescriptor()입니다.

openTypedAssetFile()

이 메서드는 제공된 MIME 유형을 제공업체에서 지원하는 경우에만 애셋 파일 설명자를 반환합니다. 호출자(붙여넣기를 실행하는 애플리케이션)는 MIME 유형 패턴을 제공합니다. URI를 클립보드에 복사하는 애플리케이션의 콘텐츠 제공업체는 MIME 유형을 제공할 수 있는 경우 AssetFileDescriptor 파일 핸들을 반환하고 제공할 수 없는 경우 예외를 발생시킵니다.

이 메서드는 파일의 하위 섹션을 처리합니다. 이 메서드를 사용하여 콘텐츠 제공업체가 클립보드에 복사한 애셋을 읽을 수 있습니다.

openAssetFile()
이 메서드는 더 일반적인 형식의 openTypedAssetFile()입니다. 허용된 MIME 유형은 필터링하지 않지만 파일의 하위 섹션은 읽을 수 있습니다.
openFile()
더 일반적인 형식의 openAssetFile()입니다. 파일의 하위 섹션을 읽을 수 없습니다.

파일 설명자 메서드와 함께 선택적으로 openPipeHelper() 메서드를 사용할 수 있습니다. 이렇게 하면 붙여넣기 애플리케이션이 파이프를 사용하여 백그라운드 스레드에서 스트림 데이터를 읽을 수 있습니다. 이 메서드를 사용하려면 ContentProvider.PipeDataWriter 인터페이스를 구현합니다.

효과적인 복사 및 붙여넣기 기능 디자인

애플리케이션에 효과적인 복사 및 붙여넣기 기능을 디자인하려면 다음 사항에 유의하세요.

  • 언제나 클립보드에는 클립이 하나만 있습니다. 시스템의 모든 애플리케이션에서 실행하는 새로운 복사 작업은 이전 클립을 덮어씁니다. 사용자가 애플리케이션에서 이동하여 돌아오기 전에 복사할 수 있으므로 클립보드에 사용자가 개발자의 애플리케이션에서 이전에 복사한 클립이 포함되어 있다고 가정할 수 없습니다.
  • 클립당 여러 ClipData.Item 객체의 용도는 다양한 형식의 단일 선택 참조가 아니라 여러 선택의 복사 및 붙여넣기를 지원하는 것입니다. 일반적으로 클립의 모든 ClipData.Item 객체가 동일한 형식이어야 합니다. 즉, 모두 단순하게 텍스트나 콘텐츠 URI, Intent여야 하고 혼합되어서는 안 됩니다.
  • 데이터를 제공할 때 다양한 MIME 표시를 제공할 수 있습니다. 지원하는 MIME 유형을 ClipDescription에 추가하고 콘텐츠 제공업체에서 MIME 유형을 구현하세요.
  • 클립보드에서 데이터를 가져올 때 애플리케이션은 사용 가능한 MIME 유형을 확인하고 사용할 MIME 유형을 판단해야 합니다. 클립보드에 클립이 있고 사용자가 붙여넣기를 요청하더라도 애플리케이션은 붙여넣기를 실행할 필요가 없습니다. MIME 유형이 호환되는 경우 붙여넣기를 실행합니다. coerceToText()를 사용하여 클립보드의 데이터를 텍스트로 강제 변환할 수 있습니다. 애플리케이션이 사용 가능한 MIME 유형을 둘 이상 지원하는 경우 사용자가 사용할 유형을 선택할 수 있습니다.