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