Home | History | Annotate | Download | only in content
      1 /**
      2  * Copyright (c) 2010, The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *     http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.content;
     18 
     19 import static android.content.ContentProvider.maybeAddUserId;
     20 import static android.content.ContentResolver.SCHEME_ANDROID_RESOURCE;
     21 import static android.content.ContentResolver.SCHEME_CONTENT;
     22 import static android.content.ContentResolver.SCHEME_FILE;
     23 
     24 import android.content.res.AssetFileDescriptor;
     25 import android.graphics.Bitmap;
     26 import android.net.Uri;
     27 import android.os.Parcel;
     28 import android.os.Parcelable;
     29 import android.os.StrictMode;
     30 import android.text.Html;
     31 import android.text.Spannable;
     32 import android.text.SpannableStringBuilder;
     33 import android.text.Spanned;
     34 import android.text.TextUtils;
     35 import android.text.style.URLSpan;
     36 import android.util.Log;
     37 import android.util.proto.ProtoOutputStream;
     38 
     39 import com.android.internal.util.ArrayUtils;
     40 
     41 import libcore.io.IoUtils;
     42 
     43 import java.io.FileInputStream;
     44 import java.io.FileNotFoundException;
     45 import java.io.IOException;
     46 import java.io.InputStreamReader;
     47 import java.util.ArrayList;
     48 import java.util.List;
     49 
     50 /**
     51  * Representation of a clipped data on the clipboard.
     52  *
     53  * <p>ClipData is a complex type containing one or more Item instances,
     54  * each of which can hold one or more representations of an item of data.
     55  * For display to the user, it also has a label.</p>
     56  *
     57  * <p>A ClipData contains a {@link ClipDescription}, which describes
     58  * important meta-data about the clip.  In particular, its
     59  * {@link ClipDescription#getMimeType(int) getDescription().getMimeType(int)}
     60  * must return correct MIME type(s) describing the data in the clip.  For help
     61  * in correctly constructing a clip with the correct MIME type, use
     62  * {@link #newPlainText(CharSequence, CharSequence)},
     63  * {@link #newUri(ContentResolver, CharSequence, Uri)}, and
     64  * {@link #newIntent(CharSequence, Intent)}.
     65  *
     66  * <p>Each Item instance can be one of three main classes of data: a simple
     67  * CharSequence of text, a single Intent object, or a Uri.  See {@link Item}
     68  * for more details.
     69  *
     70  * <div class="special reference">
     71  * <h3>Developer Guides</h3>
     72  * <p>For more information about using the clipboard framework, read the
     73  * <a href="{@docRoot}guide/topics/clipboard/copy-paste.html">Copy and Paste</a>
     74  * developer guide.</p>
     75  * </div>
     76  *
     77  * <a name="ImplementingPaste"></a>
     78  * <h3>Implementing Paste or Drop</h3>
     79  *
     80  * <p>To implement a paste or drop of a ClipData object into an application,
     81  * the application must correctly interpret the data for its use.  If the {@link Item}
     82  * it contains is simple text or an Intent, there is little to be done: text
     83  * can only be interpreted as text, and an Intent will typically be used for
     84  * creating shortcuts (such as placing icons on the home screen) or other
     85  * actions.
     86  *
     87  * <p>If all you want is the textual representation of the clipped data, you
     88  * can use the convenience method {@link Item#coerceToText Item.coerceToText}.
     89  * In this case there is generally no need to worry about the MIME types
     90  * reported by {@link ClipDescription#getMimeType(int) getDescription().getMimeType(int)},
     91  * since any clip item can always be converted to a string.
     92  *
     93  * <p>More complicated exchanges will be done through URIs, in particular
     94  * "content:" URIs.  A content URI allows the recipient of a ClipData item
     95  * to interact closely with the ContentProvider holding the data in order to
     96  * negotiate the transfer of that data.  The clip must also be filled in with
     97  * the available MIME types; {@link #newUri(ContentResolver, CharSequence, Uri)}
     98  * will take care of correctly doing this.
     99  *
    100  * <p>For example, here is the paste function of a simple NotePad application.
    101  * When retrieving the data from the clipboard, it can do either two things:
    102  * if the clipboard contains a URI reference to an existing note, it copies
    103  * the entire structure of the note into a new note; otherwise, it simply
    104  * coerces the clip into text and uses that as the new note's contents.
    105  *
    106  * {@sample development/samples/NotePad/src/com/example/android/notepad/NoteEditor.java
    107  *      paste}
    108  *
    109  * <p>In many cases an application can paste various types of streams of data.  For
    110  * example, an e-mail application may want to allow the user to paste an image
    111  * or other binary data as an attachment.  This is accomplished through the
    112  * ContentResolver {@link ContentResolver#getStreamTypes(Uri, String)} and
    113  * {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, android.os.Bundle)}
    114  * methods.  These allow a client to discover the type(s) of data that a particular
    115  * content URI can make available as a stream and retrieve the stream of data.
    116  *
    117  * <p>For example, the implementation of {@link Item#coerceToText Item.coerceToText}
    118  * itself uses this to try to retrieve a URI clip as a stream of text:
    119  *
    120  * {@sample frameworks/base/core/java/android/content/ClipData.java coerceToText}
    121  *
    122  * <a name="ImplementingCopy"></a>
    123  * <h3>Implementing Copy or Drag</h3>
    124  *
    125  * <p>To be the source of a clip, the application must construct a ClipData
    126  * object that any recipient can interpret best for their context.  If the clip
    127  * is to contain a simple text, Intent, or URI, this is easy: an {@link Item}
    128  * containing the appropriate data type can be constructed and used.
    129  *
    130  * <p>More complicated data types require the implementation of support in
    131  * a ContentProvider for describing and generating the data for the recipient.
    132  * A common scenario is one where an application places on the clipboard the
    133  * content: URI of an object that the user has copied, with the data at that
    134  * URI consisting of a complicated structure that only other applications with
    135  * direct knowledge of the structure can use.
    136  *
    137  * <p>For applications that do not have intrinsic knowledge of the data structure,
    138  * the content provider holding it can make the data available as an arbitrary
    139  * number of types of data streams.  This is done by implementing the
    140  * ContentProvider {@link ContentProvider#getStreamTypes(Uri, String)} and
    141  * {@link ContentProvider#openTypedAssetFile(Uri, String, android.os.Bundle)}
    142  * methods.
    143  *
    144  * <p>Going back to our simple NotePad application, this is the implementation
    145  * it may have to convert a single note URI (consisting of a title and the note
    146  * text) into a stream of plain text data.
    147  *
    148  * {@sample development/samples/NotePad/src/com/example/android/notepad/NotePadProvider.java
    149  *      stream}
    150  *
    151  * <p>The copy operation in our NotePad application is now just a simple matter
    152  * of making a clip containing the URI of the note being copied:
    153  *
    154  * {@sample development/samples/NotePad/src/com/example/android/notepad/NotesList.java
    155  *      copy}
    156  *
    157  * <p>Note if a paste operation needs this clip as text (for example to paste
    158  * into an editor), then {@link Item#coerceToText(Context)} will ask the content
    159  * provider for the clip URI as text and successfully paste the entire note.
    160  */
    161 public class ClipData implements Parcelable {
    162     static final String[] MIMETYPES_TEXT_PLAIN = new String[] {
    163         ClipDescription.MIMETYPE_TEXT_PLAIN };
    164     static final String[] MIMETYPES_TEXT_HTML = new String[] {
    165         ClipDescription.MIMETYPE_TEXT_HTML };
    166     static final String[] MIMETYPES_TEXT_URILIST = new String[] {
    167         ClipDescription.MIMETYPE_TEXT_URILIST };
    168     static final String[] MIMETYPES_TEXT_INTENT = new String[] {
    169         ClipDescription.MIMETYPE_TEXT_INTENT };
    170 
    171     final ClipDescription mClipDescription;
    172 
    173     final Bitmap mIcon;
    174 
    175     final ArrayList<Item> mItems;
    176 
    177     /**
    178      * Description of a single item in a ClipData.
    179      *
    180      * <p>The types than an individual item can currently contain are:</p>
    181      *
    182      * <ul>
    183      * <li> Text: a basic string of text.  This is actually a CharSequence,
    184      * so it can be formatted text supported by corresponding Android built-in
    185      * style spans.  (Custom application spans are not supported and will be
    186      * stripped when transporting through the clipboard.)
    187      * <li> Intent: an arbitrary Intent object.  A typical use is the shortcut
    188      * to create when pasting a clipped item on to the home screen.
    189      * <li> Uri: a URI reference.  This may be any URI (such as an http: URI
    190      * representing a bookmark), however it is often a content: URI.  Using
    191      * content provider references as clips like this allows an application to
    192      * share complex or large clips through the standard content provider
    193      * facilities.
    194      * </ul>
    195      */
    196     public static class Item {
    197         final CharSequence mText;
    198         final String mHtmlText;
    199         final Intent mIntent;
    200         Uri mUri;
    201 
    202         /** @hide */
    203         public Item(Item other) {
    204             mText = other.mText;
    205             mHtmlText = other.mHtmlText;
    206             mIntent = other.mIntent;
    207             mUri = other.mUri;
    208         }
    209 
    210         /**
    211          * Create an Item consisting of a single block of (possibly styled) text.
    212          */
    213         public Item(CharSequence text) {
    214             mText = text;
    215             mHtmlText = null;
    216             mIntent = null;
    217             mUri = null;
    218         }
    219 
    220         /**
    221          * Create an Item consisting of a single block of (possibly styled) text,
    222          * with an alternative HTML formatted representation.  You <em>must</em>
    223          * supply a plain text representation in addition to HTML text; coercion
    224          * will not be done from HTML formated text into plain text.
    225          */
    226         public Item(CharSequence text, String htmlText) {
    227             mText = text;
    228             mHtmlText = htmlText;
    229             mIntent = null;
    230             mUri = null;
    231         }
    232 
    233         /**
    234          * Create an Item consisting of an arbitrary Intent.
    235          */
    236         public Item(Intent intent) {
    237             mText = null;
    238             mHtmlText = null;
    239             mIntent = intent;
    240             mUri = null;
    241         }
    242 
    243         /**
    244          * Create an Item consisting of an arbitrary URI.
    245          */
    246         public Item(Uri uri) {
    247             mText = null;
    248             mHtmlText = null;
    249             mIntent = null;
    250             mUri = uri;
    251         }
    252 
    253         /**
    254          * Create a complex Item, containing multiple representations of
    255          * text, Intent, and/or URI.
    256          */
    257         public Item(CharSequence text, Intent intent, Uri uri) {
    258             mText = text;
    259             mHtmlText = null;
    260             mIntent = intent;
    261             mUri = uri;
    262         }
    263 
    264         /**
    265          * Create a complex Item, containing multiple representations of
    266          * text, HTML text, Intent, and/or URI.  If providing HTML text, you
    267          * <em>must</em> supply a plain text representation as well; coercion
    268          * will not be done from HTML formated text into plain text.
    269          */
    270         public Item(CharSequence text, String htmlText, Intent intent, Uri uri) {
    271             if (htmlText != null && text == null) {
    272                 throw new IllegalArgumentException(
    273                         "Plain text must be supplied if HTML text is supplied");
    274             }
    275             mText = text;
    276             mHtmlText = htmlText;
    277             mIntent = intent;
    278             mUri = uri;
    279         }
    280 
    281         /**
    282          * Retrieve the raw text contained in this Item.
    283          */
    284         public CharSequence getText() {
    285             return mText;
    286         }
    287 
    288         /**
    289          * Retrieve the raw HTML text contained in this Item.
    290          */
    291         public String getHtmlText() {
    292             return mHtmlText;
    293         }
    294 
    295         /**
    296          * Retrieve the raw Intent contained in this Item.
    297          */
    298         public Intent getIntent() {
    299             return mIntent;
    300         }
    301 
    302         /**
    303          * Retrieve the raw URI contained in this Item.
    304          */
    305         public Uri getUri() {
    306             return mUri;
    307         }
    308 
    309         /**
    310          * Turn this item into text, regardless of the type of data it
    311          * actually contains.
    312          *
    313          * <p>The algorithm for deciding what text to return is:
    314          * <ul>
    315          * <li> If {@link #getText} is non-null, return that.
    316          * <li> If {@link #getUri} is non-null, try to retrieve its data
    317          * as a text stream from its content provider.  If this succeeds, copy
    318          * the text into a String and return it.  If it is not a content: URI or
    319          * the content provider does not supply a text representation, return
    320          * the raw URI as a string.
    321          * <li> If {@link #getIntent} is non-null, convert that to an intent:
    322          * URI and return it.
    323          * <li> Otherwise, return an empty string.
    324          * </ul>
    325          *
    326          * @param context The caller's Context, from which its ContentResolver
    327          * and other things can be retrieved.
    328          * @return Returns the item's textual representation.
    329          */
    330 //BEGIN_INCLUDE(coerceToText)
    331         public CharSequence coerceToText(Context context) {
    332             // If this Item has an explicit textual value, simply return that.
    333             CharSequence text = getText();
    334             if (text != null) {
    335                 return text;
    336             }
    337 
    338             // If this Item has a URI value, try using that.
    339             Uri uri = getUri();
    340             if (uri != null) {
    341                 // First see if the URI can be opened as a plain text stream
    342                 // (of any sub-type).  If so, this is the best textual
    343                 // representation for it.
    344                 final ContentResolver resolver = context.getContentResolver();
    345                 AssetFileDescriptor descr = null;
    346                 FileInputStream stream = null;
    347                 InputStreamReader reader = null;
    348                 try {
    349                     try {
    350                         // Ask for a stream of the desired type.
    351                         descr = resolver.openTypedAssetFileDescriptor(uri, "text/*", null);
    352                     } catch (SecurityException e) {
    353                         Log.w("ClipData", "Failure opening stream", e);
    354                     } catch (FileNotFoundException|RuntimeException e) {
    355                         // Unable to open content URI as text...  not really an
    356                         // error, just something to ignore.
    357                     }
    358                     if (descr != null) {
    359                         try {
    360                             stream = descr.createInputStream();
    361                             reader = new InputStreamReader(stream, "UTF-8");
    362 
    363                             // Got it...  copy the stream into a local string and return it.
    364                             final StringBuilder builder = new StringBuilder(128);
    365                             char[] buffer = new char[8192];
    366                             int len;
    367                             while ((len=reader.read(buffer)) > 0) {
    368                                 builder.append(buffer, 0, len);
    369                             }
    370                             return builder.toString();
    371                         } catch (IOException e) {
    372                             // Something bad has happened.
    373                             Log.w("ClipData", "Failure loading text", e);
    374                             return e.toString();
    375                         }
    376                     }
    377                 } finally {
    378                     IoUtils.closeQuietly(descr);
    379                     IoUtils.closeQuietly(stream);
    380                     IoUtils.closeQuietly(reader);
    381                 }
    382 
    383                 // If we couldn't open the URI as a stream, use the URI itself as a textual
    384                 // representation (but not for "content", "android.resource" or "file" schemes).
    385                 final String scheme = uri.getScheme();
    386                 if (SCHEME_CONTENT.equals(scheme)
    387                         || SCHEME_ANDROID_RESOURCE.equals(scheme)
    388                         || SCHEME_FILE.equals(scheme)) {
    389                     return "";
    390                 }
    391                 return uri.toString();
    392             }
    393 
    394             // Finally, if all we have is an Intent, then we can just turn that
    395             // into text.  Not the most user-friendly thing, but it's something.
    396             Intent intent = getIntent();
    397             if (intent != null) {
    398                 return intent.toUri(Intent.URI_INTENT_SCHEME);
    399             }
    400 
    401             // Shouldn't get here, but just in case...
    402             return "";
    403         }
    404 //END_INCLUDE(coerceToText)
    405 
    406         /**
    407          * Like {@link #coerceToHtmlText(Context)}, but any text that would
    408          * be returned as HTML formatting will be returned as text with
    409          * style spans.
    410          * @param context The caller's Context, from which its ContentResolver
    411          * and other things can be retrieved.
    412          * @return Returns the item's textual representation.
    413          */
    414         public CharSequence coerceToStyledText(Context context) {
    415             CharSequence text = getText();
    416             if (text instanceof Spanned) {
    417                 return text;
    418             }
    419             String htmlText = getHtmlText();
    420             if (htmlText != null) {
    421                 try {
    422                     CharSequence newText = Html.fromHtml(htmlText);
    423                     if (newText != null) {
    424                         return newText;
    425                     }
    426                 } catch (RuntimeException e) {
    427                     // If anything bad happens, we'll fall back on the plain text.
    428                 }
    429             }
    430 
    431             if (text != null) {
    432                 return text;
    433             }
    434             return coerceToHtmlOrStyledText(context, true);
    435         }
    436 
    437         /**
    438          * Turn this item into HTML text, regardless of the type of data it
    439          * actually contains.
    440          *
    441          * <p>The algorithm for deciding what text to return is:
    442          * <ul>
    443          * <li> If {@link #getHtmlText} is non-null, return that.
    444          * <li> If {@link #getText} is non-null, return that, converting to
    445          * valid HTML text.  If this text contains style spans,
    446          * {@link Html#toHtml(Spanned) Html.toHtml(Spanned)} is used to
    447          * convert them to HTML formatting.
    448          * <li> If {@link #getUri} is non-null, try to retrieve its data
    449          * as a text stream from its content provider.  If the provider can
    450          * supply text/html data, that will be preferred and returned as-is.
    451          * Otherwise, any text/* data will be returned and escaped to HTML.
    452          * If it is not a content: URI or the content provider does not supply
    453          * a text representation, HTML text containing a link to the URI
    454          * will be returned.
    455          * <li> If {@link #getIntent} is non-null, convert that to an intent:
    456          * URI and return as an HTML link.
    457          * <li> Otherwise, return an empty string.
    458          * </ul>
    459          *
    460          * @param context The caller's Context, from which its ContentResolver
    461          * and other things can be retrieved.
    462          * @return Returns the item's representation as HTML text.
    463          */
    464         public String coerceToHtmlText(Context context) {
    465             // If the item has an explicit HTML value, simply return that.
    466             String htmlText = getHtmlText();
    467             if (htmlText != null) {
    468                 return htmlText;
    469             }
    470 
    471             // If this Item has a plain text value, return it as HTML.
    472             CharSequence text = getText();
    473             if (text != null) {
    474                 if (text instanceof Spanned) {
    475                     return Html.toHtml((Spanned)text);
    476                 }
    477                 return Html.escapeHtml(text);
    478             }
    479 
    480             text = coerceToHtmlOrStyledText(context, false);
    481             return text != null ? text.toString() : null;
    482         }
    483 
    484         private CharSequence coerceToHtmlOrStyledText(Context context, boolean styled) {
    485             // If this Item has a URI value, try using that.
    486             if (mUri != null) {
    487 
    488                 // Check to see what data representations the content
    489                 // provider supports.  We would like HTML text, but if that
    490                 // is not possible we'll live with plan text.
    491                 String[] types = null;
    492                 try {
    493                     types = context.getContentResolver().getStreamTypes(mUri, "text/*");
    494                 } catch (SecurityException e) {
    495                     // No read permission for mUri, assume empty stream types list.
    496                 }
    497                 boolean hasHtml = false;
    498                 boolean hasText = false;
    499                 if (types != null) {
    500                     for (String type : types) {
    501                         if ("text/html".equals(type)) {
    502                             hasHtml = true;
    503                         } else if (type.startsWith("text/")) {
    504                             hasText = true;
    505                         }
    506                     }
    507                 }
    508 
    509                 // If the provider can serve data we can use, open and load it.
    510                 if (hasHtml || hasText) {
    511                     FileInputStream stream = null;
    512                     try {
    513                         // Ask for a stream of the desired type.
    514                         AssetFileDescriptor descr = context.getContentResolver()
    515                                 .openTypedAssetFileDescriptor(mUri,
    516                                         hasHtml ? "text/html" : "text/plain", null);
    517                         stream = descr.createInputStream();
    518                         InputStreamReader reader = new InputStreamReader(stream, "UTF-8");
    519 
    520                         // Got it...  copy the stream into a local string and return it.
    521                         StringBuilder builder = new StringBuilder(128);
    522                         char[] buffer = new char[8192];
    523                         int len;
    524                         while ((len=reader.read(buffer)) > 0) {
    525                             builder.append(buffer, 0, len);
    526                         }
    527                         String text = builder.toString();
    528                         if (hasHtml) {
    529                             if (styled) {
    530                                 // We loaded HTML formatted text and the caller
    531                                 // want styled text, convert it.
    532                                 try {
    533                                     CharSequence newText = Html.fromHtml(text);
    534                                     return newText != null ? newText : text;
    535                                 } catch (RuntimeException e) {
    536                                     return text;
    537                                 }
    538                             } else {
    539                                 // We loaded HTML formatted text and that is what
    540                                 // the caller wants, just return it.
    541                                 return text.toString();
    542                             }
    543                         }
    544                         if (styled) {
    545                             // We loaded plain text and the caller wants styled
    546                             // text, that is all we have so return it.
    547                             return text;
    548                         } else {
    549                             // We loaded plain text and the caller wants HTML
    550                             // text, escape it for HTML.
    551                             return Html.escapeHtml(text);
    552                         }
    553 
    554                     } catch (SecurityException e) {
    555                         Log.w("ClipData", "Failure opening stream", e);
    556 
    557                     } catch (FileNotFoundException e) {
    558                         // Unable to open content URI as text...  not really an
    559                         // error, just something to ignore.
    560 
    561                     } catch (IOException e) {
    562                         // Something bad has happened.
    563                         Log.w("ClipData", "Failure loading text", e);
    564                         return Html.escapeHtml(e.toString());
    565 
    566                     } finally {
    567                         if (stream != null) {
    568                             try {
    569                                 stream.close();
    570                             } catch (IOException e) {
    571                             }
    572                         }
    573                     }
    574                 }
    575 
    576                 // If we couldn't open the URI as a stream, use the URI itself as a textual
    577                 // representation (but not for "content", "android.resource" or "file" schemes).
    578                 final String scheme = mUri.getScheme();
    579                 if (SCHEME_CONTENT.equals(scheme)
    580                         || SCHEME_ANDROID_RESOURCE.equals(scheme)
    581                         || SCHEME_FILE.equals(scheme)) {
    582                     return "";
    583                 }
    584 
    585                 if (styled) {
    586                     return uriToStyledText(mUri.toString());
    587                 } else {
    588                     return uriToHtml(mUri.toString());
    589                 }
    590             }
    591 
    592             // Finally, if all we have is an Intent, then we can just turn that
    593             // into text.  Not the most user-friendly thing, but it's something.
    594             if (mIntent != null) {
    595                 if (styled) {
    596                     return uriToStyledText(mIntent.toUri(Intent.URI_INTENT_SCHEME));
    597                 } else {
    598                     return uriToHtml(mIntent.toUri(Intent.URI_INTENT_SCHEME));
    599                 }
    600             }
    601 
    602             // Shouldn't get here, but just in case...
    603             return "";
    604         }
    605 
    606         private String uriToHtml(String uri) {
    607             StringBuilder builder = new StringBuilder(256);
    608             builder.append("<a href=\"");
    609             builder.append(Html.escapeHtml(uri));
    610             builder.append("\">");
    611             builder.append(Html.escapeHtml(uri));
    612             builder.append("</a>");
    613             return builder.toString();
    614         }
    615 
    616         private CharSequence uriToStyledText(String uri) {
    617             SpannableStringBuilder builder = new SpannableStringBuilder();
    618             builder.append(uri);
    619             builder.setSpan(new URLSpan(uri), 0, builder.length(),
    620                     Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    621             return builder;
    622         }
    623 
    624         @Override
    625         public String toString() {
    626             StringBuilder b = new StringBuilder(128);
    627 
    628             b.append("ClipData.Item { ");
    629             toShortString(b);
    630             b.append(" }");
    631 
    632             return b.toString();
    633         }
    634 
    635         /** @hide */
    636         public void toShortString(StringBuilder b) {
    637             if (mHtmlText != null) {
    638                 b.append("H:");
    639                 b.append(mHtmlText);
    640             } else if (mText != null) {
    641                 b.append("T:");
    642                 b.append(mText);
    643             } else if (mUri != null) {
    644                 b.append("U:");
    645                 b.append(mUri);
    646             } else if (mIntent != null) {
    647                 b.append("I:");
    648                 mIntent.toShortString(b, true, true, true, true);
    649             } else {
    650                 b.append("NULL");
    651             }
    652         }
    653 
    654         /** @hide */
    655         public void toShortSummaryString(StringBuilder b) {
    656             if (mHtmlText != null) {
    657                 b.append("HTML");
    658             } else if (mText != null) {
    659                 b.append("TEXT");
    660             } else if (mUri != null) {
    661                 b.append("U:");
    662                 b.append(mUri);
    663             } else if (mIntent != null) {
    664                 b.append("I:");
    665                 mIntent.toShortString(b, true, true, true, true);
    666             } else {
    667                 b.append("NULL");
    668             }
    669         }
    670 
    671         /** @hide */
    672         public void writeToProto(ProtoOutputStream proto, long fieldId) {
    673             final long token = proto.start(fieldId);
    674 
    675             if (mHtmlText != null) {
    676                 proto.write(ClipDataProto.Item.HTML_TEXT, mHtmlText);
    677             } else if (mText != null) {
    678                 proto.write(ClipDataProto.Item.TEXT, mText.toString());
    679             } else if (mUri != null) {
    680                 proto.write(ClipDataProto.Item.URI, mUri.toString());
    681             } else if (mIntent != null) {
    682                 mIntent.writeToProto(proto, ClipDataProto.Item.INTENT, true, true, true, true);
    683             } else {
    684                 proto.write(ClipDataProto.Item.NOTHING, true);
    685             }
    686 
    687             proto.end(token);
    688         }
    689     }
    690 
    691     /**
    692      * Create a new clip.
    693      *
    694      * @param label Label to show to the user describing this clip.
    695      * @param mimeTypes An array of MIME types this data is available as.
    696      * @param item The contents of the first item in the clip.
    697      */
    698     public ClipData(CharSequence label, String[] mimeTypes, Item item) {
    699         mClipDescription = new ClipDescription(label, mimeTypes);
    700         if (item == null) {
    701             throw new NullPointerException("item is null");
    702         }
    703         mIcon = null;
    704         mItems = new ArrayList<Item>();
    705         mItems.add(item);
    706     }
    707 
    708     /**
    709      * Create a new clip.
    710      *
    711      * @param description The ClipDescription describing the clip contents.
    712      * @param item The contents of the first item in the clip.
    713      */
    714     public ClipData(ClipDescription description, Item item) {
    715         mClipDescription = description;
    716         if (item == null) {
    717             throw new NullPointerException("item is null");
    718         }
    719         mIcon = null;
    720         mItems = new ArrayList<Item>();
    721         mItems.add(item);
    722     }
    723 
    724     /**
    725      * Create a new clip.
    726      *
    727      * @param description The ClipDescription describing the clip contents.
    728      * @param items The items in the clip. Not that a defensive copy of this
    729      *     list is not made, so caution should be taken to ensure the
    730      *     list is not available for further modification.
    731      * @hide
    732      */
    733     public ClipData(ClipDescription description, ArrayList<Item> items) {
    734         mClipDescription = description;
    735         if (items == null) {
    736             throw new NullPointerException("item is null");
    737         }
    738         mIcon = null;
    739         mItems = items;
    740     }
    741 
    742     /**
    743      * Create a new clip that is a copy of another clip.  This does a deep-copy
    744      * of all items in the clip.
    745      *
    746      * @param other The existing ClipData that is to be copied.
    747      */
    748     public ClipData(ClipData other) {
    749         mClipDescription = other.mClipDescription;
    750         mIcon = other.mIcon;
    751         mItems = new ArrayList<Item>(other.mItems);
    752     }
    753 
    754     /**
    755      * Create a new ClipData holding data of the type
    756      * {@link ClipDescription#MIMETYPE_TEXT_PLAIN}.
    757      *
    758      * @param label User-visible label for the clip data.
    759      * @param text The actual text in the clip.
    760      * @return Returns a new ClipData containing the specified data.
    761      */
    762     static public ClipData newPlainText(CharSequence label, CharSequence text) {
    763         Item item = new Item(text);
    764         return new ClipData(label, MIMETYPES_TEXT_PLAIN, item);
    765     }
    766 
    767     /**
    768      * Create a new ClipData holding data of the type
    769      * {@link ClipDescription#MIMETYPE_TEXT_HTML}.
    770      *
    771      * @param label User-visible label for the clip data.
    772      * @param text The text of clip as plain text, for receivers that don't
    773      * handle HTML.  This is required.
    774      * @param htmlText The actual HTML text in the clip.
    775      * @return Returns a new ClipData containing the specified data.
    776      */
    777     static public ClipData newHtmlText(CharSequence label, CharSequence text,
    778             String htmlText) {
    779         Item item = new Item(text, htmlText);
    780         return new ClipData(label, MIMETYPES_TEXT_HTML, item);
    781     }
    782 
    783     /**
    784      * Create a new ClipData holding an Intent with MIME type
    785      * {@link ClipDescription#MIMETYPE_TEXT_INTENT}.
    786      *
    787      * @param label User-visible label for the clip data.
    788      * @param intent The actual Intent in the clip.
    789      * @return Returns a new ClipData containing the specified data.
    790      */
    791     static public ClipData newIntent(CharSequence label, Intent intent) {
    792         Item item = new Item(intent);
    793         return new ClipData(label, MIMETYPES_TEXT_INTENT, item);
    794     }
    795 
    796     /**
    797      * Create a new ClipData holding a URI.  If the URI is a content: URI,
    798      * this will query the content provider for the MIME type of its data and
    799      * use that as the MIME type.  Otherwise, it will use the MIME type
    800      * {@link ClipDescription#MIMETYPE_TEXT_URILIST}.
    801      *
    802      * @param resolver ContentResolver used to get information about the URI.
    803      * @param label User-visible label for the clip data.
    804      * @param uri The URI in the clip.
    805      * @return Returns a new ClipData containing the specified data.
    806      */
    807     static public ClipData newUri(ContentResolver resolver, CharSequence label,
    808             Uri uri) {
    809         Item item = new Item(uri);
    810         String[] mimeTypes = getMimeTypes(resolver, uri);
    811         return new ClipData(label, mimeTypes, item);
    812     }
    813 
    814     /**
    815      * Finds all applicable MIME types for a given URI.
    816      *
    817      * @param resolver ContentResolver used to get information about the URI.
    818      * @param uri The URI.
    819      * @return Returns an array of MIME types.
    820      */
    821     private static String[] getMimeTypes(ContentResolver resolver, Uri uri) {
    822         String[] mimeTypes = null;
    823         if (SCHEME_CONTENT.equals(uri.getScheme())) {
    824             String realType = resolver.getType(uri);
    825             mimeTypes = resolver.getStreamTypes(uri, "*/*");
    826             if (realType != null) {
    827                 if (mimeTypes == null) {
    828                     mimeTypes = new String[] { realType };
    829                 } else if (!ArrayUtils.contains(mimeTypes, realType)) {
    830                     String[] tmp = new String[mimeTypes.length + 1];
    831                     tmp[0] = realType;
    832                     System.arraycopy(mimeTypes, 0, tmp, 1, mimeTypes.length);
    833                     mimeTypes = tmp;
    834                 }
    835             }
    836         }
    837         if (mimeTypes == null) {
    838             mimeTypes = MIMETYPES_TEXT_URILIST;
    839         }
    840         return mimeTypes;
    841     }
    842 
    843     /**
    844      * Create a new ClipData holding an URI with MIME type
    845      * {@link ClipDescription#MIMETYPE_TEXT_URILIST}.
    846      * Unlike {@link #newUri(ContentResolver, CharSequence, Uri)}, nothing
    847      * is inferred about the URI -- if it is a content: URI holding a bitmap,
    848      * the reported type will still be uri-list.  Use this with care!
    849      *
    850      * @param label User-visible label for the clip data.
    851      * @param uri The URI in the clip.
    852      * @return Returns a new ClipData containing the specified data.
    853      */
    854     static public ClipData newRawUri(CharSequence label, Uri uri) {
    855         Item item = new Item(uri);
    856         return new ClipData(label, MIMETYPES_TEXT_URILIST, item);
    857     }
    858 
    859     /**
    860      * Return the {@link ClipDescription} associated with this data, describing
    861      * what it contains.
    862      */
    863     public ClipDescription getDescription() {
    864         return mClipDescription;
    865     }
    866 
    867     /**
    868      * Add a new Item to the overall ClipData container.
    869      * <p> This method will <em>not</em> update the list of available MIME types in the
    870      * {@link ClipDescription}. It should be used only when adding items which do not add new
    871      * MIME types to this clip. If this is not the case, use {@link #addItem(ContentResolver, Item)}
    872      * or call {@link #ClipData(CharSequence, String[], Item)} with a complete list of MIME types.
    873      * @param item Item to be added.
    874      */
    875     public void addItem(Item item) {
    876         if (item == null) {
    877             throw new NullPointerException("item is null");
    878         }
    879         mItems.add(item);
    880     }
    881 
    882     /** @removed use #addItem(ContentResolver, Item) instead */
    883     @Deprecated
    884     public void addItem(Item item, ContentResolver resolver) {
    885         addItem(resolver, item);
    886     }
    887 
    888     /**
    889      * Add a new Item to the overall ClipData container.
    890      * <p> Unlike {@link #addItem(Item)}, this method will update the list of available MIME types
    891      * in the {@link ClipDescription}.
    892      * @param resolver ContentResolver used to get information about the URI possibly contained in
    893      * the item.
    894      * @param item Item to be added.
    895      */
    896     public void addItem(ContentResolver resolver, Item item) {
    897         addItem(item);
    898 
    899         if (item.getHtmlText() != null) {
    900             mClipDescription.addMimeTypes(MIMETYPES_TEXT_HTML);
    901         } else if (item.getText() != null) {
    902             mClipDescription.addMimeTypes(MIMETYPES_TEXT_PLAIN);
    903         }
    904 
    905         if (item.getIntent() != null) {
    906             mClipDescription.addMimeTypes(MIMETYPES_TEXT_INTENT);
    907         }
    908 
    909         if (item.getUri() != null) {
    910             mClipDescription.addMimeTypes(getMimeTypes(resolver, item.getUri()));
    911         }
    912     }
    913 
    914     /** @hide */
    915     public Bitmap getIcon() {
    916         return mIcon;
    917     }
    918 
    919     /**
    920      * Return the number of items in the clip data.
    921      */
    922     public int getItemCount() {
    923         return mItems.size();
    924     }
    925 
    926     /**
    927      * Return a single item inside of the clip data.  The index can range
    928      * from 0 to {@link #getItemCount()}-1.
    929      */
    930     public Item getItemAt(int index) {
    931         return mItems.get(index);
    932     }
    933 
    934     /** @hide */
    935     public void setItemAt(int index, Item item) {
    936         mItems.set(index, item);
    937     }
    938 
    939     /**
    940      * Prepare this {@link ClipData} to leave an app process.
    941      *
    942      * @hide
    943      */
    944     public void prepareToLeaveProcess(boolean leavingPackage) {
    945         // Assume that callers are going to be granting permissions
    946         prepareToLeaveProcess(leavingPackage, Intent.FLAG_GRANT_READ_URI_PERMISSION);
    947     }
    948 
    949     /**
    950      * Prepare this {@link ClipData} to leave an app process.
    951      *
    952      * @hide
    953      */
    954     public void prepareToLeaveProcess(boolean leavingPackage, int intentFlags) {
    955         final int size = mItems.size();
    956         for (int i = 0; i < size; i++) {
    957             final Item item = mItems.get(i);
    958             if (item.mIntent != null) {
    959                 item.mIntent.prepareToLeaveProcess(leavingPackage);
    960             }
    961             if (item.mUri != null && leavingPackage) {
    962                 if (StrictMode.vmFileUriExposureEnabled()) {
    963                     item.mUri.checkFileUriExposed("ClipData.Item.getUri()");
    964                 }
    965                 if (StrictMode.vmContentUriWithoutPermissionEnabled()) {
    966                     item.mUri.checkContentUriWithoutPermission("ClipData.Item.getUri()",
    967                             intentFlags);
    968                 }
    969             }
    970         }
    971     }
    972 
    973     /** {@hide} */
    974     public void prepareToEnterProcess() {
    975         final int size = mItems.size();
    976         for (int i = 0; i < size; i++) {
    977             final Item item = mItems.get(i);
    978             if (item.mIntent != null) {
    979                 item.mIntent.prepareToEnterProcess();
    980             }
    981         }
    982     }
    983 
    984     /** @hide */
    985     public void fixUris(int contentUserHint) {
    986         final int size = mItems.size();
    987         for (int i = 0; i < size; i++) {
    988             final Item item = mItems.get(i);
    989             if (item.mIntent != null) {
    990                 item.mIntent.fixUris(contentUserHint);
    991             }
    992             if (item.mUri != null) {
    993                 item.mUri = maybeAddUserId(item.mUri, contentUserHint);
    994             }
    995         }
    996     }
    997 
    998     /**
    999      * Only fixing the data field of the intents
   1000      * @hide
   1001      */
   1002     public void fixUrisLight(int contentUserHint) {
   1003         final int size = mItems.size();
   1004         for (int i = 0; i < size; i++) {
   1005             final Item item = mItems.get(i);
   1006             if (item.mIntent != null) {
   1007                 Uri data = item.mIntent.getData();
   1008                 if (data != null) {
   1009                     item.mIntent.setData(maybeAddUserId(data, contentUserHint));
   1010                 }
   1011             }
   1012             if (item.mUri != null) {
   1013                 item.mUri = maybeAddUserId(item.mUri, contentUserHint);
   1014             }
   1015         }
   1016     }
   1017 
   1018     @Override
   1019     public String toString() {
   1020         StringBuilder b = new StringBuilder(128);
   1021 
   1022         b.append("ClipData { ");
   1023         toShortString(b);
   1024         b.append(" }");
   1025 
   1026         return b.toString();
   1027     }
   1028 
   1029     /** @hide */
   1030     public void toShortString(StringBuilder b) {
   1031         boolean first;
   1032         if (mClipDescription != null) {
   1033             first = !mClipDescription.toShortString(b);
   1034         } else {
   1035             first = true;
   1036         }
   1037         if (mIcon != null) {
   1038             if (!first) {
   1039                 b.append(' ');
   1040             }
   1041             first = false;
   1042             b.append("I:");
   1043             b.append(mIcon.getWidth());
   1044             b.append('x');
   1045             b.append(mIcon.getHeight());
   1046         }
   1047         for (int i=0; i<mItems.size(); i++) {
   1048             if (!first) {
   1049                 b.append(' ');
   1050             }
   1051             first = false;
   1052             b.append('{');
   1053             mItems.get(i).toShortString(b);
   1054             b.append('}');
   1055         }
   1056     }
   1057 
   1058     /** @hide */
   1059     public void toShortStringShortItems(StringBuilder b, boolean first) {
   1060         if (mItems.size() > 0) {
   1061             if (!first) {
   1062                 b.append(' ');
   1063             }
   1064             mItems.get(0).toShortString(b);
   1065             if (mItems.size() > 1) {
   1066                 b.append(" ...");
   1067             }
   1068         }
   1069     }
   1070 
   1071     /** @hide */
   1072     public void writeToProto(ProtoOutputStream proto, long fieldId) {
   1073         final long token = proto.start(fieldId);
   1074 
   1075         if (mClipDescription != null) {
   1076             mClipDescription.writeToProto(proto, ClipDataProto.DESCRIPTION);
   1077         }
   1078         if (mIcon != null) {
   1079             final long iToken = proto.start(ClipDataProto.ICON);
   1080             proto.write(ClipDataProto.Icon.WIDTH, mIcon.getWidth());
   1081             proto.write(ClipDataProto.Icon.HEIGHT, mIcon.getHeight());
   1082             proto.end(iToken);
   1083         }
   1084         for (int i = 0; i < mItems.size(); i++) {
   1085             mItems.get(i).writeToProto(proto, ClipDataProto.ITEMS);
   1086         }
   1087 
   1088         proto.end(token);
   1089     }
   1090 
   1091     /** @hide */
   1092     public void collectUris(List<Uri> out) {
   1093         for (int i = 0; i < mItems.size(); ++i) {
   1094             ClipData.Item item = getItemAt(i);
   1095 
   1096             if (item.getUri() != null) {
   1097                 out.add(item.getUri());
   1098             }
   1099 
   1100             Intent intent = item.getIntent();
   1101             if (intent != null) {
   1102                 if (intent.getData() != null) {
   1103                     out.add(intent.getData());
   1104                 }
   1105                 if (intent.getClipData() != null) {
   1106                     intent.getClipData().collectUris(out);
   1107                 }
   1108             }
   1109         }
   1110     }
   1111 
   1112     @Override
   1113     public int describeContents() {
   1114         return 0;
   1115     }
   1116 
   1117     @Override
   1118     public void writeToParcel(Parcel dest, int flags) {
   1119         mClipDescription.writeToParcel(dest, flags);
   1120         if (mIcon != null) {
   1121             dest.writeInt(1);
   1122             mIcon.writeToParcel(dest, flags);
   1123         } else {
   1124             dest.writeInt(0);
   1125         }
   1126         final int N = mItems.size();
   1127         dest.writeInt(N);
   1128         for (int i=0; i<N; i++) {
   1129             Item item = mItems.get(i);
   1130             TextUtils.writeToParcel(item.mText, dest, flags);
   1131             dest.writeString(item.mHtmlText);
   1132             if (item.mIntent != null) {
   1133                 dest.writeInt(1);
   1134                 item.mIntent.writeToParcel(dest, flags);
   1135             } else {
   1136                 dest.writeInt(0);
   1137             }
   1138             if (item.mUri != null) {
   1139                 dest.writeInt(1);
   1140                 item.mUri.writeToParcel(dest, flags);
   1141             } else {
   1142                 dest.writeInt(0);
   1143             }
   1144         }
   1145     }
   1146 
   1147     ClipData(Parcel in) {
   1148         mClipDescription = new ClipDescription(in);
   1149         if (in.readInt() != 0) {
   1150             mIcon = Bitmap.CREATOR.createFromParcel(in);
   1151         } else {
   1152             mIcon = null;
   1153         }
   1154         mItems = new ArrayList<Item>();
   1155         final int N = in.readInt();
   1156         for (int i=0; i<N; i++) {
   1157             CharSequence text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
   1158             String htmlText = in.readString();
   1159             Intent intent = in.readInt() != 0 ? Intent.CREATOR.createFromParcel(in) : null;
   1160             Uri uri = in.readInt() != 0 ? Uri.CREATOR.createFromParcel(in) : null;
   1161             mItems.add(new Item(text, htmlText, intent, uri));
   1162         }
   1163     }
   1164 
   1165     public static final Parcelable.Creator<ClipData> CREATOR =
   1166         new Parcelable.Creator<ClipData>() {
   1167 
   1168             @Override
   1169             public ClipData createFromParcel(Parcel source) {
   1170                 return new ClipData(source);
   1171             }
   1172 
   1173             @Override
   1174             public ClipData[] newArray(int size) {
   1175                 return new ClipData[size];
   1176             }
   1177         };
   1178 }
   1179