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