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 */ 16 package android.content.pm; 17 18 import android.annotation.IntDef; 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.TestApi; 22 import android.annotation.UserIdInt; 23 import android.app.TaskStackBuilder; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.pm.LauncherApps.ShortcutQuery; 28 import android.content.res.Resources; 29 import android.content.res.Resources.NotFoundException; 30 import android.graphics.Bitmap; 31 import android.graphics.drawable.Icon; 32 import android.os.Bundle; 33 import android.os.Parcel; 34 import android.os.Parcelable; 35 import android.os.PersistableBundle; 36 import android.os.UserHandle; 37 import android.text.TextUtils; 38 import android.util.ArraySet; 39 import android.util.Log; 40 41 import com.android.internal.annotations.VisibleForTesting; 42 import com.android.internal.util.Preconditions; 43 44 import java.lang.annotation.Retention; 45 import java.lang.annotation.RetentionPolicy; 46 import java.util.List; 47 import java.util.Set; 48 49 /** 50 * Represents a shortcut that can be published via {@link ShortcutManager}. 51 * 52 * @see ShortcutManager 53 */ 54 public final class ShortcutInfo implements Parcelable { 55 static final String TAG = "Shortcut"; 56 57 private static final String RES_TYPE_STRING = "string"; 58 59 private static final String ANDROID_PACKAGE_NAME = "android"; 60 61 private static final int IMPLICIT_RANK_MASK = 0x7fffffff; 62 63 private static final int RANK_CHANGED_BIT = ~IMPLICIT_RANK_MASK; 64 65 /** @hide */ 66 public static final int RANK_NOT_SET = Integer.MAX_VALUE; 67 68 /** @hide */ 69 public static final int FLAG_DYNAMIC = 1 << 0; 70 71 /** @hide */ 72 public static final int FLAG_PINNED = 1 << 1; 73 74 /** @hide */ 75 public static final int FLAG_HAS_ICON_RES = 1 << 2; 76 77 /** @hide */ 78 public static final int FLAG_HAS_ICON_FILE = 1 << 3; 79 80 /** @hide */ 81 public static final int FLAG_KEY_FIELDS_ONLY = 1 << 4; 82 83 /** @hide */ 84 public static final int FLAG_MANIFEST = 1 << 5; 85 86 /** @hide */ 87 public static final int FLAG_DISABLED = 1 << 6; 88 89 /** @hide */ 90 public static final int FLAG_STRINGS_RESOLVED = 1 << 7; 91 92 /** @hide */ 93 public static final int FLAG_IMMUTABLE = 1 << 8; 94 95 /** @hide */ 96 public static final int FLAG_ADAPTIVE_BITMAP = 1 << 9; 97 98 /** @hide */ 99 public static final int FLAG_RETURNED_BY_SERVICE = 1 << 10; 100 101 /** @hide When this is set, the bitmap icon is waiting to be saved. */ 102 public static final int FLAG_ICON_FILE_PENDING_SAVE = 1 << 11; 103 104 /** 105 * "Shadow" shortcuts are the ones that are restored, but the owner package hasn't been 106 * installed yet. 107 * @hide 108 */ 109 public static final int FLAG_SHADOW = 1 << 12; 110 111 /** @hide */ 112 @IntDef(flag = true, prefix = { "FLAG_" }, value = { 113 FLAG_DYNAMIC, 114 FLAG_PINNED, 115 FLAG_HAS_ICON_RES, 116 FLAG_HAS_ICON_FILE, 117 FLAG_KEY_FIELDS_ONLY, 118 FLAG_MANIFEST, 119 FLAG_DISABLED, 120 FLAG_STRINGS_RESOLVED, 121 FLAG_IMMUTABLE, 122 FLAG_ADAPTIVE_BITMAP, 123 FLAG_RETURNED_BY_SERVICE, 124 FLAG_ICON_FILE_PENDING_SAVE, 125 }) 126 @Retention(RetentionPolicy.SOURCE) 127 public @interface ShortcutFlags {} 128 129 // Cloning options. 130 131 /** @hide */ 132 private static final int CLONE_REMOVE_ICON = 1 << 0; 133 134 /** @hide */ 135 private static final int CLONE_REMOVE_INTENT = 1 << 1; 136 137 /** @hide */ 138 public static final int CLONE_REMOVE_NON_KEY_INFO = 1 << 2; 139 140 /** @hide */ 141 public static final int CLONE_REMOVE_RES_NAMES = 1 << 3; 142 143 /** @hide */ 144 public static final int CLONE_REMOVE_FOR_CREATOR = CLONE_REMOVE_ICON | CLONE_REMOVE_RES_NAMES; 145 146 /** @hide */ 147 public static final int CLONE_REMOVE_FOR_LAUNCHER = CLONE_REMOVE_ICON | CLONE_REMOVE_INTENT 148 | CLONE_REMOVE_RES_NAMES; 149 150 /** @hide */ 151 public static final int CLONE_REMOVE_FOR_LAUNCHER_APPROVAL = CLONE_REMOVE_INTENT 152 | CLONE_REMOVE_RES_NAMES; 153 154 /** @hide */ 155 @IntDef(flag = true, prefix = { "CLONE_" }, value = { 156 CLONE_REMOVE_ICON, 157 CLONE_REMOVE_INTENT, 158 CLONE_REMOVE_NON_KEY_INFO, 159 CLONE_REMOVE_RES_NAMES, 160 CLONE_REMOVE_FOR_CREATOR, 161 CLONE_REMOVE_FOR_LAUNCHER 162 }) 163 @Retention(RetentionPolicy.SOURCE) 164 public @interface CloneFlags {} 165 166 /** 167 * Shortcut is not disabled. 168 */ 169 public static final int DISABLED_REASON_NOT_DISABLED = 0; 170 171 /** 172 * Shortcut has been disabled by the publisher app with the 173 * {@link ShortcutManager#disableShortcuts(List)} API. 174 */ 175 public static final int DISABLED_REASON_BY_APP = 1; 176 177 /** 178 * Shortcut has been disabled due to changes to the publisher app. (e.g. a manifest shortcut 179 * no longer exists.) 180 */ 181 public static final int DISABLED_REASON_APP_CHANGED = 2; 182 183 /** 184 * Shortcut is disabled for an unknown reason. 185 */ 186 public static final int DISABLED_REASON_UNKNOWN = 3; 187 188 /** 189 * A disabled reason that's equal to or bigger than this is due to backup and restore issue. 190 * A shortcut with such a reason wil be visible to the launcher, but not to the publisher. 191 * ({@link #isVisibleToPublisher()} will be false.) 192 */ 193 private static final int DISABLED_REASON_RESTORE_ISSUE_START = 100; 194 195 /** 196 * Shortcut has been restored from the previous device, but the publisher app on the current 197 * device is of a lower version. The shortcut will not be usable until the app is upgraded to 198 * the same version or higher. 199 */ 200 public static final int DISABLED_REASON_VERSION_LOWER = 100; 201 202 /** 203 * Shortcut has not been restored because the publisher app does not support backup and restore. 204 */ 205 public static final int DISABLED_REASON_BACKUP_NOT_SUPPORTED = 101; 206 207 /** 208 * Shortcut has not been restored because the publisher app's signature has changed. 209 */ 210 public static final int DISABLED_REASON_SIGNATURE_MISMATCH = 102; 211 212 /** 213 * Shortcut has not been restored for unknown reason. 214 */ 215 public static final int DISABLED_REASON_OTHER_RESTORE_ISSUE = 103; 216 217 /** @hide */ 218 @IntDef(prefix = { "DISABLED_REASON_" }, value = { 219 DISABLED_REASON_NOT_DISABLED, 220 DISABLED_REASON_BY_APP, 221 DISABLED_REASON_APP_CHANGED, 222 DISABLED_REASON_UNKNOWN, 223 DISABLED_REASON_VERSION_LOWER, 224 DISABLED_REASON_BACKUP_NOT_SUPPORTED, 225 DISABLED_REASON_SIGNATURE_MISMATCH, 226 DISABLED_REASON_OTHER_RESTORE_ISSUE, 227 }) 228 @Retention(RetentionPolicy.SOURCE) 229 public @interface DisabledReason{} 230 231 /** 232 * Return a label for disabled reasons, which are *not* supposed to be shown to the user. 233 * @hide 234 */ getDisabledReasonDebugString(@isabledReason int disabledReason)235 public static String getDisabledReasonDebugString(@DisabledReason int disabledReason) { 236 switch (disabledReason) { 237 case DISABLED_REASON_NOT_DISABLED: 238 return "[Not disabled]"; 239 case DISABLED_REASON_BY_APP: 240 return "[Disabled: by app]"; 241 case DISABLED_REASON_APP_CHANGED: 242 return "[Disabled: app changed]"; 243 case DISABLED_REASON_VERSION_LOWER: 244 return "[Disabled: lower version]"; 245 case DISABLED_REASON_BACKUP_NOT_SUPPORTED: 246 return "[Disabled: backup not supported]"; 247 case DISABLED_REASON_SIGNATURE_MISMATCH: 248 return "[Disabled: signature mismatch]"; 249 case DISABLED_REASON_OTHER_RESTORE_ISSUE: 250 return "[Disabled: unknown restore issue]"; 251 } 252 return "[Disabled: unknown reason:" + disabledReason + "]"; 253 } 254 255 /** 256 * Return a label for a disabled reason for shortcuts that are disabled due to a backup and 257 * restore issue. If the reason is not due to backup & restore, then it'll return null. 258 * 259 * This method returns localized, user-facing strings, which will be returned by 260 * {@link #getDisabledMessage()}. 261 * 262 * @hide 263 */ getDisabledReasonForRestoreIssue(Context context, @DisabledReason int disabledReason)264 public static String getDisabledReasonForRestoreIssue(Context context, 265 @DisabledReason int disabledReason) { 266 final Resources res = context.getResources(); 267 268 switch (disabledReason) { 269 case DISABLED_REASON_VERSION_LOWER: 270 return res.getString( 271 com.android.internal.R.string.shortcut_restored_on_lower_version); 272 case DISABLED_REASON_BACKUP_NOT_SUPPORTED: 273 return res.getString( 274 com.android.internal.R.string.shortcut_restore_not_supported); 275 case DISABLED_REASON_SIGNATURE_MISMATCH: 276 return res.getString( 277 com.android.internal.R.string.shortcut_restore_signature_mismatch); 278 case DISABLED_REASON_OTHER_RESTORE_ISSUE: 279 return res.getString( 280 com.android.internal.R.string.shortcut_restore_unknown_issue); 281 case DISABLED_REASON_UNKNOWN: 282 return res.getString( 283 com.android.internal.R.string.shortcut_disabled_reason_unknown); 284 } 285 return null; 286 } 287 288 /** @hide */ isDisabledForRestoreIssue(@isabledReason int disabledReason)289 public static boolean isDisabledForRestoreIssue(@DisabledReason int disabledReason) { 290 return disabledReason >= DISABLED_REASON_RESTORE_ISSUE_START; 291 } 292 293 /** 294 * Shortcut category for messaging related actions, such as chat. 295 */ 296 public static final String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation"; 297 298 private final String mId; 299 300 @NonNull 301 private final String mPackageName; 302 303 @Nullable 304 private ComponentName mActivity; 305 306 @Nullable 307 private Icon mIcon; 308 309 private int mTitleResId; 310 311 private String mTitleResName; 312 313 @Nullable 314 private CharSequence mTitle; 315 316 private int mTextResId; 317 318 private String mTextResName; 319 320 @Nullable 321 private CharSequence mText; 322 323 private int mDisabledMessageResId; 324 325 private String mDisabledMessageResName; 326 327 @Nullable 328 private CharSequence mDisabledMessage; 329 330 @Nullable 331 private ArraySet<String> mCategories; 332 333 /** 334 * Intents *with extras removed*. 335 */ 336 @Nullable 337 private Intent[] mIntents; 338 339 /** 340 * Extras for the intents. 341 */ 342 @Nullable 343 private PersistableBundle[] mIntentPersistableExtrases; 344 345 private int mRank; 346 347 /** 348 * Internally used for auto-rank-adjustment. 349 * 350 * RANK_CHANGED_BIT is used to denote that the rank of a shortcut is changing. 351 * The rest of the bits are used to denote the order in which shortcuts are passed to 352 * APIs, which is used to preserve the argument order when ranks are tie. 353 */ 354 private int mImplicitRank; 355 356 @Nullable 357 private PersistableBundle mExtras; 358 359 private long mLastChangedTimestamp; 360 361 // Internal use only. 362 @ShortcutFlags 363 private int mFlags; 364 365 // Internal use only. 366 private int mIconResId; 367 368 private String mIconResName; 369 370 // Internal use only. 371 @Nullable 372 private String mBitmapPath; 373 374 private final int mUserId; 375 376 /** @hide */ 377 public static final int VERSION_CODE_UNKNOWN = -1; 378 379 private int mDisabledReason; 380 ShortcutInfo(Builder b)381 private ShortcutInfo(Builder b) { 382 mUserId = b.mContext.getUserId(); 383 384 mId = Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided"); 385 386 // Note we can't do other null checks here because SM.updateShortcuts() takes partial 387 // information. 388 mPackageName = b.mContext.getPackageName(); 389 mActivity = b.mActivity; 390 mIcon = b.mIcon; 391 mTitle = b.mTitle; 392 mTitleResId = b.mTitleResId; 393 mText = b.mText; 394 mTextResId = b.mTextResId; 395 mDisabledMessage = b.mDisabledMessage; 396 mDisabledMessageResId = b.mDisabledMessageResId; 397 mCategories = cloneCategories(b.mCategories); 398 mIntents = cloneIntents(b.mIntents); 399 fixUpIntentExtras(); 400 mRank = b.mRank; 401 mExtras = b.mExtras; 402 updateTimestamp(); 403 } 404 405 /** 406 * Extract extras from {@link #mIntents} and set them to {@link #mIntentPersistableExtrases} 407 * as {@link PersistableBundle}, and remove extras from the original intents. 408 */ fixUpIntentExtras()409 private void fixUpIntentExtras() { 410 if (mIntents == null) { 411 mIntentPersistableExtrases = null; 412 return; 413 } 414 mIntentPersistableExtrases = new PersistableBundle[mIntents.length]; 415 for (int i = 0; i < mIntents.length; i++) { 416 final Intent intent = mIntents[i]; 417 final Bundle extras = intent.getExtras(); 418 if (extras == null) { 419 mIntentPersistableExtrases[i] = null; 420 } else { 421 mIntentPersistableExtrases[i] = new PersistableBundle(extras); 422 intent.replaceExtras((Bundle) null); 423 } 424 } 425 } 426 cloneCategories(Set<String> source)427 private static ArraySet<String> cloneCategories(Set<String> source) { 428 if (source == null) { 429 return null; 430 } 431 final ArraySet<String> ret = new ArraySet<>(source.size()); 432 for (CharSequence s : source) { 433 if (!TextUtils.isEmpty(s)) { 434 ret.add(s.toString().intern()); 435 } 436 } 437 return ret; 438 } 439 cloneIntents(Intent[] intents)440 private static Intent[] cloneIntents(Intent[] intents) { 441 if (intents == null) { 442 return null; 443 } 444 final Intent[] ret = new Intent[intents.length]; 445 for (int i = 0; i < ret.length; i++) { 446 if (intents[i] != null) { 447 ret[i] = new Intent(intents[i]); 448 } 449 } 450 return ret; 451 } 452 clonePersistableBundle(PersistableBundle[] bundle)453 private static PersistableBundle[] clonePersistableBundle(PersistableBundle[] bundle) { 454 if (bundle == null) { 455 return null; 456 } 457 final PersistableBundle[] ret = new PersistableBundle[bundle.length]; 458 for (int i = 0; i < ret.length; i++) { 459 if (bundle[i] != null) { 460 ret[i] = new PersistableBundle(bundle[i]); 461 } 462 } 463 return ret; 464 } 465 466 /** 467 * Throws if any of the mandatory fields is not set. 468 * 469 * @hide 470 */ enforceMandatoryFields(boolean forPinned)471 public void enforceMandatoryFields(boolean forPinned) { 472 Preconditions.checkStringNotEmpty(mId, "Shortcut ID must be provided"); 473 if (!forPinned) { 474 Preconditions.checkNotNull(mActivity, "Activity must be provided"); 475 } 476 if (mTitle == null && mTitleResId == 0) { 477 throw new IllegalArgumentException("Short label must be provided"); 478 } 479 Preconditions.checkNotNull(mIntents, "Shortcut Intent must be provided"); 480 Preconditions.checkArgument(mIntents.length > 0, "Shortcut Intent must be provided"); 481 } 482 483 /** 484 * Copy constructor. 485 */ ShortcutInfo(ShortcutInfo source, @CloneFlags int cloneFlags)486 private ShortcutInfo(ShortcutInfo source, @CloneFlags int cloneFlags) { 487 mUserId = source.mUserId; 488 mId = source.mId; 489 mPackageName = source.mPackageName; 490 mActivity = source.mActivity; 491 mFlags = source.mFlags; 492 mLastChangedTimestamp = source.mLastChangedTimestamp; 493 mDisabledReason = source.mDisabledReason; 494 495 // Just always keep it since it's cheep. 496 mIconResId = source.mIconResId; 497 498 if ((cloneFlags & CLONE_REMOVE_NON_KEY_INFO) == 0) { 499 500 if ((cloneFlags & CLONE_REMOVE_ICON) == 0) { 501 mIcon = source.mIcon; 502 mBitmapPath = source.mBitmapPath; 503 } 504 505 mTitle = source.mTitle; 506 mTitleResId = source.mTitleResId; 507 mText = source.mText; 508 mTextResId = source.mTextResId; 509 mDisabledMessage = source.mDisabledMessage; 510 mDisabledMessageResId = source.mDisabledMessageResId; 511 mCategories = cloneCategories(source.mCategories); 512 if ((cloneFlags & CLONE_REMOVE_INTENT) == 0) { 513 mIntents = cloneIntents(source.mIntents); 514 mIntentPersistableExtrases = 515 clonePersistableBundle(source.mIntentPersistableExtrases); 516 } 517 mRank = source.mRank; 518 mExtras = source.mExtras; 519 520 if ((cloneFlags & CLONE_REMOVE_RES_NAMES) == 0) { 521 mTitleResName = source.mTitleResName; 522 mTextResName = source.mTextResName; 523 mDisabledMessageResName = source.mDisabledMessageResName; 524 mIconResName = source.mIconResName; 525 } 526 } else { 527 // Set this bit. 528 mFlags |= FLAG_KEY_FIELDS_ONLY; 529 } 530 } 531 532 /** 533 * Load a string resource from the publisher app. 534 * 535 * @param resId resource ID 536 * @param defValue default value to be returned when the specified resource isn't found. 537 */ getResourceString(Resources res, int resId, CharSequence defValue)538 private CharSequence getResourceString(Resources res, int resId, CharSequence defValue) { 539 try { 540 return res.getString(resId); 541 } catch (NotFoundException e) { 542 Log.e(TAG, "Resource for ID=" + resId + " not found in package " + mPackageName); 543 return defValue; 544 } 545 } 546 547 /** 548 * Load the string resources for the text fields and set them to the actual value fields. 549 * This will set {@link #FLAG_STRINGS_RESOLVED}. 550 * 551 * @param res {@link Resources} for the publisher. Must have been loaded with 552 * {@link PackageManager#getResourcesForApplicationAsUser}. 553 * 554 * @hide 555 */ resolveResourceStrings(@onNull Resources res)556 public void resolveResourceStrings(@NonNull Resources res) { 557 mFlags |= FLAG_STRINGS_RESOLVED; 558 559 if ((mTitleResId == 0) && (mTextResId == 0) && (mDisabledMessageResId == 0)) { 560 return; // Bail early. 561 } 562 563 if (mTitleResId != 0) { 564 mTitle = getResourceString(res, mTitleResId, mTitle); 565 } 566 if (mTextResId != 0) { 567 mText = getResourceString(res, mTextResId, mText); 568 } 569 if (mDisabledMessageResId != 0) { 570 mDisabledMessage = getResourceString(res, mDisabledMessageResId, mDisabledMessage); 571 } 572 } 573 574 /** 575 * Look up resource name for a given resource ID. 576 * 577 * @return a simple resource name (e.g. "text_1") when {@code withType} is false, or with the 578 * type (e.g. "string/text_1"). 579 * 580 * @hide 581 */ 582 @VisibleForTesting lookUpResourceName(@onNull Resources res, int resId, boolean withType, @NonNull String packageName)583 public static String lookUpResourceName(@NonNull Resources res, int resId, boolean withType, 584 @NonNull String packageName) { 585 if (resId == 0) { 586 return null; 587 } 588 try { 589 final String fullName = res.getResourceName(resId); 590 591 if (ANDROID_PACKAGE_NAME.equals(getResourcePackageName(fullName))) { 592 // If it's a framework resource, the value won't change, so just return the ID 593 // value as a string. 594 return String.valueOf(resId); 595 } 596 return withType ? getResourceTypeAndEntryName(fullName) 597 : getResourceEntryName(fullName); 598 } catch (NotFoundException e) { 599 Log.e(TAG, "Resource name for ID=" + resId + " not found in package " + packageName 600 + ". Resource IDs may change when the application is upgraded, and the system" 601 + " may not be able to find the correct resource."); 602 return null; 603 } 604 } 605 606 /** 607 * Extract the package name from a fully-donated resource name. 608 * e.g. "com.android.app1:drawable/icon1" -> "com.android.app1" 609 * @hide 610 */ 611 @VisibleForTesting getResourcePackageName(@onNull String fullResourceName)612 public static String getResourcePackageName(@NonNull String fullResourceName) { 613 final int p1 = fullResourceName.indexOf(':'); 614 if (p1 < 0) { 615 return null; 616 } 617 return fullResourceName.substring(0, p1); 618 } 619 620 /** 621 * Extract the type name from a fully-donated resource name. 622 * e.g. "com.android.app1:drawable/icon1" -> "drawable" 623 * @hide 624 */ 625 @VisibleForTesting getResourceTypeName(@onNull String fullResourceName)626 public static String getResourceTypeName(@NonNull String fullResourceName) { 627 final int p1 = fullResourceName.indexOf(':'); 628 if (p1 < 0) { 629 return null; 630 } 631 final int p2 = fullResourceName.indexOf('/', p1 + 1); 632 if (p2 < 0) { 633 return null; 634 } 635 return fullResourceName.substring(p1 + 1, p2); 636 } 637 638 /** 639 * Extract the type name + the entry name from a fully-donated resource name. 640 * e.g. "com.android.app1:drawable/icon1" -> "drawable/icon1" 641 * @hide 642 */ 643 @VisibleForTesting getResourceTypeAndEntryName(@onNull String fullResourceName)644 public static String getResourceTypeAndEntryName(@NonNull String fullResourceName) { 645 final int p1 = fullResourceName.indexOf(':'); 646 if (p1 < 0) { 647 return null; 648 } 649 return fullResourceName.substring(p1 + 1); 650 } 651 652 /** 653 * Extract the entry name from a fully-donated resource name. 654 * e.g. "com.android.app1:drawable/icon1" -> "icon1" 655 * @hide 656 */ 657 @VisibleForTesting getResourceEntryName(@onNull String fullResourceName)658 public static String getResourceEntryName(@NonNull String fullResourceName) { 659 final int p1 = fullResourceName.indexOf('/'); 660 if (p1 < 0) { 661 return null; 662 } 663 return fullResourceName.substring(p1 + 1); 664 } 665 666 /** 667 * Return the resource ID for a given resource ID. 668 * 669 * Basically its' a wrapper over {@link Resources#getIdentifier(String, String, String)}, except 670 * if {@code resourceName} is an integer then it'll just return its value. (Which also the 671 * aforementioned method would do internally, but not documented, so doing here explicitly.) 672 * 673 * @param res {@link Resources} for the publisher. Must have been loaded with 674 * {@link PackageManager#getResourcesForApplicationAsUser}. 675 * 676 * @hide 677 */ 678 @VisibleForTesting lookUpResourceId(@onNull Resources res, @Nullable String resourceName, @Nullable String resourceType, String packageName)679 public static int lookUpResourceId(@NonNull Resources res, @Nullable String resourceName, 680 @Nullable String resourceType, String packageName) { 681 if (resourceName == null) { 682 return 0; 683 } 684 try { 685 try { 686 // It the name can be parsed as an integer, just use it. 687 return Integer.parseInt(resourceName); 688 } catch (NumberFormatException ignore) { 689 } 690 691 return res.getIdentifier(resourceName, resourceType, packageName); 692 } catch (NotFoundException e) { 693 Log.e(TAG, "Resource ID for name=" + resourceName + " not found in package " 694 + packageName); 695 return 0; 696 } 697 } 698 699 /** 700 * Look up resource names from the resource IDs for the icon res and the text fields, and fill 701 * in the resource name fields. 702 * 703 * @param res {@link Resources} for the publisher. Must have been loaded with 704 * {@link PackageManager#getResourcesForApplicationAsUser}. 705 * 706 * @hide 707 */ lookupAndFillInResourceNames(@onNull Resources res)708 public void lookupAndFillInResourceNames(@NonNull Resources res) { 709 if ((mTitleResId == 0) && (mTextResId == 0) && (mDisabledMessageResId == 0) 710 && (mIconResId == 0)) { 711 return; // Bail early. 712 } 713 714 // We don't need types for strings because their types are always "string". 715 mTitleResName = lookUpResourceName(res, mTitleResId, /*withType=*/ false, mPackageName); 716 mTextResName = lookUpResourceName(res, mTextResId, /*withType=*/ false, mPackageName); 717 mDisabledMessageResName = lookUpResourceName(res, mDisabledMessageResId, 718 /*withType=*/ false, mPackageName); 719 720 // But icons have multiple possible types, so include the type. 721 mIconResName = lookUpResourceName(res, mIconResId, /*withType=*/ true, mPackageName); 722 } 723 724 /** 725 * Look up resource IDs from the resource names for the icon res and the text fields, and fill 726 * in the resource ID fields. 727 * 728 * This is called when an app is updated. 729 * 730 * @hide 731 */ lookupAndFillInResourceIds(@onNull Resources res)732 public void lookupAndFillInResourceIds(@NonNull Resources res) { 733 if ((mTitleResName == null) && (mTextResName == null) && (mDisabledMessageResName == null) 734 && (mIconResName == null)) { 735 return; // Bail early. 736 } 737 738 mTitleResId = lookUpResourceId(res, mTitleResName, RES_TYPE_STRING, mPackageName); 739 mTextResId = lookUpResourceId(res, mTextResName, RES_TYPE_STRING, mPackageName); 740 mDisabledMessageResId = lookUpResourceId(res, mDisabledMessageResName, RES_TYPE_STRING, 741 mPackageName); 742 743 // mIconResName already contains the type, so the third argument is not needed. 744 mIconResId = lookUpResourceId(res, mIconResName, null, mPackageName); 745 } 746 747 /** 748 * Copy a {@link ShortcutInfo}, optionally removing fields. 749 * @hide 750 */ clone(@loneFlags int cloneFlags)751 public ShortcutInfo clone(@CloneFlags int cloneFlags) { 752 return new ShortcutInfo(this, cloneFlags); 753 } 754 755 /** 756 * @hide 757 * 758 * @isUpdating set true if it's "update", as opposed to "replace". 759 */ ensureUpdatableWith(ShortcutInfo source, boolean isUpdating)760 public void ensureUpdatableWith(ShortcutInfo source, boolean isUpdating) { 761 if (isUpdating) { 762 Preconditions.checkState(isVisibleToPublisher(), 763 "[Framework BUG] Invisible shortcuts can't be updated"); 764 } 765 Preconditions.checkState(mUserId == source.mUserId, "Owner User ID must match"); 766 Preconditions.checkState(mId.equals(source.mId), "ID must match"); 767 Preconditions.checkState(mPackageName.equals(source.mPackageName), 768 "Package name must match"); 769 770 if (isVisibleToPublisher()) { 771 // Don't do this check for restore-blocked shortcuts. 772 Preconditions.checkState(!isImmutable(), "Target ShortcutInfo is immutable"); 773 } 774 } 775 776 /** 777 * Copy non-null/zero fields from another {@link ShortcutInfo}. Only "public" information 778 * will be overwritten. The timestamp will *not* be updated to be consistent with other 779 * setters (and also the clock is not injectable in this file). 780 * 781 * - Flags will not change 782 * - mBitmapPath will not change 783 * - Current time will be set to timestamp 784 * 785 * @throws IllegalStateException if source is not compatible. 786 * 787 * @hide 788 */ copyNonNullFieldsFrom(ShortcutInfo source)789 public void copyNonNullFieldsFrom(ShortcutInfo source) { 790 ensureUpdatableWith(source, /*isUpdating=*/ true); 791 792 if (source.mActivity != null) { 793 mActivity = source.mActivity; 794 } 795 796 if (source.mIcon != null) { 797 mIcon = source.mIcon; 798 799 mIconResId = 0; 800 mIconResName = null; 801 mBitmapPath = null; 802 } 803 if (source.mTitle != null) { 804 mTitle = source.mTitle; 805 mTitleResId = 0; 806 mTitleResName = null; 807 } else if (source.mTitleResId != 0) { 808 mTitle = null; 809 mTitleResId = source.mTitleResId; 810 mTitleResName = null; 811 } 812 813 if (source.mText != null) { 814 mText = source.mText; 815 mTextResId = 0; 816 mTextResName = null; 817 } else if (source.mTextResId != 0) { 818 mText = null; 819 mTextResId = source.mTextResId; 820 mTextResName = null; 821 } 822 if (source.mDisabledMessage != null) { 823 mDisabledMessage = source.mDisabledMessage; 824 mDisabledMessageResId = 0; 825 mDisabledMessageResName = null; 826 } else if (source.mDisabledMessageResId != 0) { 827 mDisabledMessage = null; 828 mDisabledMessageResId = source.mDisabledMessageResId; 829 mDisabledMessageResName = null; 830 } 831 if (source.mCategories != null) { 832 mCategories = cloneCategories(source.mCategories); 833 } 834 if (source.mIntents != null) { 835 mIntents = cloneIntents(source.mIntents); 836 mIntentPersistableExtrases = 837 clonePersistableBundle(source.mIntentPersistableExtrases); 838 } 839 if (source.mRank != RANK_NOT_SET) { 840 mRank = source.mRank; 841 } 842 if (source.mExtras != null) { 843 mExtras = source.mExtras; 844 } 845 } 846 847 /** 848 * @hide 849 */ validateIcon(Icon icon)850 public static Icon validateIcon(Icon icon) { 851 switch (icon.getType()) { 852 case Icon.TYPE_RESOURCE: 853 case Icon.TYPE_BITMAP: 854 case Icon.TYPE_ADAPTIVE_BITMAP: 855 break; // OK 856 default: 857 throw getInvalidIconException(); 858 } 859 if (icon.hasTint()) { 860 throw new IllegalArgumentException("Icons with tints are not supported"); 861 } 862 863 return icon; 864 } 865 866 /** @hide */ getInvalidIconException()867 public static IllegalArgumentException getInvalidIconException() { 868 return new IllegalArgumentException("Unsupported icon type:" 869 +" only the bitmap and resource types are supported"); 870 } 871 872 /** 873 * Builder class for {@link ShortcutInfo} objects. 874 * 875 * @see ShortcutManager 876 */ 877 public static class Builder { 878 private final Context mContext; 879 880 private String mId; 881 882 private ComponentName mActivity; 883 884 private Icon mIcon; 885 886 private int mTitleResId; 887 888 private CharSequence mTitle; 889 890 private int mTextResId; 891 892 private CharSequence mText; 893 894 private int mDisabledMessageResId; 895 896 private CharSequence mDisabledMessage; 897 898 private Set<String> mCategories; 899 900 private Intent[] mIntents; 901 902 private int mRank = RANK_NOT_SET; 903 904 private PersistableBundle mExtras; 905 906 /** 907 * Old style constructor. 908 * @hide 909 */ 910 @Deprecated Builder(Context context)911 public Builder(Context context) { 912 mContext = context; 913 } 914 915 /** 916 * Used with the old style constructor, kept for unit tests. 917 * @hide 918 */ 919 @NonNull 920 @Deprecated setId(@onNull String id)921 public Builder setId(@NonNull String id) { 922 mId = Preconditions.checkStringNotEmpty(id, "id cannot be empty"); 923 return this; 924 } 925 926 /** 927 * Constructor. 928 * 929 * @param context Client context. 930 * @param id ID of the shortcut. 931 */ Builder(Context context, String id)932 public Builder(Context context, String id) { 933 mContext = context; 934 mId = Preconditions.checkStringNotEmpty(id, "id cannot be empty"); 935 } 936 937 /** 938 * Sets the target activity. A shortcut will be shown along with this activity's icon 939 * on the launcher. 940 * 941 * When selecting a target activity, keep the following in mind: 942 * <ul> 943 * <li>All dynamic shortcuts must have a target activity. When a shortcut with no target 944 * activity is published using 945 * {@link ShortcutManager#addDynamicShortcuts(List)} or 946 * {@link ShortcutManager#setDynamicShortcuts(List)}, 947 * the first main activity defined in the app's <code>AndroidManifest.xml</code> 948 * file is used. 949 * 950 * <li>Only "main" activities—ones that define the {@link Intent#ACTION_MAIN} 951 * and {@link Intent#CATEGORY_LAUNCHER} intent filters—can be target 952 * activities. 953 * 954 * <li>By default, the first main activity defined in the app's manifest is 955 * the target activity. 956 * 957 * <li>A target activity must belong to the publisher app. 958 * </ul> 959 * 960 * @see ShortcutInfo#getActivity() 961 */ 962 @NonNull setActivity(@onNull ComponentName activity)963 public Builder setActivity(@NonNull ComponentName activity) { 964 mActivity = Preconditions.checkNotNull(activity, "activity cannot be null"); 965 return this; 966 } 967 968 /** 969 * Sets an icon of a shortcut. 970 * 971 * <p>Icons are not available on {@link ShortcutInfo} instances 972 * returned by {@link ShortcutManager} or {@link LauncherApps}. The default launcher 973 * app can use {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)} 974 * or {@link LauncherApps#getShortcutBadgedIconDrawable(ShortcutInfo, int)} to fetch 975 * shortcut icons. 976 * 977 * <p>Tints set with {@link Icon#setTint} or {@link Icon#setTintList} are not supported 978 * and will be ignored. 979 * 980 * <p>Only icons created with {@link Icon#createWithBitmap(Bitmap)}, 981 * {@link Icon#createWithAdaptiveBitmap(Bitmap)} 982 * and {@link Icon#createWithResource} are supported. 983 * Other types, such as URI-based icons, are not supported. 984 * 985 * @see LauncherApps#getShortcutIconDrawable(ShortcutInfo, int) 986 * @see LauncherApps#getShortcutBadgedIconDrawable(ShortcutInfo, int) 987 */ 988 @NonNull setIcon(Icon icon)989 public Builder setIcon(Icon icon) { 990 mIcon = validateIcon(icon); 991 return this; 992 } 993 994 /** 995 * @hide We don't support resource strings for dynamic shortcuts for now. (But unit tests 996 * use it.) 997 */ 998 @Deprecated setShortLabelResId(int shortLabelResId)999 public Builder setShortLabelResId(int shortLabelResId) { 1000 Preconditions.checkState(mTitle == null, "shortLabel already set"); 1001 mTitleResId = shortLabelResId; 1002 return this; 1003 } 1004 1005 /** 1006 * Sets the short title of a shortcut. 1007 * 1008 * <p>This is a mandatory field when publishing a new shortcut with 1009 * {@link ShortcutManager#addDynamicShortcuts(List)} or 1010 * {@link ShortcutManager#setDynamicShortcuts(List)}. 1011 * 1012 * <p>This field is intended to be a concise description of a shortcut. 1013 * 1014 * <p>The recommended maximum length is 10 characters. 1015 * 1016 * @see ShortcutInfo#getShortLabel() 1017 */ 1018 @NonNull setShortLabel(@onNull CharSequence shortLabel)1019 public Builder setShortLabel(@NonNull CharSequence shortLabel) { 1020 Preconditions.checkState(mTitleResId == 0, "shortLabelResId already set"); 1021 mTitle = Preconditions.checkStringNotEmpty(shortLabel, "shortLabel cannot be empty"); 1022 return this; 1023 } 1024 1025 /** 1026 * @hide We don't support resource strings for dynamic shortcuts for now. (But unit tests 1027 * use it.) 1028 */ 1029 @Deprecated setLongLabelResId(int longLabelResId)1030 public Builder setLongLabelResId(int longLabelResId) { 1031 Preconditions.checkState(mText == null, "longLabel already set"); 1032 mTextResId = longLabelResId; 1033 return this; 1034 } 1035 1036 /** 1037 * Sets the text of a shortcut. 1038 * 1039 * <p>This field is intended to be more descriptive than the shortcut title. The launcher 1040 * shows this instead of the short title when it has enough space. 1041 * 1042 * <p>The recommend maximum length is 25 characters. 1043 * 1044 * @see ShortcutInfo#getLongLabel() 1045 */ 1046 @NonNull setLongLabel(@onNull CharSequence longLabel)1047 public Builder setLongLabel(@NonNull CharSequence longLabel) { 1048 Preconditions.checkState(mTextResId == 0, "longLabelResId already set"); 1049 mText = Preconditions.checkStringNotEmpty(longLabel, "longLabel cannot be empty"); 1050 return this; 1051 } 1052 1053 /** @hide -- old signature, the internal code still uses it. */ 1054 @Deprecated setTitle(@onNull CharSequence value)1055 public Builder setTitle(@NonNull CharSequence value) { 1056 return setShortLabel(value); 1057 } 1058 1059 /** @hide -- old signature, the internal code still uses it. */ 1060 @Deprecated setTitleResId(int value)1061 public Builder setTitleResId(int value) { 1062 return setShortLabelResId(value); 1063 } 1064 1065 /** @hide -- old signature, the internal code still uses it. */ 1066 @Deprecated setText(@onNull CharSequence value)1067 public Builder setText(@NonNull CharSequence value) { 1068 return setLongLabel(value); 1069 } 1070 1071 /** @hide -- old signature, the internal code still uses it. */ 1072 @Deprecated setTextResId(int value)1073 public Builder setTextResId(int value) { 1074 return setLongLabelResId(value); 1075 } 1076 1077 /** 1078 * @hide We don't support resource strings for dynamic shortcuts for now. (But unit tests 1079 * use it.) 1080 */ 1081 @Deprecated setDisabledMessageResId(int disabledMessageResId)1082 public Builder setDisabledMessageResId(int disabledMessageResId) { 1083 Preconditions.checkState(mDisabledMessage == null, "disabledMessage already set"); 1084 mDisabledMessageResId = disabledMessageResId; 1085 return this; 1086 } 1087 1088 /** 1089 * Sets the message that should be shown when the user attempts to start a shortcut that 1090 * is disabled. 1091 * 1092 * @see ShortcutInfo#getDisabledMessage() 1093 */ 1094 @NonNull setDisabledMessage(@onNull CharSequence disabledMessage)1095 public Builder setDisabledMessage(@NonNull CharSequence disabledMessage) { 1096 Preconditions.checkState( 1097 mDisabledMessageResId == 0, "disabledMessageResId already set"); 1098 mDisabledMessage = 1099 Preconditions.checkStringNotEmpty(disabledMessage, 1100 "disabledMessage cannot be empty"); 1101 return this; 1102 } 1103 1104 /** 1105 * Sets categories for a shortcut. Launcher apps may use this information to 1106 * categorize shortcuts. 1107 * 1108 * @see #SHORTCUT_CATEGORY_CONVERSATION 1109 * @see ShortcutInfo#getCategories() 1110 */ 1111 @NonNull setCategories(Set<String> categories)1112 public Builder setCategories(Set<String> categories) { 1113 mCategories = categories; 1114 return this; 1115 } 1116 1117 /** 1118 * Sets the intent of a shortcut. Alternatively, {@link #setIntents(Intent[])} can be used 1119 * to launch an activity with other activities in the back stack. 1120 * 1121 * <p>This is a mandatory field when publishing a new shortcut with 1122 * {@link ShortcutManager#addDynamicShortcuts(List)} or 1123 * {@link ShortcutManager#setDynamicShortcuts(List)}. 1124 * 1125 * <p>A shortcut can launch any intent that the publisher app has permission to 1126 * launch. For example, a shortcut can launch an unexported activity within the publisher 1127 * app. A shortcut intent doesn't have to point at the target activity. 1128 * 1129 * <p>The given {@code intent} can contain extras, but these extras must contain values 1130 * of primitive types in order for the system to persist these values. 1131 * 1132 * @see ShortcutInfo#getIntent() 1133 * @see #setIntents(Intent[]) 1134 */ 1135 @NonNull setIntent(@onNull Intent intent)1136 public Builder setIntent(@NonNull Intent intent) { 1137 return setIntents(new Intent[]{intent}); 1138 } 1139 1140 /** 1141 * Sets multiple intents instead of a single intent, in order to launch an activity with 1142 * other activities in back stack. Use {@link TaskStackBuilder} to build intents. The 1143 * last element in the list represents the only intent that doesn't place an activity on 1144 * the back stack. 1145 * See the {@link ShortcutManager} javadoc for details. 1146 * 1147 * @see Builder#setIntent(Intent) 1148 * @see ShortcutInfo#getIntents() 1149 * @see Context#startActivities(Intent[]) 1150 * @see TaskStackBuilder 1151 */ 1152 @NonNull setIntents(@onNull Intent[] intents)1153 public Builder setIntents(@NonNull Intent[] intents) { 1154 Preconditions.checkNotNull(intents, "intents cannot be null"); 1155 Preconditions.checkNotNull(intents.length, "intents cannot be empty"); 1156 for (Intent intent : intents) { 1157 Preconditions.checkNotNull(intent, "intents cannot contain null"); 1158 Preconditions.checkNotNull(intent.getAction(), "intent's action must be set"); 1159 } 1160 // Make sure always clone incoming intents. 1161 mIntents = cloneIntents(intents); 1162 return this; 1163 } 1164 1165 /** 1166 * "Rank" of a shortcut, which is a non-negative value that's used by the launcher app 1167 * to sort shortcuts. 1168 * 1169 * See {@link ShortcutInfo#getRank()} for details. 1170 */ 1171 @NonNull setRank(int rank)1172 public Builder setRank(int rank) { 1173 Preconditions.checkArgument((0 <= rank), 1174 "Rank cannot be negative or bigger than MAX_RANK"); 1175 mRank = rank; 1176 return this; 1177 } 1178 1179 /** 1180 * Extras that the app can set for any purpose. 1181 * 1182 * <p>Apps can store arbitrary shortcut metadata in extras and retrieve the 1183 * metadata later using {@link ShortcutInfo#getExtras()}. 1184 */ 1185 @NonNull setExtras(@onNull PersistableBundle extras)1186 public Builder setExtras(@NonNull PersistableBundle extras) { 1187 mExtras = extras; 1188 return this; 1189 } 1190 1191 /** 1192 * Creates a {@link ShortcutInfo} instance. 1193 */ 1194 @NonNull build()1195 public ShortcutInfo build() { 1196 return new ShortcutInfo(this); 1197 } 1198 } 1199 1200 /** 1201 * Returns the ID of a shortcut. 1202 * 1203 * <p>Shortcut IDs are unique within each publisher app and must be stable across 1204 * devices so that shortcuts will still be valid when restored on a different device. 1205 * See {@link ShortcutManager} for details. 1206 */ 1207 @NonNull getId()1208 public String getId() { 1209 return mId; 1210 } 1211 1212 /** 1213 * Return the package name of the publisher app. 1214 */ 1215 @NonNull getPackage()1216 public String getPackage() { 1217 return mPackageName; 1218 } 1219 1220 /** 1221 * Return the target activity. 1222 * 1223 * <p>This has nothing to do with the activity that this shortcut will launch. 1224 * Launcher apps should show the launcher icon for the returned activity alongside 1225 * this shortcut. 1226 * 1227 * @see Builder#setActivity 1228 */ 1229 @Nullable getActivity()1230 public ComponentName getActivity() { 1231 return mActivity; 1232 } 1233 1234 /** @hide */ setActivity(ComponentName activity)1235 public void setActivity(ComponentName activity) { 1236 mActivity = activity; 1237 } 1238 1239 /** 1240 * Returns the shortcut icon. 1241 * 1242 * @hide 1243 */ 1244 @Nullable getIcon()1245 public Icon getIcon() { 1246 return mIcon; 1247 } 1248 1249 /** @hide -- old signature, the internal code still uses it. */ 1250 @Nullable 1251 @Deprecated getTitle()1252 public CharSequence getTitle() { 1253 return mTitle; 1254 } 1255 1256 /** @hide -- old signature, the internal code still uses it. */ 1257 @Deprecated getTitleResId()1258 public int getTitleResId() { 1259 return mTitleResId; 1260 } 1261 1262 /** @hide -- old signature, the internal code still uses it. */ 1263 @Nullable 1264 @Deprecated getText()1265 public CharSequence getText() { 1266 return mText; 1267 } 1268 1269 /** @hide -- old signature, the internal code still uses it. */ 1270 @Deprecated getTextResId()1271 public int getTextResId() { 1272 return mTextResId; 1273 } 1274 1275 /** 1276 * Return the short description of a shortcut. 1277 * 1278 * @see Builder#setShortLabel(CharSequence) 1279 */ 1280 @Nullable getShortLabel()1281 public CharSequence getShortLabel() { 1282 return mTitle; 1283 } 1284 1285 /** @hide */ getShortLabelResourceId()1286 public int getShortLabelResourceId() { 1287 return mTitleResId; 1288 } 1289 1290 /** 1291 * Return the long description of a shortcut. 1292 * 1293 * @see Builder#setLongLabel(CharSequence) 1294 */ 1295 @Nullable getLongLabel()1296 public CharSequence getLongLabel() { 1297 return mText; 1298 } 1299 1300 /** @hide */ getLongLabelResourceId()1301 public int getLongLabelResourceId() { 1302 return mTextResId; 1303 } 1304 1305 /** 1306 * Return the message that should be shown when the user attempts to start a shortcut 1307 * that is disabled. 1308 * 1309 * @see Builder#setDisabledMessage(CharSequence) 1310 */ 1311 @Nullable getDisabledMessage()1312 public CharSequence getDisabledMessage() { 1313 return mDisabledMessage; 1314 } 1315 1316 /** @hide */ getDisabledMessageResourceId()1317 public int getDisabledMessageResourceId() { 1318 return mDisabledMessageResId; 1319 } 1320 1321 /** @hide */ setDisabledReason(@isabledReason int reason)1322 public void setDisabledReason(@DisabledReason int reason) { 1323 mDisabledReason = reason; 1324 } 1325 1326 /** 1327 * Returns why a shortcut has been disabled. 1328 */ 1329 @DisabledReason getDisabledReason()1330 public int getDisabledReason() { 1331 return mDisabledReason; 1332 } 1333 1334 /** 1335 * Return the shortcut's categories. 1336 * 1337 * @see Builder#setCategories(Set) 1338 */ 1339 @Nullable getCategories()1340 public Set<String> getCategories() { 1341 return mCategories; 1342 } 1343 1344 /** 1345 * Returns the intent that is executed when the user selects this shortcut. 1346 * If setIntents() was used, then return the last intent in the array. 1347 * 1348 * <p>Launcher apps <b>cannot</b> see the intent. If a {@link ShortcutInfo} is 1349 * obtained via {@link LauncherApps}, then this method will always return null. 1350 * Launchers can only start a shortcut intent with {@link LauncherApps#startShortcut}. 1351 * 1352 * @see Builder#setIntent(Intent) 1353 */ 1354 @Nullable getIntent()1355 public Intent getIntent() { 1356 if (mIntents == null || mIntents.length == 0) { 1357 return null; 1358 } 1359 final int last = mIntents.length - 1; 1360 final Intent intent = new Intent(mIntents[last]); 1361 return setIntentExtras(intent, mIntentPersistableExtrases[last]); 1362 } 1363 1364 /** 1365 * Return the intent set with {@link Builder#setIntents(Intent[])}. 1366 * 1367 * <p>Launcher apps <b>cannot</b> see the intents. If a {@link ShortcutInfo} is 1368 * obtained via {@link LauncherApps}, then this method will always return null. 1369 * Launchers can only start a shortcut intent with {@link LauncherApps#startShortcut}. 1370 * 1371 * @see Builder#setIntents(Intent[]) 1372 */ 1373 @Nullable getIntents()1374 public Intent[] getIntents() { 1375 final Intent[] ret = new Intent[mIntents.length]; 1376 1377 for (int i = 0; i < ret.length; i++) { 1378 ret[i] = new Intent(mIntents[i]); 1379 setIntentExtras(ret[i], mIntentPersistableExtrases[i]); 1380 } 1381 1382 return ret; 1383 } 1384 1385 /** 1386 * Return "raw" intents, which is the original intents without the extras. 1387 * @hide 1388 */ 1389 @Nullable getIntentsNoExtras()1390 public Intent[] getIntentsNoExtras() { 1391 return mIntents; 1392 } 1393 1394 /** 1395 * The extras in the intents. We convert extras into {@link PersistableBundle} so we can 1396 * persist them. 1397 * @hide 1398 */ 1399 @Nullable getIntentPersistableExtrases()1400 public PersistableBundle[] getIntentPersistableExtrases() { 1401 return mIntentPersistableExtrases; 1402 } 1403 1404 /** 1405 * "Rank" of a shortcut, which is a non-negative, sequential value that's unique for each 1406 * {@link #getActivity} for each of the two types of shortcuts (static and dynamic). 1407 * 1408 * <p>Because static shortcuts and dynamic shortcuts have overlapping ranks, 1409 * when a launcher app shows shortcuts for an activity, it should first show 1410 * the static shortcuts, followed by the dynamic shortcuts. Within each of those categories, 1411 * shortcuts should be sorted by rank in ascending order. 1412 * 1413 * <p><em>Floating shortcuts</em>, or shortcuts that are neither static nor dynamic, will all 1414 * have rank 0, because they aren't sorted. 1415 * 1416 * See the {@link ShortcutManager}'s class javadoc for details. 1417 * 1418 * @see Builder#setRank(int) 1419 */ getRank()1420 public int getRank() { 1421 return mRank; 1422 } 1423 1424 /** @hide */ hasRank()1425 public boolean hasRank() { 1426 return mRank != RANK_NOT_SET; 1427 } 1428 1429 /** @hide */ setRank(int rank)1430 public void setRank(int rank) { 1431 mRank = rank; 1432 } 1433 1434 /** @hide */ clearImplicitRankAndRankChangedFlag()1435 public void clearImplicitRankAndRankChangedFlag() { 1436 mImplicitRank = 0; 1437 } 1438 1439 /** @hide */ setImplicitRank(int rank)1440 public void setImplicitRank(int rank) { 1441 // Make sure to keep RANK_CHANGED_BIT. 1442 mImplicitRank = (mImplicitRank & RANK_CHANGED_BIT) | (rank & IMPLICIT_RANK_MASK); 1443 } 1444 1445 /** @hide */ getImplicitRank()1446 public int getImplicitRank() { 1447 return mImplicitRank & IMPLICIT_RANK_MASK; 1448 } 1449 1450 /** @hide */ setRankChanged()1451 public void setRankChanged() { 1452 mImplicitRank |= RANK_CHANGED_BIT; 1453 } 1454 1455 /** @hide */ isRankChanged()1456 public boolean isRankChanged() { 1457 return (mImplicitRank & RANK_CHANGED_BIT) != 0; 1458 } 1459 1460 /** 1461 * Extras that the app can set for any purpose. 1462 * 1463 * @see Builder#setExtras(PersistableBundle) 1464 */ 1465 @Nullable getExtras()1466 public PersistableBundle getExtras() { 1467 return mExtras; 1468 } 1469 1470 /** @hide */ getUserId()1471 public int getUserId() { 1472 return mUserId; 1473 } 1474 1475 /** 1476 * {@link UserHandle} on which the publisher created this shortcut. 1477 */ getUserHandle()1478 public UserHandle getUserHandle() { 1479 return UserHandle.of(mUserId); 1480 } 1481 1482 /** 1483 * Last time when any of the fields was updated. 1484 */ getLastChangedTimestamp()1485 public long getLastChangedTimestamp() { 1486 return mLastChangedTimestamp; 1487 } 1488 1489 /** @hide */ 1490 @ShortcutFlags getFlags()1491 public int getFlags() { 1492 return mFlags; 1493 } 1494 1495 /** @hide*/ replaceFlags(@hortcutFlags int flags)1496 public void replaceFlags(@ShortcutFlags int flags) { 1497 mFlags = flags; 1498 } 1499 1500 /** @hide*/ addFlags(@hortcutFlags int flags)1501 public void addFlags(@ShortcutFlags int flags) { 1502 mFlags |= flags; 1503 } 1504 1505 /** @hide*/ clearFlags(@hortcutFlags int flags)1506 public void clearFlags(@ShortcutFlags int flags) { 1507 mFlags &= ~flags; 1508 } 1509 1510 /** @hide*/ hasFlags(@hortcutFlags int flags)1511 public boolean hasFlags(@ShortcutFlags int flags) { 1512 return (mFlags & flags) == flags; 1513 } 1514 1515 /** @hide */ isReturnedByServer()1516 public boolean isReturnedByServer() { 1517 return hasFlags(FLAG_RETURNED_BY_SERVICE); 1518 } 1519 1520 /** @hide */ setReturnedByServer()1521 public void setReturnedByServer() { 1522 addFlags(FLAG_RETURNED_BY_SERVICE); 1523 } 1524 1525 /** Return whether a shortcut is dynamic. */ isDynamic()1526 public boolean isDynamic() { 1527 return hasFlags(FLAG_DYNAMIC); 1528 } 1529 1530 /** Return whether a shortcut is pinned. */ isPinned()1531 public boolean isPinned() { 1532 return hasFlags(FLAG_PINNED); 1533 } 1534 1535 /** 1536 * Return whether a shortcut is static; that is, whether a shortcut is 1537 * published from AndroidManifest.xml. If {@code true}, the shortcut is 1538 * also {@link #isImmutable()}. 1539 * 1540 * <p>When an app is upgraded and a shortcut is no longer published from AndroidManifest.xml, 1541 * this will be set to {@code false}. If the shortcut is not pinned, then it'll disappear. 1542 * However, if it's pinned, it will still be visible, {@link #isEnabled()} will be 1543 * {@code false} and {@link #isImmutable()} will be {@code true}. 1544 */ isDeclaredInManifest()1545 public boolean isDeclaredInManifest() { 1546 return hasFlags(FLAG_MANIFEST); 1547 } 1548 1549 /** @hide kept for unit tests */ 1550 @Deprecated isManifestShortcut()1551 public boolean isManifestShortcut() { 1552 return isDeclaredInManifest(); 1553 } 1554 1555 /** 1556 * @return true if pinned but neither static nor dynamic. 1557 * @hide 1558 */ isFloating()1559 public boolean isFloating() { 1560 return isPinned() && !(isDynamic() || isManifestShortcut()); 1561 } 1562 1563 /** @hide */ isOriginallyFromManifest()1564 public boolean isOriginallyFromManifest() { 1565 return hasFlags(FLAG_IMMUTABLE); 1566 } 1567 1568 /** @hide */ isDynamicVisible()1569 public boolean isDynamicVisible() { 1570 return isDynamic() && isVisibleToPublisher(); 1571 } 1572 1573 /** @hide */ isPinnedVisible()1574 public boolean isPinnedVisible() { 1575 return isPinned() && isVisibleToPublisher(); 1576 } 1577 1578 /** @hide */ isManifestVisible()1579 public boolean isManifestVisible() { 1580 return isDeclaredInManifest() && isVisibleToPublisher(); 1581 } 1582 1583 /** 1584 * Return if a shortcut is immutable, in which case it cannot be modified with any of 1585 * {@link ShortcutManager} APIs. 1586 * 1587 * <p>All static shortcuts are immutable. When a static shortcut is pinned and is then 1588 * disabled because it doesn't appear in AndroidManifest.xml for a newer version of the 1589 * app, {@link #isDeclaredInManifest()} returns {@code false}, but the shortcut 1590 * is still immutable. 1591 * 1592 * <p>All shortcuts originally published via the {@link ShortcutManager} APIs 1593 * are all mutable. 1594 */ isImmutable()1595 public boolean isImmutable() { 1596 return hasFlags(FLAG_IMMUTABLE); 1597 } 1598 1599 /** 1600 * Returns {@code false} if a shortcut is disabled with 1601 * {@link ShortcutManager#disableShortcuts}. 1602 */ isEnabled()1603 public boolean isEnabled() { 1604 return !hasFlags(FLAG_DISABLED); 1605 } 1606 1607 /** @hide */ isAlive()1608 public boolean isAlive() { 1609 return hasFlags(FLAG_PINNED) || hasFlags(FLAG_DYNAMIC) || hasFlags(FLAG_MANIFEST); 1610 } 1611 1612 /** @hide */ usesQuota()1613 public boolean usesQuota() { 1614 return hasFlags(FLAG_DYNAMIC) || hasFlags(FLAG_MANIFEST); 1615 } 1616 1617 /** 1618 * Return whether a shortcut's icon is a resource in the owning package. 1619 * 1620 * @hide internal/unit tests only 1621 */ hasIconResource()1622 public boolean hasIconResource() { 1623 return hasFlags(FLAG_HAS_ICON_RES); 1624 } 1625 1626 /** @hide */ hasStringResources()1627 public boolean hasStringResources() { 1628 return (mTitleResId != 0) || (mTextResId != 0) || (mDisabledMessageResId != 0); 1629 } 1630 1631 /** @hide */ hasAnyResources()1632 public boolean hasAnyResources() { 1633 return hasIconResource() || hasStringResources(); 1634 } 1635 1636 /** 1637 * Return whether a shortcut's icon is stored as a file. 1638 * 1639 * @hide internal/unit tests only 1640 */ hasIconFile()1641 public boolean hasIconFile() { 1642 return hasFlags(FLAG_HAS_ICON_FILE); 1643 } 1644 1645 /** 1646 * Return whether a shortcut's icon is adaptive bitmap following design guideline 1647 * defined in {@link android.graphics.drawable.AdaptiveIconDrawable}. 1648 * 1649 * @hide internal/unit tests only 1650 */ hasAdaptiveBitmap()1651 public boolean hasAdaptiveBitmap() { 1652 return hasFlags(FLAG_ADAPTIVE_BITMAP); 1653 } 1654 1655 /** @hide */ isIconPendingSave()1656 public boolean isIconPendingSave() { 1657 return hasFlags(FLAG_ICON_FILE_PENDING_SAVE); 1658 } 1659 1660 /** @hide */ setIconPendingSave()1661 public void setIconPendingSave() { 1662 addFlags(FLAG_ICON_FILE_PENDING_SAVE); 1663 } 1664 1665 /** @hide */ clearIconPendingSave()1666 public void clearIconPendingSave() { 1667 clearFlags(FLAG_ICON_FILE_PENDING_SAVE); 1668 } 1669 1670 /** 1671 * When the system wasn't able to restore a shortcut, it'll still be registered to the system 1672 * but disabled, and such shortcuts will not be visible to the publisher. They're still visible 1673 * to launchers though. 1674 * 1675 * @hide 1676 */ 1677 @TestApi isVisibleToPublisher()1678 public boolean isVisibleToPublisher() { 1679 return !isDisabledForRestoreIssue(mDisabledReason); 1680 } 1681 1682 /** 1683 * Return whether a shortcut only contains "key" information only or not. If true, only the 1684 * following fields are available. 1685 * <ul> 1686 * <li>{@link #getId()} 1687 * <li>{@link #getPackage()} 1688 * <li>{@link #getActivity()} 1689 * <li>{@link #getLastChangedTimestamp()} 1690 * <li>{@link #isDynamic()} 1691 * <li>{@link #isPinned()} 1692 * <li>{@link #isDeclaredInManifest()} 1693 * <li>{@link #isImmutable()} 1694 * <li>{@link #isEnabled()} 1695 * <li>{@link #getUserHandle()} 1696 * </ul> 1697 * 1698 * <p>For performance reasons, shortcuts passed to 1699 * {@link LauncherApps.Callback#onShortcutsChanged(String, List, UserHandle)} as well as those 1700 * returned from {@link LauncherApps#getShortcuts(ShortcutQuery, UserHandle)} 1701 * while using the {@link ShortcutQuery#FLAG_GET_KEY_FIELDS_ONLY} option contain only key 1702 * information. 1703 */ hasKeyFieldsOnly()1704 public boolean hasKeyFieldsOnly() { 1705 return hasFlags(FLAG_KEY_FIELDS_ONLY); 1706 } 1707 1708 /** @hide */ hasStringResourcesResolved()1709 public boolean hasStringResourcesResolved() { 1710 return hasFlags(FLAG_STRINGS_RESOLVED); 1711 } 1712 1713 /** @hide */ updateTimestamp()1714 public void updateTimestamp() { 1715 mLastChangedTimestamp = System.currentTimeMillis(); 1716 } 1717 1718 /** @hide */ 1719 // VisibleForTesting setTimestamp(long value)1720 public void setTimestamp(long value) { 1721 mLastChangedTimestamp = value; 1722 } 1723 1724 /** @hide */ clearIcon()1725 public void clearIcon() { 1726 mIcon = null; 1727 } 1728 1729 /** @hide */ setIconResourceId(int iconResourceId)1730 public void setIconResourceId(int iconResourceId) { 1731 if (mIconResId != iconResourceId) { 1732 mIconResName = null; 1733 } 1734 mIconResId = iconResourceId; 1735 } 1736 1737 /** 1738 * Get the resource ID for the icon, valid only when {@link #hasIconResource()} } is true. 1739 * @hide internal / tests only. 1740 */ getIconResourceId()1741 public int getIconResourceId() { 1742 return mIconResId; 1743 } 1744 1745 /** 1746 * Bitmap path. Note this will be null even if {@link #hasIconFile()} is set when the save 1747 * is pending. Use {@link #isIconPendingSave()} to check it. 1748 * 1749 * @hide 1750 */ getBitmapPath()1751 public String getBitmapPath() { 1752 return mBitmapPath; 1753 } 1754 1755 /** @hide */ setBitmapPath(String bitmapPath)1756 public void setBitmapPath(String bitmapPath) { 1757 mBitmapPath = bitmapPath; 1758 } 1759 1760 /** @hide */ setDisabledMessageResId(int disabledMessageResId)1761 public void setDisabledMessageResId(int disabledMessageResId) { 1762 if (mDisabledMessageResId != disabledMessageResId) { 1763 mDisabledMessageResName = null; 1764 } 1765 mDisabledMessageResId = disabledMessageResId; 1766 mDisabledMessage = null; 1767 } 1768 1769 /** @hide */ setDisabledMessage(String disabledMessage)1770 public void setDisabledMessage(String disabledMessage) { 1771 mDisabledMessage = disabledMessage; 1772 mDisabledMessageResId = 0; 1773 mDisabledMessageResName = null; 1774 } 1775 1776 /** @hide */ getTitleResName()1777 public String getTitleResName() { 1778 return mTitleResName; 1779 } 1780 1781 /** @hide */ setTitleResName(String titleResName)1782 public void setTitleResName(String titleResName) { 1783 mTitleResName = titleResName; 1784 } 1785 1786 /** @hide */ getTextResName()1787 public String getTextResName() { 1788 return mTextResName; 1789 } 1790 1791 /** @hide */ setTextResName(String textResName)1792 public void setTextResName(String textResName) { 1793 mTextResName = textResName; 1794 } 1795 1796 /** @hide */ getDisabledMessageResName()1797 public String getDisabledMessageResName() { 1798 return mDisabledMessageResName; 1799 } 1800 1801 /** @hide */ setDisabledMessageResName(String disabledMessageResName)1802 public void setDisabledMessageResName(String disabledMessageResName) { 1803 mDisabledMessageResName = disabledMessageResName; 1804 } 1805 1806 /** @hide */ getIconResName()1807 public String getIconResName() { 1808 return mIconResName; 1809 } 1810 1811 /** @hide */ setIconResName(String iconResName)1812 public void setIconResName(String iconResName) { 1813 mIconResName = iconResName; 1814 } 1815 1816 /** 1817 * Replaces the intent. 1818 * 1819 * @throws IllegalArgumentException when extra is not compatible with {@link PersistableBundle}. 1820 * 1821 * @hide 1822 */ setIntents(Intent[] intents)1823 public void setIntents(Intent[] intents) throws IllegalArgumentException { 1824 Preconditions.checkNotNull(intents); 1825 Preconditions.checkArgument(intents.length > 0); 1826 1827 mIntents = cloneIntents(intents); 1828 fixUpIntentExtras(); 1829 } 1830 1831 /** @hide */ setIntentExtras(Intent intent, PersistableBundle extras)1832 public static Intent setIntentExtras(Intent intent, PersistableBundle extras) { 1833 if (extras == null) { 1834 intent.replaceExtras((Bundle) null); 1835 } else { 1836 intent.replaceExtras(new Bundle(extras)); 1837 } 1838 return intent; 1839 } 1840 1841 /** 1842 * Replaces the categories. 1843 * 1844 * @hide 1845 */ setCategories(Set<String> categories)1846 public void setCategories(Set<String> categories) { 1847 mCategories = cloneCategories(categories); 1848 } 1849 ShortcutInfo(Parcel source)1850 private ShortcutInfo(Parcel source) { 1851 final ClassLoader cl = getClass().getClassLoader(); 1852 1853 mUserId = source.readInt(); 1854 mId = source.readString(); 1855 mPackageName = source.readString(); 1856 mActivity = source.readParcelable(cl); 1857 mFlags = source.readInt(); 1858 mIconResId = source.readInt(); 1859 mLastChangedTimestamp = source.readLong(); 1860 mDisabledReason = source.readInt(); 1861 1862 if (source.readInt() == 0) { 1863 return; // key information only. 1864 } 1865 1866 mIcon = source.readParcelable(cl); 1867 mTitle = source.readCharSequence(); 1868 mTitleResId = source.readInt(); 1869 mText = source.readCharSequence(); 1870 mTextResId = source.readInt(); 1871 mDisabledMessage = source.readCharSequence(); 1872 mDisabledMessageResId = source.readInt(); 1873 mIntents = source.readParcelableArray(cl, Intent.class); 1874 mIntentPersistableExtrases = source.readParcelableArray(cl, PersistableBundle.class); 1875 mRank = source.readInt(); 1876 mExtras = source.readParcelable(cl); 1877 mBitmapPath = source.readString(); 1878 1879 mIconResName = source.readString(); 1880 mTitleResName = source.readString(); 1881 mTextResName = source.readString(); 1882 mDisabledMessageResName = source.readString(); 1883 1884 int N = source.readInt(); 1885 if (N == 0) { 1886 mCategories = null; 1887 } else { 1888 mCategories = new ArraySet<>(N); 1889 for (int i = 0; i < N; i++) { 1890 mCategories.add(source.readString().intern()); 1891 } 1892 } 1893 } 1894 1895 @Override writeToParcel(Parcel dest, int flags)1896 public void writeToParcel(Parcel dest, int flags) { 1897 dest.writeInt(mUserId); 1898 dest.writeString(mId); 1899 dest.writeString(mPackageName); 1900 dest.writeParcelable(mActivity, flags); 1901 dest.writeInt(mFlags); 1902 dest.writeInt(mIconResId); 1903 dest.writeLong(mLastChangedTimestamp); 1904 dest.writeInt(mDisabledReason); 1905 1906 if (hasKeyFieldsOnly()) { 1907 dest.writeInt(0); 1908 return; 1909 } 1910 dest.writeInt(1); 1911 1912 dest.writeParcelable(mIcon, flags); 1913 dest.writeCharSequence(mTitle); 1914 dest.writeInt(mTitleResId); 1915 dest.writeCharSequence(mText); 1916 dest.writeInt(mTextResId); 1917 dest.writeCharSequence(mDisabledMessage); 1918 dest.writeInt(mDisabledMessageResId); 1919 1920 dest.writeParcelableArray(mIntents, flags); 1921 dest.writeParcelableArray(mIntentPersistableExtrases, flags); 1922 dest.writeInt(mRank); 1923 dest.writeParcelable(mExtras, flags); 1924 dest.writeString(mBitmapPath); 1925 1926 dest.writeString(mIconResName); 1927 dest.writeString(mTitleResName); 1928 dest.writeString(mTextResName); 1929 dest.writeString(mDisabledMessageResName); 1930 1931 if (mCategories != null) { 1932 final int N = mCategories.size(); 1933 dest.writeInt(N); 1934 for (int i = 0; i < N; i++) { 1935 dest.writeString(mCategories.valueAt(i)); 1936 } 1937 } else { 1938 dest.writeInt(0); 1939 } 1940 } 1941 1942 public static final Creator<ShortcutInfo> CREATOR = 1943 new Creator<ShortcutInfo>() { 1944 public ShortcutInfo createFromParcel(Parcel source) { 1945 return new ShortcutInfo(source); 1946 } 1947 public ShortcutInfo[] newArray(int size) { 1948 return new ShortcutInfo[size]; 1949 } 1950 }; 1951 1952 @Override describeContents()1953 public int describeContents() { 1954 return 0; 1955 } 1956 1957 1958 /** 1959 * Return a string representation, intended for logging. Some fields will be retracted. 1960 */ 1961 @Override toString()1962 public String toString() { 1963 return toStringInner(/* secure =*/ true, /* includeInternalData =*/ false, 1964 /*indent=*/ null); 1965 } 1966 1967 /** @hide */ toInsecureString()1968 public String toInsecureString() { 1969 return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true, 1970 /*indent=*/ null); 1971 } 1972 1973 /** @hide */ toDumpString(String indent)1974 public String toDumpString(String indent) { 1975 return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true, indent); 1976 } 1977 addIndentOrComma(StringBuilder sb, String indent)1978 private void addIndentOrComma(StringBuilder sb, String indent) { 1979 if (indent != null) { 1980 sb.append("\n "); 1981 sb.append(indent); 1982 } else { 1983 sb.append(", "); 1984 } 1985 } 1986 toStringInner(boolean secure, boolean includeInternalData, String indent)1987 private String toStringInner(boolean secure, boolean includeInternalData, String indent) { 1988 final StringBuilder sb = new StringBuilder(); 1989 1990 if (indent != null) { 1991 sb.append(indent); 1992 } 1993 1994 sb.append("ShortcutInfo {"); 1995 1996 sb.append("id="); 1997 sb.append(secure ? "***" : mId); 1998 1999 sb.append(", flags=0x"); 2000 sb.append(Integer.toHexString(mFlags)); 2001 sb.append(" ["); 2002 if ((mFlags & FLAG_SHADOW) != 0) { 2003 // Note the shadow flag isn't actually used anywhere and it's just for dumpsys, so 2004 // we don't have an isXxx for this. 2005 sb.append("Sdw"); 2006 } 2007 if (!isEnabled()) { 2008 sb.append("Dis"); 2009 } 2010 if (isImmutable()) { 2011 sb.append("Im"); 2012 } 2013 if (isManifestShortcut()) { 2014 sb.append("Man"); 2015 } 2016 if (isDynamic()) { 2017 sb.append("Dyn"); 2018 } 2019 if (isPinned()) { 2020 sb.append("Pin"); 2021 } 2022 if (hasIconFile()) { 2023 sb.append("Ic-f"); 2024 } 2025 if (isIconPendingSave()) { 2026 sb.append("Pens"); 2027 } 2028 if (hasIconResource()) { 2029 sb.append("Ic-r"); 2030 } 2031 if (hasKeyFieldsOnly()) { 2032 sb.append("Key"); 2033 } 2034 if (hasStringResourcesResolved()) { 2035 sb.append("Str"); 2036 } 2037 if (isReturnedByServer()) { 2038 sb.append("Rets"); 2039 } 2040 sb.append("]"); 2041 2042 addIndentOrComma(sb, indent); 2043 2044 sb.append("packageName="); 2045 sb.append(mPackageName); 2046 2047 addIndentOrComma(sb, indent); 2048 2049 sb.append("activity="); 2050 sb.append(mActivity); 2051 2052 addIndentOrComma(sb, indent); 2053 2054 sb.append("shortLabel="); 2055 sb.append(secure ? "***" : mTitle); 2056 sb.append(", resId="); 2057 sb.append(mTitleResId); 2058 sb.append("["); 2059 sb.append(mTitleResName); 2060 sb.append("]"); 2061 2062 addIndentOrComma(sb, indent); 2063 2064 sb.append("longLabel="); 2065 sb.append(secure ? "***" : mText); 2066 sb.append(", resId="); 2067 sb.append(mTextResId); 2068 sb.append("["); 2069 sb.append(mTextResName); 2070 sb.append("]"); 2071 2072 addIndentOrComma(sb, indent); 2073 2074 sb.append("disabledMessage="); 2075 sb.append(secure ? "***" : mDisabledMessage); 2076 sb.append(", resId="); 2077 sb.append(mDisabledMessageResId); 2078 sb.append("["); 2079 sb.append(mDisabledMessageResName); 2080 sb.append("]"); 2081 2082 addIndentOrComma(sb, indent); 2083 2084 sb.append("disabledReason="); 2085 sb.append(getDisabledReasonDebugString(mDisabledReason)); 2086 2087 addIndentOrComma(sb, indent); 2088 2089 sb.append("categories="); 2090 sb.append(mCategories); 2091 2092 addIndentOrComma(sb, indent); 2093 2094 sb.append("icon="); 2095 sb.append(mIcon); 2096 2097 addIndentOrComma(sb, indent); 2098 2099 sb.append("rank="); 2100 sb.append(mRank); 2101 2102 sb.append(", timestamp="); 2103 sb.append(mLastChangedTimestamp); 2104 2105 addIndentOrComma(sb, indent); 2106 2107 sb.append("intents="); 2108 if (mIntents == null) { 2109 sb.append("null"); 2110 } else { 2111 if (secure) { 2112 sb.append("size:"); 2113 sb.append(mIntents.length); 2114 } else { 2115 final int size = mIntents.length; 2116 sb.append("["); 2117 String sep = ""; 2118 for (int i = 0; i < size; i++) { 2119 sb.append(sep); 2120 sep = ", "; 2121 sb.append(mIntents[i]); 2122 sb.append("/"); 2123 sb.append(mIntentPersistableExtrases[i]); 2124 } 2125 sb.append("]"); 2126 } 2127 } 2128 2129 addIndentOrComma(sb, indent); 2130 2131 sb.append("extras="); 2132 sb.append(mExtras); 2133 2134 if (includeInternalData) { 2135 addIndentOrComma(sb, indent); 2136 2137 sb.append("iconRes="); 2138 sb.append(mIconResId); 2139 sb.append("["); 2140 sb.append(mIconResName); 2141 sb.append("]"); 2142 2143 sb.append(", bitmapPath="); 2144 sb.append(mBitmapPath); 2145 } 2146 2147 sb.append("}"); 2148 return sb.toString(); 2149 } 2150 2151 /** @hide */ ShortcutInfo( @serIdInt int userId, String id, String packageName, ComponentName activity, Icon icon, CharSequence title, int titleResId, String titleResName, CharSequence text, int textResId, String textResName, CharSequence disabledMessage, int disabledMessageResId, String disabledMessageResName, Set<String> categories, Intent[] intentsWithExtras, int rank, PersistableBundle extras, long lastChangedTimestamp, int flags, int iconResId, String iconResName, String bitmapPath, int disabledReason)2152 public ShortcutInfo( 2153 @UserIdInt int userId, String id, String packageName, ComponentName activity, 2154 Icon icon, CharSequence title, int titleResId, String titleResName, 2155 CharSequence text, int textResId, String textResName, 2156 CharSequence disabledMessage, int disabledMessageResId, String disabledMessageResName, 2157 Set<String> categories, Intent[] intentsWithExtras, int rank, PersistableBundle extras, 2158 long lastChangedTimestamp, 2159 int flags, int iconResId, String iconResName, String bitmapPath, int disabledReason) { 2160 mUserId = userId; 2161 mId = id; 2162 mPackageName = packageName; 2163 mActivity = activity; 2164 mIcon = icon; 2165 mTitle = title; 2166 mTitleResId = titleResId; 2167 mTitleResName = titleResName; 2168 mText = text; 2169 mTextResId = textResId; 2170 mTextResName = textResName; 2171 mDisabledMessage = disabledMessage; 2172 mDisabledMessageResId = disabledMessageResId; 2173 mDisabledMessageResName = disabledMessageResName; 2174 mCategories = cloneCategories(categories); 2175 mIntents = cloneIntents(intentsWithExtras); 2176 fixUpIntentExtras(); 2177 mRank = rank; 2178 mExtras = extras; 2179 mLastChangedTimestamp = lastChangedTimestamp; 2180 mFlags = flags; 2181 mIconResId = iconResId; 2182 mIconResName = iconResName; 2183 mBitmapPath = bitmapPath; 2184 mDisabledReason = disabledReason; 2185 } 2186 } 2187