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