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