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