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