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.UserIdInt; 22 import android.app.TaskStackBuilder; 23 import android.content.ComponentName; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.pm.LauncherApps.ShortcutQuery; 27 import android.content.res.Resources; 28 import android.content.res.Resources.NotFoundException; 29 import android.graphics.Bitmap; 30 import android.graphics.drawable.Icon; 31 import android.os.Bundle; 32 import android.os.Parcel; 33 import android.os.Parcelable; 34 import android.os.PersistableBundle; 35 import android.os.UserHandle; 36 import android.text.TextUtils; 37 import android.util.ArraySet; 38 import android.util.Log; 39 40 import com.android.internal.annotations.VisibleForTesting; 41 import com.android.internal.util.Preconditions; 42 43 import java.lang.annotation.Retention; 44 import java.lang.annotation.RetentionPolicy; 45 import java.util.List; 46 import java.util.Set; 47 48 /** 49 * Represents a "launcher shortcut" that can be published via {@link ShortcutManager}. 50 * 51 * @see ShortcutManager 52 */ 53 public final class ShortcutInfo implements Parcelable { 54 static final String TAG = "Shortcut"; 55 56 private static final String RES_TYPE_STRING = "string"; 57 58 private static final String ANDROID_PACKAGE_NAME = "android"; 59 60 private static final int IMPLICIT_RANK_MASK = 0x7fffffff; 61 62 private static final int RANK_CHANGED_BIT = ~IMPLICIT_RANK_MASK; 63 64 /** @hide */ 65 public static final int RANK_NOT_SET = Integer.MAX_VALUE; 66 67 /** @hide */ 68 public static final int FLAG_DYNAMIC = 1 << 0; 69 70 /** @hide */ 71 public static final int FLAG_PINNED = 1 << 1; 72 73 /** @hide */ 74 public static final int FLAG_HAS_ICON_RES = 1 << 2; 75 76 /** @hide */ 77 public static final int FLAG_HAS_ICON_FILE = 1 << 3; 78 79 /** @hide */ 80 public static final int FLAG_KEY_FIELDS_ONLY = 1 << 4; 81 82 /** @hide */ 83 public static final int FLAG_MANIFEST = 1 << 5; 84 85 /** @hide */ 86 public static final int FLAG_DISABLED = 1 << 6; 87 88 /** @hide */ 89 public static final int FLAG_STRINGS_RESOLVED = 1 << 7; 90 91 /** @hide */ 92 public static final int FLAG_IMMUTABLE = 1 << 8; 93 94 /** @hide */ 95 @IntDef(flag = true, 96 value = { 97 FLAG_DYNAMIC, 98 FLAG_PINNED, 99 FLAG_HAS_ICON_RES, 100 FLAG_HAS_ICON_FILE, 101 FLAG_KEY_FIELDS_ONLY, 102 FLAG_MANIFEST, 103 FLAG_DISABLED, 104 FLAG_STRINGS_RESOLVED, 105 FLAG_IMMUTABLE, 106 }) 107 @Retention(RetentionPolicy.SOURCE) 108 public @interface ShortcutFlags {} 109 110 // Cloning options. 111 112 /** @hide */ 113 private static final int CLONE_REMOVE_ICON = 1 << 0; 114 115 /** @hide */ 116 private static final int CLONE_REMOVE_INTENT = 1 << 1; 117 118 /** @hide */ 119 public static final int CLONE_REMOVE_NON_KEY_INFO = 1 << 2; 120 121 /** @hide */ 122 public static final int CLONE_REMOVE_RES_NAMES = 1 << 3; 123 124 /** @hide */ 125 public static final int CLONE_REMOVE_FOR_CREATOR = CLONE_REMOVE_ICON | CLONE_REMOVE_RES_NAMES; 126 127 /** @hide */ 128 public static final int CLONE_REMOVE_FOR_LAUNCHER = CLONE_REMOVE_ICON | CLONE_REMOVE_INTENT 129 | CLONE_REMOVE_RES_NAMES; 130 131 /** @hide */ 132 @IntDef(flag = true, 133 value = { 134 CLONE_REMOVE_ICON, 135 CLONE_REMOVE_INTENT, 136 CLONE_REMOVE_NON_KEY_INFO, 137 CLONE_REMOVE_RES_NAMES, 138 CLONE_REMOVE_FOR_CREATOR, 139 CLONE_REMOVE_FOR_LAUNCHER 140 }) 141 @Retention(RetentionPolicy.SOURCE) 142 public @interface CloneFlags {} 143 144 /** 145 * Shortcut category for messaging related actions, such as chat. 146 */ 147 public static final String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation"; 148 149 private final String mId; 150 151 @NonNull 152 private final String mPackageName; 153 154 @Nullable 155 private ComponentName mActivity; 156 157 @Nullable 158 private Icon mIcon; 159 160 private int mTitleResId; 161 162 private String mTitleResName; 163 164 @Nullable 165 private CharSequence mTitle; 166 167 private int mTextResId; 168 169 private String mTextResName; 170 171 @Nullable 172 private CharSequence mText; 173 174 private int mDisabledMessageResId; 175 176 private String mDisabledMessageResName; 177 178 @Nullable 179 private CharSequence mDisabledMessage; 180 181 @Nullable 182 private ArraySet<String> mCategories; 183 184 /** 185 * Intents *with extras removed*. 186 */ 187 @Nullable 188 private Intent[] mIntents; 189 190 /** 191 * Extras for the intents. 192 */ 193 @Nullable 194 private PersistableBundle[] mIntentPersistableExtrases; 195 196 private int mRank; 197 198 /** 199 * Internally used for auto-rank-adjustment. 200 * 201 * RANK_CHANGED_BIT is used to denote that the rank of a shortcut is changing. 202 * The rest of the bits are used to denote the order in which shortcuts are passed to 203 * APIs, which is used to preserve the argument order when ranks are tie. 204 */ 205 private int mImplicitRank; 206 207 @Nullable 208 private PersistableBundle mExtras; 209 210 private long mLastChangedTimestamp; 211 212 // Internal use only. 213 @ShortcutFlags 214 private int mFlags; 215 216 // Internal use only. 217 private int mIconResId; 218 219 private String mIconResName; 220 221 // Internal use only. 222 @Nullable 223 private String mBitmapPath; 224 225 private final int mUserId; 226 227 private ShortcutInfo(Builder b) { 228 mUserId = b.mContext.getUserId(); 229 230 mId = Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided"); 231 232 // Note we can't do other null checks here because SM.updateShortcuts() takes partial 233 // information. 234 mPackageName = b.mContext.getPackageName(); 235 mActivity = b.mActivity; 236 mIcon = b.mIcon; 237 mTitle = b.mTitle; 238 mTitleResId = b.mTitleResId; 239 mText = b.mText; 240 mTextResId = b.mTextResId; 241 mDisabledMessage = b.mDisabledMessage; 242 mDisabledMessageResId = b.mDisabledMessageResId; 243 mCategories = cloneCategories(b.mCategories); 244 mIntents = cloneIntents(b.mIntents); 245 fixUpIntentExtras(); 246 mRank = b.mRank; 247 mExtras = b.mExtras; 248 updateTimestamp(); 249 } 250 251 /** 252 * Extract extras from {@link #mIntents} and set them to {@link #mIntentPersistableExtrases} 253 * as {@link PersistableBundle}, and remove extras from the original intents. 254 */ 255 private void fixUpIntentExtras() { 256 if (mIntents == null) { 257 mIntentPersistableExtrases = null; 258 return; 259 } 260 mIntentPersistableExtrases = new PersistableBundle[mIntents.length]; 261 for (int i = 0; i < mIntents.length; i++) { 262 final Intent intent = mIntents[i]; 263 final Bundle extras = intent.getExtras(); 264 if (extras == null) { 265 mIntentPersistableExtrases[i] = null; 266 } else { 267 mIntentPersistableExtrases[i] = new PersistableBundle(extras); 268 intent.replaceExtras((Bundle) null); 269 } 270 } 271 } 272 273 private static ArraySet<String> cloneCategories(Set<String> source) { 274 if (source == null) { 275 return null; 276 } 277 final ArraySet<String> ret = new ArraySet<>(source.size()); 278 for (CharSequence s : source) { 279 if (!TextUtils.isEmpty(s)) { 280 ret.add(s.toString().intern()); 281 } 282 } 283 return ret; 284 } 285 286 private static Intent[] cloneIntents(Intent[] intents) { 287 if (intents == null) { 288 return null; 289 } 290 final Intent[] ret = new Intent[intents.length]; 291 for (int i = 0; i < ret.length; i++) { 292 if (intents[i] != null) { 293 ret[i] = new Intent(intents[i]); 294 } 295 } 296 return ret; 297 } 298 299 private static PersistableBundle[] clonePersistableBundle(PersistableBundle[] bundle) { 300 if (bundle == null) { 301 return null; 302 } 303 final PersistableBundle[] ret = new PersistableBundle[bundle.length]; 304 for (int i = 0; i < ret.length; i++) { 305 if (bundle[i] != null) { 306 ret[i] = new PersistableBundle(bundle[i]); 307 } 308 } 309 return ret; 310 } 311 312 /** 313 * Throws if any of the mandatory fields is not set. 314 * 315 * @hide 316 */ 317 public void enforceMandatoryFields() { 318 Preconditions.checkStringNotEmpty(mId, "Shortcut ID must be provided"); 319 Preconditions.checkNotNull(mActivity, "Activity must be provided"); 320 if (mTitle == null && mTitleResId == 0) { 321 throw new IllegalArgumentException("Short label must be provided"); 322 } 323 Preconditions.checkNotNull(mIntents, "Shortcut Intent must be provided"); 324 Preconditions.checkArgument(mIntents.length > 0, "Shortcut Intent must be provided"); 325 } 326 327 /** 328 * Copy constructor. 329 */ 330 private ShortcutInfo(ShortcutInfo source, @CloneFlags int cloneFlags) { 331 mUserId = source.mUserId; 332 mId = source.mId; 333 mPackageName = source.mPackageName; 334 mActivity = source.mActivity; 335 mFlags = source.mFlags; 336 mLastChangedTimestamp = source.mLastChangedTimestamp; 337 338 // Just always keep it since it's cheep. 339 mIconResId = source.mIconResId; 340 341 if ((cloneFlags & CLONE_REMOVE_NON_KEY_INFO) == 0) { 342 343 if ((cloneFlags & CLONE_REMOVE_ICON) == 0) { 344 mIcon = source.mIcon; 345 mBitmapPath = source.mBitmapPath; 346 } 347 348 mTitle = source.mTitle; 349 mTitleResId = source.mTitleResId; 350 mText = source.mText; 351 mTextResId = source.mTextResId; 352 mDisabledMessage = source.mDisabledMessage; 353 mDisabledMessageResId = source.mDisabledMessageResId; 354 mCategories = cloneCategories(source.mCategories); 355 if ((cloneFlags & CLONE_REMOVE_INTENT) == 0) { 356 mIntents = cloneIntents(source.mIntents); 357 mIntentPersistableExtrases = 358 clonePersistableBundle(source.mIntentPersistableExtrases); 359 } 360 mRank = source.mRank; 361 mExtras = source.mExtras; 362 363 if ((cloneFlags & CLONE_REMOVE_RES_NAMES) == 0) { 364 mTitleResName = source.mTitleResName; 365 mTextResName = source.mTextResName; 366 mDisabledMessageResName = source.mDisabledMessageResName; 367 mIconResName = source.mIconResName; 368 } 369 } else { 370 // Set this bit. 371 mFlags |= FLAG_KEY_FIELDS_ONLY; 372 } 373 } 374 375 /** 376 * Load a string resource from the publisher app. 377 * 378 * @param resId resource ID 379 * @param defValue default value to be returned when the specified resource isn't found. 380 */ 381 private CharSequence getResourceString(Resources res, int resId, CharSequence defValue) { 382 try { 383 return res.getString(resId); 384 } catch (NotFoundException e) { 385 Log.e(TAG, "Resource for ID=" + resId + " not found in package " + mPackageName); 386 return defValue; 387 } 388 } 389 390 /** 391 * Load the string resources for the text fields and set them to the actual value fields. 392 * This will set {@link #FLAG_STRINGS_RESOLVED}. 393 * 394 * @param res {@link Resources} for the publisher. Must have been loaded with 395 * {@link PackageManager#getResourcesForApplicationAsUser}. 396 * 397 * @hide 398 */ 399 public void resolveResourceStrings(@NonNull Resources res) { 400 mFlags |= FLAG_STRINGS_RESOLVED; 401 402 if ((mTitleResId == 0) && (mTextResId == 0) && (mDisabledMessageResId == 0)) { 403 return; // Bail early. 404 } 405 406 if (mTitleResId != 0) { 407 mTitle = getResourceString(res, mTitleResId, mTitle); 408 } 409 if (mTextResId != 0) { 410 mText = getResourceString(res, mTextResId, mText); 411 } 412 if (mDisabledMessageResId != 0) { 413 mDisabledMessage = getResourceString(res, mDisabledMessageResId, mDisabledMessage); 414 } 415 } 416 417 /** 418 * Look up resource name for a given resource ID. 419 * 420 * @return a simple resource name (e.g. "text_1") when {@code withType} is false, or with the 421 * type (e.g. "string/text_1"). 422 * 423 * @hide 424 */ 425 @VisibleForTesting 426 public static String lookUpResourceName(@NonNull Resources res, int resId, boolean withType, 427 @NonNull String packageName) { 428 if (resId == 0) { 429 return null; 430 } 431 try { 432 final String fullName = res.getResourceName(resId); 433 434 if (ANDROID_PACKAGE_NAME.equals(getResourcePackageName(fullName))) { 435 // If it's a framework resource, the value won't change, so just return the ID 436 // value as a string. 437 return String.valueOf(resId); 438 } 439 return withType ? getResourceTypeAndEntryName(fullName) 440 : getResourceEntryName(fullName); 441 } catch (NotFoundException e) { 442 Log.e(TAG, "Resource name for ID=" + resId + " not found in package " + packageName 443 + ". Resource IDs may change when the application is upgraded, and the system" 444 + " may not be able to find the correct resource."); 445 return null; 446 } 447 } 448 449 /** 450 * Extract the package name from a fully-donated resource name. 451 * e.g. "com.android.app1:drawable/icon1" -> "com.android.app1" 452 * @hide 453 */ 454 @VisibleForTesting 455 public static String getResourcePackageName(@NonNull String fullResourceName) { 456 final int p1 = fullResourceName.indexOf(':'); 457 if (p1 < 0) { 458 return null; 459 } 460 return fullResourceName.substring(0, p1); 461 } 462 463 /** 464 * Extract the type name from a fully-donated resource name. 465 * e.g. "com.android.app1:drawable/icon1" -> "drawable" 466 * @hide 467 */ 468 @VisibleForTesting 469 public static String getResourceTypeName(@NonNull String fullResourceName) { 470 final int p1 = fullResourceName.indexOf(':'); 471 if (p1 < 0) { 472 return null; 473 } 474 final int p2 = fullResourceName.indexOf('/', p1 + 1); 475 if (p2 < 0) { 476 return null; 477 } 478 return fullResourceName.substring(p1 + 1, p2); 479 } 480 481 /** 482 * Extract the type name + the entry name from a fully-donated resource name. 483 * e.g. "com.android.app1:drawable/icon1" -> "drawable/icon1" 484 * @hide 485 */ 486 @VisibleForTesting 487 public static String getResourceTypeAndEntryName(@NonNull String fullResourceName) { 488 final int p1 = fullResourceName.indexOf(':'); 489 if (p1 < 0) { 490 return null; 491 } 492 return fullResourceName.substring(p1 + 1); 493 } 494 495 /** 496 * Extract the entry name from a fully-donated resource name. 497 * e.g. "com.android.app1:drawable/icon1" -> "icon1" 498 * @hide 499 */ 500 @VisibleForTesting 501 public static String getResourceEntryName(@NonNull String fullResourceName) { 502 final int p1 = fullResourceName.indexOf('/'); 503 if (p1 < 0) { 504 return null; 505 } 506 return fullResourceName.substring(p1 + 1); 507 } 508 509 /** 510 * Return the resource ID for a given resource ID. 511 * 512 * Basically its' a wrapper over {@link Resources#getIdentifier(String, String, String)}, except 513 * if {@code resourceName} is an integer then it'll just return its value. (Which also the 514 * aforementioned method would do internally, but not documented, so doing here explicitly.) 515 * 516 * @param res {@link Resources} for the publisher. Must have been loaded with 517 * {@link PackageManager#getResourcesForApplicationAsUser}. 518 * 519 * @hide 520 */ 521 @VisibleForTesting 522 public static int lookUpResourceId(@NonNull Resources res, @Nullable String resourceName, 523 @Nullable String resourceType, String packageName) { 524 if (resourceName == null) { 525 return 0; 526 } 527 try { 528 try { 529 // It the name can be parsed as an integer, just use it. 530 return Integer.parseInt(resourceName); 531 } catch (NumberFormatException ignore) { 532 } 533 534 return res.getIdentifier(resourceName, resourceType, packageName); 535 } catch (NotFoundException e) { 536 Log.e(TAG, "Resource ID for name=" + resourceName + " not found in package " 537 + packageName); 538 return 0; 539 } 540 } 541 542 /** 543 * Look up resource names from the resource IDs for the icon res and the text fields, and fill 544 * in the resource name fields. 545 * 546 * @param res {@link Resources} for the publisher. Must have been loaded with 547 * {@link PackageManager#getResourcesForApplicationAsUser}. 548 * 549 * @hide 550 */ 551 public void lookupAndFillInResourceNames(@NonNull Resources res) { 552 if ((mTitleResId == 0) && (mTextResId == 0) && (mDisabledMessageResId == 0) 553 && (mIconResId == 0)) { 554 return; // Bail early. 555 } 556 557 // We don't need types for strings because their types are always "string". 558 mTitleResName = lookUpResourceName(res, mTitleResId, /*withType=*/ false, mPackageName); 559 mTextResName = lookUpResourceName(res, mTextResId, /*withType=*/ false, mPackageName); 560 mDisabledMessageResName = lookUpResourceName(res, mDisabledMessageResId, 561 /*withType=*/ false, mPackageName); 562 563 // But icons have multiple possible types, so include the type. 564 mIconResName = lookUpResourceName(res, mIconResId, /*withType=*/ true, mPackageName); 565 } 566 567 /** 568 * Look up resource IDs from the resource names for the icon res and the text fields, and fill 569 * in the resource ID fields. 570 * 571 * This is called when an app is updated. 572 * 573 * @hide 574 */ 575 public void lookupAndFillInResourceIds(@NonNull Resources res) { 576 if ((mTitleResName == null) && (mTextResName == null) && (mDisabledMessageResName == null) 577 && (mIconResName == null)) { 578 return; // Bail early. 579 } 580 581 mTitleResId = lookUpResourceId(res, mTitleResName, RES_TYPE_STRING, mPackageName); 582 mTextResId = lookUpResourceId(res, mTextResName, RES_TYPE_STRING, mPackageName); 583 mDisabledMessageResId = lookUpResourceId(res, mDisabledMessageResName, RES_TYPE_STRING, 584 mPackageName); 585 586 // mIconResName already contains the type, so the third argument is not needed. 587 mIconResId = lookUpResourceId(res, mIconResName, null, mPackageName); 588 } 589 590 /** 591 * Copy a {@link ShortcutInfo}, optionally removing fields. 592 * @hide 593 */ 594 public ShortcutInfo clone(@CloneFlags int cloneFlags) { 595 return new ShortcutInfo(this, cloneFlags); 596 } 597 598 /** 599 * @hide 600 */ 601 public void ensureUpdatableWith(ShortcutInfo source) { 602 Preconditions.checkState(mUserId == source.mUserId, "Owner User ID must match"); 603 Preconditions.checkState(mId.equals(source.mId), "ID must match"); 604 Preconditions.checkState(mPackageName.equals(source.mPackageName), 605 "Package name must match"); 606 Preconditions.checkState(!isImmutable(), "Target ShortcutInfo is immutable"); 607 } 608 609 /** 610 * Copy non-null/zero fields from another {@link ShortcutInfo}. Only "public" information 611 * will be overwritten. The timestamp will *not* be updated to be consistent with other 612 * setters (and also the clock is not injectable in this file). 613 * 614 * - Flags will not change 615 * - mBitmapPath will not change 616 * - Current time will be set to timestamp 617 * 618 * @throws IllegalStateException if source is not compatible. 619 * 620 * @hide 621 */ 622 public void copyNonNullFieldsFrom(ShortcutInfo source) { 623 ensureUpdatableWith(source); 624 625 if (source.mActivity != null) { 626 mActivity = source.mActivity; 627 } 628 629 if (source.mIcon != null) { 630 mIcon = source.mIcon; 631 632 mIconResId = 0; 633 mIconResName = null; 634 mBitmapPath = null; 635 } 636 if (source.mTitle != null) { 637 mTitle = source.mTitle; 638 mTitleResId = 0; 639 mTitleResName = null; 640 } else if (source.mTitleResId != 0) { 641 mTitle = null; 642 mTitleResId = source.mTitleResId; 643 mTitleResName = null; 644 } 645 646 if (source.mText != null) { 647 mText = source.mText; 648 mTextResId = 0; 649 mTextResName = null; 650 } else if (source.mTextResId != 0) { 651 mText = null; 652 mTextResId = source.mTextResId; 653 mTextResName = null; 654 } 655 if (source.mDisabledMessage != null) { 656 mDisabledMessage = source.mDisabledMessage; 657 mDisabledMessageResId = 0; 658 mDisabledMessageResName = null; 659 } else if (source.mDisabledMessageResId != 0) { 660 mDisabledMessage = null; 661 mDisabledMessageResId = source.mDisabledMessageResId; 662 mDisabledMessageResName = null; 663 } 664 if (source.mCategories != null) { 665 mCategories = cloneCategories(source.mCategories); 666 } 667 if (source.mIntents != null) { 668 mIntents = cloneIntents(source.mIntents); 669 mIntentPersistableExtrases = 670 clonePersistableBundle(source.mIntentPersistableExtrases); 671 } 672 if (source.mRank != RANK_NOT_SET) { 673 mRank = source.mRank; 674 } 675 if (source.mExtras != null) { 676 mExtras = source.mExtras; 677 } 678 } 679 680 /** 681 * @hide 682 */ 683 public static Icon validateIcon(Icon icon) { 684 switch (icon.getType()) { 685 case Icon.TYPE_RESOURCE: 686 case Icon.TYPE_BITMAP: 687 break; // OK 688 default: 689 throw getInvalidIconException(); 690 } 691 if (icon.hasTint()) { 692 throw new IllegalArgumentException("Icons with tints are not supported"); 693 } 694 695 return icon; 696 } 697 698 /** @hide */ 699 public static IllegalArgumentException getInvalidIconException() { 700 return new IllegalArgumentException("Unsupported icon type:" 701 +" only the bitmap and resource types are supported"); 702 } 703 704 /** 705 * Builder class for {@link ShortcutInfo} objects. 706 * 707 * @see ShortcutManager 708 */ 709 public static class Builder { 710 private final Context mContext; 711 712 private String mId; 713 714 private ComponentName mActivity; 715 716 private Icon mIcon; 717 718 private int mTitleResId; 719 720 private CharSequence mTitle; 721 722 private int mTextResId; 723 724 private CharSequence mText; 725 726 private int mDisabledMessageResId; 727 728 private CharSequence mDisabledMessage; 729 730 private Set<String> mCategories; 731 732 private Intent[] mIntents; 733 734 private int mRank = RANK_NOT_SET; 735 736 private PersistableBundle mExtras; 737 738 /** 739 * Old style constructor. 740 * @hide 741 */ 742 @Deprecated 743 public Builder(Context context) { 744 mContext = context; 745 } 746 747 /** 748 * Used with the old style constructor, kept for unit tests. 749 * @hide 750 */ 751 @NonNull 752 @Deprecated 753 public Builder setId(@NonNull String id) { 754 mId = Preconditions.checkStringNotEmpty(id, "id cannot be empty"); 755 return this; 756 } 757 758 /** 759 * Constructor. 760 * 761 * @param context Client context. 762 * @param id ID of the shortcut. 763 */ 764 public Builder(Context context, String id) { 765 mContext = context; 766 mId = Preconditions.checkStringNotEmpty(id, "id cannot be empty"); 767 } 768 769 /** 770 * Sets the target activity. A shortcut will be shown along with this activity's icon 771 * on the launcher. 772 * 773 * When selecting a target activity, keep the following in mind: 774 * <ul> 775 * <li>All dynamic shortcuts must have a target activity. When a shortcut with no target 776 * activity is published using 777 * {@link ShortcutManager#addDynamicShortcuts(List)} or 778 * {@link ShortcutManager#setDynamicShortcuts(List)}, 779 * the first main activity defined in the application's <code>AndroidManifest.xml</code> 780 * file is used. 781 * 782 * <li>Only "main" activities—ones that define the {@link Intent#ACTION_MAIN} 783 * and {@link Intent#CATEGORY_LAUNCHER} intent filters—can be target 784 * activities. 785 * 786 * <li>By default, the first main activity defined in the application manifest is 787 * the target activity. 788 * 789 * <li>A target activity must belong to the publisher application. 790 * </ul> 791 * 792 * @see ShortcutInfo#getActivity() 793 */ 794 @NonNull 795 public Builder setActivity(@NonNull ComponentName activity) { 796 mActivity = Preconditions.checkNotNull(activity, "activity cannot be null"); 797 return this; 798 } 799 800 /** 801 * Sets an icon of a shortcut. 802 * 803 * <p>Icons are not available on {@link ShortcutInfo} instances 804 * returned by {@link ShortcutManager} or {@link LauncherApps}. The default launcher 805 * application can use {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)} 806 * or {@link LauncherApps#getShortcutBadgedIconDrawable(ShortcutInfo, int)} to fetch 807 * shortcut icons. 808 * 809 * <p>Tints set with {@link Icon#setTint} or {@link Icon#setTintList} are not supported 810 * and will be ignored. 811 * 812 * <p>Only icons created with {@link Icon#createWithBitmap(Bitmap)} and 813 * {@link Icon#createWithResource} are supported. 814 * Other types, such as URI-based icons, are not supported. 815 * 816 * @see LauncherApps#getShortcutIconDrawable(ShortcutInfo, int) 817 * @see LauncherApps#getShortcutBadgedIconDrawable(ShortcutInfo, int) 818 */ 819 @NonNull 820 public Builder setIcon(Icon icon) { 821 mIcon = validateIcon(icon); 822 return this; 823 } 824 825 /** 826 * @hide We don't support resource strings for dynamic shortcuts for now. (But unit tests 827 * use it.) 828 */ 829 @Deprecated 830 public Builder setShortLabelResId(int shortLabelResId) { 831 Preconditions.checkState(mTitle == null, "shortLabel already set"); 832 mTitleResId = shortLabelResId; 833 return this; 834 } 835 836 /** 837 * Sets the short title of a shortcut. 838 * 839 * <p>This is a mandatory field when publishing a new shortcut with 840 * {@link ShortcutManager#addDynamicShortcuts(List)} or 841 * {@link ShortcutManager#setDynamicShortcuts(List)}. 842 * 843 * <p>This field is intended to be a concise description of a shortcut. 844 * 845 * <p>The recommended maximum length is 10 characters. 846 * 847 * @see ShortcutInfo#getShortLabel() 848 */ 849 @NonNull 850 public Builder setShortLabel(@NonNull CharSequence shortLabel) { 851 Preconditions.checkState(mTitleResId == 0, "shortLabelResId already set"); 852 mTitle = Preconditions.checkStringNotEmpty(shortLabel, "shortLabel cannot be empty"); 853 return this; 854 } 855 856 /** 857 * @hide We don't support resource strings for dynamic shortcuts for now. (But unit tests 858 * use it.) 859 */ 860 @Deprecated 861 public Builder setLongLabelResId(int longLabelResId) { 862 Preconditions.checkState(mText == null, "longLabel already set"); 863 mTextResId = longLabelResId; 864 return this; 865 } 866 867 /** 868 * Sets the text of a shortcut. 869 * 870 * <p>This field is intended to be more descriptive than the shortcut title. The launcher 871 * shows this instead of the short title when it has enough space. 872 * 873 * <p>The recommend maximum length is 25 characters. 874 * 875 * @see ShortcutInfo#getLongLabel() 876 */ 877 @NonNull 878 public Builder setLongLabel(@NonNull CharSequence longLabel) { 879 Preconditions.checkState(mTextResId == 0, "longLabelResId already set"); 880 mText = Preconditions.checkStringNotEmpty(longLabel, "longLabel cannot be empty"); 881 return this; 882 } 883 884 /** @hide -- old signature, the internal code still uses it. */ 885 @Deprecated 886 public Builder setTitle(@NonNull CharSequence value) { 887 return setShortLabel(value); 888 } 889 890 /** @hide -- old signature, the internal code still uses it. */ 891 @Deprecated 892 public Builder setTitleResId(int value) { 893 return setShortLabelResId(value); 894 } 895 896 /** @hide -- old signature, the internal code still uses it. */ 897 @Deprecated 898 public Builder setText(@NonNull CharSequence value) { 899 return setLongLabel(value); 900 } 901 902 /** @hide -- old signature, the internal code still uses it. */ 903 @Deprecated 904 public Builder setTextResId(int value) { 905 return setLongLabelResId(value); 906 } 907 908 /** 909 * @hide We don't support resource strings for dynamic shortcuts for now. (But unit tests 910 * use it.) 911 */ 912 @Deprecated 913 public Builder setDisabledMessageResId(int disabledMessageResId) { 914 Preconditions.checkState(mDisabledMessage == null, "disabledMessage already set"); 915 mDisabledMessageResId = disabledMessageResId; 916 return this; 917 } 918 919 /** 920 * Sets the message that should be shown when the user attempts to start a shortcut that 921 * is disabled. 922 * 923 * @see ShortcutInfo#getDisabledMessage() 924 */ 925 @NonNull 926 public Builder setDisabledMessage(@NonNull CharSequence disabledMessage) { 927 Preconditions.checkState( 928 mDisabledMessageResId == 0, "disabledMessageResId already set"); 929 mDisabledMessage = 930 Preconditions.checkStringNotEmpty(disabledMessage, 931 "disabledMessage cannot be empty"); 932 return this; 933 } 934 935 /** 936 * Sets categories for a shortcut. Launcher applications may use this information to 937 * categorise shortcuts. 938 * 939 * @see #SHORTCUT_CATEGORY_CONVERSATION 940 * @see ShortcutInfo#getCategories() 941 */ 942 @NonNull 943 public Builder setCategories(Set<String> categories) { 944 mCategories = categories; 945 return this; 946 } 947 948 /** 949 * Sets the intent of a shortcut. Alternatively, {@link #setIntents(Intent[])} can be used 950 * to launch an activity with other activities in the back stack. 951 * 952 * <p>This is a mandatory field when publishing a new shortcut with 953 * {@link ShortcutManager#addDynamicShortcuts(List)} or 954 * {@link ShortcutManager#setDynamicShortcuts(List)}. 955 * 956 * <p>A shortcut can launch any intent that the publisher application has permission to 957 * launch. For example, a shortcut can launch an unexported activity within the publisher 958 * application. A shortcut intent doesn't have to point at the target activity. 959 * 960 * <p>The given {@code intent} can contain extras, but these extras must contain values 961 * of primitive types in order for the system to persist these values. 962 * 963 * @see ShortcutInfo#getIntent() 964 * @see #setIntents(Intent[]) 965 */ 966 @NonNull 967 public Builder setIntent(@NonNull Intent intent) { 968 return setIntents(new Intent[]{intent}); 969 } 970 971 /** 972 * Sets multiple intents instead of a single intent, in order to launch an activity with 973 * other activities in back stack. Use {@link TaskStackBuilder} to build intents. 974 * See the {@link ShortcutManager} javadoc for details. 975 * 976 * @see Builder#setIntent(Intent) 977 * @see ShortcutInfo#getIntents() 978 * @see Context#startActivities(Intent[]) 979 * @see TaskStackBuilder 980 */ 981 @NonNull 982 public Builder setIntents(@NonNull Intent[] intents) { 983 Preconditions.checkNotNull(intents, "intents cannot be null"); 984 Preconditions.checkNotNull(intents.length, "intents cannot be empty"); 985 for (Intent intent : intents) { 986 Preconditions.checkNotNull(intent, "intents cannot contain null"); 987 Preconditions.checkNotNull(intent.getAction(), "intent's action must be set"); 988 } 989 // Make sure always clone incoming intents. 990 mIntents = cloneIntents(intents); 991 return this; 992 } 993 994 /** 995 * "Rank" of a shortcut, which is a non-negative value that's used by the launcher app 996 * to sort shortcuts. 997 * 998 * See {@link ShortcutInfo#getRank()} for details. 999 */ 1000 @NonNull 1001 public Builder setRank(int rank) { 1002 Preconditions.checkArgument((0 <= rank), 1003 "Rank cannot be negative or bigger than MAX_RANK"); 1004 mRank = rank; 1005 return this; 1006 } 1007 1008 /** 1009 * Extras that application can set for any purpose. 1010 * 1011 * <p>Applications can store arbitrary shortcut metadata in extras and retrieve the 1012 * metadata later using {@link ShortcutInfo#getExtras()}. 1013 */ 1014 @NonNull 1015 public Builder setExtras(@NonNull PersistableBundle extras) { 1016 mExtras = extras; 1017 return this; 1018 } 1019 1020 /** 1021 * Creates a {@link ShortcutInfo} instance. 1022 */ 1023 @NonNull 1024 public ShortcutInfo build() { 1025 return new ShortcutInfo(this); 1026 } 1027 } 1028 1029 /** 1030 * Returns the ID of a shortcut. 1031 * 1032 * <p>Shortcut IDs are unique within each publisher application and must be stable across 1033 * devices so that shortcuts will still be valid when restored on a different device. 1034 * See {@link ShortcutManager} for details. 1035 */ 1036 @NonNull 1037 public String getId() { 1038 return mId; 1039 } 1040 1041 /** 1042 * Return the package name of the publisher application. 1043 */ 1044 @NonNull 1045 public String getPackage() { 1046 return mPackageName; 1047 } 1048 1049 /** 1050 * Return the target activity. 1051 * 1052 * <p>This has nothing to do with the activity that this shortcut will launch. 1053 * Launcher applications should show the launcher icon for the returned activity alongside 1054 * this shortcut. 1055 * 1056 * @see Builder#setActivity 1057 */ 1058 @Nullable 1059 public ComponentName getActivity() { 1060 return mActivity; 1061 } 1062 1063 /** @hide */ 1064 public void setActivity(ComponentName activity) { 1065 mActivity = activity; 1066 } 1067 1068 /** 1069 * Returns the shortcut icon. 1070 * 1071 * @hide 1072 */ 1073 @Nullable 1074 public Icon getIcon() { 1075 return mIcon; 1076 } 1077 1078 /** @hide -- old signature, the internal code still uses it. */ 1079 @Nullable 1080 @Deprecated 1081 public CharSequence getTitle() { 1082 return mTitle; 1083 } 1084 1085 /** @hide -- old signature, the internal code still uses it. */ 1086 @Deprecated 1087 public int getTitleResId() { 1088 return mTitleResId; 1089 } 1090 1091 /** @hide -- old signature, the internal code still uses it. */ 1092 @Nullable 1093 @Deprecated 1094 public CharSequence getText() { 1095 return mText; 1096 } 1097 1098 /** @hide -- old signature, the internal code still uses it. */ 1099 @Deprecated 1100 public int getTextResId() { 1101 return mTextResId; 1102 } 1103 1104 /** 1105 * Return the shorter description of a shortcut. 1106 * 1107 * @see Builder#setShortLabel(CharSequence) 1108 */ 1109 @Nullable 1110 public CharSequence getShortLabel() { 1111 return mTitle; 1112 } 1113 1114 /** @hide */ 1115 public int getShortLabelResourceId() { 1116 return mTitleResId; 1117 } 1118 1119 /** 1120 * Return the longer description of a shortcut. 1121 * 1122 * @see Builder#setLongLabel(CharSequence) 1123 */ 1124 @Nullable 1125 public CharSequence getLongLabel() { 1126 return mText; 1127 } 1128 1129 /** @hide */ 1130 public int getLongLabelResourceId() { 1131 return mTextResId; 1132 } 1133 1134 /** 1135 * Return the message that should be shown when the user attempts to start a shortcut 1136 * that is disabled. 1137 * 1138 * @see Builder#setDisabledMessage(CharSequence) 1139 */ 1140 @Nullable 1141 public CharSequence getDisabledMessage() { 1142 return mDisabledMessage; 1143 } 1144 1145 /** @hide */ 1146 public int getDisabledMessageResourceId() { 1147 return mDisabledMessageResId; 1148 } 1149 1150 /** 1151 * Return the shortcut's categories. 1152 * 1153 * @see Builder#setCategories(Set) 1154 */ 1155 @Nullable 1156 public Set<String> getCategories() { 1157 return mCategories; 1158 } 1159 1160 /** 1161 * Returns the intent that is executed when the user selects this shortcut. 1162 * If setIntents() was used, then return the last intent in the array. 1163 * 1164 * <p>Launcher applications <b>cannot</b> see the intent. If a {@link ShortcutInfo} is 1165 * obtained via {@link LauncherApps}, then this method will always return null. 1166 * Launchers can only start a shortcut intent with {@link LauncherApps#startShortcut}. 1167 * 1168 * @see Builder#setIntent(Intent) 1169 */ 1170 @Nullable 1171 public Intent getIntent() { 1172 if (mIntents == null || mIntents.length == 0) { 1173 return null; 1174 } 1175 final int last = mIntents.length - 1; 1176 final Intent intent = new Intent(mIntents[last]); 1177 return setIntentExtras(intent, mIntentPersistableExtrases[last]); 1178 } 1179 1180 /** 1181 * Return the intent set with {@link Builder#setIntents(Intent[])}. 1182 * 1183 * <p>Launcher applications <b>cannot</b> see the intents. If a {@link ShortcutInfo} is 1184 * obtained via {@link LauncherApps}, then this method will always return null. 1185 * Launchers can only start a shortcut intent with {@link LauncherApps#startShortcut}. 1186 * 1187 * @see Builder#setIntents(Intent[]) 1188 */ 1189 @Nullable 1190 public Intent[] getIntents() { 1191 final Intent[] ret = new Intent[mIntents.length]; 1192 1193 for (int i = 0; i < ret.length; i++) { 1194 ret[i] = new Intent(mIntents[i]); 1195 setIntentExtras(ret[i], mIntentPersistableExtrases[i]); 1196 } 1197 1198 return ret; 1199 } 1200 1201 /** 1202 * Return "raw" intents, which is the original intents without the extras. 1203 * @hide 1204 */ 1205 @Nullable 1206 public Intent[] getIntentsNoExtras() { 1207 return mIntents; 1208 } 1209 1210 /** 1211 * The extras in the intents. We convert extras into {@link PersistableBundle} so we can 1212 * persist them. 1213 * @hide 1214 */ 1215 @Nullable 1216 public PersistableBundle[] getIntentPersistableExtrases() { 1217 return mIntentPersistableExtrases; 1218 } 1219 1220 /** 1221 * "Rank" of a shortcut, which is a non-negative, sequential value that's unique for each 1222 * {@link #getActivity} for each of the two kinds, dynamic shortcuts and manifest shortcuts. 1223 * 1224 * <p>Because manifest shortcuts and dynamic shortcuts have overlapping ranks, 1225 * when a launcher application shows shortcuts for an activity, it should first show 1226 * the manifest shortcuts followed by the dynamic shortcuts. Within each of those categories, 1227 * shortcuts should be sorted by rank in ascending order. 1228 * 1229 * <p>"Floating" shortcuts (i.e. shortcuts that are neither dynamic nor manifest) will all 1230 * have rank 0, because there's no sorting for them. 1231 * 1232 * See the {@link ShortcutManager}'s class javadoc for details. 1233 * 1234 * @see Builder#setRank(int) 1235 */ 1236 public int getRank() { 1237 return mRank; 1238 } 1239 1240 /** @hide */ 1241 public boolean hasRank() { 1242 return mRank != RANK_NOT_SET; 1243 } 1244 1245 /** @hide */ 1246 public void setRank(int rank) { 1247 mRank = rank; 1248 } 1249 1250 /** @hide */ 1251 public void clearImplicitRankAndRankChangedFlag() { 1252 mImplicitRank = 0; 1253 } 1254 1255 /** @hide */ 1256 public void setImplicitRank(int rank) { 1257 // Make sure to keep RANK_CHANGED_BIT. 1258 mImplicitRank = (mImplicitRank & RANK_CHANGED_BIT) | (rank & IMPLICIT_RANK_MASK); 1259 } 1260 1261 /** @hide */ 1262 public int getImplicitRank() { 1263 return mImplicitRank & IMPLICIT_RANK_MASK; 1264 } 1265 1266 /** @hide */ 1267 public void setRankChanged() { 1268 mImplicitRank |= RANK_CHANGED_BIT; 1269 } 1270 1271 /** @hide */ 1272 public boolean isRankChanged() { 1273 return (mImplicitRank & RANK_CHANGED_BIT) != 0; 1274 } 1275 1276 /** 1277 * Extras that application can set to any purposes. 1278 * 1279 * @see Builder#setExtras(PersistableBundle) 1280 */ 1281 @Nullable 1282 public PersistableBundle getExtras() { 1283 return mExtras; 1284 } 1285 1286 /** @hide */ 1287 public int getUserId() { 1288 return mUserId; 1289 } 1290 1291 /** 1292 * {@link UserHandle} on which the publisher created this shortcut. 1293 */ 1294 public UserHandle getUserHandle() { 1295 return UserHandle.of(mUserId); 1296 } 1297 1298 /** 1299 * Last time when any of the fields was updated. 1300 */ 1301 public long getLastChangedTimestamp() { 1302 return mLastChangedTimestamp; 1303 } 1304 1305 /** @hide */ 1306 @ShortcutFlags 1307 public int getFlags() { 1308 return mFlags; 1309 } 1310 1311 /** @hide*/ 1312 public void replaceFlags(@ShortcutFlags int flags) { 1313 mFlags = flags; 1314 } 1315 1316 /** @hide*/ 1317 public void addFlags(@ShortcutFlags int flags) { 1318 mFlags |= flags; 1319 } 1320 1321 /** @hide*/ 1322 public void clearFlags(@ShortcutFlags int flags) { 1323 mFlags &= ~flags; 1324 } 1325 1326 /** @hide*/ 1327 public boolean hasFlags(@ShortcutFlags int flags) { 1328 return (mFlags & flags) == flags; 1329 } 1330 1331 /** Return whether a shortcut is dynamic. */ 1332 public boolean isDynamic() { 1333 return hasFlags(FLAG_DYNAMIC); 1334 } 1335 1336 /** Return whether a shortcut is pinned. */ 1337 public boolean isPinned() { 1338 return hasFlags(FLAG_PINNED); 1339 } 1340 1341 /** 1342 * Return whether a shortcut is published from AndroidManifest.xml or not. If {@code true}, 1343 * it's also {@link #isImmutable()}. 1344 * 1345 * <p>When an app is upgraded and a shortcut is no longer published from AndroidManifest.xml, 1346 * this will be set to {@code false}. If the shortcut is not pinned, then it'll just disappear. 1347 * However, if it's pinned, it will still be alive, and {@link #isEnabled()} will be 1348 * {@code false} and {@link #isImmutable()} will be {@code true}. 1349 */ 1350 public boolean isDeclaredInManifest() { 1351 return hasFlags(FLAG_MANIFEST); 1352 } 1353 1354 /** @hide kept for unit tests */ 1355 @Deprecated 1356 public boolean isManifestShortcut() { 1357 return isDeclaredInManifest(); 1358 } 1359 1360 /** 1361 * @return true if pinned but neither dynamic nor manifest. 1362 * @hide 1363 */ 1364 public boolean isFloating() { 1365 return isPinned() && !(isDynamic() || isManifestShortcut()); 1366 } 1367 1368 /** @hide */ 1369 public boolean isOriginallyFromManifest() { 1370 return hasFlags(FLAG_IMMUTABLE); 1371 } 1372 1373 /** 1374 * Return if a shortcut is immutable, in which case it cannot be modified with any of 1375 * {@link ShortcutManager} APIs. 1376 * 1377 * <p>All manifest shortcuts are immutable. When a manifest shortcut is pinned and then 1378 * disabled because the app is upgraded and its AndroidManifest.xml no longer publishes it, 1379 * {@link #isDeclaredInManifest()} returns {@code false}, but it is still immutable. 1380 * 1381 * <p>All shortcuts originally published via the {@link ShortcutManager} APIs 1382 * are all mutable. 1383 */ 1384 public boolean isImmutable() { 1385 return hasFlags(FLAG_IMMUTABLE); 1386 } 1387 1388 /** 1389 * Returns {@code false} if a shortcut is disabled with 1390 * {@link ShortcutManager#disableShortcuts}. 1391 */ 1392 public boolean isEnabled() { 1393 return !hasFlags(FLAG_DISABLED); 1394 } 1395 1396 /** @hide */ 1397 public boolean isAlive() { 1398 return hasFlags(FLAG_PINNED) || hasFlags(FLAG_DYNAMIC) || hasFlags(FLAG_MANIFEST); 1399 } 1400 1401 /** @hide */ 1402 public boolean usesQuota() { 1403 return hasFlags(FLAG_DYNAMIC) || hasFlags(FLAG_MANIFEST); 1404 } 1405 1406 /** 1407 * Return whether a shortcut's icon is a resource in the owning package. 1408 * 1409 * @hide internal/unit tests only 1410 */ 1411 public boolean hasIconResource() { 1412 return hasFlags(FLAG_HAS_ICON_RES); 1413 } 1414 1415 /** @hide */ 1416 public boolean hasStringResources() { 1417 return (mTitleResId != 0) || (mTextResId != 0) || (mDisabledMessageResId != 0); 1418 } 1419 1420 /** @hide */ 1421 public boolean hasAnyResources() { 1422 return hasIconResource() || hasStringResources(); 1423 } 1424 1425 /** 1426 * Return whether a shortcut's icon is stored as a file. 1427 * 1428 * @hide internal/unit tests only 1429 */ 1430 public boolean hasIconFile() { 1431 return hasFlags(FLAG_HAS_ICON_FILE); 1432 } 1433 1434 /** 1435 * Return whether a shortcut only contains "key" information only or not. If true, only the 1436 * following fields are available. 1437 * <ul> 1438 * <li>{@link #getId()} 1439 * <li>{@link #getPackage()} 1440 * <li>{@link #getActivity()} 1441 * <li>{@link #getLastChangedTimestamp()} 1442 * <li>{@link #isDynamic()} 1443 * <li>{@link #isPinned()} 1444 * <li>{@link #isDeclaredInManifest()} 1445 * <li>{@link #isImmutable()} 1446 * <li>{@link #isEnabled()} 1447 * <li>{@link #getUserHandle()} 1448 * </ul> 1449 * 1450 * <p>For performance reasons, shortcuts passed to 1451 * {@link LauncherApps.Callback#onShortcutsChanged(String, List, UserHandle)} as well as those 1452 * returned from {@link LauncherApps#getShortcuts(ShortcutQuery, UserHandle)} 1453 * while using the {@link ShortcutQuery#FLAG_GET_KEY_FIELDS_ONLY} option contain only key 1454 * information. 1455 */ 1456 public boolean hasKeyFieldsOnly() { 1457 return hasFlags(FLAG_KEY_FIELDS_ONLY); 1458 } 1459 1460 /** @hide */ 1461 public boolean hasStringResourcesResolved() { 1462 return hasFlags(FLAG_STRINGS_RESOLVED); 1463 } 1464 1465 /** @hide */ 1466 public void updateTimestamp() { 1467 mLastChangedTimestamp = System.currentTimeMillis(); 1468 } 1469 1470 /** @hide */ 1471 // VisibleForTesting 1472 public void setTimestamp(long value) { 1473 mLastChangedTimestamp = value; 1474 } 1475 1476 /** @hide */ 1477 public void clearIcon() { 1478 mIcon = null; 1479 } 1480 1481 /** @hide */ 1482 public void setIconResourceId(int iconResourceId) { 1483 if (mIconResId != iconResourceId) { 1484 mIconResName = null; 1485 } 1486 mIconResId = iconResourceId; 1487 } 1488 1489 /** 1490 * Get the resource ID for the icon, valid only when {@link #hasIconResource()} } is true. 1491 * @hide internal / tests only. 1492 */ 1493 public int getIconResourceId() { 1494 return mIconResId; 1495 } 1496 1497 /** @hide */ 1498 public String getBitmapPath() { 1499 return mBitmapPath; 1500 } 1501 1502 /** @hide */ 1503 public void setBitmapPath(String bitmapPath) { 1504 mBitmapPath = bitmapPath; 1505 } 1506 1507 /** @hide */ 1508 public void setDisabledMessageResId(int disabledMessageResId) { 1509 if (mDisabledMessageResId != disabledMessageResId) { 1510 mDisabledMessageResName = null; 1511 } 1512 mDisabledMessageResId = disabledMessageResId; 1513 mDisabledMessage = null; 1514 } 1515 1516 /** @hide */ 1517 public void setDisabledMessage(String disabledMessage) { 1518 mDisabledMessage = disabledMessage; 1519 mDisabledMessageResId = 0; 1520 mDisabledMessageResName = null; 1521 } 1522 1523 /** @hide */ 1524 public String getTitleResName() { 1525 return mTitleResName; 1526 } 1527 1528 /** @hide */ 1529 public void setTitleResName(String titleResName) { 1530 mTitleResName = titleResName; 1531 } 1532 1533 /** @hide */ 1534 public String getTextResName() { 1535 return mTextResName; 1536 } 1537 1538 /** @hide */ 1539 public void setTextResName(String textResName) { 1540 mTextResName = textResName; 1541 } 1542 1543 /** @hide */ 1544 public String getDisabledMessageResName() { 1545 return mDisabledMessageResName; 1546 } 1547 1548 /** @hide */ 1549 public void setDisabledMessageResName(String disabledMessageResName) { 1550 mDisabledMessageResName = disabledMessageResName; 1551 } 1552 1553 /** @hide */ 1554 public String getIconResName() { 1555 return mIconResName; 1556 } 1557 1558 /** @hide */ 1559 public void setIconResName(String iconResName) { 1560 mIconResName = iconResName; 1561 } 1562 1563 /** 1564 * Replaces the intent 1565 * 1566 * @throws IllegalArgumentException when extra is not compatible with {@link PersistableBundle}. 1567 * 1568 * @hide 1569 */ 1570 public void setIntents(Intent[] intents) throws IllegalArgumentException { 1571 Preconditions.checkNotNull(intents); 1572 Preconditions.checkArgument(intents.length > 0); 1573 1574 mIntents = cloneIntents(intents); 1575 fixUpIntentExtras(); 1576 } 1577 1578 /** @hide */ 1579 public static Intent setIntentExtras(Intent intent, PersistableBundle extras) { 1580 if (extras == null) { 1581 intent.replaceExtras((Bundle) null); 1582 } else { 1583 intent.replaceExtras(new Bundle(extras)); 1584 } 1585 return intent; 1586 } 1587 1588 /** 1589 * Replaces the categories. 1590 * 1591 * @hide 1592 */ 1593 public void setCategories(Set<String> categories) { 1594 mCategories = cloneCategories(categories); 1595 } 1596 1597 private ShortcutInfo(Parcel source) { 1598 final ClassLoader cl = getClass().getClassLoader(); 1599 1600 mUserId = source.readInt(); 1601 mId = source.readString(); 1602 mPackageName = source.readString(); 1603 mActivity = source.readParcelable(cl); 1604 mFlags = source.readInt(); 1605 mIconResId = source.readInt(); 1606 mLastChangedTimestamp = source.readLong(); 1607 1608 if (source.readInt() == 0) { 1609 return; // key information only. 1610 } 1611 1612 mIcon = source.readParcelable(cl); 1613 mTitle = source.readCharSequence(); 1614 mTitleResId = source.readInt(); 1615 mText = source.readCharSequence(); 1616 mTextResId = source.readInt(); 1617 mDisabledMessage = source.readCharSequence(); 1618 mDisabledMessageResId = source.readInt(); 1619 mIntents = source.readParcelableArray(cl, Intent.class); 1620 mIntentPersistableExtrases = source.readParcelableArray(cl, PersistableBundle.class); 1621 mRank = source.readInt(); 1622 mExtras = source.readParcelable(cl); 1623 mBitmapPath = source.readString(); 1624 1625 mIconResName = source.readString(); 1626 mTitleResName = source.readString(); 1627 mTextResName = source.readString(); 1628 mDisabledMessageResName = source.readString(); 1629 1630 int N = source.readInt(); 1631 if (N == 0) { 1632 mCategories = null; 1633 } else { 1634 mCategories = new ArraySet<>(N); 1635 for (int i = 0; i < N; i++) { 1636 mCategories.add(source.readString().intern()); 1637 } 1638 } 1639 } 1640 1641 @Override 1642 public void writeToParcel(Parcel dest, int flags) { 1643 dest.writeInt(mUserId); 1644 dest.writeString(mId); 1645 dest.writeString(mPackageName); 1646 dest.writeParcelable(mActivity, flags); 1647 dest.writeInt(mFlags); 1648 dest.writeInt(mIconResId); 1649 dest.writeLong(mLastChangedTimestamp); 1650 1651 if (hasKeyFieldsOnly()) { 1652 dest.writeInt(0); 1653 return; 1654 } 1655 dest.writeInt(1); 1656 1657 dest.writeParcelable(mIcon, flags); 1658 dest.writeCharSequence(mTitle); 1659 dest.writeInt(mTitleResId); 1660 dest.writeCharSequence(mText); 1661 dest.writeInt(mTextResId); 1662 dest.writeCharSequence(mDisabledMessage); 1663 dest.writeInt(mDisabledMessageResId); 1664 1665 dest.writeParcelableArray(mIntents, flags); 1666 dest.writeParcelableArray(mIntentPersistableExtrases, flags); 1667 dest.writeInt(mRank); 1668 dest.writeParcelable(mExtras, flags); 1669 dest.writeString(mBitmapPath); 1670 1671 dest.writeString(mIconResName); 1672 dest.writeString(mTitleResName); 1673 dest.writeString(mTextResName); 1674 dest.writeString(mDisabledMessageResName); 1675 1676 if (mCategories != null) { 1677 final int N = mCategories.size(); 1678 dest.writeInt(N); 1679 for (int i = 0; i < N; i++) { 1680 dest.writeString(mCategories.valueAt(i)); 1681 } 1682 } else { 1683 dest.writeInt(0); 1684 } 1685 } 1686 1687 public static final Creator<ShortcutInfo> CREATOR = 1688 new Creator<ShortcutInfo>() { 1689 public ShortcutInfo createFromParcel(Parcel source) { 1690 return new ShortcutInfo(source); 1691 } 1692 public ShortcutInfo[] newArray(int size) { 1693 return new ShortcutInfo[size]; 1694 } 1695 }; 1696 1697 @Override 1698 public int describeContents() { 1699 return 0; 1700 } 1701 1702 /** 1703 * Return a string representation, intended for logging. Some fields will be retracted. 1704 */ 1705 @Override 1706 public String toString() { 1707 return toStringInner(/* secure =*/ true, /* includeInternalData =*/ false); 1708 } 1709 1710 /** @hide */ 1711 public String toInsecureString() { 1712 return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true); 1713 } 1714 1715 private String toStringInner(boolean secure, boolean includeInternalData) { 1716 final StringBuilder sb = new StringBuilder(); 1717 sb.append("ShortcutInfo {"); 1718 1719 sb.append("id="); 1720 sb.append(secure ? "***" : mId); 1721 1722 sb.append(", flags=0x"); 1723 sb.append(Integer.toHexString(mFlags)); 1724 sb.append(" ["); 1725 if (!isEnabled()) { 1726 sb.append("X"); 1727 } 1728 if (isImmutable()) { 1729 sb.append("Im"); 1730 } 1731 if (isManifestShortcut()) { 1732 sb.append("M"); 1733 } 1734 if (isDynamic()) { 1735 sb.append("D"); 1736 } 1737 if (isPinned()) { 1738 sb.append("P"); 1739 } 1740 if (hasIconFile()) { 1741 sb.append("If"); 1742 } 1743 if (hasIconResource()) { 1744 sb.append("Ir"); 1745 } 1746 if (hasKeyFieldsOnly()) { 1747 sb.append("K"); 1748 } 1749 if (hasStringResourcesResolved()) { 1750 sb.append("Sr"); 1751 } 1752 sb.append("]"); 1753 1754 sb.append(", packageName="); 1755 sb.append(mPackageName); 1756 1757 sb.append(", activity="); 1758 sb.append(mActivity); 1759 1760 sb.append(", shortLabel="); 1761 sb.append(secure ? "***" : mTitle); 1762 sb.append(", resId="); 1763 sb.append(mTitleResId); 1764 sb.append("["); 1765 sb.append(mTitleResName); 1766 sb.append("]"); 1767 1768 sb.append(", longLabel="); 1769 sb.append(secure ? "***" : mText); 1770 sb.append(", resId="); 1771 sb.append(mTextResId); 1772 sb.append("["); 1773 sb.append(mTextResName); 1774 sb.append("]"); 1775 1776 sb.append(", disabledMessage="); 1777 sb.append(secure ? "***" : mDisabledMessage); 1778 sb.append(", resId="); 1779 sb.append(mDisabledMessageResId); 1780 sb.append("["); 1781 sb.append(mDisabledMessageResName); 1782 sb.append("]"); 1783 1784 sb.append(", categories="); 1785 sb.append(mCategories); 1786 1787 sb.append(", icon="); 1788 sb.append(mIcon); 1789 1790 sb.append(", rank="); 1791 sb.append(mRank); 1792 1793 sb.append(", timestamp="); 1794 sb.append(mLastChangedTimestamp); 1795 1796 sb.append(", intents="); 1797 if (mIntents == null) { 1798 sb.append("null"); 1799 } else { 1800 if (secure) { 1801 sb.append("size:"); 1802 sb.append(mIntents.length); 1803 } else { 1804 final int size = mIntents.length; 1805 sb.append("["); 1806 String sep = ""; 1807 for (int i = 0; i < size; i++) { 1808 sb.append(sep); 1809 sep = ", "; 1810 sb.append(mIntents[i]); 1811 sb.append("/"); 1812 sb.append(mIntentPersistableExtrases[i]); 1813 } 1814 sb.append("]"); 1815 } 1816 } 1817 1818 sb.append(", extras="); 1819 sb.append(mExtras); 1820 1821 if (includeInternalData) { 1822 1823 sb.append(", iconRes="); 1824 sb.append(mIconResId); 1825 sb.append("["); 1826 sb.append(mIconResName); 1827 sb.append("]"); 1828 1829 sb.append(", bitmapPath="); 1830 sb.append(mBitmapPath); 1831 } 1832 1833 sb.append("}"); 1834 return sb.toString(); 1835 } 1836 1837 /** @hide */ 1838 public ShortcutInfo( 1839 @UserIdInt int userId, String id, String packageName, ComponentName activity, 1840 Icon icon, CharSequence title, int titleResId, String titleResName, 1841 CharSequence text, int textResId, String textResName, 1842 CharSequence disabledMessage, int disabledMessageResId, String disabledMessageResName, 1843 Set<String> categories, Intent[] intentsWithExtras, int rank, PersistableBundle extras, 1844 long lastChangedTimestamp, 1845 int flags, int iconResId, String iconResName, String bitmapPath) { 1846 mUserId = userId; 1847 mId = id; 1848 mPackageName = packageName; 1849 mActivity = activity; 1850 mIcon = icon; 1851 mTitle = title; 1852 mTitleResId = titleResId; 1853 mTitleResName = titleResName; 1854 mText = text; 1855 mTextResId = textResId; 1856 mTextResName = textResName; 1857 mDisabledMessage = disabledMessage; 1858 mDisabledMessageResId = disabledMessageResId; 1859 mDisabledMessageResName = disabledMessageResName; 1860 mCategories = cloneCategories(categories); 1861 mIntents = cloneIntents(intentsWithExtras); 1862 fixUpIntentExtras(); 1863 mRank = rank; 1864 mExtras = extras; 1865 mLastChangedTimestamp = lastChangedTimestamp; 1866 mFlags = flags; 1867 mIconResId = iconResId; 1868 mIconResName = iconResName; 1869 mBitmapPath = bitmapPath; 1870 } 1871 } 1872