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