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