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