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