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