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