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