ShortcutInfo.java revision 2c0ae91f2d22b2c9a3b506d3a7f60bc31f72c57d
1/* 2 * Copyright (C) 2016 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 */ 16package android.content.pm; 17 18import android.annotation.IntDef; 19import android.annotation.NonNull; 20import android.annotation.Nullable; 21import android.content.ComponentName; 22import android.content.ContentResolver; 23import android.content.Context; 24import android.content.Intent; 25import android.graphics.drawable.Icon; 26import android.os.Bundle; 27import android.os.Parcel; 28import android.os.Parcelable; 29import android.os.PersistableBundle; 30import android.os.UserHandle; 31 32import com.android.internal.util.Preconditions; 33 34import java.lang.annotation.Retention; 35import java.lang.annotation.RetentionPolicy; 36 37// TODO Enhance javadoc 38/** 39 * 40 * Represents a shortcut from an application. 41 * 42 * <p>Notes about icons: 43 * <ul> 44 * <li>If an {@link Icon} is a resource, the system keeps the package name and the resource ID. 45 * Otherwise, the bitmap is fetched when it's registered to ShortcutManager, 46 * then shrunk if necessary, and persisted. 47 * <li>The system disallows byte[] icons, because they can easily go over the binder size limit. 48 * </ul> 49 * 50 * @see {@link ShortcutManager}. 51 */ 52public final class ShortcutInfo implements Parcelable { 53 /* @hide */ 54 public static final int FLAG_DYNAMIC = 1 << 0; 55 56 /* @hide */ 57 public static final int FLAG_PINNED = 1 << 1; 58 59 /* @hide */ 60 public static final int FLAG_HAS_ICON_RES = 1 << 2; 61 62 /* @hide */ 63 public static final int FLAG_HAS_ICON_FILE = 1 << 3; 64 65 /* @hide */ 66 public static final int FLAG_KEY_FIELDS_ONLY = 1 << 4; 67 68 /** @hide */ 69 @IntDef(flag = true, 70 value = { 71 FLAG_DYNAMIC, 72 FLAG_PINNED, 73 FLAG_HAS_ICON_RES, 74 FLAG_HAS_ICON_FILE, 75 FLAG_KEY_FIELDS_ONLY, 76 }) 77 @Retention(RetentionPolicy.SOURCE) 78 public @interface ShortcutFlags {} 79 80 // Cloning options. 81 82 /* @hide */ 83 private static final int CLONE_REMOVE_ICON = 1 << 0; 84 85 /* @hide */ 86 private static final int CLONE_REMOVE_INTENT = 1 << 1; 87 88 /* @hide */ 89 public static final int CLONE_REMOVE_NON_KEY_INFO = 1 << 2; 90 91 /* @hide */ 92 public static final int CLONE_REMOVE_FOR_CREATOR = CLONE_REMOVE_ICON; 93 94 /* @hide */ 95 public static final int CLONE_REMOVE_FOR_LAUNCHER = CLONE_REMOVE_ICON | CLONE_REMOVE_INTENT; 96 97 /** @hide */ 98 @IntDef(flag = true, 99 value = { 100 CLONE_REMOVE_ICON, 101 CLONE_REMOVE_INTENT, 102 CLONE_REMOVE_NON_KEY_INFO, 103 CLONE_REMOVE_FOR_CREATOR, 104 CLONE_REMOVE_FOR_LAUNCHER 105 }) 106 @Retention(RetentionPolicy.SOURCE) 107 public @interface CloneFlags {} 108 109 private final String mId; 110 111 @NonNull 112 private final String mPackageName; 113 114 @Nullable 115 private ComponentName mActivityComponent; 116 117 @Nullable 118 private Icon mIcon; 119 120 @NonNull 121 private String mTitle; 122 123 @Nullable 124 private String mText; 125 126 /** 127 * Intent *with extras removed*. 128 */ 129 @NonNull 130 private Intent mIntent; 131 132 /** 133 * Extras for the intent. 134 */ 135 @NonNull 136 private PersistableBundle mIntentPersistableExtras; 137 138 private int mWeight; 139 140 @Nullable 141 private PersistableBundle mExtras; 142 143 private long mLastChangedTimestamp; 144 145 // Internal use only. 146 @ShortcutFlags 147 private int mFlags; 148 149 // Internal use only. 150 private int mIconResourceId; 151 152 // Internal use only. 153 @Nullable 154 private String mBitmapPath; 155 156 private ShortcutInfo(Builder b) { 157 mId = Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided"); 158 159 // Note we can't do other null checks here because SM.updateShortcuts() takes partial 160 // information. 161 mPackageName = b.mContext.getPackageName(); 162 mActivityComponent = b.mActivityComponent; 163 mIcon = b.mIcon; 164 mTitle = b.mTitle; 165 mText = b.mText; 166 mIntent = b.mIntent; 167 if (mIntent != null) { 168 final Bundle intentExtras = mIntent.getExtras(); 169 if (intentExtras != null) { 170 mIntent.replaceExtras((Bundle) null); 171 mIntentPersistableExtras = new PersistableBundle(intentExtras); 172 } 173 } 174 mWeight = b.mWeight; 175 mExtras = b.mExtras; 176 updateTimestamp(); 177 } 178 179 /** 180 * Throws if any of the mandatory fields is not set. 181 * 182 * @hide 183 */ 184 public void enforceMandatoryFields() { 185 Preconditions.checkStringNotEmpty(mId, "Shortcut ID must be provided"); 186 Preconditions.checkStringNotEmpty(mTitle, "Shortcut title must be provided"); 187 Preconditions.checkNotNull(mIntent, "Shortcut Intent must be provided"); 188 } 189 190 /** 191 * Copy constructor. 192 */ 193 private ShortcutInfo(ShortcutInfo source, @CloneFlags int cloneFlags) { 194 mId = source.mId; 195 mPackageName = source.mPackageName; 196 mFlags = source.mFlags; 197 mLastChangedTimestamp = source.mLastChangedTimestamp; 198 199 if ((cloneFlags & CLONE_REMOVE_NON_KEY_INFO) == 0) { 200 mActivityComponent = source.mActivityComponent; 201 202 if ((cloneFlags & CLONE_REMOVE_ICON) == 0) { 203 mIcon = source.mIcon; 204 mBitmapPath = source.mBitmapPath; 205 mIconResourceId = source.mIconResourceId; 206 } 207 208 mTitle = source.mTitle; 209 mText = source.mText; 210 if ((cloneFlags & CLONE_REMOVE_INTENT) == 0) { 211 mIntent = source.mIntent; 212 mIntentPersistableExtras = source.mIntentPersistableExtras; 213 } 214 mWeight = source.mWeight; 215 mExtras = source.mExtras; 216 } else { 217 // Set this bit. 218 mFlags |= FLAG_KEY_FIELDS_ONLY; 219 } 220 } 221 222 /** 223 * Copy a {@link ShortcutInfo}, optionally removing fields. 224 * @hide 225 */ 226 public ShortcutInfo clone(@CloneFlags int cloneFlags) { 227 return new ShortcutInfo(this, cloneFlags); 228 } 229 230 /** 231 * Copy non-null/zero fields from another {@link ShortcutInfo}. Only "public" information 232 * will be overwritten. The timestamp will be updated. 233 * 234 * - Flags will not change 235 * - mBitmapPath will not change 236 * - Current time will be set to timestamp 237 * 238 * @hide 239 */ 240 public void copyNonNullFieldsFrom(ShortcutInfo source) { 241 Preconditions.checkState(mId.equals(source.mId), "ID must match"); 242 Preconditions.checkState(mPackageName.equals(source.mPackageName), 243 "Package name must match"); 244 245 if (source.mActivityComponent != null) { 246 mActivityComponent = source.mActivityComponent; 247 } 248 249 if (source.mIcon != null) { 250 mIcon = source.mIcon; 251 } 252 if (source.mTitle != null) { 253 mTitle = source.mTitle; 254 } 255 if (source.mText != null) { 256 mText = source.mText; 257 } 258 if (source.mIntent != null) { 259 mIntent = source.mIntent; 260 mIntentPersistableExtras = source.mIntentPersistableExtras; 261 } 262 if (source.mWeight != 0) { 263 mWeight = source.mWeight; 264 } 265 if (source.mExtras != null) { 266 mExtras = source.mExtras; 267 } 268 269 updateTimestamp(); 270 } 271 272 /** 273 * @hide 274 */ 275 public static Icon validateIcon(Icon icon) { 276 switch (icon.getType()) { 277 case Icon.TYPE_RESOURCE: 278 case Icon.TYPE_BITMAP: 279 break; // OK 280 case Icon.TYPE_URI: 281 if (ContentResolver.SCHEME_CONTENT.equals(icon.getUri().getScheme())) { 282 break; 283 } 284 // Note "file:" is not supported, because depending on the path, system server 285 // cannot access it. // TODO Revisit "file:" icon support 286 287 // fall through 288 default: 289 throw getInvalidIconException(); 290 } 291 if (icon.hasTint()) { 292 // TODO support it 293 throw new IllegalArgumentException("Icons with tints are not supported"); 294 } 295 296 return icon; 297 } 298 299 /** @hide */ 300 public static IllegalArgumentException getInvalidIconException() { 301 return new IllegalArgumentException("Unsupported icon type:" 302 +" only bitmap, resource and content URI are supported"); 303 } 304 305 /** 306 * Builder class for {@link ShortcutInfo} objects. 307 */ 308 public static class Builder { 309 private final Context mContext; 310 311 private String mId; 312 313 private ComponentName mActivityComponent; 314 315 private Icon mIcon; 316 317 private String mTitle; 318 319 private String mText; 320 321 private Intent mIntent; 322 323 private int mWeight; 324 325 private PersistableBundle mExtras; 326 327 /** Constructor. */ 328 public Builder(Context context) { 329 mContext = context; 330 } 331 332 /** 333 * Sets the ID of the shortcut. This is a mandatory field. 334 */ 335 @NonNull 336 public Builder setId(@NonNull String id) { 337 mId = Preconditions.checkStringNotEmpty(id, "id"); 338 return this; 339 } 340 341 /** 342 * Optionally sets the target activity. If it's not set, and if the caller application 343 * has multiple launcher icons, this shortcut will be shown on all those icons. 344 * If it's set, this shortcut will be only shown on this activity. 345 */ 346 @NonNull 347 public Builder setActivityComponent(@NonNull ComponentName activityComponent) { 348 mActivityComponent = Preconditions.checkNotNull(activityComponent, "activityComponent"); 349 return this; 350 } 351 352 /** 353 * Optionally sets an icon. 354 * 355 * <ul> 356 * <li>Tints are not supported. 357 * <li>Bitmaps, resources and "content:" URIs are supported. 358 * <li>"content:" URI will be fetched when a shortcut is registered to 359 * {@link ShortcutManager}. Changing the content from the same URI later will 360 * not be reflected to launcher icons. 361 * </ul> 362 * 363 * <p>For performance reasons, icons will <b>NOT</b> be available on instances 364 * returned by {@link ShortcutManager} or {@link LauncherApps}. Launcher applications 365 * need to use {@link LauncherApps#getShortcutIconFd(ShortcutInfo, UserHandle)} 366 * and {@link LauncherApps#getShortcutIconResId(ShortcutInfo, UserHandle)}. 367 */ 368 @NonNull 369 public Builder setIcon(Icon icon) { 370 mIcon = validateIcon(icon); 371 return this; 372 } 373 374 /** 375 * Sets the title of a shortcut. This is a mandatory field. 376 * 377 * <p>This field is intended for a concise description of a shortcut displayed under 378 * an icon. The recommend max length is 10 characters. 379 */ 380 @NonNull 381 public Builder setTitle(@NonNull String title) { 382 mTitle = Preconditions.checkStringNotEmpty(title, "title"); 383 return this; 384 } 385 386 /** 387 * Sets the text of a shortcut. This is an optional field. 388 * 389 * <p>This field is intended to be more descriptive than the shortcut title. 390 * The recommend max length is 25 characters. 391 */ 392 @NonNull 393 public Builder setText(@NonNull String text) { 394 mText = Preconditions.checkStringNotEmpty(text, "text"); 395 return this; 396 } 397 398 /** 399 * Sets the intent of a shortcut. This is a mandatory field. The extras must only contain 400 * persistable information. (See {@link PersistableBundle}). 401 */ 402 @NonNull 403 public Builder setIntent(@NonNull Intent intent) { 404 mIntent = Preconditions.checkNotNull(intent, "intent"); 405 return this; 406 } 407 408 /** 409 * Optionally sets the weight of a shortcut, which will be used by the launcher for sorting. 410 * The larger the weight, the more "important" a shortcut is. 411 */ 412 @NonNull 413 public Builder setWeight(int weight) { 414 mWeight = weight; 415 return this; 416 } 417 418 /** 419 * Optional values that applications can set. Applications can store any meta-data of 420 * shortcuts in this, and retrieve later from {@link ShortcutInfo#getExtras()}. 421 */ 422 @NonNull 423 public Builder setExtras(@NonNull PersistableBundle extras) { 424 mExtras = extras; 425 return this; 426 } 427 428 /** 429 * Creates a {@link ShortcutInfo} instance. 430 */ 431 @NonNull 432 public ShortcutInfo build() { 433 return new ShortcutInfo(this); 434 } 435 } 436 437 /** 438 * Return the ID of the shortcut. 439 */ 440 @NonNull 441 public String getId() { 442 return mId; 443 } 444 445 /** 446 * Return the package name of the creator application. 447 */ 448 @NonNull 449 public String getPackageName() { 450 return mPackageName; 451 } 452 453 /** 454 * Return the target activity, which may be null, in which case the shortcut is not associated 455 * with a specific activity. 456 */ 457 @Nullable 458 public ComponentName getActivityComponent() { 459 return mActivityComponent; 460 } 461 462 /** 463 * Icon. 464 * 465 * For performance reasons, this will <b>NOT</b> be available when an instance is returned 466 * by {@link ShortcutManager} or {@link LauncherApps}. A launcher application needs to use 467 * other APIs in LauncherApps to fetch the bitmap. 468 * 469 * @hide 470 */ 471 @Nullable 472 public Icon getIcon() { 473 return mIcon; 474 } 475 476 /** 477 * Return the shortcut title. 478 * 479 * <p>All shortcuts must have a non-empty title, but this method will return null when 480 * {@link #hasKeyFieldsOnly()} is true. 481 */ 482 @Nullable 483 public String getTitle() { 484 return mTitle; 485 } 486 487 /** 488 * Return the shortcut text. 489 */ 490 @Nullable 491 public String getText() { 492 return mText; 493 } 494 495 /** 496 * Return the intent. 497 * 498 * <p>All shortcuts must have an intent, but this method will return null when 499 * {@link #hasKeyFieldsOnly()} is true. 500 */ 501 @Nullable 502 public Intent getIntent() { 503 if (mIntent == null) { 504 return null; 505 } 506 final Intent intent = new Intent(mIntent); 507 intent.replaceExtras( 508 mIntentPersistableExtras != null ? new Bundle(mIntentPersistableExtras) : null); 509 return intent; 510 } 511 512 /** 513 * Return "raw" intent, which is the original intent without the extras. 514 * @hide 515 */ 516 @Nullable 517 public Intent getIntentNoExtras() { 518 return mIntent; 519 } 520 521 /** 522 * The extras in the intent. We convert extras into {@link PersistableBundle} so we can 523 * persist them. 524 * @hide 525 */ 526 @Nullable 527 public PersistableBundle getIntentPersistableExtras() { 528 return mIntentPersistableExtras; 529 } 530 531 /** 532 * Return the weight of a shortcut, which will be used by Launcher for sorting. 533 * The larger the weight, the more "important" a shortcut is. 534 */ 535 public int getWeight() { 536 return mWeight; 537 } 538 539 /** 540 * Optional values that application can set. 541 */ 542 @Nullable 543 public PersistableBundle getExtras() { 544 return mExtras; 545 } 546 547 /** 548 * Last time when any of the fields was updated. 549 */ 550 public long getLastChangedTimestamp() { 551 return mLastChangedTimestamp; 552 } 553 554 /** @hide */ 555 @ShortcutFlags 556 public int getFlags() { 557 return mFlags; 558 } 559 560 /** @hide*/ 561 public void replaceFlags(@ShortcutFlags int flags) { 562 mFlags = flags; 563 } 564 565 /** @hide*/ 566 public void addFlags(@ShortcutFlags int flags) { 567 mFlags |= flags; 568 } 569 570 /** @hide*/ 571 public void clearFlags(@ShortcutFlags int flags) { 572 mFlags &= ~flags; 573 } 574 575 /** @hide*/ 576 public boolean hasFlags(@ShortcutFlags int flags) { 577 return (mFlags & flags) == flags; 578 } 579 580 /** Return whether a shortcut is dynamic. */ 581 public boolean isDynamic() { 582 return hasFlags(FLAG_DYNAMIC); 583 } 584 585 /** Return whether a shortcut is pinned. */ 586 public boolean isPinned() { 587 return hasFlags(FLAG_PINNED); 588 } 589 590 /** 591 * Return whether a shortcut's icon is a resource in the owning package. 592 * 593 * @see LauncherApps#getShortcutIconResId(ShortcutInfo, UserHandle) 594 */ 595 public boolean hasIconResource() { 596 return hasFlags(FLAG_HAS_ICON_RES); 597 } 598 599 /** 600 * Return whether a shortcut's icon is stored as a file. 601 * 602 * @see LauncherApps#getShortcutIconFd(ShortcutInfo, UserHandle) 603 */ 604 public boolean hasIconFile() { 605 return hasFlags(FLAG_HAS_ICON_FILE); 606 } 607 608 /** 609 * Return whether a shortcut only contains "key" information only or not. If true, only the 610 * following fields are available. 611 * <ul> 612 * <li>{@link #getId()} 613 * <li>{@link #getPackageName()} 614 * <li>{@link #getLastChangedTimestamp()} 615 * <li>{@link #isDynamic()} 616 * <li>{@link #isPinned()} 617 * <li>{@link #hasIconResource()} 618 * <li>{@link #hasIconFile()} 619 * </ul> 620 */ 621 public boolean hasKeyFieldsOnly() { 622 return hasFlags(FLAG_KEY_FIELDS_ONLY); 623 } 624 625 /** @hide */ 626 public void updateTimestamp() { 627 mLastChangedTimestamp = System.currentTimeMillis(); 628 } 629 630 /** @hide */ 631 // VisibleForTesting 632 public void setTimestamp(long value) { 633 mLastChangedTimestamp = value; 634 } 635 636 /** @hide */ 637 public void clearIcon() { 638 mIcon = null; 639 } 640 641 /** @hide */ 642 public void setIconResourceId(int iconResourceId) { 643 mIconResourceId = iconResourceId; 644 } 645 646 /** @hide */ 647 public int getIconResourceId() { 648 return mIconResourceId; 649 } 650 651 /** @hide */ 652 public String getBitmapPath() { 653 return mBitmapPath; 654 } 655 656 /** @hide */ 657 public void setBitmapPath(String bitmapPath) { 658 mBitmapPath = bitmapPath; 659 } 660 661 private ShortcutInfo(Parcel source) { 662 final ClassLoader cl = getClass().getClassLoader(); 663 664 mId = source.readString(); 665 mPackageName = source.readString(); 666 mActivityComponent = source.readParcelable(cl); 667 mIcon = source.readParcelable(cl); 668 mTitle = source.readString(); 669 mText = source.readString(); 670 mIntent = source.readParcelable(cl); 671 mIntentPersistableExtras = source.readParcelable(cl); 672 mWeight = source.readInt(); 673 mExtras = source.readParcelable(cl); 674 mLastChangedTimestamp = source.readLong(); 675 mFlags = source.readInt(); 676 mIconResourceId = source.readInt(); 677 mBitmapPath = source.readString(); 678 } 679 680 @Override 681 public void writeToParcel(Parcel dest, int flags) { 682 dest.writeString(mId); 683 dest.writeString(mPackageName); 684 dest.writeParcelable(mActivityComponent, flags); 685 dest.writeParcelable(mIcon, flags); 686 dest.writeString(mTitle); 687 dest.writeString(mText); 688 dest.writeParcelable(mIntent, flags); 689 dest.writeParcelable(mIntentPersistableExtras, flags); 690 dest.writeInt(mWeight); 691 dest.writeParcelable(mExtras, flags); 692 dest.writeLong(mLastChangedTimestamp); 693 dest.writeInt(mFlags); 694 dest.writeInt(mIconResourceId); 695 dest.writeString(mBitmapPath); 696 } 697 698 public static final Creator<ShortcutInfo> CREATOR = 699 new Creator<ShortcutInfo>() { 700 public ShortcutInfo createFromParcel(Parcel source) { 701 return new ShortcutInfo(source); 702 } 703 public ShortcutInfo[] newArray(int size) { 704 return new ShortcutInfo[size]; 705 } 706 }; 707 708 @Override 709 public int describeContents() { 710 return 0; 711 } 712 713 /** 714 * Return a string representation, intended for logging. Some fields will be retracted. 715 */ 716 @Override 717 public String toString() { 718 return toStringInner(/* secure =*/ true, /* includeInternalData =*/ false); 719 } 720 721 /** @hide */ 722 public String toInsecureString() { 723 return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true); 724 } 725 726 private String toStringInner(boolean secure, boolean includeInternalData) { 727 final StringBuilder sb = new StringBuilder(); 728 sb.append("ShortcutInfo {"); 729 730 sb.append("id="); 731 sb.append(secure ? "***" : mId); 732 733 sb.append(", packageName="); 734 sb.append(mPackageName); 735 736 if (isDynamic()) { 737 sb.append(", dynamic"); 738 } 739 if (isPinned()) { 740 sb.append(", pinned"); 741 } 742 743 sb.append(", activity="); 744 sb.append(mActivityComponent); 745 746 sb.append(", title="); 747 sb.append(secure ? "***" : mTitle); 748 749 sb.append(", text="); 750 sb.append(secure ? "***" : mText); 751 752 sb.append(", icon="); 753 sb.append(mIcon); 754 755 sb.append(", weight="); 756 sb.append(mWeight); 757 758 sb.append(", timestamp="); 759 sb.append(mLastChangedTimestamp); 760 761 sb.append(", intent="); 762 sb.append(mIntent); 763 764 sb.append(", intentExtras="); 765 sb.append(secure ? "***" : mIntentPersistableExtras); 766 767 sb.append(", extras="); 768 sb.append(mExtras); 769 770 sb.append(", flags="); 771 sb.append(mFlags); 772 773 if (includeInternalData) { 774 775 sb.append(", iconRes="); 776 sb.append(mIconResourceId); 777 778 sb.append(", bitmapPath="); 779 sb.append(mBitmapPath); 780 } 781 782 sb.append("}"); 783 return sb.toString(); 784 } 785 786 /** @hide */ 787 public ShortcutInfo(String id, String packageName, ComponentName activityComponent, 788 Icon icon, String title, String text, Intent intent, 789 PersistableBundle intentPersistableExtras, 790 int weight, PersistableBundle extras, long lastChangedTimestamp, 791 int flags, int iconResId, String bitmapPath) { 792 mId = id; 793 mPackageName = packageName; 794 mActivityComponent = activityComponent; 795 mIcon = icon; 796 mTitle = title; 797 mText = text; 798 mIntent = intent; 799 mIntentPersistableExtras = intentPersistableExtras; 800 mWeight = weight; 801 mExtras = extras; 802 mLastChangedTimestamp = lastChangedTimestamp; 803 mFlags = flags; 804 mIconResourceId = iconResId; 805 mBitmapPath = bitmapPath; 806 } 807} 808