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