The following is the first few sections of a chapter from The Busy Coder's Guide to Android Development, plus headings for the remaining major sections, to give you an idea about the content of the chapter.


Working with the Clipboard

Being able to copy and paste is something that mobile device users seem to want almost as much as their desktop brethren. Most of the time, we think of this as copying and pasting text, but one could copy and paste other things, such as Uri values pointing to more elaborate forms of content.

In this chapter, we will explore how to work with the modern clipboard APIs. Here, “modern” refers to android.content.ClipboardManager. Android 1.x and 2.x used android.text.ClipboardManager, which still exists in the Android SDK for backwards-compatibility reasons. However, most modern development should use android.content.ClipboardManager.

Prerequisites

Understanding this chapter requires that you have read the core chapters of this book.

Working with the Clipboard

ClipboardManager can be obtained via a call to getSystemService() on any handy Context.

The old Android 1.x/2.x API was dominated by three methods, all focused on plain text:

Those methods still exist, but they have been deprecated as of API Level 11.

Their replacements are:

Here, the “something” winds up being in the form of ClipData objects, which can hold:

  1. plain text
  2. a Uri (e.g., to a piece of music)
  3. an Intent

The Uri means that you can put anything on the clipboard that can be referenced by a Uri… and if there is nothing in Android that lets you reference some data via a Uri, you can invent your own content provider to handle that chore for you. Furthermore, a single ClipData can actually hold as many of these as you want, each represented as individual ClipData.Item objects. As such, the possibilities are endless.

There are static factory methods on ClipData, such as newUri(), that you can use to create your ClipData objects. In fact, that is what we use in the SystemServices/ClipMusic sample project and the MusicClipper activity.

MusicClipper has the classic two-big-button layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="horizontal"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  >
  <Button android:id="@+id/pick"
    android:layout_width="match_parent" 
    android:layout_height="match_parent"
    android:layout_weight="1"
    android:text="Pick"
    android:onClick="pickMusic"
  />
  <Button android:id="@+id/view"
    android:layout_width="match_parent" 
    android:layout_height="match_parent"
    android:layout_weight="1"
    android:text="Play"
    android:onClick="playMusic"
  />
</LinearLayout>

The Music Clipper main screen
Figure 853: The Music Clipper main screen

In onCreate(), we get our hands on our ClipboardManager system service:

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    clipboard=(ClipboardManager)getSystemService(CLIPBOARD_SERVICE);
  }

Tapping the “Pick” button will let you pick a piece of music, courtesy of the pickMusic() method wired to that Button object:

  public void pickMusic(View v) {
    Intent i=new Intent(Intent.ACTION_GET_CONTENT);

    i.setType("audio/*");
    startActivityForResult(i, PICK_REQUEST);
  }

Here, we tell Android to let us pick a piece of music from any available audio MIME type (audio/*). Fortunately, Android has an activity that lets us do that:

The XOOM tablets music track picker
Figure 854: The XOOM tablet’s music track picker

We get the result in onActivityResult(), since we used startActivityForResult() to pick the music. There, we package up the content:// Uri to the music into a ClipData object and put it on the clipboard:

  @Override
  protected void onActivityResult(int requestCode, int resultCode,
                                  Intent data) {
    if (requestCode == PICK_REQUEST) {
      if (resultCode == RESULT_OK) {
        ClipData clip=
            ClipData.newUri(getContentResolver(), "Some music",
                            data.getData());

        try {
          clipboard.setPrimaryClip(clip);
        }
        catch (Exception e) {
          Log.e(getClass().getSimpleName(), "Exception clipping Uri", e);
          Toast.makeText(this, "Exception: " + e.getMessage(),
                         Toast.LENGTH_SHORT).show();
        }
      }
    }
  }

Note that there is a significant bug in Android 4.3 that, until it is fixed, will require you to do a bit more error-handling with your clipboard operations. That is why we have our setPrimaryClip() call wrapped in a try/catch blog, even though setPrimaryClip() does not throw a checked exception. The rationale for this will be discussed later in this chapter.

The catch with rich data on the clipboard is that somebody has to know about the sort of information you are placing on the clipboard. Eventually, the Android development community will work out common practices in this area. Right now, though, you can certainly use it within your own application (e.g., clipping a note and pasting it into another folder).

Since putting ClipData onto the clipboard involves a call to setPrimaryClip(), it should not be surprising that the reverse operation — getting a ClipData from the clipboard — uses getPrimaryClip(). However, since you do not know where this clip came from, you need to validate that it has what you expect and to let the user know when the clipboard contents are not something you can leverage.

The “Play” button in our UI is wired to a playMusic() method. This will only work when we have pasted a Uri ClipData to the clipboard pointing to a piece of music. Since we cannot be sure that the user has done that, we have to sniff around:

  public void playMusic(View v) {
    ClipData clip=clipboard.getPrimaryClip();

    if (clip == null) {
      Toast.makeText(this, "There is no clip!", Toast.LENGTH_LONG)
           .show();
    }
    else {
      ClipData.Item item=clip.getItemAt(0);
      Uri song=item.getUri();

      if (song != null
          && getContentResolver().getType(song).startsWith("audio/")) {
        startActivity(new Intent(Intent.ACTION_VIEW, song));
      }
      else {
        Toast.makeText(this, "There is no song!", Toast.LENGTH_LONG)
             .show();
      }
    }
  }

First, there may be nothing on the clipboard, in which case the ClipData returned by getPrimaryClip() would be null. Or, there may be stuff on the clipboard, but it may not have a Uri associated with it (getUri() on ClipData). Even then, the Uri may point to something other than music, so even if we get a Uri, we need to use a ContentResolver to check the MIME type (getContentResolver().getType()) and make sure it seems like it is music (e.g., starts with audio/). Then, and only then, does it make sense to try to start an ACTION_VIEW activity on that Uri and hope that something useful happens. Assuming you clipped a piece of music with the “Pick” button, “Play” will kick off playback of that song.

ClipData and Drag-and-Drop

The preview of this section is being chased by zombies.

Monitoring the Clipboard

The preview of this section was the victim of a MITM ('Martian in the middle') attack.

The Android 4.3 Clipboard Bug

The preview of this section was whisked away by a shark-infested tornado.