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