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.annotation.FloatRange; 20 import android.annotation.IntDef; 21 import android.annotation.NonNull; 22 import android.os.Bundle; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 import android.os.PersistableBundle; 26 import android.text.TextUtils; 27 import android.util.ArrayMap; 28 import android.util.TimeUtils; 29 import android.util.proto.ProtoOutputStream; 30 import android.view.textclassifier.TextClassifier; 31 import android.view.textclassifier.TextLinks; 32 33 import java.lang.annotation.Retention; 34 import java.lang.annotation.RetentionPolicy; 35 import java.util.ArrayList; 36 import java.util.Arrays; 37 import java.util.Map; 38 39 /** 40 * Meta-data describing the contents of a {@link ClipData}. Provides enough 41 * information to know if you can handle the ClipData, but not the data 42 * itself. 43 * 44 * <div class="special reference"> 45 * <h3>Developer Guides</h3> 46 * <p>For more information about using the clipboard framework, read the 47 * <a href="{@docRoot}guide/topics/clipboard/copy-paste.html">Copy and Paste</a> 48 * developer guide.</p> 49 * </div> 50 */ 51 public class ClipDescription implements Parcelable { 52 /** 53 * The MIME type for a clip holding plain text. 54 */ 55 public static final String MIMETYPE_TEXT_PLAIN = "text/plain"; 56 57 /** 58 * The MIME type for a clip holding HTML text. 59 */ 60 public static final String MIMETYPE_TEXT_HTML = "text/html"; 61 62 /** 63 * The MIME type for a clip holding one or more URIs. This should be 64 * used for URIs that are meaningful to a user (such as an http: URI). 65 * It should <em>not</em> be used for a content: URI that references some 66 * other piece of data; in that case the MIME type should be the type 67 * of the referenced data. 68 */ 69 public static final String MIMETYPE_TEXT_URILIST = "text/uri-list"; 70 71 /** 72 * The MIME type for a clip holding an Intent. 73 */ 74 public static final String MIMETYPE_TEXT_INTENT = "text/vnd.android.intent"; 75 76 /** 77 * The MIME type for an activity. The ClipData must include intents with required extras 78 * {@link #EXTRA_PENDING_INTENT} and {@link Intent#EXTRA_USER}, and an optional 79 * {@link #EXTRA_ACTIVITY_OPTIONS}. 80 * @hide 81 */ 82 public static final String MIMETYPE_APPLICATION_ACTIVITY = "application/vnd.android.activity"; 83 84 /** 85 * The MIME type for a shortcut. The ClipData must include intents with required extras 86 * {@link Intent#EXTRA_SHORTCUT_ID}, {@link Intent#EXTRA_PACKAGE_NAME} and 87 * {@link Intent#EXTRA_USER}, and an optional {@link #EXTRA_ACTIVITY_OPTIONS}. 88 * @hide 89 */ 90 public static final String MIMETYPE_APPLICATION_SHORTCUT = "application/vnd.android.shortcut"; 91 92 /** 93 * The MIME type for a task. The ClipData must include an intent with a required extra 94 * {@link Intent#EXTRA_TASK_ID} of the task to launch. 95 * @hide 96 */ 97 public static final String MIMETYPE_APPLICATION_TASK = "application/vnd.android.task"; 98 99 /** 100 * The MIME type for data whose type is otherwise unknown. 101 * <p> 102 * Per RFC 2046, the "application" media type is to be used for discrete 103 * data which do not fit in any of the other categories, and the 104 * "octet-stream" subtype is used to indicate that a body contains arbitrary 105 * binary data. 106 */ 107 public static final String MIMETYPE_UNKNOWN = "application/octet-stream"; 108 109 /** 110 * The pending intent for the activity to launch. 111 * <p> 112 * Type: PendingIntent 113 * </p> 114 * @hide 115 */ 116 public static final String EXTRA_PENDING_INTENT = "android.intent.extra.PENDING_INTENT"; 117 118 /** 119 * The activity options bundle to use when launching an activity. 120 * <p> 121 * Type: Bundle 122 * </p> 123 * @hide 124 */ 125 public static final String EXTRA_ACTIVITY_OPTIONS = "android.intent.extra.ACTIVITY_OPTIONS"; 126 127 /** 128 * An instance id used for logging. 129 * <p> 130 * Type: {@link com.android.internal.logging.InstanceId} 131 * </p> 132 * @hide 133 */ 134 public static final String EXTRA_LOGGING_INSTANCE_ID = 135 "android.intent.extra.LOGGING_INSTANCE_ID"; 136 137 /** @hide */ 138 @Retention(RetentionPolicy.SOURCE) 139 @IntDef(value = 140 { CLASSIFICATION_NOT_COMPLETE, CLASSIFICATION_NOT_PERFORMED, CLASSIFICATION_COMPLETE}) 141 @interface ClassificationStatus {} 142 143 /** 144 * Value returned by {@link #getConfidenceScore(String)} if text classification has not been 145 * completed on the associated clip. This will be always be the case if the clip has not been 146 * copied to clipboard, or if there is no associated clip. 147 */ 148 public static final int CLASSIFICATION_NOT_COMPLETE = 1; 149 150 /** 151 * Value returned by {@link #getConfidenceScore(String)} if text classification was not and will 152 * not be performed on the associated clip. This may be the case if the clip does not contain 153 * text in its first item, or if the text is too long. 154 */ 155 public static final int CLASSIFICATION_NOT_PERFORMED = 2; 156 157 /** 158 * Value returned by {@link #getConfidenceScore(String)} if text classification has been 159 * completed. 160 */ 161 public static final int CLASSIFICATION_COMPLETE = 3; 162 163 final CharSequence mLabel; 164 private final ArrayList<String> mMimeTypes; 165 private PersistableBundle mExtras; 166 private long mTimeStamp; 167 private boolean mIsStyledText; 168 private final ArrayMap<String, Float> mEntityConfidence = new ArrayMap<>(); 169 private int mClassificationStatus = CLASSIFICATION_NOT_COMPLETE; 170 171 /** 172 * Create a new clip. 173 * 174 * @param label Label to show to the user describing this clip. 175 * @param mimeTypes An array of MIME types this data is available as. 176 */ ClipDescription(CharSequence label, String[] mimeTypes)177 public ClipDescription(CharSequence label, String[] mimeTypes) { 178 if (mimeTypes == null) { 179 throw new NullPointerException("mimeTypes is null"); 180 } 181 mLabel = label; 182 mMimeTypes = new ArrayList<String>(Arrays.asList(mimeTypes)); 183 } 184 185 /** 186 * Create a copy of a ClipDescription. 187 */ ClipDescription(ClipDescription o)188 public ClipDescription(ClipDescription o) { 189 mLabel = o.mLabel; 190 mMimeTypes = new ArrayList<String>(o.mMimeTypes); 191 mTimeStamp = o.mTimeStamp; 192 } 193 194 /** 195 * Helper to compare two MIME types, where one may be a pattern. 196 * @param concreteType A fully-specified MIME type. 197 * @param desiredType A desired MIME type that may be a pattern such as */*. 198 * @return Returns true if the two MIME types match. 199 */ compareMimeTypes(String concreteType, String desiredType)200 public static boolean compareMimeTypes(String concreteType, String desiredType) { 201 final int typeLength = desiredType.length(); 202 if (typeLength == 3 && desiredType.equals("*/*")) { 203 return true; 204 } 205 206 final int slashpos = desiredType.indexOf('/'); 207 if (slashpos > 0) { 208 if (typeLength == slashpos+2 && desiredType.charAt(slashpos+1) == '*') { 209 if (desiredType.regionMatches(0, concreteType, 0, slashpos+1)) { 210 return true; 211 } 212 } else if (desiredType.equals(concreteType)) { 213 return true; 214 } 215 } 216 217 return false; 218 } 219 220 /** 221 * Used for setting the timestamp at which the associated {@link ClipData} is copied to 222 * global clipboard. 223 * 224 * @param timeStamp at which the associated {@link ClipData} is copied to clipboard in 225 * {@link System#currentTimeMillis()} time base. 226 * @hide 227 */ setTimestamp(long timeStamp)228 public void setTimestamp(long timeStamp) { 229 mTimeStamp = timeStamp; 230 } 231 232 /** 233 * Return the timestamp at which the associated {@link ClipData} is copied to global clipboard 234 * in the {@link System#currentTimeMillis()} time base. 235 * 236 * @return timestamp at which the associated {@link ClipData} is copied to global clipboard 237 * or {@code 0} if it is not copied to clipboard. 238 */ getTimestamp()239 public long getTimestamp() { 240 return mTimeStamp; 241 } 242 243 /** 244 * Return the label for this clip. 245 */ getLabel()246 public CharSequence getLabel() { 247 return mLabel; 248 } 249 250 /** 251 * Check whether the clip description contains the given MIME type. 252 * 253 * @param mimeType The desired MIME type. May be a pattern. 254 * @return Returns true if one of the MIME types in the clip description 255 * matches the desired MIME type, else false. 256 */ hasMimeType(String mimeType)257 public boolean hasMimeType(String mimeType) { 258 final int size = mMimeTypes.size(); 259 for (int i=0; i<size; i++) { 260 if (compareMimeTypes(mMimeTypes.get(i), mimeType)) { 261 return true; 262 } 263 } 264 return false; 265 } 266 267 /** 268 * Check whether the clip description contains any of the given MIME types. 269 * 270 * @param targetMimeTypes The target MIME types. May use patterns. 271 * @return Returns true if at least one of the MIME types in the clip description matches at 272 * least one of the target MIME types, else false. 273 * 274 * @hide 275 */ hasMimeType(@onNull String[] targetMimeTypes)276 public boolean hasMimeType(@NonNull String[] targetMimeTypes) { 277 for (String targetMimeType : targetMimeTypes) { 278 if (hasMimeType(targetMimeType)) { 279 return true; 280 } 281 } 282 return false; 283 } 284 285 /** 286 * Filter the clip description MIME types by the given MIME type. Returns 287 * all MIME types in the clip that match the given MIME type. 288 * 289 * @param mimeType The desired MIME type. May be a pattern. 290 * @return Returns an array of all matching MIME types. If there are no 291 * matching MIME types, null is returned. 292 */ filterMimeTypes(String mimeType)293 public String[] filterMimeTypes(String mimeType) { 294 ArrayList<String> array = null; 295 final int size = mMimeTypes.size(); 296 for (int i=0; i<size; i++) { 297 if (compareMimeTypes(mMimeTypes.get(i), mimeType)) { 298 if (array == null) { 299 array = new ArrayList<String>(); 300 } 301 array.add(mMimeTypes.get(i)); 302 } 303 } 304 if (array == null) { 305 return null; 306 } 307 String[] rawArray = new String[array.size()]; 308 array.toArray(rawArray); 309 return rawArray; 310 } 311 312 /** 313 * Return the number of MIME types the clip is available in. 314 */ getMimeTypeCount()315 public int getMimeTypeCount() { 316 return mMimeTypes.size(); 317 } 318 319 /** 320 * Return one of the possible clip MIME types. 321 */ getMimeType(int index)322 public String getMimeType(int index) { 323 return mMimeTypes.get(index); 324 } 325 326 /** 327 * Add MIME types to the clip description. 328 */ addMimeTypes(String[] mimeTypes)329 void addMimeTypes(String[] mimeTypes) { 330 for (int i=0; i!=mimeTypes.length; i++) { 331 final String mimeType = mimeTypes[i]; 332 if (!mMimeTypes.contains(mimeType)) { 333 mMimeTypes.add(mimeType); 334 } 335 } 336 } 337 338 /** 339 * Retrieve extended data from the clip description. 340 * 341 * @return the bundle containing extended data previously set with 342 * {@link #setExtras(PersistableBundle)}, or null if no extras have been set. 343 * 344 * @see #setExtras(PersistableBundle) 345 */ getExtras()346 public PersistableBundle getExtras() { 347 return mExtras; 348 } 349 350 /** 351 * Add extended data to the clip description. 352 * 353 * @see #getExtras() 354 */ setExtras(PersistableBundle extras)355 public void setExtras(PersistableBundle extras) { 356 mExtras = new PersistableBundle(extras); 357 } 358 359 /** @hide */ validate()360 public void validate() { 361 if (mMimeTypes == null) { 362 throw new NullPointerException("null mime types"); 363 } 364 final int size = mMimeTypes.size(); 365 if (size <= 0) { 366 throw new IllegalArgumentException("must have at least 1 mime type"); 367 } 368 for (int i=0; i<size; i++) { 369 if (mMimeTypes.get(i) == null) { 370 throw new NullPointerException("mime type at " + i + " is null"); 371 } 372 } 373 } 374 375 /** 376 * Returns true if the first item of the associated {@link ClipData} contains styled text, i.e. 377 * if it contains spans such as {@link android.text.style.CharacterStyle CharacterStyle}, {@link 378 * android.text.style.ParagraphStyle ParagraphStyle}, or {@link 379 * android.text.style.UpdateAppearance UpdateAppearance}. Returns false if it does not, or if 380 * there is no associated clip data. 381 */ isStyledText()382 public boolean isStyledText() { 383 return mIsStyledText; 384 } 385 386 /** 387 * Sets whether the associated {@link ClipData} contains styled text in its first item. This 388 * should be called when this description is associated with clip data or when the first item 389 * is added to the associated clip data. 390 */ setIsStyledText(boolean isStyledText)391 void setIsStyledText(boolean isStyledText) { 392 mIsStyledText = isStyledText; 393 } 394 395 /** 396 * Sets the current status of text classification for the associated clip. 397 * 398 * @hide 399 */ setClassificationStatus(@lassificationStatus int status)400 public void setClassificationStatus(@ClassificationStatus int status) { 401 mClassificationStatus = status; 402 } 403 404 /** 405 * Returns a score indicating confidence that an instance of the given entity is present in the 406 * first item of the clip data, if that item is plain text and text classification has been 407 * performed. The value ranges from 0 (low confidence) to 1 (high confidence). 0 indicates that 408 * the entity was not found in the classified text. 409 * 410 * <p>Entities should be as defined in the {@link TextClassifier} class, such as 411 * {@link TextClassifier#TYPE_ADDRESS}, {@link TextClassifier#TYPE_URL}, or 412 * {@link TextClassifier#TYPE_EMAIL}. 413 * 414 * <p>If the result is positive for any entity, the full classification result as a 415 * {@link TextLinks} object may be obtained using the {@link ClipData.Item#getTextLinks()} 416 * method. 417 * 418 * @throws IllegalStateException if {@link #getClassificationStatus()} is not 419 * {@link #CLASSIFICATION_COMPLETE} 420 */ 421 @FloatRange(from = 0.0, to = 1.0) getConfidenceScore(@onNull @extClassifier.EntityType String entity)422 public float getConfidenceScore(@NonNull @TextClassifier.EntityType String entity) { 423 if (mClassificationStatus != CLASSIFICATION_COMPLETE) { 424 throw new IllegalStateException("Classification not complete"); 425 } 426 return mEntityConfidence.getOrDefault(entity, 0f); 427 } 428 429 /** 430 * Returns {@link #CLASSIFICATION_COMPLETE} if text classification has been performed on the 431 * associated {@link ClipData}. If this is the case then {@link #getConfidenceScore} may be used 432 * to retrieve information about entities within the text. Otherwise, returns 433 * {@link #CLASSIFICATION_NOT_COMPLETE} if classification has not yet returned results, or 434 * {@link #CLASSIFICATION_NOT_PERFORMED} if classification was not attempted (e.g. because the 435 * text was too long). 436 */ getClassificationStatus()437 public @ClassificationStatus int getClassificationStatus() { 438 return mClassificationStatus; 439 } 440 441 /** 442 * @hide 443 */ setConfidenceScores(Map<String, Float> confidences)444 public void setConfidenceScores(Map<String, Float> confidences) { 445 mEntityConfidence.clear(); 446 mEntityConfidence.putAll(confidences); 447 mClassificationStatus = CLASSIFICATION_COMPLETE; 448 } 449 450 @Override toString()451 public String toString() { 452 StringBuilder b = new StringBuilder(128); 453 454 b.append("ClipDescription { "); 455 toShortString(b, true); 456 b.append(" }"); 457 458 return b.toString(); 459 } 460 461 /** 462 * Appends this description to the given builder. 463 * @param redactContent If true, redacts common forms of PII; otherwise appends full details. 464 * @hide 465 */ toShortString(StringBuilder b, boolean redactContent)466 public boolean toShortString(StringBuilder b, boolean redactContent) { 467 boolean first = !toShortStringTypesOnly(b); 468 if (mLabel != null) { 469 if (!first) { 470 b.append(' '); 471 } 472 first = false; 473 if (redactContent) { 474 b.append("hasLabel(").append(mLabel.length()).append(')'); 475 } else { 476 b.append('"').append(mLabel).append('"'); 477 } 478 } 479 if (mExtras != null) { 480 if (!first) { 481 b.append(' '); 482 } 483 first = false; 484 if (redactContent) { 485 if (mExtras.isParcelled()) { 486 // We don't want this toString function to trigger un-parcelling. 487 b.append("hasExtras"); 488 } else { 489 b.append("hasExtras(").append(mExtras.size()).append(')'); 490 } 491 } else { 492 b.append(mExtras.toString()); 493 } 494 } 495 if (mTimeStamp > 0) { 496 if (!first) { 497 b.append(' '); 498 } 499 first = false; 500 b.append('<'); 501 b.append(TimeUtils.logTimeOfDay(mTimeStamp)); 502 b.append('>'); 503 } 504 return !first; 505 } 506 507 /** @hide */ toShortStringTypesOnly(StringBuilder b)508 public boolean toShortStringTypesOnly(StringBuilder b) { 509 boolean first = true; 510 final int size = mMimeTypes.size(); 511 for (int i=0; i<size; i++) { 512 if (!first) { 513 b.append(' '); 514 } 515 first = false; 516 b.append(mMimeTypes.get(i)); 517 } 518 return !first; 519 } 520 521 /** @hide */ dumpDebug(ProtoOutputStream proto, long fieldId)522 public void dumpDebug(ProtoOutputStream proto, long fieldId) { 523 final long token = proto.start(fieldId); 524 525 final int size = mMimeTypes.size(); 526 for (int i = 0; i < size; i++) { 527 proto.write(ClipDescriptionProto.MIME_TYPES, mMimeTypes.get(i)); 528 } 529 530 if (mLabel != null) { 531 proto.write(ClipDescriptionProto.LABEL, mLabel.toString()); 532 } 533 if (mExtras != null) { 534 mExtras.dumpDebug(proto, ClipDescriptionProto.EXTRAS); 535 } 536 if (mTimeStamp > 0) { 537 proto.write(ClipDescriptionProto.TIMESTAMP_MS, mTimeStamp); 538 } 539 540 proto.end(token); 541 } 542 543 @Override describeContents()544 public int describeContents() { 545 return 0; 546 } 547 548 @Override writeToParcel(Parcel dest, int flags)549 public void writeToParcel(Parcel dest, int flags) { 550 TextUtils.writeToParcel(mLabel, dest, flags); 551 dest.writeStringList(mMimeTypes); 552 dest.writePersistableBundle(mExtras); 553 dest.writeLong(mTimeStamp); 554 dest.writeBoolean(mIsStyledText); 555 dest.writeInt(mClassificationStatus); 556 dest.writeBundle(confidencesToBundle()); 557 } 558 confidencesToBundle()559 private Bundle confidencesToBundle() { 560 Bundle bundle = new Bundle(); 561 int size = mEntityConfidence.size(); 562 for (int i = 0; i < size; i++) { 563 bundle.putFloat(mEntityConfidence.keyAt(i), mEntityConfidence.valueAt(i)); 564 } 565 return bundle; 566 } 567 readBundleToConfidences(Bundle bundle)568 private void readBundleToConfidences(Bundle bundle) { 569 for (String key : bundle.keySet()) { 570 mEntityConfidence.put(key, bundle.getFloat(key)); 571 } 572 } 573 ClipDescription(Parcel in)574 ClipDescription(Parcel in) { 575 mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 576 mMimeTypes = in.createStringArrayList(); 577 mExtras = in.readPersistableBundle(); 578 mTimeStamp = in.readLong(); 579 mIsStyledText = in.readBoolean(); 580 mClassificationStatus = in.readInt(); 581 readBundleToConfidences(in.readBundle()); 582 } 583 584 public static final @android.annotation.NonNull Parcelable.Creator<ClipDescription> CREATOR = 585 new Parcelable.Creator<ClipDescription>() { 586 587 public ClipDescription createFromParcel(Parcel source) { 588 return new ClipDescription(source); 589 } 590 591 public ClipDescription[] newArray(int size) { 592 return new ClipDescription[size]; 593 } 594 }; 595 } 596