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 "launcher 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 application'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 application manifest is
    787          * the target activity.
    788          *
    789          * <li>A target activity must belong to the publisher application.
    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          * application 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 applications may use this information to
    937          * categorise 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 application has permission to
    957          * launch.  For example, a shortcut can launch an unexported activity within the publisher
    958          * application.  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.
    974          * See the {@link ShortcutManager} javadoc for details.
    975          *
    976          * @see Builder#setIntent(Intent)
    977          * @see ShortcutInfo#getIntents()
    978          * @see Context#startActivities(Intent[])
    979          * @see TaskStackBuilder
    980          */
    981         @NonNull
    982         public Builder setIntents(@NonNull Intent[] intents) {
    983             Preconditions.checkNotNull(intents, "intents cannot be null");
    984             Preconditions.checkNotNull(intents.length, "intents cannot be empty");
    985             for (Intent intent : intents) {
    986                 Preconditions.checkNotNull(intent, "intents cannot contain null");
    987                 Preconditions.checkNotNull(intent.getAction(), "intent's action must be set");
    988             }
    989             // Make sure always clone incoming intents.
    990             mIntents = cloneIntents(intents);
    991             return this;
    992         }
    993 
    994         /**
    995          * "Rank" of a shortcut, which is a non-negative value that's used by the launcher app
    996          * to sort shortcuts.
    997          *
    998          * See {@link ShortcutInfo#getRank()} for details.
    999          */
   1000         @NonNull
   1001         public Builder setRank(int rank) {
   1002             Preconditions.checkArgument((0 <= rank),
   1003                     "Rank cannot be negative or bigger than MAX_RANK");
   1004             mRank = rank;
   1005             return this;
   1006         }
   1007 
   1008         /**
   1009          * Extras that application can set for any purpose.
   1010          *
   1011          * <p>Applications can store arbitrary shortcut metadata in extras and retrieve the
   1012          * metadata later using {@link ShortcutInfo#getExtras()}.
   1013          */
   1014         @NonNull
   1015         public Builder setExtras(@NonNull PersistableBundle extras) {
   1016             mExtras = extras;
   1017             return this;
   1018         }
   1019 
   1020         /**
   1021          * Creates a {@link ShortcutInfo} instance.
   1022          */
   1023         @NonNull
   1024         public ShortcutInfo build() {
   1025             return new ShortcutInfo(this);
   1026         }
   1027     }
   1028 
   1029     /**
   1030      * Returns the ID of a shortcut.
   1031      *
   1032      * <p>Shortcut IDs are unique within each publisher application and must be stable across
   1033      * devices so that shortcuts will still be valid when restored on a different device.
   1034      * See {@link ShortcutManager} for details.
   1035      */
   1036     @NonNull
   1037     public String getId() {
   1038         return mId;
   1039     }
   1040 
   1041     /**
   1042      * Return the package name of the publisher application.
   1043      */
   1044     @NonNull
   1045     public String getPackage() {
   1046         return mPackageName;
   1047     }
   1048 
   1049     /**
   1050      * Return the target activity.
   1051      *
   1052      * <p>This has nothing to do with the activity that this shortcut will launch.
   1053      * Launcher applications should show the launcher icon for the returned activity alongside
   1054      * this shortcut.
   1055      *
   1056      * @see Builder#setActivity
   1057      */
   1058     @Nullable
   1059     public ComponentName getActivity() {
   1060         return mActivity;
   1061     }
   1062 
   1063     /** @hide */
   1064     public void setActivity(ComponentName activity) {
   1065         mActivity = activity;
   1066     }
   1067 
   1068     /**
   1069      * Returns the shortcut icon.
   1070      *
   1071      * @hide
   1072      */
   1073     @Nullable
   1074     public Icon getIcon() {
   1075         return mIcon;
   1076     }
   1077 
   1078     /** @hide -- old signature, the internal code still uses it. */
   1079     @Nullable
   1080     @Deprecated
   1081     public CharSequence getTitle() {
   1082         return mTitle;
   1083     }
   1084 
   1085     /** @hide -- old signature, the internal code still uses it. */
   1086     @Deprecated
   1087     public int getTitleResId() {
   1088         return mTitleResId;
   1089     }
   1090 
   1091     /** @hide -- old signature, the internal code still uses it. */
   1092     @Nullable
   1093     @Deprecated
   1094     public CharSequence getText() {
   1095         return mText;
   1096     }
   1097 
   1098     /** @hide -- old signature, the internal code still uses it. */
   1099     @Deprecated
   1100     public int getTextResId() {
   1101         return mTextResId;
   1102     }
   1103 
   1104     /**
   1105      * Return the shorter description of a shortcut.
   1106      *
   1107      * @see Builder#setShortLabel(CharSequence)
   1108      */
   1109     @Nullable
   1110     public CharSequence getShortLabel() {
   1111         return mTitle;
   1112     }
   1113 
   1114     /** @hide */
   1115     public int getShortLabelResourceId() {
   1116         return mTitleResId;
   1117     }
   1118 
   1119     /**
   1120      * Return the longer description of a shortcut.
   1121      *
   1122      * @see Builder#setLongLabel(CharSequence)
   1123      */
   1124     @Nullable
   1125     public CharSequence getLongLabel() {
   1126         return mText;
   1127     }
   1128 
   1129     /** @hide */
   1130     public int getLongLabelResourceId() {
   1131         return mTextResId;
   1132     }
   1133 
   1134     /**
   1135      * Return the message that should be shown when the user attempts to start a shortcut
   1136      * that is disabled.
   1137      *
   1138      * @see Builder#setDisabledMessage(CharSequence)
   1139      */
   1140     @Nullable
   1141     public CharSequence getDisabledMessage() {
   1142         return mDisabledMessage;
   1143     }
   1144 
   1145     /** @hide */
   1146     public int getDisabledMessageResourceId() {
   1147         return mDisabledMessageResId;
   1148     }
   1149 
   1150     /**
   1151      * Return the shortcut's categories.
   1152      *
   1153      * @see Builder#setCategories(Set)
   1154      */
   1155     @Nullable
   1156     public Set<String> getCategories() {
   1157         return mCategories;
   1158     }
   1159 
   1160     /**
   1161      * Returns the intent that is executed when the user selects this shortcut.
   1162      * If setIntents() was used, then return the last intent in the array.
   1163      *
   1164      * <p>Launcher applications <b>cannot</b> see the intent.  If a {@link ShortcutInfo} is
   1165      * obtained via {@link LauncherApps}, then this method will always return null.
   1166      * Launchers can only start a shortcut intent with {@link LauncherApps#startShortcut}.
   1167      *
   1168      * @see Builder#setIntent(Intent)
   1169      */
   1170     @Nullable
   1171     public Intent getIntent() {
   1172         if (mIntents == null || mIntents.length == 0) {
   1173             return null;
   1174         }
   1175         final int last = mIntents.length - 1;
   1176         final Intent intent = new Intent(mIntents[last]);
   1177         return setIntentExtras(intent, mIntentPersistableExtrases[last]);
   1178     }
   1179 
   1180     /**
   1181      * Return the intent set with {@link Builder#setIntents(Intent[])}.
   1182      *
   1183      * <p>Launcher applications <b>cannot</b> see the intents.  If a {@link ShortcutInfo} is
   1184      * obtained via {@link LauncherApps}, then this method will always return null.
   1185      * Launchers can only start a shortcut intent with {@link LauncherApps#startShortcut}.
   1186      *
   1187      * @see Builder#setIntents(Intent[])
   1188      */
   1189     @Nullable
   1190     public Intent[] getIntents() {
   1191         final Intent[] ret = new Intent[mIntents.length];
   1192 
   1193         for (int i = 0; i < ret.length; i++) {
   1194             ret[i] = new Intent(mIntents[i]);
   1195             setIntentExtras(ret[i], mIntentPersistableExtrases[i]);
   1196         }
   1197 
   1198         return ret;
   1199     }
   1200 
   1201     /**
   1202      * Return "raw" intents, which is the original intents without the extras.
   1203      * @hide
   1204      */
   1205     @Nullable
   1206     public Intent[] getIntentsNoExtras() {
   1207         return mIntents;
   1208     }
   1209 
   1210     /**
   1211      * The extras in the intents.  We convert extras into {@link PersistableBundle} so we can
   1212      * persist them.
   1213      * @hide
   1214      */
   1215     @Nullable
   1216     public PersistableBundle[] getIntentPersistableExtrases() {
   1217         return mIntentPersistableExtrases;
   1218     }
   1219 
   1220     /**
   1221      * "Rank" of a shortcut, which is a non-negative, sequential value that's unique for each
   1222      * {@link #getActivity} for each of the two kinds, dynamic shortcuts and manifest shortcuts.
   1223      *
   1224      * <p>Because manifest shortcuts and dynamic shortcuts have overlapping ranks,
   1225      * when a launcher application shows shortcuts for an activity, it should first show
   1226      * the manifest shortcuts followed by the dynamic shortcuts.  Within each of those categories,
   1227      * shortcuts should be sorted by rank in ascending order.
   1228      *
   1229      * <p>"Floating" shortcuts (i.e. shortcuts that are neither dynamic nor manifest) will all
   1230      * have rank 0, because there's no sorting for them.
   1231      *
   1232      * See the {@link ShortcutManager}'s class javadoc for details.
   1233      *
   1234      * @see Builder#setRank(int)
   1235      */
   1236     public int getRank() {
   1237         return mRank;
   1238     }
   1239 
   1240     /** @hide */
   1241     public boolean hasRank() {
   1242         return mRank != RANK_NOT_SET;
   1243     }
   1244 
   1245     /** @hide */
   1246     public void setRank(int rank) {
   1247         mRank = rank;
   1248     }
   1249 
   1250     /** @hide */
   1251     public void clearImplicitRankAndRankChangedFlag() {
   1252         mImplicitRank = 0;
   1253     }
   1254 
   1255     /** @hide */
   1256     public void setImplicitRank(int rank) {
   1257         // Make sure to keep RANK_CHANGED_BIT.
   1258         mImplicitRank = (mImplicitRank & RANK_CHANGED_BIT) | (rank & IMPLICIT_RANK_MASK);
   1259     }
   1260 
   1261     /** @hide */
   1262     public int getImplicitRank() {
   1263         return mImplicitRank & IMPLICIT_RANK_MASK;
   1264     }
   1265 
   1266     /** @hide */
   1267     public void setRankChanged() {
   1268         mImplicitRank |= RANK_CHANGED_BIT;
   1269     }
   1270 
   1271     /** @hide */
   1272     public boolean isRankChanged() {
   1273         return (mImplicitRank & RANK_CHANGED_BIT) != 0;
   1274     }
   1275 
   1276     /**
   1277      * Extras that application can set to any purposes.
   1278      *
   1279      * @see Builder#setExtras(PersistableBundle)
   1280      */
   1281     @Nullable
   1282     public PersistableBundle getExtras() {
   1283         return mExtras;
   1284     }
   1285 
   1286     /** @hide */
   1287     public int getUserId() {
   1288         return mUserId;
   1289     }
   1290 
   1291     /**
   1292      * {@link UserHandle} on which the publisher created this shortcut.
   1293      */
   1294     public UserHandle getUserHandle() {
   1295         return UserHandle.of(mUserId);
   1296     }
   1297 
   1298     /**
   1299      * Last time when any of the fields was updated.
   1300      */
   1301     public long getLastChangedTimestamp() {
   1302         return mLastChangedTimestamp;
   1303     }
   1304 
   1305     /** @hide */
   1306     @ShortcutFlags
   1307     public int getFlags() {
   1308         return mFlags;
   1309     }
   1310 
   1311     /** @hide*/
   1312     public void replaceFlags(@ShortcutFlags int flags) {
   1313         mFlags = flags;
   1314     }
   1315 
   1316     /** @hide*/
   1317     public void addFlags(@ShortcutFlags int flags) {
   1318         mFlags |= flags;
   1319     }
   1320 
   1321     /** @hide*/
   1322     public void clearFlags(@ShortcutFlags int flags) {
   1323         mFlags &= ~flags;
   1324     }
   1325 
   1326     /** @hide*/
   1327     public boolean hasFlags(@ShortcutFlags int flags) {
   1328         return (mFlags & flags) == flags;
   1329     }
   1330 
   1331     /** Return whether a shortcut is dynamic. */
   1332     public boolean isDynamic() {
   1333         return hasFlags(FLAG_DYNAMIC);
   1334     }
   1335 
   1336     /** Return whether a shortcut is pinned. */
   1337     public boolean isPinned() {
   1338         return hasFlags(FLAG_PINNED);
   1339     }
   1340 
   1341     /**
   1342      * Return whether a shortcut is published from AndroidManifest.xml or not.  If {@code true},
   1343      * it's also {@link #isImmutable()}.
   1344      *
   1345      * <p>When an app is upgraded and a shortcut is no longer published from AndroidManifest.xml,
   1346      * this will be set to {@code false}.  If the shortcut is not pinned, then it'll just disappear.
   1347      * However, if it's pinned, it will still be alive, and {@link #isEnabled()} will be
   1348      * {@code false} and {@link #isImmutable()} will be {@code true}.
   1349      */
   1350     public boolean isDeclaredInManifest() {
   1351         return hasFlags(FLAG_MANIFEST);
   1352     }
   1353 
   1354     /** @hide kept for unit tests */
   1355     @Deprecated
   1356     public boolean isManifestShortcut() {
   1357         return isDeclaredInManifest();
   1358     }
   1359 
   1360     /**
   1361      * @return true if pinned but neither dynamic nor manifest.
   1362      * @hide
   1363      */
   1364     public boolean isFloating() {
   1365         return isPinned() && !(isDynamic() || isManifestShortcut());
   1366     }
   1367 
   1368     /** @hide */
   1369     public boolean isOriginallyFromManifest() {
   1370         return hasFlags(FLAG_IMMUTABLE);
   1371     }
   1372 
   1373     /**
   1374      * Return if a shortcut is immutable, in which case it cannot be modified with any of
   1375      * {@link ShortcutManager} APIs.
   1376      *
   1377      * <p>All manifest shortcuts are immutable.  When a manifest shortcut is pinned and then
   1378      * disabled because the app is upgraded and its AndroidManifest.xml no longer publishes it,
   1379      * {@link #isDeclaredInManifest()} returns {@code false}, but it is still immutable.
   1380      *
   1381      * <p>All shortcuts originally published via the {@link ShortcutManager} APIs
   1382      * are all mutable.
   1383      */
   1384     public boolean isImmutable() {
   1385         return hasFlags(FLAG_IMMUTABLE);
   1386     }
   1387 
   1388     /**
   1389      * Returns {@code false} if a shortcut is disabled with
   1390      * {@link ShortcutManager#disableShortcuts}.
   1391      */
   1392     public boolean isEnabled() {
   1393         return !hasFlags(FLAG_DISABLED);
   1394     }
   1395 
   1396     /** @hide */
   1397     public boolean isAlive() {
   1398         return hasFlags(FLAG_PINNED) || hasFlags(FLAG_DYNAMIC) || hasFlags(FLAG_MANIFEST);
   1399     }
   1400 
   1401     /** @hide */
   1402     public boolean usesQuota() {
   1403         return hasFlags(FLAG_DYNAMIC) || hasFlags(FLAG_MANIFEST);
   1404     }
   1405 
   1406     /**
   1407      * Return whether a shortcut's icon is a resource in the owning package.
   1408      *
   1409      * @hide internal/unit tests only
   1410      */
   1411     public boolean hasIconResource() {
   1412         return hasFlags(FLAG_HAS_ICON_RES);
   1413     }
   1414 
   1415     /** @hide */
   1416     public boolean hasStringResources() {
   1417         return (mTitleResId != 0) || (mTextResId != 0) || (mDisabledMessageResId != 0);
   1418     }
   1419 
   1420     /** @hide */
   1421     public boolean hasAnyResources() {
   1422         return hasIconResource() || hasStringResources();
   1423     }
   1424 
   1425     /**
   1426      * Return whether a shortcut's icon is stored as a file.
   1427      *
   1428      * @hide internal/unit tests only
   1429      */
   1430     public boolean hasIconFile() {
   1431         return hasFlags(FLAG_HAS_ICON_FILE);
   1432     }
   1433 
   1434     /**
   1435      * Return whether a shortcut only contains "key" information only or not.  If true, only the
   1436      * following fields are available.
   1437      * <ul>
   1438      *     <li>{@link #getId()}
   1439      *     <li>{@link #getPackage()}
   1440      *     <li>{@link #getActivity()}
   1441      *     <li>{@link #getLastChangedTimestamp()}
   1442      *     <li>{@link #isDynamic()}
   1443      *     <li>{@link #isPinned()}
   1444      *     <li>{@link #isDeclaredInManifest()}
   1445      *     <li>{@link #isImmutable()}
   1446      *     <li>{@link #isEnabled()}
   1447      *     <li>{@link #getUserHandle()}
   1448      * </ul>
   1449      *
   1450      * <p>For performance reasons, shortcuts passed to
   1451      * {@link LauncherApps.Callback#onShortcutsChanged(String, List, UserHandle)} as well as those
   1452      * returned from {@link LauncherApps#getShortcuts(ShortcutQuery, UserHandle)}
   1453      * while using the {@link ShortcutQuery#FLAG_GET_KEY_FIELDS_ONLY} option contain only key
   1454      * information.
   1455      */
   1456     public boolean hasKeyFieldsOnly() {
   1457         return hasFlags(FLAG_KEY_FIELDS_ONLY);
   1458     }
   1459 
   1460     /** @hide */
   1461     public boolean hasStringResourcesResolved() {
   1462         return hasFlags(FLAG_STRINGS_RESOLVED);
   1463     }
   1464 
   1465     /** @hide */
   1466     public void updateTimestamp() {
   1467         mLastChangedTimestamp = System.currentTimeMillis();
   1468     }
   1469 
   1470     /** @hide */
   1471     // VisibleForTesting
   1472     public void setTimestamp(long value) {
   1473         mLastChangedTimestamp = value;
   1474     }
   1475 
   1476     /** @hide */
   1477     public void clearIcon() {
   1478         mIcon = null;
   1479     }
   1480 
   1481     /** @hide */
   1482     public void setIconResourceId(int iconResourceId) {
   1483         if (mIconResId != iconResourceId) {
   1484             mIconResName = null;
   1485         }
   1486         mIconResId = iconResourceId;
   1487     }
   1488 
   1489     /**
   1490      * Get the resource ID for the icon, valid only when {@link #hasIconResource()} } is true.
   1491      * @hide internal / tests only.
   1492      */
   1493     public int getIconResourceId() {
   1494         return mIconResId;
   1495     }
   1496 
   1497     /** @hide */
   1498     public String getBitmapPath() {
   1499         return mBitmapPath;
   1500     }
   1501 
   1502     /** @hide */
   1503     public void setBitmapPath(String bitmapPath) {
   1504         mBitmapPath = bitmapPath;
   1505     }
   1506 
   1507     /** @hide */
   1508     public void setDisabledMessageResId(int disabledMessageResId) {
   1509         if (mDisabledMessageResId != disabledMessageResId) {
   1510             mDisabledMessageResName = null;
   1511         }
   1512         mDisabledMessageResId = disabledMessageResId;
   1513         mDisabledMessage = null;
   1514     }
   1515 
   1516     /** @hide */
   1517     public void setDisabledMessage(String disabledMessage) {
   1518         mDisabledMessage = disabledMessage;
   1519         mDisabledMessageResId = 0;
   1520         mDisabledMessageResName = null;
   1521     }
   1522 
   1523     /** @hide */
   1524     public String getTitleResName() {
   1525         return mTitleResName;
   1526     }
   1527 
   1528     /** @hide */
   1529     public void setTitleResName(String titleResName) {
   1530         mTitleResName = titleResName;
   1531     }
   1532 
   1533     /** @hide */
   1534     public String getTextResName() {
   1535         return mTextResName;
   1536     }
   1537 
   1538     /** @hide */
   1539     public void setTextResName(String textResName) {
   1540         mTextResName = textResName;
   1541     }
   1542 
   1543     /** @hide */
   1544     public String getDisabledMessageResName() {
   1545         return mDisabledMessageResName;
   1546     }
   1547 
   1548     /** @hide */
   1549     public void setDisabledMessageResName(String disabledMessageResName) {
   1550         mDisabledMessageResName = disabledMessageResName;
   1551     }
   1552 
   1553     /** @hide */
   1554     public String getIconResName() {
   1555         return mIconResName;
   1556     }
   1557 
   1558     /** @hide */
   1559     public void setIconResName(String iconResName) {
   1560         mIconResName = iconResName;
   1561     }
   1562 
   1563     /**
   1564      * Replaces the intent
   1565      *
   1566      * @throws IllegalArgumentException when extra is not compatible with {@link PersistableBundle}.
   1567      *
   1568      * @hide
   1569      */
   1570     public void setIntents(Intent[] intents) throws IllegalArgumentException {
   1571         Preconditions.checkNotNull(intents);
   1572         Preconditions.checkArgument(intents.length > 0);
   1573 
   1574         mIntents = cloneIntents(intents);
   1575         fixUpIntentExtras();
   1576     }
   1577 
   1578     /** @hide */
   1579     public static Intent setIntentExtras(Intent intent, PersistableBundle extras) {
   1580         if (extras == null) {
   1581             intent.replaceExtras((Bundle) null);
   1582         } else {
   1583             intent.replaceExtras(new Bundle(extras));
   1584         }
   1585         return intent;
   1586     }
   1587 
   1588     /**
   1589      * Replaces the categories.
   1590      *
   1591      * @hide
   1592      */
   1593     public void setCategories(Set<String> categories) {
   1594         mCategories = cloneCategories(categories);
   1595     }
   1596 
   1597     private ShortcutInfo(Parcel source) {
   1598         final ClassLoader cl = getClass().getClassLoader();
   1599 
   1600         mUserId = source.readInt();
   1601         mId = source.readString();
   1602         mPackageName = source.readString();
   1603         mActivity = source.readParcelable(cl);
   1604         mFlags = source.readInt();
   1605         mIconResId = source.readInt();
   1606         mLastChangedTimestamp = source.readLong();
   1607 
   1608         if (source.readInt() == 0) {
   1609             return; // key information only.
   1610         }
   1611 
   1612         mIcon = source.readParcelable(cl);
   1613         mTitle = source.readCharSequence();
   1614         mTitleResId = source.readInt();
   1615         mText = source.readCharSequence();
   1616         mTextResId = source.readInt();
   1617         mDisabledMessage = source.readCharSequence();
   1618         mDisabledMessageResId = source.readInt();
   1619         mIntents = source.readParcelableArray(cl, Intent.class);
   1620         mIntentPersistableExtrases = source.readParcelableArray(cl, PersistableBundle.class);
   1621         mRank = source.readInt();
   1622         mExtras = source.readParcelable(cl);
   1623         mBitmapPath = source.readString();
   1624 
   1625         mIconResName = source.readString();
   1626         mTitleResName = source.readString();
   1627         mTextResName = source.readString();
   1628         mDisabledMessageResName = source.readString();
   1629 
   1630         int N = source.readInt();
   1631         if (N == 0) {
   1632             mCategories = null;
   1633         } else {
   1634             mCategories = new ArraySet<>(N);
   1635             for (int i = 0; i < N; i++) {
   1636                 mCategories.add(source.readString().intern());
   1637             }
   1638         }
   1639     }
   1640 
   1641     @Override
   1642     public void writeToParcel(Parcel dest, int flags) {
   1643         dest.writeInt(mUserId);
   1644         dest.writeString(mId);
   1645         dest.writeString(mPackageName);
   1646         dest.writeParcelable(mActivity, flags);
   1647         dest.writeInt(mFlags);
   1648         dest.writeInt(mIconResId);
   1649         dest.writeLong(mLastChangedTimestamp);
   1650 
   1651         if (hasKeyFieldsOnly()) {
   1652             dest.writeInt(0);
   1653             return;
   1654         }
   1655         dest.writeInt(1);
   1656 
   1657         dest.writeParcelable(mIcon, flags);
   1658         dest.writeCharSequence(mTitle);
   1659         dest.writeInt(mTitleResId);
   1660         dest.writeCharSequence(mText);
   1661         dest.writeInt(mTextResId);
   1662         dest.writeCharSequence(mDisabledMessage);
   1663         dest.writeInt(mDisabledMessageResId);
   1664 
   1665         dest.writeParcelableArray(mIntents, flags);
   1666         dest.writeParcelableArray(mIntentPersistableExtrases, flags);
   1667         dest.writeInt(mRank);
   1668         dest.writeParcelable(mExtras, flags);
   1669         dest.writeString(mBitmapPath);
   1670 
   1671         dest.writeString(mIconResName);
   1672         dest.writeString(mTitleResName);
   1673         dest.writeString(mTextResName);
   1674         dest.writeString(mDisabledMessageResName);
   1675 
   1676         if (mCategories != null) {
   1677             final int N = mCategories.size();
   1678             dest.writeInt(N);
   1679             for (int i = 0; i < N; i++) {
   1680                 dest.writeString(mCategories.valueAt(i));
   1681             }
   1682         } else {
   1683             dest.writeInt(0);
   1684         }
   1685     }
   1686 
   1687     public static final Creator<ShortcutInfo> CREATOR =
   1688             new Creator<ShortcutInfo>() {
   1689                 public ShortcutInfo createFromParcel(Parcel source) {
   1690                     return new ShortcutInfo(source);
   1691                 }
   1692                 public ShortcutInfo[] newArray(int size) {
   1693                     return new ShortcutInfo[size];
   1694                 }
   1695             };
   1696 
   1697     @Override
   1698     public int describeContents() {
   1699         return 0;
   1700     }
   1701 
   1702     /**
   1703      * Return a string representation, intended for logging.  Some fields will be retracted.
   1704      */
   1705     @Override
   1706     public String toString() {
   1707         return toStringInner(/* secure =*/ true, /* includeInternalData =*/ false);
   1708     }
   1709 
   1710     /** @hide */
   1711     public String toInsecureString() {
   1712         return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true);
   1713     }
   1714 
   1715     private String toStringInner(boolean secure, boolean includeInternalData) {
   1716         final StringBuilder sb = new StringBuilder();
   1717         sb.append("ShortcutInfo {");
   1718 
   1719         sb.append("id=");
   1720         sb.append(secure ? "***" : mId);
   1721 
   1722         sb.append(", flags=0x");
   1723         sb.append(Integer.toHexString(mFlags));
   1724         sb.append(" [");
   1725         if (!isEnabled()) {
   1726             sb.append("X");
   1727         }
   1728         if (isImmutable()) {
   1729             sb.append("Im");
   1730         }
   1731         if (isManifestShortcut()) {
   1732             sb.append("M");
   1733         }
   1734         if (isDynamic()) {
   1735             sb.append("D");
   1736         }
   1737         if (isPinned()) {
   1738             sb.append("P");
   1739         }
   1740         if (hasIconFile()) {
   1741             sb.append("If");
   1742         }
   1743         if (hasIconResource()) {
   1744             sb.append("Ir");
   1745         }
   1746         if (hasKeyFieldsOnly()) {
   1747             sb.append("K");
   1748         }
   1749         if (hasStringResourcesResolved()) {
   1750             sb.append("Sr");
   1751         }
   1752         sb.append("]");
   1753 
   1754         sb.append(", packageName=");
   1755         sb.append(mPackageName);
   1756 
   1757         sb.append(", activity=");
   1758         sb.append(mActivity);
   1759 
   1760         sb.append(", shortLabel=");
   1761         sb.append(secure ? "***" : mTitle);
   1762         sb.append(", resId=");
   1763         sb.append(mTitleResId);
   1764         sb.append("[");
   1765         sb.append(mTitleResName);
   1766         sb.append("]");
   1767 
   1768         sb.append(", longLabel=");
   1769         sb.append(secure ? "***" : mText);
   1770         sb.append(", resId=");
   1771         sb.append(mTextResId);
   1772         sb.append("[");
   1773         sb.append(mTextResName);
   1774         sb.append("]");
   1775 
   1776         sb.append(", disabledMessage=");
   1777         sb.append(secure ? "***" : mDisabledMessage);
   1778         sb.append(", resId=");
   1779         sb.append(mDisabledMessageResId);
   1780         sb.append("[");
   1781         sb.append(mDisabledMessageResName);
   1782         sb.append("]");
   1783 
   1784         sb.append(", categories=");
   1785         sb.append(mCategories);
   1786 
   1787         sb.append(", icon=");
   1788         sb.append(mIcon);
   1789 
   1790         sb.append(", rank=");
   1791         sb.append(mRank);
   1792 
   1793         sb.append(", timestamp=");
   1794         sb.append(mLastChangedTimestamp);
   1795 
   1796         sb.append(", intents=");
   1797         if (mIntents == null) {
   1798             sb.append("null");
   1799         } else {
   1800             if (secure) {
   1801                 sb.append("size:");
   1802                 sb.append(mIntents.length);
   1803             } else {
   1804                 final int size = mIntents.length;
   1805                 sb.append("[");
   1806                 String sep = "";
   1807                 for (int i = 0; i < size; i++) {
   1808                     sb.append(sep);
   1809                     sep = ", ";
   1810                     sb.append(mIntents[i]);
   1811                     sb.append("/");
   1812                     sb.append(mIntentPersistableExtrases[i]);
   1813                 }
   1814                 sb.append("]");
   1815             }
   1816         }
   1817 
   1818         sb.append(", extras=");
   1819         sb.append(mExtras);
   1820 
   1821         if (includeInternalData) {
   1822 
   1823             sb.append(", iconRes=");
   1824             sb.append(mIconResId);
   1825             sb.append("[");
   1826             sb.append(mIconResName);
   1827             sb.append("]");
   1828 
   1829             sb.append(", bitmapPath=");
   1830             sb.append(mBitmapPath);
   1831         }
   1832 
   1833         sb.append("}");
   1834         return sb.toString();
   1835     }
   1836 
   1837     /** @hide */
   1838     public ShortcutInfo(
   1839             @UserIdInt int userId, String id, String packageName, ComponentName activity,
   1840             Icon icon, CharSequence title, int titleResId, String titleResName,
   1841             CharSequence text, int textResId, String textResName,
   1842             CharSequence disabledMessage, int disabledMessageResId, String disabledMessageResName,
   1843             Set<String> categories, Intent[] intentsWithExtras, int rank, PersistableBundle extras,
   1844             long lastChangedTimestamp,
   1845             int flags, int iconResId, String iconResName, String bitmapPath) {
   1846         mUserId = userId;
   1847         mId = id;
   1848         mPackageName = packageName;
   1849         mActivity = activity;
   1850         mIcon = icon;
   1851         mTitle = title;
   1852         mTitleResId = titleResId;
   1853         mTitleResName = titleResName;
   1854         mText = text;
   1855         mTextResId = textResId;
   1856         mTextResName = textResName;
   1857         mDisabledMessage = disabledMessage;
   1858         mDisabledMessageResId = disabledMessageResId;
   1859         mDisabledMessageResName = disabledMessageResName;
   1860         mCategories = cloneCategories(categories);
   1861         mIntents = cloneIntents(intentsWithExtras);
   1862         fixUpIntentExtras();
   1863         mRank = rank;
   1864         mExtras = extras;
   1865         mLastChangedTimestamp = lastChangedTimestamp;
   1866         mFlags = flags;
   1867         mIconResId = iconResId;
   1868         mIconResName = iconResName;
   1869         mBitmapPath = bitmapPath;
   1870     }
   1871 }
   1872