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