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