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