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