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