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