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