ShortcutInfo.java revision 70a91541e9eb2acc6ee5a34785abdc420127a5ec
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.annotation.UserIdInt; 22import android.content.ComponentName; 23import android.content.Context; 24import android.content.Intent; 25import android.content.res.Resources; 26import android.content.res.Resources.NotFoundException; 27import android.graphics.drawable.Icon; 28import android.os.Bundle; 29import android.os.Parcel; 30import android.os.Parcelable; 31import android.os.PersistableBundle; 32import android.os.UserHandle; 33import android.util.ArraySet; 34import android.util.Log; 35 36import com.android.internal.annotations.VisibleForTesting; 37import com.android.internal.util.Preconditions; 38 39import java.lang.annotation.Retention; 40import java.lang.annotation.RetentionPolicy; 41import java.util.Set; 42 43// TODO Enhance javadoc 44/** 45 * 46 * Represents a shortcut from an application. 47 * 48 * <p>Notes about icons: 49 * <ul> 50 * <li>If an {@link Icon} is a resource, the system keeps the package name and the resource ID. 51 * Otherwise, the bitmap is fetched when it's registered to ShortcutManager, 52 * then shrunk if necessary, and persisted. 53 * <li>The system disallows byte[] icons, because they can easily go over the binder size limit. 54 * </ul> 55 * 56 * @see {@link ShortcutManager}. 57 */ 58public final class ShortcutInfo implements Parcelable { 59 static final String TAG = "Shortcut"; 60 61 private static final String RES_TYPE_STRING = "string"; 62 63 private static final String ANDROID_PACKAGE_NAME = "android"; 64 65 private static final int IMPLICIT_RANK_MASK = 0x7fffffff; 66 67 private static final int RANK_CHANGED_BIT = ~IMPLICIT_RANK_MASK; 68 69 /** @hide */ 70 public static final int RANK_NOT_SET = Integer.MAX_VALUE; 71 72 /** @hide */ 73 public static final int FLAG_DYNAMIC = 1 << 0; 74 75 /** @hide */ 76 public static final int FLAG_PINNED = 1 << 1; 77 78 /** @hide */ 79 public static final int FLAG_HAS_ICON_RES = 1 << 2; 80 81 /** @hide */ 82 public static final int FLAG_HAS_ICON_FILE = 1 << 3; 83 84 /** @hide */ 85 public static final int FLAG_KEY_FIELDS_ONLY = 1 << 4; 86 87 /** @hide */ 88 public static final int FLAG_MANIFEST = 1 << 5; 89 90 /** @hide */ 91 public static final int FLAG_DISABLED = 1 << 6; 92 93 /** @hide */ 94 public static final int FLAG_STRINGS_RESOLVED = 1 << 7; 95 96 /** @hide */ 97 public static final int FLAG_IMMUTABLE = 1 << 8; 98 99 /** @hide */ 100 @IntDef(flag = true, 101 value = { 102 FLAG_DYNAMIC, 103 FLAG_PINNED, 104 FLAG_HAS_ICON_RES, 105 FLAG_HAS_ICON_FILE, 106 FLAG_KEY_FIELDS_ONLY, 107 FLAG_MANIFEST, 108 FLAG_DISABLED, 109 FLAG_STRINGS_RESOLVED, 110 FLAG_IMMUTABLE, 111 }) 112 @Retention(RetentionPolicy.SOURCE) 113 public @interface ShortcutFlags {} 114 115 // Cloning options. 116 117 /** @hide */ 118 private static final int CLONE_REMOVE_ICON = 1 << 0; 119 120 /** @hide */ 121 private static final int CLONE_REMOVE_INTENT = 1 << 1; 122 123 /** @hide */ 124 public static final int CLONE_REMOVE_NON_KEY_INFO = 1 << 2; 125 126 /** @hide */ 127 public static final int CLONE_REMOVE_RES_NAMES = 1 << 3; 128 129 /** @hide */ 130 public static final int CLONE_REMOVE_FOR_CREATOR = CLONE_REMOVE_ICON | CLONE_REMOVE_RES_NAMES; 131 132 /** @hide */ 133 public static final int CLONE_REMOVE_FOR_LAUNCHER = CLONE_REMOVE_ICON | CLONE_REMOVE_INTENT 134 | CLONE_REMOVE_RES_NAMES; 135 136 /** @hide */ 137 @IntDef(flag = true, 138 value = { 139 CLONE_REMOVE_ICON, 140 CLONE_REMOVE_INTENT, 141 CLONE_REMOVE_NON_KEY_INFO, 142 CLONE_REMOVE_RES_NAMES, 143 CLONE_REMOVE_FOR_CREATOR, 144 CLONE_REMOVE_FOR_LAUNCHER 145 }) 146 @Retention(RetentionPolicy.SOURCE) 147 public @interface CloneFlags {} 148 149 /** 150 * Shortcut category for 151 */ 152 public static final String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation"; 153 154 private final String mId; 155 156 @NonNull 157 private final String mPackageName; 158 159 @Nullable 160 private ComponentName mActivity; 161 162 @Nullable 163 private Icon mIcon; 164 165 private int mTitleResId; 166 167 private String mTitleResName; 168 169 @Nullable 170 private CharSequence mTitle; 171 172 private int mTextResId; 173 174 private String mTextResName; 175 176 @Nullable 177 private CharSequence mText; 178 179 private int mDisabledMessageResId; 180 181 private String mDisabledMessageResName; 182 183 @Nullable 184 private CharSequence mDisabledMessage; 185 186 @Nullable 187 private ArraySet<String> mCategories; 188 189 /** 190 * Intent *with extras removed*. 191 */ 192 @Nullable 193 private Intent mIntent; 194 195 /** 196 * Extras for the intent. 197 */ 198 @Nullable 199 private PersistableBundle mIntentPersistableExtras; 200 201 private int mRank; 202 203 /** 204 * Internally used for auto-rank-adjustment. 205 * 206 * RANK_CHANGED_BIT is used to denote that the rank of a shortcut is changing. 207 * The rest of the bits are used to denote the order in which shortcuts are passed to 208 * APIs, which is used to preserve the argument order when ranks are tie. 209 */ 210 private int mImplicitRank; 211 212 @Nullable 213 private PersistableBundle mExtras; 214 215 private long mLastChangedTimestamp; 216 217 // Internal use only. 218 @ShortcutFlags 219 private int mFlags; 220 221 // Internal use only. 222 private int mIconResId; 223 224 private String mIconResName; 225 226 // Internal use only. 227 @Nullable 228 private String mBitmapPath; 229 230 private final int mUserId; 231 232 private ShortcutInfo(Builder b) { 233 mUserId = b.mContext.getUserId(); 234 235 mId = Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided"); 236 237 // Note we can't do other null checks here because SM.updateShortcuts() takes partial 238 // information. 239 mPackageName = b.mContext.getPackageName(); 240 mActivity = b.mActivity; 241 mIcon = b.mIcon; 242 mTitle = b.mTitle; 243 mTitleResId = b.mTitleResId; 244 mText = b.mText; 245 mTextResId = b.mTextResId; 246 mDisabledMessage = b.mDisabledMessage; 247 mDisabledMessageResId = b.mDisabledMessageResId; 248 mCategories = clone(b.mCategories); 249 mIntent = b.mIntent; 250 if (mIntent != null) { 251 final Bundle intentExtras = mIntent.getExtras(); 252 if (intentExtras != null) { 253 mIntent.replaceExtras((Bundle) null); 254 mIntentPersistableExtras = new PersistableBundle(intentExtras); 255 } 256 } 257 mRank = b.mRank; 258 mExtras = b.mExtras; 259 updateTimestamp(); 260 } 261 262 private <T> ArraySet<T> clone(Set<T> source) { 263 return (source == null) ? null : new ArraySet<>(source); 264 } 265 266 /** 267 * Throws if any of the mandatory fields is not set. 268 * 269 * @hide 270 */ 271 public void enforceMandatoryFields() { 272 Preconditions.checkStringNotEmpty(mId, "Shortcut ID must be provided"); 273 Preconditions.checkNotNull(mActivity, "Activity must be provided"); 274 if (mTitle == null && mTitleResId == 0) { 275 throw new IllegalArgumentException("Short label must be provided"); 276 } 277 Preconditions.checkNotNull(mIntent, "Shortcut Intent must be provided"); 278 } 279 280 /** 281 * Copy constructor. 282 */ 283 private ShortcutInfo(ShortcutInfo source, @CloneFlags int cloneFlags) { 284 mUserId = source.mUserId; 285 mId = source.mId; 286 mPackageName = source.mPackageName; 287 mFlags = source.mFlags; 288 mLastChangedTimestamp = source.mLastChangedTimestamp; 289 290 // Just always keep it since it's cheep. 291 mIconResId = source.mIconResId; 292 293 if ((cloneFlags & CLONE_REMOVE_NON_KEY_INFO) == 0) { 294 mActivity = source.mActivity; 295 296 if ((cloneFlags & CLONE_REMOVE_ICON) == 0) { 297 mIcon = source.mIcon; 298 mBitmapPath = source.mBitmapPath; 299 } 300 301 mTitle = source.mTitle; 302 mTitleResId = source.mTitleResId; 303 mText = source.mText; 304 mTextResId = source.mTextResId; 305 mDisabledMessage = source.mDisabledMessage; 306 mDisabledMessageResId = source.mDisabledMessageResId; 307 mCategories = clone(source.mCategories); 308 if ((cloneFlags & CLONE_REMOVE_INTENT) == 0) { 309 mIntent = source.mIntent; 310 mIntentPersistableExtras = source.mIntentPersistableExtras; 311 } 312 mRank = source.mRank; 313 mExtras = source.mExtras; 314 315 if ((cloneFlags & CLONE_REMOVE_RES_NAMES) == 0) { 316 mTitleResName = source.mTitleResName; 317 mTextResName = source.mTextResName; 318 mDisabledMessageResName = source.mDisabledMessageResName; 319 mIconResName = source.mIconResName; 320 } 321 } else { 322 // Set this bit. 323 mFlags |= FLAG_KEY_FIELDS_ONLY; 324 } 325 } 326 327 /** 328 * Load a string resource from the publisher app. 329 * 330 * @param resId resource ID 331 * @param defValue default value to be returned when the specified resource isn't found. 332 */ 333 private CharSequence getResourceString(Resources res, int resId, CharSequence defValue) { 334 try { 335 return res.getString(resId); 336 } catch (NotFoundException e) { 337 Log.e(TAG, "Resource for ID=" + resId + " not found in package " + mPackageName); 338 return defValue; 339 } 340 } 341 342 /** 343 * Load the string resources for the text fields and set them to the actual value fields. 344 * This will set {@link #FLAG_STRINGS_RESOLVED}. 345 * 346 * @param res {@link Resources} for the publisher. Must have been loaded with 347 * {@link PackageManager#getResourcesForApplicationAsUser}. 348 * 349 * @hide 350 */ 351 public void resolveResourceStrings(@NonNull Resources res) { 352 mFlags |= FLAG_STRINGS_RESOLVED; 353 354 if ((mTitleResId == 0) && (mTextResId == 0) && (mDisabledMessageResId == 0)) { 355 return; // Bail early. 356 } 357 358 if (mTitleResId != 0) { 359 mTitle = getResourceString(res, mTitleResId, mTitle); 360 } 361 if (mTextResId != 0) { 362 mText = getResourceString(res, mTextResId, mText); 363 } 364 if (mDisabledMessageResId != 0) { 365 mDisabledMessage = getResourceString(res, mDisabledMessageResId, mDisabledMessage); 366 } 367 } 368 369 /** 370 * Look up resource name for a given resource ID. 371 * 372 * @return a simple resource name (e.g. "text_1") when {@code withType} is false, or with the 373 * type (e.g. "string/text_1"). 374 * 375 * @hide 376 */ 377 @VisibleForTesting 378 public static String lookUpResourceName(@NonNull Resources res, int resId, boolean withType, 379 @NonNull String packageName) { 380 if (resId == 0) { 381 return null; 382 } 383 try { 384 final String fullName = res.getResourceName(resId); 385 386 if (ANDROID_PACKAGE_NAME.equals(getResourcePackageName(fullName))) { 387 // If it's a framework resource, the value won't change, so just return the ID 388 // value as a string. 389 return String.valueOf(resId); 390 } 391 return withType ? getResourceTypeAndEntryName(fullName) 392 : getResourceEntryName(fullName); 393 } catch (NotFoundException e) { 394 Log.e(TAG, "Resource name for ID=" + resId + " not found in package " + packageName 395 + ". Resource IDs may change when the application is upgraded, and the system" 396 + " may not be able to find the correct resource."); 397 return null; 398 } 399 } 400 401 /** 402 * Extract the package name from a fully-donated resource name. 403 * e.g. "com.android.app1:drawable/icon1" -> "com.android.app1" 404 * @hide 405 */ 406 @VisibleForTesting 407 public static String getResourcePackageName(@NonNull String fullResourceName) { 408 final int p1 = fullResourceName.indexOf(':'); 409 if (p1 < 0) { 410 return null; 411 } 412 return fullResourceName.substring(0, p1); 413 } 414 415 /** 416 * Extract the type name from a fully-donated resource name. 417 * e.g. "com.android.app1:drawable/icon1" -> "drawable" 418 * @hide 419 */ 420 @VisibleForTesting 421 public static String getResourceTypeName(@NonNull String fullResourceName) { 422 final int p1 = fullResourceName.indexOf(':'); 423 if (p1 < 0) { 424 return null; 425 } 426 final int p2 = fullResourceName.indexOf('/', p1 + 1); 427 if (p2 < 0) { 428 return null; 429 } 430 return fullResourceName.substring(p1 + 1, p2); 431 } 432 433 /** 434 * Extract the type name + the entry name from a fully-donated resource name. 435 * e.g. "com.android.app1:drawable/icon1" -> "drawable/icon1" 436 * @hide 437 */ 438 @VisibleForTesting 439 public static String getResourceTypeAndEntryName(@NonNull String fullResourceName) { 440 final int p1 = fullResourceName.indexOf(':'); 441 if (p1 < 0) { 442 return null; 443 } 444 return fullResourceName.substring(p1 + 1); 445 } 446 447 /** 448 * Extract the entry name from a fully-donated resource name. 449 * e.g. "com.android.app1:drawable/icon1" -> "icon1" 450 * @hide 451 */ 452 @VisibleForTesting 453 public static String getResourceEntryName(@NonNull String fullResourceName) { 454 final int p1 = fullResourceName.indexOf('/'); 455 if (p1 < 0) { 456 return null; 457 } 458 return fullResourceName.substring(p1 + 1); 459 } 460 461 /** 462 * Return the resource ID for a given resource ID. 463 * 464 * Basically its' a wrapper over {@link Resources#getIdentifier(String, String, String)}, except 465 * if {@code resourceName} is an integer then it'll just return its value. (Which also the 466 * aforementioned method would do internally, but not documented, so doing here explicitly.) 467 * 468 * @param res {@link Resources} for the publisher. Must have been loaded with 469 * {@link PackageManager#getResourcesForApplicationAsUser}. 470 * 471 * @hide 472 */ 473 @VisibleForTesting 474 public static int lookUpResourceId(@NonNull Resources res, @Nullable String resourceName, 475 @Nullable String resourceType, String packageName) { 476 if (resourceName == null) { 477 return 0; 478 } 479 try { 480 try { 481 // It the name can be parsed as an integer, just use it. 482 return Integer.parseInt(resourceName); 483 } catch (NumberFormatException ignore) { 484 } 485 486 return res.getIdentifier(resourceName, resourceType, packageName); 487 } catch (NotFoundException e) { 488 Log.e(TAG, "Resource ID for name=" + resourceName + " not found in package " 489 + packageName); 490 return 0; 491 } 492 } 493 494 /** 495 * Look up resource names from the resource IDs for the icon res and the text fields, and fill 496 * in the resource name fields. 497 * 498 * @param res {@link Resources} for the publisher. Must have been loaded with 499 * {@link PackageManager#getResourcesForApplicationAsUser}. 500 * 501 * @hide 502 */ 503 public void lookupAndFillInResourceNames(@NonNull Resources res) { 504 if ((mTitleResId == 0) && (mTextResId == 0) && (mDisabledMessageResId == 0) 505 && (mIconResId == 0)) { 506 return; // Bail early. 507 } 508 509 // We don't need types for strings because their types are always "string". 510 mTitleResName = lookUpResourceName(res, mTitleResId, /*withType=*/ false, mPackageName); 511 mTextResName = lookUpResourceName(res, mTextResId, /*withType=*/ false, mPackageName); 512 mDisabledMessageResName = lookUpResourceName(res, mDisabledMessageResId, 513 /*withType=*/ false, mPackageName); 514 515 // But icons have multiple possible types, so include the type. 516 mIconResName = lookUpResourceName(res, mIconResId, /*withType=*/ true, mPackageName); 517 } 518 519 /** 520 * Look up resource IDs from the resource names for the icon res and the text fields, and fill 521 * in the resource ID fields. 522 * 523 * This is called when an app is updated. 524 * 525 * @hide 526 */ 527 public void lookupAndFillInResourceIds(@NonNull Resources res) { 528 if ((mTitleResName == null) && (mTextResName == null) && (mDisabledMessageResName == null) 529 && (mIconResName == null)) { 530 return; // Bail early. 531 } 532 533 mTitleResId = lookUpResourceId(res, mTitleResName, RES_TYPE_STRING, mPackageName); 534 mTextResId = lookUpResourceId(res, mTextResName, RES_TYPE_STRING, mPackageName); 535 mDisabledMessageResId = lookUpResourceId(res, mDisabledMessageResName, RES_TYPE_STRING, 536 mPackageName); 537 538 // mIconResName already contains the type, so the third argument is not needed. 539 mIconResId = lookUpResourceId(res, mIconResName, null, mPackageName); 540 } 541 542 /** 543 * Copy a {@link ShortcutInfo}, optionally removing fields. 544 * @hide 545 */ 546 public ShortcutInfo clone(@CloneFlags int cloneFlags) { 547 return new ShortcutInfo(this, cloneFlags); 548 } 549 550 /** 551 * @hide 552 */ 553 public void ensureUpdatableWith(ShortcutInfo source) { 554 Preconditions.checkState(mUserId == source.mUserId, "Owner User ID must match"); 555 Preconditions.checkState(mId.equals(source.mId), "ID must match"); 556 Preconditions.checkState(mPackageName.equals(source.mPackageName), 557 "Package name must match"); 558 Preconditions.checkState(!isImmutable(), "Target ShortcutInfo is immutable"); 559 } 560 561 /** 562 * Copy non-null/zero fields from another {@link ShortcutInfo}. Only "public" information 563 * will be overwritten. The timestamp will *not* be updated to be consistent with other 564 * setters (and also the clock is not injectable in this file). 565 * 566 * - Flags will not change 567 * - mBitmapPath will not change 568 * - Current time will be set to timestamp 569 * 570 * @throws IllegalStateException if source is not compatible. 571 * 572 * @hide 573 */ 574 public void copyNonNullFieldsFrom(ShortcutInfo source) { 575 ensureUpdatableWith(source); 576 577 if (source.mActivity != null) { 578 mActivity = source.mActivity; 579 } 580 581 if (source.mIcon != null) { 582 mIcon = source.mIcon; 583 584 mIconResId = 0; 585 mIconResName = null; 586 mBitmapPath = null; 587 } 588 if (source.mTitle != null) { 589 mTitle = source.mTitle; 590 mTitleResId = 0; 591 mTitleResName = null; 592 } else if (source.mTitleResId != 0) { 593 mTitle = null; 594 mTitleResId = source.mTitleResId; 595 mTitleResName = null; 596 } 597 598 if (source.mText != null) { 599 mText = source.mText; 600 mTextResId = 0; 601 mTextResName = null; 602 } else if (source.mTextResId != 0) { 603 mText = null; 604 mTextResId = source.mTextResId; 605 mTextResName = null; 606 } 607 if (source.mDisabledMessage != null) { 608 mDisabledMessage = source.mDisabledMessage; 609 mDisabledMessageResId = 0; 610 mDisabledMessageResName = null; 611 } else if (source.mDisabledMessageResId != 0) { 612 mDisabledMessage = null; 613 mDisabledMessageResId = source.mDisabledMessageResId; 614 mDisabledMessageResName = null; 615 } 616 if (source.mCategories != null) { 617 mCategories = clone(source.mCategories); 618 } 619 if (source.mIntent != null) { 620 mIntent = source.mIntent; 621 mIntentPersistableExtras = source.mIntentPersistableExtras; 622 } 623 if (source.mRank != RANK_NOT_SET) { 624 mRank = source.mRank; 625 } 626 if (source.mExtras != null) { 627 mExtras = source.mExtras; 628 } 629 } 630 631 /** 632 * @hide 633 */ 634 public static Icon validateIcon(Icon icon) { 635 switch (icon.getType()) { 636 case Icon.TYPE_RESOURCE: 637 case Icon.TYPE_BITMAP: 638 break; // OK 639 default: 640 throw getInvalidIconException(); 641 } 642 if (icon.hasTint()) { 643 throw new IllegalArgumentException("Icons with tints are not supported"); 644 } 645 646 return icon; 647 } 648 649 /** @hide */ 650 public static IllegalArgumentException getInvalidIconException() { 651 return new IllegalArgumentException("Unsupported icon type:" 652 +" only bitmap, resource and content URI are supported"); 653 } 654 655 /** 656 * Builder class for {@link ShortcutInfo} objects. 657 */ 658 public static class Builder { 659 private final Context mContext; 660 661 private String mId; 662 663 private ComponentName mActivity; 664 665 private Icon mIcon; 666 667 private int mTitleResId; 668 669 private CharSequence mTitle; 670 671 private int mTextResId; 672 673 private CharSequence mText; 674 675 private int mDisabledMessageResId; 676 677 private CharSequence mDisabledMessage; 678 679 private Set<String> mCategories; 680 681 private Intent mIntent; 682 683 private int mRank = RANK_NOT_SET; 684 685 private PersistableBundle mExtras; 686 687 /** Constructor. */ 688 public Builder(Context context) { 689 mContext = context; 690 } 691 692 /** 693 * Sets the ID of the shortcut. This is a mandatory field. 694 */ 695 @NonNull 696 public Builder setId(@NonNull String id) { 697 mId = Preconditions.checkStringNotEmpty(id, "id"); 698 return this; 699 } 700 701 /** 702 * Optionally sets the target activity. If it's not set, and if the caller application 703 * has multiple launcher icons, this shortcut will be shown on all those icons. 704 * If it's set, this shortcut will be only shown on this activity. 705 * 706 * <p>The package name of the target activity must match the package name of the shortcut 707 * publisher. 708 * 709 * <p>This has nothing to do with the activity that this shortcut will launch. This is 710 * a hint to the launcher app about which launcher icon to associate this shortcut with. 711 */ 712 @NonNull 713 public Builder setActivity(@NonNull ComponentName activity) { 714 mActivity = Preconditions.checkNotNull(activity, "activity"); 715 return this; 716 } 717 718 /** 719 * Optionally sets an icon. 720 * 721 * <ul> 722 * <li>Tints set by {@link Icon#setTint} or {@link Icon#setTintList} are not supported. 723 * <li>Bitmaps and resources are supported, but "content:" URIs are not supported. 724 * </ul> 725 * 726 * <p>For performance reasons, icons will <b>NOT</b> be available on instances 727 * returned by {@link ShortcutManager} or {@link LauncherApps}. Launcher applications 728 * can use {@link ShortcutInfo#getIconResourceId()} if {@link #hasIconResource()} is true. 729 * Otherwise, if {@link #hasIconFile()} is true, use 730 * {@link LauncherApps#getShortcutIconFd} to load the image. 731 */ 732 @NonNull 733 public Builder setIcon(Icon icon) { 734 mIcon = validateIcon(icon); 735 return this; 736 } 737 738 /** 739 * @hide We don't support resource strings for dynamic shortcuts for now. (But unit tests 740 * use it.) 741 */ 742 public Builder setShortLabelResId(int shortLabelResId) { 743 Preconditions.checkState(mTitle == null, "shortLabel already set"); 744 mTitleResId = shortLabelResId; 745 return this; 746 } 747 748 /** 749 * Sets the short title of a shortcut. This is a mandatory field. 750 * 751 * <p>This field is intended for a concise description of a shortcut displayed under 752 * an icon. The recommend max length is 10 characters. 753 */ 754 @NonNull 755 public Builder setShortLabel(@NonNull String shortLabel) { 756 Preconditions.checkState(mTitleResId == 0, "shortLabelResId already set"); 757 mTitle = Preconditions.checkStringNotEmpty(shortLabel, "shortLabel"); 758 return this; 759 } 760 761 /** 762 * @hide We don't support resource strings for dynamic shortcuts for now. (But unit tests 763 * use it.) 764 */ 765 public Builder setLongLabelResId(int longLabelResId) { 766 Preconditions.checkState(mText == null, "longLabel already set"); 767 mTextResId = longLabelResId; 768 return this; 769 } 770 771 /** 772 * Sets the text of a shortcut. This is an optional field. 773 * 774 * <p>This field is intended to be more descriptive than the shortcut title. The launcher 775 * shows this instead of the short title, when it has enough space. 776 * The recommend max length is 25 characters. 777 */ 778 @NonNull 779 public Builder setLongLabel(@NonNull String longLabel) { 780 Preconditions.checkState(mTextResId == 0, "longLabelResId already set"); 781 mText = Preconditions.checkStringNotEmpty(longLabel, "longLabel"); 782 return this; 783 } 784 785 /** @hide -- old signature, the internal code still uses it. */ 786 public Builder setTitle(@NonNull String value) { 787 return setShortLabel(value); 788 } 789 790 /** @hide -- old signature, the internal code still uses it. */ 791 public Builder setTitleResId(int value) { 792 return setShortLabelResId(value); 793 } 794 795 /** @hide -- old signature, the internal code still uses it. */ 796 public Builder setText(@NonNull String value) { 797 return setLongLabel(value); 798 } 799 800 /** @hide -- old signature, the internal code still uses it. */ 801 public Builder setTextResId(int value) { 802 return setLongLabelResId(value); 803 } 804 805 /** 806 * @hide We don't support resource strings for dynamic shortcuts for now. (But unit tests 807 * use it.) 808 */ 809 public Builder setDisabledMessageResId(int disabledMessageResId) { 810 Preconditions.checkState(mDisabledMessage == null, "disabledMessage already set"); 811 mDisabledMessageResId = disabledMessageResId; 812 return this; 813 } 814 815 @NonNull 816 public Builder setDisabledMessage(@NonNull String disabledMessage) { 817 Preconditions.checkState( 818 mDisabledMessageResId == 0, "disabledMessageResId already set"); 819 mDisabledMessage = 820 Preconditions.checkStringNotEmpty(disabledMessage, "disabledMessage"); 821 return this; 822 } 823 824 /** 825 * Sets categories for a shortcut. Launcher applications may use this information to 826 * categorise shortcuts. 827 * 828 * @see #SHORTCUT_CATEGORY_CONVERSATION 829 */ 830 @NonNull 831 public Builder setCategories(Set<String> categories) { 832 mCategories = categories; 833 return this; 834 } 835 836 /** 837 * Sets the intent of a shortcut. This is a mandatory field. The extras must only contain 838 * persistable information. (See {@link PersistableBundle}). 839 */ 840 @NonNull 841 public Builder setIntent(@NonNull Intent intent) { 842 mIntent = Preconditions.checkNotNull(intent, "intent"); 843 Preconditions.checkNotNull(mIntent.getAction(), "Intent action must be set"); 844 return this; 845 } 846 847 /** 848 * "Rank" of a shortcut, which is a non-negative value that's used by the launcher app 849 * to sort shortcuts. 850 */ 851 @NonNull 852 public Builder setRank(int rank) { 853 Preconditions.checkArgument((0 <= rank), 854 "Rank cannot be negative or bigger than MAX_RANK"); 855 mRank = rank; 856 return this; 857 } 858 859 /** 860 * Optional values that applications can set. Applications can store any meta-data of 861 * shortcuts in this, and retrieve later from {@link ShortcutInfo#getExtras()}. 862 */ 863 @NonNull 864 public Builder setExtras(@NonNull PersistableBundle extras) { 865 mExtras = extras; 866 return this; 867 } 868 869 /** 870 * Creates a {@link ShortcutInfo} instance. 871 */ 872 @NonNull 873 public ShortcutInfo build() { 874 return new ShortcutInfo(this); 875 } 876 } 877 878 /** 879 * Return the ID of the shortcut. 880 */ 881 @NonNull 882 public String getId() { 883 return mId; 884 } 885 886 /** 887 * Return the package name of the creator application. 888 */ 889 @NonNull 890 public String getPackage() { 891 return mPackageName; 892 } 893 894 /** 895 * Return the target activity, which may be null, in which case the shortcut is not associated 896 * with a specific activity. 897 * 898 * <p>This has nothing to do with the activity that this shortcut will launch. This is 899 * a hint to the launcher app that on which launcher icon this shortcut should be shown. 900 * 901 * @see Builder#setActivity 902 */ 903 @Nullable 904 public ComponentName getActivity() { 905 return mActivity; 906 } 907 908 /** 909 * Icon. 910 * 911 * For performance reasons, this will <b>NOT</b> be available when an instance is returned 912 * by {@link ShortcutManager} or {@link LauncherApps}. A launcher application needs to use 913 * other APIs in LauncherApps to fetch the bitmap. 914 * 915 * @hide 916 */ 917 @Nullable 918 public Icon getIcon() { 919 return mIcon; 920 } 921 922 /** @hide -- old signature, the internal code still uses it. */ 923 @Nullable 924 public CharSequence getTitle() { 925 return mTitle; 926 } 927 928 /** @hide -- old signature, the internal code still uses it. */ 929 public int getTitleResId() { 930 return mTitleResId; 931 } 932 933 /** @hide -- old signature, the internal code still uses it. */ 934 @Nullable 935 public CharSequence getText() { 936 return mText; 937 } 938 939 /** @hide -- old signature, the internal code still uses it. */ 940 public int getTextResId() { 941 return mTextResId; 942 } 943 944 /** 945 * Return the shorter version of the shortcut title. 946 * 947 * <p>All shortcuts must have a non-empty title, but this method will return null when 948 * {@link #hasKeyFieldsOnly()} is true. 949 */ 950 @Nullable 951 public CharSequence getShortLabel() { 952 return mTitle; 953 } 954 955 /** TODO Javadoc */ 956 public int getShortLabelResourceId() { 957 return mTitleResId; 958 } 959 960 /** 961 * Return the shortcut text. 962 */ 963 @Nullable 964 public CharSequence getLongLabel() { 965 return mText; 966 } 967 968 /** TODO Javadoc */ 969 public int getLongLabelResourceId() { 970 return mTextResId; 971 } 972 973 /** 974 * Return the message that should be shown when a shortcut in disabled state is launched. 975 */ 976 @Nullable 977 public CharSequence getDisabledMessage() { 978 return mDisabledMessage; 979 } 980 981 /** TODO Javadoc */ 982 public int getDisabledMessageResourceId() { 983 return mDisabledMessageResId; 984 } 985 986 /** 987 * Return the categories. 988 */ 989 @Nullable 990 public Set<String> getCategories() { 991 return mCategories; 992 } 993 994 /** 995 * Return the intent. 996 * 997 * <p>All shortcuts must have an intent, but this method will return null when 998 * {@link #hasKeyFieldsOnly()} is true. 999 * 1000 * <p>Launcher apps <b>cannot</b> see the intent. If a {@link ShortcutInfo} is obtained via 1001 * {@link LauncherApps}, then this method will always return null. Launcher apps can only 1002 * start a shortcut intent with {@link LauncherApps#startShortcut}. 1003 */ 1004 @Nullable 1005 public Intent getIntent() { 1006 if (mIntent == null) { 1007 return null; 1008 } 1009 final Intent intent = new Intent(mIntent); 1010 intent.replaceExtras( 1011 mIntentPersistableExtras != null ? new Bundle(mIntentPersistableExtras) : null); 1012 return intent; 1013 } 1014 1015 /** 1016 * Return "raw" intent, which is the original intent without the extras. 1017 * @hide 1018 */ 1019 @Nullable 1020 public Intent getIntentNoExtras() { 1021 return mIntent; 1022 } 1023 1024 /** 1025 * The extras in the intent. We convert extras into {@link PersistableBundle} so we can 1026 * persist them. 1027 * @hide 1028 */ 1029 @Nullable 1030 public PersistableBundle getIntentPersistableExtras() { 1031 return mIntentPersistableExtras; 1032 } 1033 1034 /** 1035 * "Rank" of a shortcut, which is a non-negative, sequential value that's unique for each 1036 * {@link #getActivity} for each of the two kinds, dynamic shortcuts and manifest shortcuts. 1037 * 1038 * <p>Because manifest shortcuts and dynamic shortcuts have overlapping ranks, 1039 * when a launcher application shows shortcuts for an activity, it should first show 1040 * the manifest shortcuts followed by the dynamic shortcuts. Within each of those categories, 1041 * shortcuts should be sorted by rank in ascending order. 1042 * 1043 * <p>"Floating" shortcuts (i.e. shortcuts that are neither dynamic nor manifest) will all 1044 * have rank 0, because there's no sorting for them. 1045 */ 1046 public int getRank() { 1047 return mRank; 1048 } 1049 1050 /** @hide */ 1051 public boolean hasRank() { 1052 return mRank != RANK_NOT_SET; 1053 } 1054 1055 /** @hide */ 1056 public void setRank(int rank) { 1057 mRank = rank; 1058 } 1059 1060 /** @hide */ 1061 public void clearImplicitRankAndRankChangedFlag() { 1062 mImplicitRank = 0; 1063 } 1064 1065 /** @hide */ 1066 public void setImplicitRank(int rank) { 1067 // Make sure to keep RANK_CHANGED_BIT. 1068 mImplicitRank = (mImplicitRank & RANK_CHANGED_BIT) | (rank & IMPLICIT_RANK_MASK); 1069 } 1070 1071 /** @hide */ 1072 public int getImplicitRank() { 1073 return mImplicitRank & IMPLICIT_RANK_MASK; 1074 } 1075 1076 /** @hide */ 1077 public void setRankChanged() { 1078 mImplicitRank |= RANK_CHANGED_BIT; 1079 } 1080 1081 /** @hide */ 1082 public boolean isRankChanged() { 1083 return (mImplicitRank & RANK_CHANGED_BIT) != 0; 1084 } 1085 1086 /** 1087 * Optional values that application can set. 1088 */ 1089 @Nullable 1090 public PersistableBundle getExtras() { 1091 return mExtras; 1092 } 1093 1094 /** @hide */ 1095 public int getUserId() { 1096 return mUserId; 1097 } 1098 1099 /** 1100 * {@link UserHandle} on which the publisher created shortcuts. 1101 */ 1102 public UserHandle getUserHandle() { 1103 return UserHandle.of(mUserId); 1104 } 1105 1106 /** 1107 * Last time when any of the fields was updated. 1108 */ 1109 public long getLastChangedTimestamp() { 1110 return mLastChangedTimestamp; 1111 } 1112 1113 /** @hide */ 1114 @ShortcutFlags 1115 public int getFlags() { 1116 return mFlags; 1117 } 1118 1119 /** @hide*/ 1120 public void replaceFlags(@ShortcutFlags int flags) { 1121 mFlags = flags; 1122 } 1123 1124 /** @hide*/ 1125 public void addFlags(@ShortcutFlags int flags) { 1126 mFlags |= flags; 1127 } 1128 1129 /** @hide*/ 1130 public void clearFlags(@ShortcutFlags int flags) { 1131 mFlags &= ~flags; 1132 } 1133 1134 /** @hide*/ 1135 public boolean hasFlags(@ShortcutFlags int flags) { 1136 return (mFlags & flags) == flags; 1137 } 1138 1139 /** Return whether a shortcut is dynamic. */ 1140 public boolean isDynamic() { 1141 return hasFlags(FLAG_DYNAMIC); 1142 } 1143 1144 /** Return whether a shortcut is pinned. */ 1145 public boolean isPinned() { 1146 return hasFlags(FLAG_PINNED); 1147 } 1148 1149 /** 1150 * Return whether a shortcut is published via AndroidManifest.xml or not. If {@code true}, 1151 * it's also {@link #isImmutable()}. 1152 * 1153 * <p>When an app is upgraded and a shortcut is no longer published from AndroidManifest.xml, 1154 * this will be set to {@code false}. If the shortcut is not pinned, then it'll just disappear. 1155 * However, if it's pinned, it will still be alive, and {@link #isEnabled()} will be 1156 * {@code false} and {@link #isImmutable()} will be {@code true}. 1157 * 1158 * <p>NOTE this is whether a shortcut is published from the <b>current version's</b> 1159 * AndroidManifest.xml. 1160 */ 1161 public boolean isManifestShortcut() { 1162 return hasFlags(FLAG_MANIFEST); 1163 } 1164 1165 /** 1166 * @return true if pinned but neither dynamic nor manifest. 1167 * @hide 1168 */ 1169 public boolean isFloating() { 1170 return isPinned() && !(isDynamic() || isManifestShortcut()); 1171 } 1172 1173 /** @hide */ 1174 public boolean isOriginallyFromManifest() { 1175 return hasFlags(FLAG_IMMUTABLE); 1176 } 1177 1178 /** 1179 * Return if a shortcut is immutable, in which case it cannot be modified with any of 1180 * {@link ShortcutManager} APIs. 1181 * 1182 * <p>All manifest shortcuts are immutable. When a manifest shortcut is pinned and then 1183 * disabled because the app is upgraded and its AndroidManifest.xml no longer publishes it, 1184 * {@link #isManifestShortcut} returns {@code false}, but it is still immutable. 1185 * 1186 * <p>All shortcuts originally published via the {@link ShortcutManager} APIs 1187 * are all mutable. 1188 */ 1189 public boolean isImmutable() { 1190 return hasFlags(FLAG_IMMUTABLE); 1191 } 1192 1193 /** 1194 * Returns {@code false} if a shortcut is disabled with 1195 * {@link ShortcutManager#disableShortcuts}. 1196 */ 1197 public boolean isEnabled() { 1198 return !hasFlags(FLAG_DISABLED); 1199 } 1200 1201 /** @hide */ 1202 public boolean isAlive() { 1203 return hasFlags(FLAG_PINNED) || hasFlags(FLAG_DYNAMIC) || hasFlags(FLAG_MANIFEST); 1204 } 1205 1206 /** @hide */ 1207 public boolean usesQuota() { 1208 return hasFlags(FLAG_DYNAMIC) || hasFlags(FLAG_MANIFEST); 1209 } 1210 1211 /** 1212 * Return whether a shortcut's icon is a resource in the owning package. 1213 * 1214 * @see LauncherApps#getShortcutIconResId(ShortcutInfo) 1215 */ 1216 public boolean hasIconResource() { 1217 return hasFlags(FLAG_HAS_ICON_RES); 1218 } 1219 1220 /** @hide */ 1221 public boolean hasStringResources() { 1222 return (mTitleResId != 0) || (mTextResId != 0) || (mDisabledMessageResId != 0); 1223 } 1224 1225 /** @hide */ 1226 public boolean hasAnyResources() { 1227 return hasIconResource() || hasStringResources(); 1228 } 1229 1230 /** 1231 * Return whether a shortcut's icon is stored as a file. 1232 * 1233 * @see LauncherApps#getShortcutIconFd(ShortcutInfo) 1234 */ 1235 public boolean hasIconFile() { 1236 return hasFlags(FLAG_HAS_ICON_FILE); 1237 } 1238 1239 /** 1240 * Return whether a shortcut only contains "key" information only or not. If true, only the 1241 * following fields are available. 1242 * <ul> 1243 * <li>{@link #getId()} 1244 * <li>{@link #getPackage()} 1245 * <li>{@link #getLastChangedTimestamp()} 1246 * <li>{@link #isDynamic()} 1247 * <li>{@link #isPinned()} 1248 * <li>{@link #hasIconResource()} 1249 * <li>{@link #hasIconFile()} 1250 * </ul> 1251 */ 1252 public boolean hasKeyFieldsOnly() { 1253 return hasFlags(FLAG_KEY_FIELDS_ONLY); 1254 } 1255 1256 /** TODO Javadoc */ 1257 public boolean hasStringResourcesResolved() { 1258 return hasFlags(FLAG_STRINGS_RESOLVED); 1259 } 1260 1261 /** @hide */ 1262 public void updateTimestamp() { 1263 mLastChangedTimestamp = System.currentTimeMillis(); 1264 } 1265 1266 /** @hide */ 1267 // VisibleForTesting 1268 public void setTimestamp(long value) { 1269 mLastChangedTimestamp = value; 1270 } 1271 1272 /** @hide */ 1273 public void clearIcon() { 1274 mIcon = null; 1275 } 1276 1277 /** @hide */ 1278 public void setIconResourceId(int iconResourceId) { 1279 if (mIconResId != iconResourceId) { 1280 mIconResName = null; 1281 } 1282 mIconResId = iconResourceId; 1283 } 1284 1285 /** 1286 * Get the resource ID for the icon, valid only when {@link #hasIconResource()} } is true. 1287 */ 1288 public int getIconResourceId() { 1289 return mIconResId; 1290 } 1291 1292 /** @hide */ 1293 public String getBitmapPath() { 1294 return mBitmapPath; 1295 } 1296 1297 /** @hide */ 1298 public void setBitmapPath(String bitmapPath) { 1299 mBitmapPath = bitmapPath; 1300 } 1301 1302 /** @hide */ 1303 public void setDisabledMessageResId(int disabledMessageResId) { 1304 if (mDisabledMessageResId != disabledMessageResId) { 1305 mDisabledMessageResName = null; 1306 } 1307 mDisabledMessageResId = disabledMessageResId; 1308 mDisabledMessage = null; 1309 } 1310 1311 /** @hide */ 1312 public void setDisabledMessage(String disabledMessage) { 1313 mDisabledMessage = disabledMessage; 1314 mDisabledMessageResId = 0; 1315 mDisabledMessageResName = null; 1316 } 1317 1318 /** @hide */ 1319 public String getTitleResName() { 1320 return mTitleResName; 1321 } 1322 1323 /** @hide */ 1324 public void setTitleResName(String titleResName) { 1325 mTitleResName = titleResName; 1326 } 1327 1328 /** @hide */ 1329 public String getTextResName() { 1330 return mTextResName; 1331 } 1332 1333 /** @hide */ 1334 public void setTextResName(String textResName) { 1335 mTextResName = textResName; 1336 } 1337 1338 /** @hide */ 1339 public String getDisabledMessageResName() { 1340 return mDisabledMessageResName; 1341 } 1342 1343 /** @hide */ 1344 public void setDisabledMessageResName(String disabledMessageResName) { 1345 mDisabledMessageResName = disabledMessageResName; 1346 } 1347 1348 /** @hide */ 1349 public String getIconResName() { 1350 return mIconResName; 1351 } 1352 1353 /** @hide */ 1354 public void setIconResName(String iconResName) { 1355 mIconResName = iconResName; 1356 } 1357 1358 private ShortcutInfo(Parcel source) { 1359 final ClassLoader cl = getClass().getClassLoader(); 1360 1361 mUserId = source.readInt(); 1362 mId = source.readString(); 1363 mPackageName = source.readString(); 1364 mActivity = source.readParcelable(cl); 1365 mIcon = source.readParcelable(cl); 1366 mTitle = source.readCharSequence(); 1367 mTitleResId = source.readInt(); 1368 mText = source.readCharSequence(); 1369 mTextResId = source.readInt(); 1370 mDisabledMessage = source.readCharSequence(); 1371 mDisabledMessageResId = source.readInt(); 1372 mIntent = source.readParcelable(cl); 1373 mIntentPersistableExtras = source.readParcelable(cl); 1374 mRank = source.readInt(); 1375 mExtras = source.readParcelable(cl); 1376 mLastChangedTimestamp = source.readLong(); 1377 mFlags = source.readInt(); 1378 mIconResId = source.readInt(); 1379 mBitmapPath = source.readString(); 1380 1381 mIconResName = source.readString(); 1382 mTitleResName = source.readString(); 1383 mTextResName = source.readString(); 1384 mDisabledMessageResName = source.readString(); 1385 1386 int N = source.readInt(); 1387 if (N == 0) { 1388 mCategories = null; 1389 } else { 1390 mCategories = new ArraySet<>(N); 1391 for (int i = 0; i < N; i++) { 1392 mCategories.add(source.readString().intern()); 1393 } 1394 } 1395 } 1396 1397 @Override 1398 public void writeToParcel(Parcel dest, int flags) { 1399 dest.writeInt(mUserId); 1400 dest.writeString(mId); 1401 dest.writeString(mPackageName); 1402 dest.writeParcelable(mActivity, flags); 1403 dest.writeParcelable(mIcon, flags); 1404 dest.writeCharSequence(mTitle); 1405 dest.writeInt(mTitleResId); 1406 dest.writeCharSequence(mText); 1407 dest.writeInt(mTextResId); 1408 dest.writeCharSequence(mDisabledMessage); 1409 dest.writeInt(mDisabledMessageResId); 1410 1411 dest.writeParcelable(mIntent, flags); 1412 dest.writeParcelable(mIntentPersistableExtras, flags); 1413 dest.writeInt(mRank); 1414 dest.writeParcelable(mExtras, flags); 1415 dest.writeLong(mLastChangedTimestamp); 1416 dest.writeInt(mFlags); 1417 dest.writeInt(mIconResId); 1418 dest.writeString(mBitmapPath); 1419 1420 dest.writeString(mIconResName); 1421 dest.writeString(mTitleResName); 1422 dest.writeString(mTextResName); 1423 dest.writeString(mDisabledMessageResName); 1424 1425 if (mCategories != null) { 1426 final int N = mCategories.size(); 1427 dest.writeInt(N); 1428 for (int i = 0; i < N; i++) { 1429 dest.writeString(mCategories.valueAt(i)); 1430 } 1431 } else { 1432 dest.writeInt(0); 1433 } 1434 } 1435 1436 public static final Creator<ShortcutInfo> CREATOR = 1437 new Creator<ShortcutInfo>() { 1438 public ShortcutInfo createFromParcel(Parcel source) { 1439 return new ShortcutInfo(source); 1440 } 1441 public ShortcutInfo[] newArray(int size) { 1442 return new ShortcutInfo[size]; 1443 } 1444 }; 1445 1446 @Override 1447 public int describeContents() { 1448 return 0; 1449 } 1450 1451 /** 1452 * Return a string representation, intended for logging. Some fields will be retracted. 1453 */ 1454 @Override 1455 public String toString() { 1456 return toStringInner(/* secure =*/ true, /* includeInternalData =*/ false); 1457 } 1458 1459 /** @hide */ 1460 public String toInsecureString() { 1461 return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true); 1462 } 1463 1464 private String toStringInner(boolean secure, boolean includeInternalData) { 1465 final StringBuilder sb = new StringBuilder(); 1466 sb.append("ShortcutInfo {"); 1467 1468 sb.append("id="); 1469 sb.append(secure ? "***" : mId); 1470 1471 sb.append(", flags=0x"); 1472 sb.append(Integer.toHexString(mFlags)); 1473 sb.append(" ["); 1474 if (!isEnabled()) { 1475 sb.append("X"); 1476 } 1477 if (isImmutable()) { 1478 sb.append("Im"); 1479 } 1480 if (isManifestShortcut()) { 1481 sb.append("M"); 1482 } 1483 if (isDynamic()) { 1484 sb.append("D"); 1485 } 1486 if (isPinned()) { 1487 sb.append("P"); 1488 } 1489 if (hasIconFile()) { 1490 sb.append("If"); 1491 } 1492 if (hasIconResource()) { 1493 sb.append("Ir"); 1494 } 1495 if (hasKeyFieldsOnly()) { 1496 sb.append("K"); 1497 } 1498 if (hasStringResourcesResolved()) { 1499 sb.append("Sr"); 1500 } 1501 sb.append("]"); 1502 1503 sb.append(", packageName="); 1504 sb.append(mPackageName); 1505 1506 sb.append(", activity="); 1507 sb.append(mActivity); 1508 1509 sb.append(", shortLabel="); 1510 sb.append(secure ? "***" : mTitle); 1511 sb.append(", resId="); 1512 sb.append(mTitleResId); 1513 sb.append("["); 1514 sb.append(mTitleResName); 1515 sb.append("]"); 1516 1517 sb.append(", longLabel="); 1518 sb.append(secure ? "***" : mText); 1519 sb.append(", resId="); 1520 sb.append(mTextResId); 1521 sb.append("["); 1522 sb.append(mTextResName); 1523 sb.append("]"); 1524 1525 sb.append(", disabledMessage="); 1526 sb.append(secure ? "***" : mDisabledMessage); 1527 sb.append(", resId="); 1528 sb.append(mDisabledMessageResId); 1529 sb.append("["); 1530 sb.append(mDisabledMessageResName); 1531 sb.append("]"); 1532 1533 sb.append(", categories="); 1534 sb.append(mCategories); 1535 1536 sb.append(", icon="); 1537 sb.append(mIcon); 1538 1539 sb.append(", rank="); 1540 sb.append(mRank); 1541 1542 sb.append(", timestamp="); 1543 sb.append(mLastChangedTimestamp); 1544 1545 sb.append(", intent="); 1546 sb.append(mIntent); 1547 1548 sb.append(", intentExtras="); 1549 sb.append(secure ? "***" : mIntentPersistableExtras); 1550 1551 sb.append(", extras="); 1552 sb.append(mExtras); 1553 1554 if (includeInternalData) { 1555 1556 sb.append(", iconRes="); 1557 sb.append(mIconResId); 1558 sb.append("["); 1559 sb.append(mIconResName); 1560 sb.append("]"); 1561 1562 sb.append(", bitmapPath="); 1563 sb.append(mBitmapPath); 1564 } 1565 1566 sb.append("}"); 1567 return sb.toString(); 1568 } 1569 1570 /** @hide */ 1571 public ShortcutInfo( 1572 @UserIdInt int userId, String id, String packageName, ComponentName activity, 1573 Icon icon, CharSequence title, int titleResId, String titleResName, 1574 CharSequence text, int textResId, String textResName, 1575 CharSequence disabledMessage, int disabledMessageResId, String disabledMessageResName, 1576 Set<String> categories, 1577 Intent intent, PersistableBundle intentPersistableExtras, 1578 int rank, PersistableBundle extras, long lastChangedTimestamp, 1579 int flags, int iconResId, String iconResName, String bitmapPath) { 1580 mUserId = userId; 1581 mId = id; 1582 mPackageName = packageName; 1583 mActivity = activity; 1584 mIcon = icon; 1585 mTitle = title; 1586 mTitleResId = titleResId; 1587 mTitleResName = titleResName; 1588 mText = text; 1589 mTextResId = textResId; 1590 mTextResName = textResName; 1591 mDisabledMessage = disabledMessage; 1592 mDisabledMessageResId = disabledMessageResId; 1593 mDisabledMessageResName = disabledMessageResName; 1594 mCategories = clone(categories); 1595 mIntent = intent; 1596 mIntentPersistableExtras = intentPersistableExtras; 1597 mRank = rank; 1598 mExtras = extras; 1599 mLastChangedTimestamp = lastChangedTimestamp; 1600 mFlags = flags; 1601 mIconResId = iconResId; 1602 mIconResName = iconResName; 1603 mBitmapPath = bitmapPath; 1604 } 1605} 1606