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