Improve clipboard API.

- Rename ClippedData to ClipData.
- Introudce ClipDescription subclass.
- Add convenience APIs for creating a ClipData.
- Add ClipboardManager API to get just the ClipDescription.
- Define MIME types associated with a clip.

Change-Id: If97ef91aa99a4dd0ec74ccaea504345c9ef12b5c
diff --git a/core/java/android/content/ClippedData.aidl b/core/java/android/content/ClipData.aidl
similarity index 96%
rename from core/java/android/content/ClippedData.aidl
rename to core/java/android/content/ClipData.aidl
index 5246526..5fc12ce 100644
--- a/core/java/android/content/ClippedData.aidl
+++ b/core/java/android/content/ClipData.aidl
@@ -16,4 +16,4 @@
 
 package android.content;
 
-parcelable ClippedData;
+parcelable ClipData;
diff --git a/core/java/android/content/ClippedData.java b/core/java/android/content/ClipData.java
similarity index 71%
rename from core/java/android/content/ClippedData.java
rename to core/java/android/content/ClipData.java
index c3f0237..0c0c0ec 100644
--- a/core/java/android/content/ClippedData.java
+++ b/core/java/android/content/ClipData.java
@@ -37,6 +37,14 @@
  * each of which can hold one or more representations of an item of data.
  * For display to the user, it also has a label and iconic representation.</p>
  *
