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