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 android.os.Parcel;
20import android.os.Parcelable;
21import android.os.PersistableBundle;
22import android.text.TextUtils;
23import android.util.TimeUtils;
24
25import java.util.ArrayList;
26import java.util.Arrays;
27
28/**
29 * Meta-data describing the contents of a {@link ClipData}.  Provides enough
30 * information to know if you can handle the ClipData, but not the data
31 * itself.
32 *
33 * <div class="special reference">
34 * <h3>Developer Guides</h3>
35 * <p>For more information about using the clipboard framework, read the
36 * <a href="{@docRoot}guide/topics/clipboard/copy-paste.html">Copy and Paste</a>
37 * developer guide.</p>
38 * </div>
39 */
40public class ClipDescription implements Parcelable {
41    /**
42     * The MIME type for a clip holding plain text.
43     */
44    public static final String MIMETYPE_TEXT_PLAIN = "text/plain";
45
46    /**
47     * The MIME type for a clip holding HTML text.
48     */
49    public static final String MIMETYPE_TEXT_HTML = "text/html";
50
51    /**
52     * The MIME type for a clip holding one or more URIs.  This should be
53     * used for URIs that are meaningful to a user (such as an http: URI).
54     * It should <em>not</em> be used for a content: URI that references some
55     * other piece of data; in that case the MIME type should be the type
56     * of the referenced data.
57     */
58    public static final String MIMETYPE_TEXT_URILIST = "text/uri-list";
59
60    /**
61     * The MIME type for a clip holding an Intent.
62     */
63    public static final String MIMETYPE_TEXT_INTENT = "text/vnd.android.intent";
64
65    /**
66     * The name of the extra used to define a component name when copying/dragging
67     * an app icon from Launcher.
68     * <p>
69     * Type: String
70     * </p>
71     * <p>
72     * Use {@link ComponentName#unflattenFromString(String)}
73     * and {@link ComponentName#flattenToString()} to convert the extra value
74     * to/from {@link ComponentName}.
75     * </p>
76     * @hide
77     */
78    public static final String EXTRA_TARGET_COMPONENT_NAME =
79            "android.content.extra.TARGET_COMPONENT_NAME";
80
81    /**
82     * The name of the extra used to define a user serial number when copying/dragging
83     * an app icon from Launcher.
84     * <p>
85     * Type: long
86     * </p>
87     * @hide
88     */
89    public static final String EXTRA_USER_SERIAL_NUMBER =
90            "android.content.extra.USER_SERIAL_NUMBER";
91
92
93    final CharSequence mLabel;
94    private final ArrayList<String> mMimeTypes;
95    private PersistableBundle mExtras;
96    private long mTimeStamp;
97
98    /**
99     * Create a new clip.
100     *
101     * @param label Label to show to the user describing this clip.
102     * @param mimeTypes An array of MIME types this data is available as.
103     */
104    public ClipDescription(CharSequence label, String[] mimeTypes) {
105        if (mimeTypes == null) {
106            throw new NullPointerException("mimeTypes is null");
107        }
108        mLabel = label;
109        mMimeTypes = new ArrayList<String>(Arrays.asList(mimeTypes));
110    }
111
112    /**
113     * Create a copy of a ClipDescription.
114     */
115    public ClipDescription(ClipDescription o) {
116        mLabel = o.mLabel;
117        mMimeTypes = new ArrayList<String>(o.mMimeTypes);
118        mTimeStamp = o.mTimeStamp;
119    }
120
121    /**
122     * Helper to compare two MIME types, where one may be a pattern.
123     * @param concreteType A fully-specified MIME type.
124     * @param desiredType A desired MIME type that may be a pattern such as *&#47;*.
125     * @return Returns true if the two MIME types match.
126     */
127    public static boolean compareMimeTypes(String concreteType, String desiredType) {
128        final int typeLength = desiredType.length();
129        if (typeLength == 3 && desiredType.equals("*/*")) {
130            return true;
131        }
132
133        final int slashpos = desiredType.indexOf('/');
134        if (slashpos > 0) {
135            if (typeLength == slashpos+2 && desiredType.charAt(slashpos+1) == '*') {
136                if (desiredType.regionMatches(0, concreteType, 0, slashpos+1)) {
137                    return true;
138                }
139            } else if (desiredType.equals(concreteType)) {
140                return true;
141            }
142        }
143
144        return false;
145    }
146
147    /**
148     * Used for setting the timestamp at which the associated {@link ClipData} is copied to
149     * global clipboard.
150     *
151     * @param timeStamp at which the associated {@link ClipData} is copied to clipboard in
152     *                  {@link System#currentTimeMillis()} time base.
153     * @hide
154     */
155    public void setTimestamp(long timeStamp) {
156        mTimeStamp = timeStamp;
157    }
158
159    /**
160     * Return the timestamp at which the associated {@link ClipData} is copied to global clipboard
161     * in the {@link System#currentTimeMillis()} time base.
162     *
163     * @return timestamp at which the associated {@link ClipData} is copied to global clipboard
164     *         or {@code 0} if it is not copied to clipboard.
165     */
166    public long getTimestamp() {
167        return mTimeStamp;
168    }
169
170    /**
171     * Return the label for this clip.
172     */
173    public CharSequence getLabel() {
174        return mLabel;
175    }
176
177    /**
178     * Check whether the clip description contains the given MIME type.
179     *
180     * @param mimeType The desired MIME type.  May be a pattern.
181     * @return Returns true if one of the MIME types in the clip description
182     * matches the desired MIME type, else false.
183     */
184    public boolean hasMimeType(String mimeType) {
185        final int size = mMimeTypes.size();
186        for (int i=0; i<size; i++) {
187            if (compareMimeTypes(mMimeTypes.get(i), mimeType)) {
188                return true;
189            }
190        }
191        return false;
192    }
193
194    /**
195     * Filter the clip description MIME types by the given MIME type.  Returns
196     * all MIME types in the clip that match the given MIME type.
197     *
198     * @param mimeType The desired MIME type.  May be a pattern.
199     * @return Returns an array of all matching MIME types.  If there are no
200     * matching MIME types, null is returned.
201     */
202    public String[] filterMimeTypes(String mimeType) {
203        ArrayList<String> array = null;
204        final int size = mMimeTypes.size();
205        for (int i=0; i<size; i++) {
206            if (compareMimeTypes(mMimeTypes.get(i), mimeType)) {
207                if (array == null) {
208                    array = new ArrayList<String>();
209                }
210                array.add(mMimeTypes.get(i));
211            }
212        }
213        if (array == null) {
214            return null;
215        }
216        String[] rawArray = new String[array.size()];
217        array.toArray(rawArray);
218        return rawArray;
219    }
220
221    /**
222     * Return the number of MIME types the clip is available in.
223     */
224    public int getMimeTypeCount() {
225        return mMimeTypes.size();
226    }
227
228    /**
229     * Return one of the possible clip MIME types.
230     */
231    public String getMimeType(int index) {
232        return mMimeTypes.get(index);
233    }
234
235    /**
236     * Add MIME types to the clip description.
237     */
238    void addMimeTypes(String[] mimeTypes) {
239        for (int i=0; i!=mimeTypes.length; i++) {
240            final String mimeType = mimeTypes[i];
241            if (!mMimeTypes.contains(mimeType)) {
242                mMimeTypes.add(mimeType);
243            }
244        }
245    }
246
247    /**
248     * Retrieve extended data from the clip description.
249     *
250     * @return the bundle containing extended data previously set with
251     * {@link #setExtras(PersistableBundle)}, or null if no extras have been set.
252     *
253     * @see #setExtras(PersistableBundle)
254     */
255    public PersistableBundle getExtras() {
256        return mExtras;
257    }
258
259    /**
260     * Add extended data to the clip description.
261     *
262     * @see #getExtras()
263     */
264    public void setExtras(PersistableBundle extras) {
265        mExtras = new PersistableBundle(extras);
266    }
267
268    /** @hide */
269    public void validate() {
270        if (mMimeTypes == null) {
271            throw new NullPointerException("null mime types");
272        }
273        final int size = mMimeTypes.size();
274        if (size <= 0) {
275            throw new IllegalArgumentException("must have at least 1 mime type");
276        }
277        for (int i=0; i<size; i++) {
278            if (mMimeTypes.get(i) == null) {
279                throw new NullPointerException("mime type at " + i + " is null");
280            }
281        }
282    }
283
284    @Override
285    public String toString() {
286        StringBuilder b = new StringBuilder(128);
287
288        b.append("ClipDescription { ");
289        toShortString(b);
290        b.append(" }");
291
292        return b.toString();
293    }
294
295    /** @hide */
296    public boolean toShortString(StringBuilder b) {
297        boolean first = !toShortStringTypesOnly(b);
298        if (mLabel != null) {
299            if (!first) {
300                b.append(' ');
301            }
302            first = false;
303            b.append('"');
304            b.append(mLabel);
305            b.append('"');
306        }
307        if (mExtras != null) {
308            if (!first) {
309                b.append(' ');
310            }
311            first = false;
312            b.append(mExtras.toString());
313        }
314        if (mTimeStamp > 0) {
315            if (!first) {
316                b.append(' ');
317            }
318            first = false;
319            b.append('<');
320            b.append(TimeUtils.logTimeOfDay(mTimeStamp));
321            b.append('>');
322        }
323        return !first;
324    }
325
326    /** @hide */
327    public boolean toShortStringTypesOnly(StringBuilder b) {
328        boolean first = true;
329        final int size = mMimeTypes.size();
330        for (int i=0; i<size; i++) {
331            if (!first) {
332                b.append(' ');
333            }
334            first = false;
335            b.append(mMimeTypes.get(i));
336        }
337        return !first;
338    }
339
340    @Override
341    public int describeContents() {
342        return 0;
343    }
344
345    @Override
346    public void writeToParcel(Parcel dest, int flags) {
347        TextUtils.writeToParcel(mLabel, dest, flags);
348        dest.writeStringList(mMimeTypes);
349        dest.writePersistableBundle(mExtras);
350        dest.writeLong(mTimeStamp);
351    }
352
353    ClipDescription(Parcel in) {
354        mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
355        mMimeTypes = in.createStringArrayList();
356        mExtras = in.readPersistableBundle();
357        mTimeStamp = in.readLong();
358    }
359
360    public static final Parcelable.Creator<ClipDescription> CREATOR =
361        new Parcelable.Creator<ClipDescription>() {
362
363            public ClipDescription createFromParcel(Parcel source) {
364                return new ClipDescription(source);
365            }
366
367            public ClipDescription[] newArray(int size) {
368                return new ClipDescription[size];
369            }
370        };
371}
372