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