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 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 app'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 app's manifest is 787 * the target activity. 788 * 789 * <li>A target activity must belong to the publisher app. 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 * app 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 apps may use this information to 937 * categorize 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 app has permission to 957 * launch. For example, a shortcut can launch an unexported activity within the publisher 958 * app. 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. The 974 * last element in the list represents the only intent that doesn't place an activity on 975 * the back stack. 976 * See the {@link ShortcutManager} javadoc for details. 977 * 978 * @see Builder#setIntent(Intent) 979 * @see ShortcutInfo#getIntents() 980 * @see Context#startActivities(Intent[]) 981 * @see TaskStackBuilder 982 */ 983 @NonNull 984 public Builder setIntents(@NonNull Intent[] intents) { 985 Preconditions.checkNotNull(intents, "intents cannot be null"); 986 Preconditions.checkNotNull(intents.length, "intents cannot be empty"); 987 for (Intent intent : intents) { 988 Preconditions.checkNotNull(intent, "intents cannot contain null"); 989 Preconditions.checkNotNull(intent.getAction(), "intent's action must be set"); 990 } 991 // Make sure always clone incoming intents. 992 mIntents = cloneIntents(intents); 993 return this; 994 } 995 996 /** 997 * "Rank" of a shortcut, which is a non-negative value that's used by the launcher app 998 * to sort shortcuts. 999 * 1000 * See {@link ShortcutInfo#getRank()} for details. 1001 */ 1002 @NonNull 1003 public Builder setRank(int rank) { 1004 Preconditions.checkArgument((0 <= rank), 1005 "Rank cannot be negative or bigger than MAX_RANK"); 1006 mRank = rank; 1007 return this; 1008 } 1009 1010 /** 1011 * Extras that the app can set for any purpose. 1012 * 1013 * <p>Apps can store arbitrary shortcut metadata in extras and retrieve the 1014 * metadata later using {@link ShortcutInfo#getExtras()}. 1015 */ 1016 @NonNull 1017 public Builder setExtras(@NonNull PersistableBundle extras) { 1018 mExtras = extras; 1019 return this; 1020 } 1021 1022 /** 1023 * Creates a {@link ShortcutInfo} instance. 1024 */ 1025 @NonNull 1026 public ShortcutInfo build() { 1027 return new ShortcutInfo(this); 1028 } 1029 } 1030 1031 /** 1032 * Returns the ID of a shortcut. 1033 * 1034 * <p>Shortcut IDs are unique within each publisher app and must be stable across 1035 * devices so that shortcuts will still be valid when restored on a different device. 1036 * See {@link ShortcutManager} for details. 1037 */ 1038 @NonNull 1039 public String getId() { 1040 return mId; 1041 } 1042 1043 /** 1044 * Return the package name of the publisher app. 1045 */ 1046 @NonNull 1047 public String getPackage() { 1048 return mPackageName; 1049 } 1050 1051 /** 1052 * Return the target activity. 1053 * 1054 * <p>This has nothing to do with the activity that this shortcut will launch. 1055 * Launcher apps should show the launcher icon for the returned activity alongside 1056 * this shortcut. 1057 * 1058 * @see Builder#setActivity 1059 */ 1060 @Nullable 1061 public ComponentName getActivity() { 1062 return mActivity; 1063 } 1064 1065 /** @hide */ 1066 public void setActivity(ComponentName activity) { 1067 mActivity = activity; 1068 } 1069 1070 /** 1071 * Returns the shortcut icon. 1072 * 1073 * @hide 1074 */ 1075 @Nullable 1076 public Icon getIcon() { 1077 return mIcon; 1078 } 1079 1080 /** @hide -- old signature, the internal code still uses it. */ 1081 @Nullable 1082 @Deprecated 1083 public CharSequence getTitle() { 1084 return mTitle; 1085 } 1086 1087 /** @hide -- old signature, the internal code still uses it. */ 1088 @Deprecated 1089 public int getTitleResId() { 1090 return mTitleResId; 1091 } 1092 1093 /** @hide -- old signature, the internal code still uses it. */ 1094 @Nullable 1095 @Deprecated 1096 public CharSequence getText() { 1097 return mText; 1098 } 1099 1100 /** @hide -- old signature, the internal code still uses it. */ 1101 @Deprecated 1102 public int getTextResId() { 1103 return mTextResId; 1104 } 1105 1106 /** 1107 * Return the short description of a shortcut. 1108 * 1109 * @see Builder#setShortLabel(CharSequence) 1110 */ 1111 @Nullable 1112 public CharSequence getShortLabel() { 1113 return mTitle; 1114 } 1115 1116 /** @hide */ 1117 public int getShortLabelResourceId() { 1118 return mTitleResId; 1119 } 1120 1121 /** 1122 * Return the long description of a shortcut. 1123 * 1124 * @see Builder#setLongLabel(CharSequence) 1125 */ 1126 @Nullable 1127 public CharSequence getLongLabel() { 1128 return mText; 1129 } 1130 1131 /** @hide */ 1132 public int getLongLabelResourceId() { 1133 return mTextResId; 1134 } 1135 1136 /** 1137 * Return the message that should be shown when the user attempts to start a shortcut 1138 * that is disabled. 1139 * 1140 * @see Builder#setDisabledMessage(CharSequence) 1141 */ 1142 @Nullable 1143 public CharSequence getDisabledMessage() { 1144 return mDisabledMessage; 1145 } 1146 1147 /** @hide */ 1148 public int getDisabledMessageResourceId() { 1149 return mDisabledMessageResId; 1150 } 1151 1152 /** 1153 * Return the shortcut's categories. 1154 * 1155 * @see Builder#setCategories(Set) 1156 */ 1157 @Nullable 1158 public Set<String> getCategories() { 1159 return mCategories; 1160 } 1161 1162 /** 1163 * Returns the intent that is executed when the user selects this shortcut. 1164 * If setIntents() was used, then return the last intent in the array. 1165 * 1166 * <p>Launcher apps <b>cannot</b> see the intent. If a {@link ShortcutInfo} is 1167 * obtained via {@link LauncherApps}, then this method will always return null. 1168 * Launchers can only start a shortcut intent with {@link LauncherApps#startShortcut}. 1169 * 1170 * @see Builder#setIntent(Intent) 1171 */ 1172 @Nullable 1173 public Intent getIntent() { 1174 if (mIntents == null || mIntents.length == 0) { 1175 return null; 1176 } 1177 final int last = mIntents.length - 1; 1178 final Intent intent = new Intent(mIntents[last]); 1179 return setIntentExtras(intent, mIntentPersistableExtrases[last]); 1180 } 1181 1182 /** 1183 * Return the intent set with {@link Builder#setIntents(Intent[])}. 1184 * 1185 * <p>Launcher apps <b>cannot</b> see the intents. If a {@link ShortcutInfo} is 1186 * obtained via {@link LauncherApps}, then this method will always return null. 1187 * Launchers can only start a shortcut intent with {@link LauncherApps#startShortcut}. 1188 * 1189 * @see Builder#setIntents(Intent[]) 1190 */ 1191 @Nullable 1192 public Intent[] getIntents() { 1193 final Intent[] ret = new Intent[mIntents.length]; 1194 1195 for (int i = 0; i < ret.length; i++) { 1196 ret[i] = new Intent(mIntents[i]); 1197 setIntentExtras(ret[i], mIntentPersistableExtrases[i]); 1198 } 1199 1200 return ret; 1201 } 1202 1203 /** 1204 * Return "raw" intents, which is the original intents without the extras. 1205 * @hide 1206 */ 1207 @Nullable 1208 public Intent[] getIntentsNoExtras() { 1209 return mIntents; 1210 } 1211 1212 /** 1213 * The extras in the intents. We convert extras into {@link PersistableBundle} so we can 1214 * persist them. 1215 * @hide 1216 */ 1217 @Nullable 1218 public PersistableBundle[] getIntentPersistableExtrases() { 1219 return mIntentPersistableExtrases; 1220 } 1221 1222 /** 1223 * "Rank" of a shortcut, which is a non-negative, sequential value that's unique for each 1224 * {@link #getActivity} for each of the two types of shortcuts (static and dynamic). 1225 * 1226 * <p>Because static shortcuts and dynamic shortcuts have overlapping ranks, 1227 * when a launcher app shows shortcuts for an activity, it should first show 1228 * the static shortcuts, followed by the dynamic shortcuts. Within each of those categories, 1229 * shortcuts should be sorted by rank in ascending order. 1230 * 1231 * <p><em>Floating shortcuts</em>, or shortcuts that are neither static nor dynamic, will all 1232 * have rank 0, because they aren't sorted. 1233 * 1234 * See the {@link ShortcutManager}'s class javadoc for details. 1235 * 1236 * @see Builder#setRank(int) 1237 */ 1238 public int getRank() { 1239 return mRank; 1240 } 1241 1242 /** @hide */ 1243 public boolean hasRank() { 1244 return mRank != RANK_NOT_SET; 1245 } 1246 1247 /** @hide */ 1248 public void setRank(int rank) { 1249 mRank = rank; 1250 } 1251 1252 /** @hide */ 1253 public void clearImplicitRankAndRankChangedFlag() { 1254 mImplicitRank = 0; 1255 } 1256 1257 /** @hide */ 1258 public void setImplicitRank(int rank) { 1259 // Make sure to keep RANK_CHANGED_BIT. 1260 mImplicitRank = (mImplicitRank & RANK_CHANGED_BIT) | (rank & IMPLICIT_RANK_MASK); 1261 } 1262 1263 /** @hide */ 1264 public int getImplicitRank() { 1265 return mImplicitRank & IMPLICIT_RANK_MASK; 1266 } 1267 1268 /** @hide */ 1269 public void setRankChanged() { 1270 mImplicitRank |= RANK_CHANGED_BIT; 1271 } 1272 1273 /** @hide */ 1274 public boolean isRankChanged() { 1275 return (mImplicitRank & RANK_CHANGED_BIT) != 0; 1276 } 1277 1278 /** 1279 * Extras that the app can set for any purpose. 1280 * 1281 * @see Builder#setExtras(PersistableBundle) 1282 */ 1283 @Nullable 1284 public PersistableBundle getExtras() { 1285 return mExtras; 1286 } 1287 1288 /** @hide */ 1289 public int getUserId() { 1290 return mUserId; 1291 } 1292 1293 /** 1294 * {@link UserHandle} on which the publisher created this shortcut. 1295 */ 1296 public UserHandle getUserHandle() { 1297 return UserHandle.of(mUserId); 1298 } 1299 1300 /** 1301 * Last time when any of the fields was updated. 1302 */ 1303 public long getLastChangedTimestamp() { 1304 return mLastChangedTimestamp; 1305 } 1306 1307 /** @hide */ 1308 @ShortcutFlags 1309 public int getFlags() { 1310 return mFlags; 1311 } 1312 1313 /** @hide*/ 1314 public void replaceFlags(@ShortcutFlags int flags) { 1315 mFlags = flags; 1316 } 1317 1318 /** @hide*/ 1319 public void addFlags(@ShortcutFlags int flags) { 1320 mFlags |= flags; 1321 } 1322 1323 /** @hide*/ 1324 public void clearFlags(@ShortcutFlags int flags) { 1325 mFlags &= ~flags; 1326 } 1327 1328 /** @hide*/ 1329 public boolean hasFlags(@ShortcutFlags int flags) { 1330 return (mFlags & flags) == flags; 1331 } 1332 1333 /** Return whether a shortcut is dynamic. */ 1334 public boolean isDynamic() { 1335 return hasFlags(FLAG_DYNAMIC); 1336 } 1337 1338 /** Return whether a shortcut is pinned. */ 1339 public boolean isPinned() { 1340 return hasFlags(FLAG_PINNED); 1341 } 1342 1343 /** 1344 * Return whether a shortcut is static; that is, whether a shortcut is 1345 * published from AndroidManifest.xml. If {@code true}, the shortcut is 1346 * also {@link #isImmutable()}. 1347 * 1348 * <p>When an app is upgraded and a shortcut is no longer published from AndroidManifest.xml, 1349 * this will be set to {@code false}. If the shortcut is not pinned, then it'll disappear. 1350 * However, if it's pinned, it will still be visible, {@link #isEnabled()} will be 1351 * {@code false} and {@link #isImmutable()} will be {@code true}. 1352 */ 1353 public boolean isDeclaredInManifest() { 1354 return hasFlags(FLAG_MANIFEST); 1355 } 1356 1357 /** @hide kept for unit tests */ 1358 @Deprecated 1359 public boolean isManifestShortcut() { 1360 return isDeclaredInManifest(); 1361 } 1362 1363 /** 1364 * @return true if pinned but neither static nor dynamic. 1365 * @hide 1366 */ 1367 public boolean isFloating() { 1368 return isPinned() && !(isDynamic() || isManifestShortcut()); 1369 } 1370 1371 /** @hide */ 1372 public boolean isOriginallyFromManifest() { 1373 return hasFlags(FLAG_IMMUTABLE); 1374 } 1375 1376 /** 1377 * Return if a shortcut is immutable, in which case it cannot be modified with any of 1378 * {@link ShortcutManager} APIs. 1379 * 1380 * <p>All static shortcuts are immutable. When a static shortcut is pinned and is then 1381 * disabled because it doesn't appear in AndroidManifest.xml for a newer version of the 1382 * app, {@link #isDeclaredInManifest()} returns {@code false}, but the shortcut 1383 * is still immutable. 1384 * 1385 * <p>All shortcuts originally published via the {@link ShortcutManager} APIs 1386 * are all mutable. 1387 */ 1388 public boolean isImmutable() { 1389 return hasFlags(FLAG_IMMUTABLE); 1390 } 1391 1392 /** 1393 * Returns {@code false} if a shortcut is disabled with 1394 * {@link ShortcutManager#disableShortcuts}. 1395 */ 1396 public boolean isEnabled() { 1397 return !hasFlags(FLAG_DISABLED); 1398 } 1399 1400 /** @hide */ 1401 public boolean isAlive() { 1402 return hasFlags(FLAG_PINNED) || hasFlags(FLAG_DYNAMIC) || hasFlags(FLAG_MANIFEST); 1403 } 1404 1405 /** @hide */ 1406 public boolean usesQuota() { 1407 return hasFlags(FLAG_DYNAMIC) || hasFlags(FLAG_MANIFEST); 1408 } 1409 1410 /** 1411 * Return whether a shortcut's icon is a resource in the owning package. 1412 * 1413 * @hide internal/unit tests only 1414 */ 1415 public boolean hasIconResource() { 1416 return hasFlags(FLAG_HAS_ICON_RES); 1417 } 1418 1419 /** @hide */ 1420 public boolean hasStringResources() { 1421 return (mTitleResId != 0) || (mTextResId != 0) || (mDisabledMessageResId != 0); 1422 } 1423 1424 /** @hide */ 1425 public boolean hasAnyResources() { 1426 return hasIconResource() || hasStringResources(); 1427 } 1428 1429 /** 1430 * Return whether a shortcut's icon is stored as a file. 1431 * 1432 * @hide internal/unit tests only 1433 */ 1434 public boolean hasIconFile() { 1435 return hasFlags(FLAG_HAS_ICON_FILE); 1436 } 1437 1438 /** 1439 * Return whether a shortcut only contains "key" information only or not. If true, only the 1440 * following fields are available. 1441 * <ul> 1442 * <li>{@link #getId()} 1443 * <li>{@link #getPackage()} 1444 * <li>{@link #getActivity()} 1445 * <li>{@link #getLastChangedTimestamp()} 1446 * <li>{@link #isDynamic()} 1447 * <li>{@link #isPinned()} 1448 * <li>{@link #isDeclaredInManifest()} 1449 * <li>{@link #isImmutable()} 1450 * <li>{@link #isEnabled()} 1451 * <li>{@link #getUserHandle()} 1452 * </ul> 1453 * 1454 * <p>For performance reasons, shortcuts passed to 1455 * {@link LauncherApps.Callback#onShortcutsChanged(String, List, UserHandle)} as well as those 1456 * returned from {@link LauncherApps#getShortcuts(ShortcutQuery, UserHandle)} 1457 * while using the {@link ShortcutQuery#FLAG_GET_KEY_FIELDS_ONLY} option contain only key 1458 * information. 1459 */ 1460 public boolean hasKeyFieldsOnly() { 1461 return hasFlags(FLAG_KEY_FIELDS_ONLY); 1462 } 1463 1464 /** @hide */ 1465 public boolean hasStringResourcesResolved() { 1466 return hasFlags(FLAG_STRINGS_RESOLVED); 1467 } 1468 1469 /** @hide */ 1470 public void updateTimestamp() { 1471 mLastChangedTimestamp = System.currentTimeMillis(); 1472 } 1473 1474 /** @hide */ 1475 // VisibleForTesting 1476 public void setTimestamp(long value) { 1477 mLastChangedTimestamp = value; 1478 } 1479 1480 /** @hide */ 1481 public void clearIcon() { 1482 mIcon = null; 1483 } 1484 1485 /** @hide */ 1486 public void setIconResourceId(int iconResourceId) { 1487 if (mIconResId != iconResourceId) { 1488 mIconResName = null; 1489 } 1490 mIconResId = iconResourceId; 1491 } 1492 1493 /** 1494 * Get the resource ID for the icon, valid only when {@link #hasIconResource()} } is true. 1495 * @hide internal / tests only. 1496 */ 1497 public int getIconResourceId() { 1498 return mIconResId; 1499 } 1500 1501 /** @hide */ 1502 public String getBitmapPath() { 1503 return mBitmapPath; 1504 } 1505 1506 /** @hide */ 1507 public void setBitmapPath(String bitmapPath) { 1508 mBitmapPath = bitmapPath; 1509 } 1510 1511 /** @hide */ 1512 public void setDisabledMessageResId(int disabledMessageResId) { 1513 if (mDisabledMessageResId != disabledMessageResId) { 1514 mDisabledMessageResName = null; 1515 } 1516 mDisabledMessageResId = disabledMessageResId; 1517 mDisabledMessage = null; 1518 } 1519 1520 /** @hide */ 1521 public void setDisabledMessage(String disabledMessage) { 1522 mDisabledMessage = disabledMessage; 1523 mDisabledMessageResId = 0; 1524 mDisabledMessageResName = null; 1525 } 1526 1527 /** @hide */ 1528 public String getTitleResName() { 1529 return mTitleResName; 1530 } 1531 1532 /** @hide */ 1533 public void setTitleResName(String titleResName) { 1534 mTitleResName = titleResName; 1535 } 1536 1537 /** @hide */ 1538 public String getTextResName() { 1539 return mTextResName; 1540 } 1541 1542 /** @hide */ 1543 public void setTextResName(String textResName) { 1544 mTextResName = textResName; 1545 } 1546 1547 /** @hide */ 1548 public String getDisabledMessageResName() { 1549 return mDisabledMessageResName; 1550 } 1551 1552 /** @hide */ 1553 public void setDisabledMessageResName(String disabledMessageResName) { 1554 mDisabledMessageResName = disabledMessageResName; 1555 } 1556 1557 /** @hide */ 1558 public String getIconResName() { 1559 return mIconResName; 1560 } 1561 1562 /** @hide */ 1563 public void setIconResName(String iconResName) { 1564 mIconResName = iconResName; 1565 } 1566 1567 /** 1568 * Replaces the intent. 1569 * 1570 * @throws IllegalArgumentException when extra is not compatible with {@link PersistableBundle}. 1571 * 1572 * @hide 1573 */ 1574 public void setIntents(Intent[] intents) throws IllegalArgumentException { 1575 Preconditions.checkNotNull(intents); 1576 Preconditions.checkArgument(intents.length > 0); 1577 1578 mIntents = cloneIntents(intents); 1579 fixUpIntentExtras(); 1580 } 1581 1582 /** @hide */ 1583 public static Intent setIntentExtras(Intent intent, PersistableBundle extras) { 1584 if (extras == null) { 1585 intent.replaceExtras((Bundle) null); 1586 } else { 1587 intent.replaceExtras(new Bundle(extras)); 1588 } 1589 return intent; 1590 } 1591 1592 /** 1593 * Replaces the categories. 1594 * 1595 * @hide 1596 */ 1597 public void setCategories(Set<String> categories) { 1598 mCategories = cloneCategories(categories); 1599 } 1600 1601 private ShortcutInfo(Parcel source) { 1602 final ClassLoader cl = getClass().getClassLoader(); 1603 1604 mUserId = source.readInt(); 1605 mId = source.readString(); 1606 mPackageName = source.readString(); 1607 mActivity = source.readParcelable(cl); 1608 mFlags = source.readInt(); 1609 mIconResId = source.readInt(); 1610 mLastChangedTimestamp = source.readLong(); 1611 1612 if (source.readInt() == 0) { 1613 return; // key information only. 1614 } 1615 1616 mIcon = source.readParcelable(cl); 1617 mTitle = source.readCharSequence(); 1618 mTitleResId = source.readInt(); 1619 mText = source.readCharSequence(); 1620 mTextResId = source.readInt(); 1621 mDisabledMessage = source.readCharSequence(); 1622 mDisabledMessageResId = source.readInt(); 1623 mIntents = source.readParcelableArray(cl, Intent.class); 1624 mIntentPersistableExtrases = source.readParcelableArray(cl, PersistableBundle.class); 1625 mRank = source.readInt(); 1626 mExtras = source.readParcelable(cl); 1627 mBitmapPath = source.readString(); 1628 1629 mIconResName = source.readString(); 1630 mTitleResName = source.readString(); 1631 mTextResName = source.readString(); 1632 mDisabledMessageResName = source.readString(); 1633 1634 int N = source.readInt(); 1635 if (N == 0) { 1636 mCategories = null; 1637 } else { 1638 mCategories = new ArraySet<>(N); 1639 for (int i = 0; i < N; i++) { 1640 mCategories.add(source.readString().intern()); 1641 } 1642 } 1643 } 1644 1645 @Override 1646 public void writeToParcel(Parcel dest, int flags) { 1647 dest.writeInt(mUserId); 1648 dest.writeString(mId); 1649 dest.writeString(mPackageName); 1650 dest.writeParcelable(mActivity, flags); 1651 dest.writeInt(mFlags); 1652 dest.writeInt(mIconResId); 1653 dest.writeLong(mLastChangedTimestamp); 1654 1655 if (hasKeyFieldsOnly()) { 1656 dest.writeInt(0); 1657 return; 1658 } 1659 dest.writeInt(1); 1660 1661 dest.writeParcelable(mIcon, flags); 1662 dest.writeCharSequence(mTitle); 1663 dest.writeInt(mTitleResId); 1664 dest.writeCharSequence(mText); 1665 dest.writeInt(mTextResId); 1666 dest.writeCharSequence(mDisabledMessage); 1667 dest.writeInt(mDisabledMessageResId); 1668 1669 dest.writeParcelableArray(mIntents, flags); 1670 dest.writeParcelableArray(mIntentPersistableExtrases, flags); 1671 dest.writeInt(mRank); 1672 dest.writeParcelable(mExtras, flags); 1673 dest.writeString(mBitmapPath); 1674 1675 dest.writeString(mIconResName); 1676 dest.writeString(mTitleResName); 1677 dest.writeString(mTextResName); 1678 dest.writeString(mDisabledMessageResName); 1679 1680 if (mCategories != null) { 1681 final int N = mCategories.size(); 1682 dest.writeInt(N); 1683 for (int i = 0; i < N; i++) { 1684 dest.writeString(mCategories.valueAt(i)); 1685 } 1686 } else { 1687 dest.writeInt(0); 1688 } 1689 } 1690 1691 public static final Creator<ShortcutInfo> CREATOR = 1692 new Creator<ShortcutInfo>() { 1693 public ShortcutInfo createFromParcel(Parcel source) { 1694 return new ShortcutInfo(source); 1695 } 1696 public ShortcutInfo[] newArray(int size) { 1697 return new ShortcutInfo[size]; 1698 } 1699 }; 1700 1701 @Override 1702 public int describeContents() { 1703 return 0; 1704 } 1705 1706 /** 1707 * Return a string representation, intended for logging. Some fields will be retracted. 1708 */ 1709 @Override 1710 public String toString() { 1711 return toStringInner(/* secure =*/ true, /* includeInternalData =*/ false); 1712 } 1713 1714 /** @hide */ 1715 public String toInsecureString() { 1716 return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true); 1717 } 1718 1719 private String toStringInner(boolean secure, boolean includeInternalData) { 1720 final StringBuilder sb = new StringBuilder(); 1721 sb.append("ShortcutInfo {"); 1722 1723 sb.append("id="); 1724 sb.append(secure ? "***" : mId); 1725 1726 sb.append(", flags=0x"); 1727 sb.append(Integer.toHexString(mFlags)); 1728 sb.append(" ["); 1729 if (!isEnabled()) { 1730 sb.append("X"); 1731 } 1732 if (isImmutable()) { 1733 sb.append("Im"); 1734 } 1735 if (isManifestShortcut()) { 1736 sb.append("M"); 1737 } 1738 if (isDynamic()) { 1739 sb.append("D"); 1740 } 1741 if (isPinned()) { 1742 sb.append("P"); 1743 } 1744 if (hasIconFile()) { 1745 sb.append("If"); 1746 } 1747 if (hasIconResource()) { 1748 sb.append("Ir"); 1749 } 1750 if (hasKeyFieldsOnly()) { 1751 sb.append("K"); 1752 } 1753 if (hasStringResourcesResolved()) { 1754 sb.append("Sr"); 1755 } 1756 sb.append("]"); 1757 1758 sb.append(", packageName="); 1759 sb.append(mPackageName); 1760 1761 sb.append(", activity="); 1762 sb.append(mActivity); 1763 1764 sb.append(", shortLabel="); 1765 sb.append(secure ? "***" : mTitle); 1766 sb.append(", resId="); 1767 sb.append(mTitleResId); 1768 sb.append("["); 1769 sb.append(mTitleResName); 1770 sb.append("]"); 1771 1772 sb.append(", longLabel="); 1773 sb.append(secure ? "***" : mText); 1774 sb.append(", resId="); 1775 sb.append(mTextResId); 1776 sb.append("["); 1777 sb.append(mTextResName); 1778 sb.append("]"); 1779 1780 sb.append(", disabledMessage="); 1781 sb.append(secure ? "***" : mDisabledMessage); 1782 sb.append(", resId="); 1783 sb.append(mDisabledMessageResId); 1784 sb.append("["); 1785 sb.append(mDisabledMessageResName); 1786 sb.append("]"); 1787 1788 sb.append(", categories="); 1789 sb.append(mCategories); 1790 1791 sb.append(", icon="); 1792 sb.append(mIcon); 1793 1794 sb.append(", rank="); 1795 sb.append(mRank); 1796 1797 sb.append(", timestamp="); 1798 sb.append(mLastChangedTimestamp); 1799 1800 sb.append(", intents="); 1801 if (mIntents == null) { 1802 sb.append("null"); 1803 } else { 1804 if (secure) { 1805 sb.append("size:"); 1806 sb.append(mIntents.length); 1807 } else { 1808 final int size = mIntents.length; 1809 sb.append("["); 1810 String sep = ""; 1811 for (int i = 0; i < size; i++) { 1812 sb.append(sep); 1813 sep = ", "; 1814 sb.append(mIntents[i]); 1815 sb.append("/"); 1816 sb.append(mIntentPersistableExtrases[i]); 1817 } 1818 sb.append("]"); 1819 } 1820 } 1821 1822 sb.append(", extras="); 1823 sb.append(mExtras); 1824 1825 if (includeInternalData) { 1826 1827 sb.append(", iconRes="); 1828 sb.append(mIconResId); 1829 sb.append("["); 1830 sb.append(mIconResName); 1831 sb.append("]"); 1832 1833 sb.append(", bitmapPath="); 1834 sb.append(mBitmapPath); 1835 } 1836 1837 sb.append("}"); 1838 return sb.toString(); 1839 } 1840 1841 /** @hide */ 1842 public ShortcutInfo( 1843 @UserIdInt int userId, String id, String packageName, ComponentName activity, 1844 Icon icon, CharSequence title, int titleResId, String titleResName, 1845 CharSequence text, int textResId, String textResName, 1846 CharSequence disabledMessage, int disabledMessageResId, String disabledMessageResName, 1847 Set<String> categories, Intent[] intentsWithExtras, int rank, PersistableBundle extras, 1848 long lastChangedTimestamp, 1849 int flags, int iconResId, String iconResName, String bitmapPath) { 1850 mUserId = userId; 1851 mId = id; 1852 mPackageName = packageName; 1853 mActivity = activity; 1854 mIcon = icon; 1855 mTitle = title; 1856 mTitleResId = titleResId; 1857 mTitleResName = titleResName; 1858 mText = text; 1859 mTextResId = textResId; 1860 mTextResName = textResName; 1861 mDisabledMessage = disabledMessage; 1862 mDisabledMessageResId = disabledMessageResId; 1863 mDisabledMessageResName = disabledMessageResName; 1864 mCategories = cloneCategories(categories); 1865 mIntents = cloneIntents(intentsWithExtras); 1866 fixUpIntentExtras(); 1867 mRank = rank; 1868 mExtras = extras; 1869 mLastChangedTimestamp = lastChangedTimestamp; 1870 mFlags = flags; 1871 mIconResId = iconResId; 1872 mIconResName = iconResName; 1873 mBitmapPath = bitmapPath; 1874 } 1875 } 1876