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