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