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