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