+ * <p>A ClipData is a sub-class of {@link ClipDescription}, which describes
+ * important meta-data about the clip.  In particular, its {@link #getMimeType(int)}
+ * must return correct MIME type(s) describing the data in the clip.  For help
+ * in correctly constructing a clip with the correct MIME type, use
+ * {@link #newPlainText(CharSequence, Bitmap, CharSequence)},
+ * {@link #newUri(ContentResolver, CharSequence, Bitmap, Uri)}, and
+ * {@link #newIntent(CharSequence, Bitmap, Intent)}.
+ *
  * <p>Each Item instance can be one of three main classes of data: a simple
  * CharSequence of text, a single Intent object, or a Uri.  See {@link Item}
  * for more details.
@@ -53,11 +61,16 @@
  *
  * <p>If all you want is the textual representation of the clipped data, you
  * can use the convenience method {@link Item#coerceToText Item.coerceToText}.
+ * In this case there is generally no need to worry about the MIME types
+ * reported by {@link #getMimeType(int)}, since any clip item an always be
+ * converted to a string.
  *
  * <p>More complicated exchanges will be done through URIs, in particular
  * "content:" URIs.  A content URI allows the recipient of a ClippedData item
  * to interact closely with the ContentProvider holding the data in order to
- * negotiate the transfer of that data.
+ * negotiate the transfer of that data.  The clip must also be filled in with
+ * the available MIME types; {@link #newUri(ContentResolver, CharSequence, Bitmap, Uri)}
+ * will take care of correctly doing this.
  *
  * <p>For example, here is the paste function of a simple NotePad application.
  * When retrieving the data from the clipboard, it can do either two things:
@@ -120,9 +133,12 @@
  * into an editor), then {@link Item#coerceToText(Context)} will ask the content
  * provider for the clip URI as text and successfully paste the entire note.
  */
-public class ClippedData implements Parcelable {
-    CharSequence mLabel;
-    Bitmap mIcon;
+public class ClipData extends ClipDescription {
+    static final String[] MIMETYPES_TEXT_PLAIN = new String[] { MIMETYPE_TEXT_PLAIN };
+    static final String[] MIMETYPES_TEXT_URILIST = new String[] { MIMETYPE_TEXT_URILIST };
+    static final String[] MIMETYPES_TEXT_INTENT = new String[] { MIMETYPE_TEXT_INTENT };
+
+    final Bitmap mIcon;
 
     final ArrayList<Item> mItems = new ArrayList<Item>();
 
@@ -146,28 +162,34 @@
      * </ul>
      */
     public static class Item {
-        CharSequence mText;
-        Intent mIntent;
-        Uri mUri;
+        final CharSequence mText;
+        final Intent mIntent;
+        final Uri mUri;
 
         /**
          * Create an Item consisting of a single block of (possibly styled) text.
          */
         public Item(CharSequence text) {
             mText = text;
+            mIntent = null;
+            mUri = null;
         }
 
         /**
          * Create an Item consisting of an arbitrary Intent.
          */
         public Item(Intent intent) {
+            mText = null;
             mIntent = intent;
+            mUri = null;
         }
 
         /**
          * Create an Item consisting of an arbitrary URI.
          */
         public Item(Uri uri) {
+            mText = null;
+            mIntent = null;
             mUri = uri;
         }
 
@@ -292,19 +314,103 @@
      * Create a new clip.
      *
      * @param label Label to show to the user describing this clip.
+     * @param mimeTypes An array of MIME types this data is available as.
      * @param icon Bitmap providing the user with an iconing representation of
      * the clip.
      * @param item The contents of the first item in the clip.
      */
-    public ClippedData(CharSequence label, Bitmap icon, Item item) {
+    public ClipData(CharSequence label, String[] mimeTypes, Bitmap icon, Item item) {
+        super(label, mimeTypes);
         if (item == null) {
             throw new NullPointerException("item is null");
         }
-        mLabel = label;
         mIcon = icon;
         mItems.add(item);
     }
 
+    /**
+     * Create a new ClipData holding data of the type {@link #MIMETYPE_TEXT_PLAIN}.
+     *
+     * @param label User-visible label for the clip data.
+     * @param icon Iconic representation of the clip data.
+     * @param text The actual text in the clip.
+     * @return Returns a new ClipData containing the specified data.
+     */
+    static public ClipData newPlainText(CharSequence label, Bitmap icon, CharSequence text) {
+        Item item = new Item(text);
+        return new ClipData(label, MIMETYPES_TEXT_PLAIN, icon, item);
+    }
+
+    /**
+     * Create a new ClipData holding an Intent with MIME type {@link #MIMETYPE_TEXT_INTENT}.
+     *
+     * @param label User-visible label for the clip data.
+     * @param icon Iconic representation of the clip data.
+     * @param intent The actual Intent in the clip.
+     * @return Returns a new ClipData containing the specified data.
+     */
+    static public ClipData newIntent(CharSequence label, Bitmap icon, Intent intent) {
+        Item item = new Item(intent);
+        return new ClipData(label, MIMETYPES_TEXT_INTENT, icon, item);
+    }
+
+    /**
+     * Create a new ClipData holding a URI.  If the URI is a content: URI,
+     * this will query the content provider for the MIME type of its data and
+     * use that as the MIME type.  Otherwise, it will use the MIME type
+     * {@link #MIMETYPE_TEXT_URILIST}.
+     *
+     * @param resolver ContentResolver used to get information about the URI.
+     * @param label User-visible label for the clip data.
+     * @param icon Iconic representation of the clip data.
+     * @param uri The URI in the clip.
+     * @return Returns a new ClipData containing the specified data.
+     */
+    static public ClipData newUri(ContentResolver resolver, CharSequence label,
+            Bitmap icon, Uri uri) {
+        Item item = new Item(uri);
+        String[] mimeTypes = null;
+        if ("content".equals(uri.getScheme())) {
+            String realType = resolver.getType(uri);
+            mimeTypes = resolver.getStreamTypes(uri, "*/*");
+            if (mimeTypes == null) {
+                if (realType != null) {
+                    mimeTypes = new String[] { realType, MIMETYPE_TEXT_URILIST };
+                }
+            } else {
+                String[] tmp = new String[mimeTypes.length + (realType != null ? 2 : 1)];
+                int i = 0;
+                if (realType != null) {
+                    tmp[0] = realType;
+                    i++;
+                }
+                System.arraycopy(mimeTypes, 0, tmp, i, mimeTypes.length);
+                tmp[i + mimeTypes.length] = MIMETYPE_TEXT_URILIST;
+                mimeTypes = tmp;
+            }
+        }
+        if (mimeTypes == null) {
+            mimeTypes = MIMETYPES_TEXT_URILIST;
+        }
+        return new ClipData(label, mimeTypes, icon, item);
+    }
+
+    /**
+     * Create a new ClipData holding an URI with MIME type {@link #MIMETYPE_TEXT_URILIST}.
+     * Unlike {@link #newUri(ContentResolver, CharSequence, Bitmap, Uri)}, nothing
+     * is inferred about the URI -- if it is a content: URI holding a bitmap,
+     * the reported type will still be uri-list.  Use this with care!
+     *
+     * @param label User-visible label for the clip data.
+     * @param icon Iconic representation of the clip data.
+     * @param uri The URI in the clip.
+     * @return Returns a new ClipData containing the specified data.
+     */
+    static public ClipData newRawUri(CharSequence label, Bitmap icon, Uri uri) {
+        Item item = new Item(uri);
+        return new ClipData(label, MIMETYPES_TEXT_URILIST, icon, item);
+    }
+
     public void addItem(Item item) {
         if (item == null) {
             throw new NullPointerException("item is null");
@@ -312,10 +418,6 @@
         mItems.add(item);
     }
 
-    public CharSequence getLabel() {
-        return mLabel;
-    }
-
     public Bitmap getIcon() {
         return mIcon;
     }
@@ -335,7 +437,7 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        TextUtils.writeToParcel(mLabel, dest, flags);
+        super.writeToParcel(dest, flags);
         if (mIcon != null) {
             dest.writeInt(1);
             mIcon.writeToParcel(dest, flags);
@@ -362,10 +464,12 @@
         }
     }
 
-    ClippedData(Parcel in) {
-        mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+    ClipData(Parcel in) {
+        super(in);
         if (in.readInt() != 0) {
             mIcon = Bitmap.CREATOR.createFromParcel(in);
+        } else {
+            mIcon = null;
         }
         final int N = in.readInt();
         for (int i=0; i<N; i++) {
@@ -376,15 +480,15 @@
         }
     }
 
-    public static final Parcelable.Creator<ClippedData> CREATOR =
-        new Parcelable.Creator<ClippedData>() {
+    public static final Parcelable.Creator<ClipData> CREATOR =
+        new Parcelable.Creator<ClipData>() {
 
-            public ClippedData createFromParcel(Parcel source) {
-                return new ClippedData(source);
+            public ClipData createFromParcel(Parcel source) {
+                return new ClipData(source);
             }
 
-            public ClippedData[] newArray(int size) {
-                return new ClippedData[size];
+            public ClipData[] newArray(int size) {
+                return new ClipData[size];
             }
         };
 }
diff --git a/core/java/android/content/ClippedData.aidl b/core/java/android/content/ClipDescription.aidl
similarity index 95%
copy from core/java/android/content/ClippedData.aidl
copy to core/java/android/content/ClipDescription.aidl
index 5246526..391fd5a 100644
--- a/core/java/android/content/ClippedData.aidl
+++ b/core/java/android/content/ClipDescription.aidl
@@ -16,4 +16,4 @@
 
 package android.content;
 
-parcelable ClippedData;
+parcelable ClipDescription;
diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java
new file mode 100644
index 0000000..3229f0f
--- /dev/null
+++ b/core/java/android/content/ClipDescription.java
@@ -0,0 +1,206 @@
+/**
+ * Copyright (c) 2010, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+
+/**
+ * Meta-data describing the contents of a {@link #ClipData}.  Provides enough
+ * information to know if you can handle the ClipData, but not the data
+ * itself.
+ */
+public class ClipDescription implements Parcelable {
+    /**
+     * The MIME type for a clip holding plain text.
+     */
+    public static final String MIMETYPE_TEXT_PLAIN = "text/plain";
+
+    /**
+     * The MIME type for a clip holding one or more URIs.  This should be
+     * used for URIs that are meaningful to a user (such as an http: URI).
+     * It should <em>not</em> be used for a content: URI that references some
+     * other piece of data; in that case the MIME type should be the type
+     * of the referenced data.
+     */
+    public static final String MIMETYPE_TEXT_URILIST = "text/uri-list";
+
+    /**
+     * The MIME type for a clip holding an Intent.
+     */
+    public static final String MIMETYPE_TEXT_INTENT = "text/vnd.android.intent";
+
+    final CharSequence mLabel;
+    final String[] mMimeTypes;
+
+    /**
+     * Create a new clip.
+     *
+     * @param label Label to show to the user describing this clip.
+     * @param mimeTypes An array of MIME types this data is available as.
+     */
+    public ClipDescription(CharSequence label, String[] mimeTypes) {
+        if (mimeTypes == null) {
+            throw new NullPointerException("mimeTypes is null");
+        }
+        mLabel = label;
+        mMimeTypes = mimeTypes;
+    }
+
+    /**
+     * Create a copy of a ClipDescription.
+     */
+    public ClipDescription(ClipDescription o) {
+        mLabel = o.mLabel;
+        mMimeTypes = o.mMimeTypes;
+    }
+
+    /**
+     * Helper to compare two MIME types, where one may be a pattern.
+     * @param concreteType A fully-specified MIME type.
+     * @param desiredType A desired MIME type that may be a pattern such as *\/*.
+     * @return Returns true if the two MIME types match.
+     */
+    public static boolean compareMimeTypes(String concreteType, String desiredType) {
+        final int typeLength = desiredType.length();
+        if (typeLength == 3 && desiredType.equals("*/*")) {
+            return true;
+        }
+
+        final int slashpos = desiredType.indexOf('/');
+        if (slashpos > 0) {
+            if (typeLength == slashpos+2 && desiredType.charAt(slashpos+1) == '*') {
+                if (desiredType.regionMatches(0, concreteType, 0, slashpos+1)) {
+                    return true;
+                }
+            } else if (desiredType.equals(concreteType)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Return the label for this clip.
+     */
+    public CharSequence getLabel() {
+        return mLabel;
+    }
+
+    /**
+     * Check whether the clip description contains the given MIME type.
+     *
+     * @param mimeType The desired MIME type.  May be a pattern.
+     * @return Returns true if one of the MIME types in the clip description
+     * matches the desired MIME type, else false.
+     */
+    public boolean hasMimeType(String mimeType) {
+        for (int i=0; i<mMimeTypes.length; i++) {
+            if (compareMimeTypes(mMimeTypes[i], mimeType)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Filter the clip description MIME types by the given MIME type.  Returns
+     * all MIME types in the clip that match the given MIME type.
+     *
+     * @param mimeType The desired MIME type.  May be a pattern.
+     * @return Returns an array of all matching MIME types.  If there are no
+     * matching MIME types, null is returned.
+     */
+    public String[] filterMimeTypes(String mimeType) {
+        ArrayList<String> array = null;
+        for (int i=0; i<mMimeTypes.length; i++) {
+            if (compareMimeTypes(mMimeTypes[i], mimeType)) {
+                if (array == null) {
+                    array = new ArrayList<String>();
+                }
+                array.add(mMimeTypes[i]);
+            }
+        }
+        if (array == null) {
+            return null;
+        }
+        String[] rawArray = new String[array.size()];
+        array.toArray(rawArray);
+        return rawArray;
+    }
+
+    /**
+     * Return the number of MIME types the clip is available in.
+     */
+    public int getMimeTypeCount() {
+        return mMimeTypes.length;
+    }
+
+    /**
+     * Return one of the possible clip MIME types.
+     */
+    public String getMimeType(int index) {
+        return mMimeTypes[index];
+    }
+
+    /** @hide */
+    public void validate() {
+        if (mMimeTypes == null) {
+            throw new NullPointerException("null mime types");
+        }
+        if (mMimeTypes.length <= 0) {
+            throw new IllegalArgumentException("must have at least 1 mime type");
+        }
+        for (int i=0; i<mMimeTypes.length; i++) {
+            if (mMimeTypes[i] == null) {
+                throw new NullPointerException("mime type at " + i + " is null");
+            }
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        TextUtils.writeToParcel(mLabel, dest, flags);
+        dest.writeStringArray(mMimeTypes);
+    }
+
+    ClipDescription(Parcel in) {
+        mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mMimeTypes = in.createStringArray();
+    }
+
+    public static final Parcelable.Creator<ClipDescription> CREATOR =
+        new Parcelable.Creator<ClipDescription>() {
+
+            public ClipDescription createFromParcel(Parcel source) {
+                return new ClipDescription(source);
+            }
+
+            public ClipDescription[] newArray(int size) {
+                return new ClipDescription[size];
+            }
+        };
+}
diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java
index d685cf3..0ea0648 100644
--- a/core/java/android/content/ClipboardManager.java
+++ b/core/java/android/content/ClipboardManager.java
@@ -37,7 +37,7 @@
  * <p>
  * The ClipboardManager API itself is very simple: it consists of methods
  * to atomically get and set the current primary clipboard data.  That data
- * is expressed as a {@link ClippedData} object, which defines the protocol
+ * is expressed as a {@link ClipData} object, which defines the protocol
  * for data exchange between applications.
  *
  * @see android.content.Context#getSystemService
@@ -96,7 +96,7 @@
      *
      * @param clip The clipped data item to set.
      */
-    public void setPrimaryClip(ClippedData clip) {
+    public void setPrimaryClip(ClipData clip) {
         try {
             getService().setPrimaryClip(clip);
         } catch (RemoteException e) {
@@ -106,7 +106,7 @@
     /**
      * Returns the current primary clip on the clipboard.
      */
-    public ClippedData getPrimaryClip() {
+    public ClipData getPrimaryClip() {
         try {
             return getService().getPrimaryClip();
         } catch (RemoteException e) {
@@ -115,6 +115,18 @@
     }
 
     /**
+     * Returns a description of the current primary clip on the clipboard
+     * but not a copy of its data.
+     */
+    public ClipDescription getPrimaryClipDescription() {
+        try {
+            return getService().getPrimaryClipDescription();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
      * Returns true if there is currently a primary clip on the clipboard.
      */
     public boolean hasPrimaryClip() {
@@ -156,7 +168,7 @@
      * the primary clip and tries to coerce it to a string.
      */
     public CharSequence getText() {
-        ClippedData clip = getPrimaryClip();
+        ClipData clip = getPrimaryClip();
         if (clip != null && clip.getItemCount() > 0) {
             return clip.getItem(0).coerceToText(mContext);
         }
@@ -164,12 +176,12 @@
     }
 
     /**
-     * @deprecated Use {@link #setPrimaryClip(ClippedData)} instead.  This
+     * @deprecated Use {@link #setPrimaryClip(ClipData)} instead.  This
      * creates a ClippedItem holding the given text and sets it as the
      * primary clip.  It has no label or icon.
      */
     public void setText(CharSequence text) {
-        setPrimaryClip(new ClippedData(null, null, new ClippedData.Item(text)));
+        setPrimaryClip(ClipData.newPlainText(null, null, text));
     }
 
     /**
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 88e0123..d3c1b4e 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -767,32 +767,6 @@
     }
 
     /**
-     * Helper to compare two MIME types, where one may be a pattern.
-     * @param concreteType A fully-specified MIME type.
-     * @param desiredType A desired MIME type that may be a pattern such as *\/*.
-     * @return Returns true if the two MIME types match.
-     */
-    public static boolean compareMimeTypes(String concreteType, String desiredType) {
-        final int typeLength = desiredType.length();
-        if (typeLength == 3 && desiredType.equals("*/*")) {
-            return true;
-        }
-
-        final int slashpos = desiredType.indexOf('/');
-        if (slashpos > 0) {
-            if (typeLength == slashpos+2 && desiredType.charAt(slashpos+1) == '*') {
-                if (desiredType.regionMatches(0, concreteType, 0, slashpos+1)) {
-                    return true;
-                }
-            } else if (desiredType.equals(concreteType)) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    /**
      * Called by a client to determine the types of data streams that this
      * content provider supports for the given URI.  The default implementation
      * returns null, meaning no types.  If your content provider stores data
@@ -809,7 +783,7 @@
      *
      * @see #getType(Uri)
      * @see #openTypedAssetFile(Uri, String, Bundle)
-     * @see #compareMimeTypes(String, String)
+     * @see ClipDescription#compareMimeTypes(String, String)
      */
     public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
         return null;
@@ -825,7 +799,7 @@
      * result of {@link #getType(Uri)} and, if the match, simple calls
      * {@link #openAssetFile(Uri, String)}.
      *
-     * <p>See {@link ClippedData} for examples of the use and implementation
+     * <p>See {@link ClipData} for examples of the use and implementation
      * of this method.
      *
      * @param uri The data in the content provider being queried.
@@ -848,7 +822,7 @@
      *
      * @see #getStreamTypes(Uri, String)
      * @see #openAssetFile(Uri, String)
-     * @see #compareMimeTypes(String, String)
+     * @see ClipDescription#compareMimeTypes(String, String)
      */
     public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
             throws FileNotFoundException {
@@ -857,7 +831,7 @@
             return openAssetFile(uri, "r");
         }
         String baseType = getType(uri);
-        if (baseType != null && compareMimeTypes(baseType, mimeTypeFilter)) {
+        if (baseType != null && ClipDescription.compareMimeTypes(baseType, mimeTypeFilter)) {
             // Use old untyped open call if this provider has a type for this
             // URI and it matches the request.
             return openAssetFile(uri, "r");
diff --git a/core/java/android/content/IClipboard.aidl b/core/java/android/content/IClipboard.aidl
index b4534a9..3e1fe55 100644
--- a/core/java/android/content/IClipboard.aidl
+++ b/core/java/android/content/IClipboard.aidl
@@ -16,7 +16,8 @@
 
 package android.content;
 
-import android.content.ClippedData;
+import android.content.ClipData;
+import android.content.ClipDescription;
 import android.content.IOnPrimaryClipChangedListener;
 
 /**
@@ -25,8 +26,9 @@
  * {@hide}
  */
 interface IClipboard {
-    void setPrimaryClip(in ClippedData clip);
-    ClippedData getPrimaryClip();
+    void setPrimaryClip(in ClipData clip);
+    ClipData getPrimaryClip();
+    ClipDescription getPrimaryClipDescription();
     boolean hasPrimaryClip();
     void addPrimaryClipChangedListener(in IOnPrimaryClipChangedListener listener);
     void removePrimaryClipChangedListener(in IOnPrimaryClipChangedListener listener);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 6db5b9c..961c782 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -22,7 +22,7 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import android.content.ClipboardManager;
-import android.content.ClippedData;
+import android.content.ClipData;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.ColorStateList;
@@ -7330,17 +7330,18 @@
 
                 URLSpan[] urls = ((Spanned) mText).getSpans(min, max, URLSpan.class);
                 if (urls.length >= 1) {
-                    ClippedData clip = null;
+                    ClipData clip = null;
                     for (int i=0; i<urls.length; i++) {
                         Uri uri = Uri.parse(urls[0].getURL());
-                        ClippedData.Item item = new ClippedData.Item(uri);
                         if (clip == null) {
-                            clip = new ClippedData(null, null, item);
+                            clip = ClipData.newRawUri(null, null, uri);
                         } else {
-                            clip.addItem(item);
+                            clip.addItem(new ClipData.Item(uri));
                         }
                     }
-                    clipboard.setPrimaryClip(clip);
+                    if (clip != null) {
+                        clipboard.setPrimaryClip(clip);
+                    }
                 }
                 return true;
 
@@ -7536,7 +7537,7 @@
 
             switch (item.getItemId()) {
                 case ID_PASTE:
-                    ClippedData clip = clipboard.getPrimaryClip();
+                    ClipData clip = clipboard.getPrimaryClip();
                     if (clip != null) {
                         boolean didfirst = false;
                         for (int i=0; i<clip.getItemCount(); i++) {
@@ -7557,15 +7558,15 @@
                     return true;
 
                 case ID_CUT:
-                    clipboard.setPrimaryClip(new ClippedData(null, null,
-                            new ClippedData.Item(mTransformed.subSequence(min, max))));
+                    clipboard.setPrimaryClip(ClipData.newPlainText(null, null,
+                            mTransformed.subSequence(min, max)));
                     ((Editable) mText).delete(min, max);
                     stopSelectionActionMode();
                     return true;
 
                 case ID_COPY:
-                    clipboard.setPrimaryClip(new ClippedData(null, null,
-                            new ClippedData.Item(mTransformed.subSequence(min, max))));
+                    clipboard.setPrimaryClip(ClipData.newPlainText(null, null,
+                            mTransformed.subSequence(min, max)));
                     stopSelectionActionMode();
                     return true;
             }