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.content.ComponentName;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.graphics.drawable.Icon;
     26 import android.os.Bundle;
     27 import android.os.Parcel;
     28 import android.os.Parcelable;
     29 import android.os.PersistableBundle;
     30 import android.os.UserHandle;
     31 import android.util.ArraySet;
     32 
     33 import com.android.internal.util.Preconditions;
     34 
     35 import java.lang.annotation.Retention;
     36 import java.lang.annotation.RetentionPolicy;
     37 import java.util.Set;
     38 
     39 // TODO Enhance javadoc
     40 /**
     41  *
     42  * Represents a shortcut from an application.
     43  *
     44  * <p>Notes about icons:
     45  * <ul>
     46  *     <li>If an {@link Icon} is a resource, the system keeps the package name and the resource ID.
     47  *     Otherwise, the bitmap is fetched when it's registered to ShortcutManager,
     48  *     then shrunk if necessary, and persisted.
     49  *     <li>The system disallows byte[] icons, because they can easily go over the binder size limit.
     50  * </ul>
     51  *
     52  * @see {@link ShortcutManager}.
     53  *
     54  * @hide
     55  */
     56 public final class ShortcutInfo implements Parcelable {
     57     /* @hide */
     58     public static final int FLAG_DYNAMIC = 1 << 0;
     59 
     60     /* @hide */
     61     public static final int FLAG_PINNED = 1 << 1;
     62 
     63     /* @hide */
     64     public static final int FLAG_HAS_ICON_RES = 1 << 2;
     65 
     66     /* @hide */
     67     public static final int FLAG_HAS_ICON_FILE = 1 << 3;
     68 
     69     /* @hide */
     70     public static final int FLAG_KEY_FIELDS_ONLY = 1 << 4;
     71 
     72     /** @hide */
     73     @IntDef(flag = true,
     74             value = {
     75             FLAG_DYNAMIC,
     76             FLAG_PINNED,
     77             FLAG_HAS_ICON_RES,
     78             FLAG_HAS_ICON_FILE,
     79             FLAG_KEY_FIELDS_ONLY,
     80     })
     81     @Retention(RetentionPolicy.SOURCE)
     82     public @interface ShortcutFlags {}
     83 
     84     // Cloning options.
     85 
     86     /* @hide */
     87     private static final int CLONE_REMOVE_ICON = 1 << 0;
     88 
     89     /* @hide */
     90     private static final int CLONE_REMOVE_INTENT = 1 << 1;
     91 
     92     /* @hide */
     93     public static final int CLONE_REMOVE_NON_KEY_INFO = 1 << 2;
     94 
     95     /* @hide */
     96     public static final int CLONE_REMOVE_FOR_CREATOR = CLONE_REMOVE_ICON;
     97 
     98     /* @hide */
     99     public static final int CLONE_REMOVE_FOR_LAUNCHER = CLONE_REMOVE_ICON | CLONE_REMOVE_INTENT;
    100 
    101     /** @hide */
    102     @IntDef(flag = true,
    103             value = {
    104                     CLONE_REMOVE_ICON,
    105                     CLONE_REMOVE_INTENT,
    106                     CLONE_REMOVE_NON_KEY_INFO,
    107                     CLONE_REMOVE_FOR_CREATOR,
    108                     CLONE_REMOVE_FOR_LAUNCHER
    109             })
    110     @Retention(RetentionPolicy.SOURCE)
    111     public @interface CloneFlags {}
    112 
    113     /**
    114      * Shortcut category for
    115      */
    116     public static final String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
    117 
    118     private final String mId;
    119 
    120     @NonNull
    121     private final String mPackageName;
    122 
    123     @Nullable
    124     private ComponentName mActivityComponent;
    125 
    126     @Nullable
    127     private Icon mIcon;
    128 
    129     @NonNull
    130     private String mTitle;
    131 
    132     @Nullable
    133     private String mText;
    134 
    135     @NonNull
    136     private ArraySet<String> mCategories;
    137 
    138     /**
    139      * Intent *with extras removed*.
    140      */
    141     @NonNull
    142     private Intent mIntent;
    143 
    144     /**
    145      * Extras for the intent.
    146      */
    147     @NonNull
    148     private PersistableBundle mIntentPersistableExtras;
    149 
    150     private int mWeight;
    151 
    152     @Nullable
    153     private PersistableBundle mExtras;
    154 
    155     private long mLastChangedTimestamp;
    156 
    157     // Internal use only.
    158     @ShortcutFlags
    159     private int mFlags;
    160 
    161     // Internal use only.
    162     private int mIconResourceId;
    163 
    164     // Internal use only.
    165     @Nullable
    166     private String mBitmapPath;
    167 
    168     private final int mUserId;
    169 
    170     private ShortcutInfo(Builder b) {
    171         mUserId = b.mContext.getUserId();
    172 
    173         mId = Preconditions.checkStringNotEmpty(b.mId, "Shortcut ID must be provided");
    174 
    175         // Note we can't do other null checks here because SM.updateShortcuts() takes partial
    176         // information.
    177         mPackageName = b.mContext.getPackageName();
    178         mActivityComponent = b.mActivityComponent;
    179         mIcon = b.mIcon;
    180         mTitle = b.mTitle;
    181         mText = b.mText;
    182         mCategories = clone(b.mCategories);
    183         mIntent = b.mIntent;
    184         if (mIntent != null) {
    185             final Bundle intentExtras = mIntent.getExtras();
    186             if (intentExtras != null) {
    187                 mIntent.replaceExtras((Bundle) null);
    188                 mIntentPersistableExtras = new PersistableBundle(intentExtras);
    189             }
    190         }
    191         mWeight = b.mWeight;
    192         mExtras = b.mExtras;
    193         updateTimestamp();
    194     }
    195 
    196     private <T> ArraySet<T> clone(Set<T> source) {
    197         return (source == null) ? null : new ArraySet<>(source);
    198     }
    199 
    200     /**
    201      * Throws if any of the mandatory fields is not set.
    202      *
    203      * @hide
    204      */
    205     public void enforceMandatoryFields() {
    206         Preconditions.checkStringNotEmpty(mId, "Shortcut ID must be provided");
    207         Preconditions.checkStringNotEmpty(mTitle, "Shortcut title must be provided");
    208         Preconditions.checkNotNull(mIntent, "Shortcut Intent must be provided");
    209     }
    210 
    211     /**
    212      * Copy constructor.
    213      */
    214     private ShortcutInfo(ShortcutInfo source, @CloneFlags int cloneFlags) {
    215         mUserId = source.mUserId;
    216         mId = source.mId;
    217         mPackageName = source.mPackageName;
    218         mFlags = source.mFlags;
    219         mLastChangedTimestamp = source.mLastChangedTimestamp;
    220 
    221         // Just always keep it since it's cheep.
    222         mIconResourceId = source.mIconResourceId;
    223 
    224         if ((cloneFlags & CLONE_REMOVE_NON_KEY_INFO) == 0) {
    225             mActivityComponent = source.mActivityComponent;
    226 
    227             if ((cloneFlags & CLONE_REMOVE_ICON) == 0) {
    228                 mIcon = source.mIcon;
    229                 mBitmapPath = source.mBitmapPath;
    230             }
    231 
    232             mTitle = source.mTitle;
    233             mText = source.mText;
    234             mCategories = clone(source.mCategories);
    235             if ((cloneFlags & CLONE_REMOVE_INTENT) == 0) {
    236                 mIntent = source.mIntent;
    237                 mIntentPersistableExtras = source.mIntentPersistableExtras;
    238             }
    239             mWeight = source.mWeight;
    240             mExtras = source.mExtras;
    241         } else {
    242             // Set this bit.
    243             mFlags |= FLAG_KEY_FIELDS_ONLY;
    244         }
    245     }
    246 
    247     /**
    248      * Copy a {@link ShortcutInfo}, optionally removing fields.
    249      * @hide
    250      */
    251     public ShortcutInfo clone(@CloneFlags int cloneFlags) {
    252         return new ShortcutInfo(this, cloneFlags);
    253     }
    254 
    255     /**
    256      * Copy non-null/zero fields from another {@link ShortcutInfo}.  Only "public" information
    257      * will be overwritten.  The timestamp will be updated.
    258      *
    259      * - Flags will not change
    260      * - mBitmapPath will not change
    261      * - Current time will be set to timestamp
    262      *
    263      * @hide
    264      */
    265     public void copyNonNullFieldsFrom(ShortcutInfo source) {
    266         Preconditions.checkState(mUserId == source.mUserId, "Owner User ID must match");
    267         Preconditions.checkState(mId.equals(source.mId), "ID must match");
    268         Preconditions.checkState(mPackageName.equals(source.mPackageName),
    269                 "Package name must match");
    270 
    271         if (source.mActivityComponent != null) {
    272             mActivityComponent = source.mActivityComponent;
    273         }
    274 
    275         if (source.mIcon != null) {
    276             mIcon = source.mIcon;
    277         }
    278         if (source.mTitle != null) {
    279             mTitle = source.mTitle;
    280         }
    281         if (source.mText != null) {
    282             mText = source.mText;
    283         }
    284         if (source.mCategories != null) {
    285             mCategories = clone(source.mCategories);
    286         }
    287         if (source.mIntent != null) {
    288             mIntent = source.mIntent;
    289             mIntentPersistableExtras = source.mIntentPersistableExtras;
    290         }
    291         if (source.mWeight != 0) {
    292             mWeight = source.mWeight;
    293         }
    294         if (source.mExtras != null) {
    295             mExtras = source.mExtras;
    296         }
    297 
    298         updateTimestamp();
    299     }
    300 
    301     /**
    302      * @hide
    303      */
    304     public static Icon validateIcon(Icon icon) {
    305         switch (icon.getType()) {
    306             case Icon.TYPE_RESOURCE:
    307             case Icon.TYPE_BITMAP:
    308                 break; // OK
    309             default:
    310                 throw getInvalidIconException();
    311         }
    312         if (icon.hasTint()) {
    313             // TODO support it
    314             throw new IllegalArgumentException("Icons with tints are not supported");
    315         }
    316 
    317         return icon;
    318     }
    319 
    320     /** @hide */
    321     public static IllegalArgumentException getInvalidIconException() {
    322         return new IllegalArgumentException("Unsupported icon type:"
    323                 +" only bitmap, resource and content URI are supported");
    324     }
    325 
    326     /**
    327      * Builder class for {@link ShortcutInfo} objects.
    328      */
    329     public static class Builder {
    330         private final Context mContext;
    331 
    332         private String mId;
    333 
    334         private ComponentName mActivityComponent;
    335 
    336         private Icon mIcon;
    337 
    338         private String mTitle;
    339 
    340         private String mText;
    341 
    342         private Set<String> mCategories;
    343 
    344         private Intent mIntent;
    345 
    346         private int mWeight;
    347 
    348         private PersistableBundle mExtras;
    349 
    350         /** Constructor. */
    351         public Builder(Context context) {
    352             mContext = context;
    353         }
    354 
    355         /**
    356          * Sets the ID of the shortcut.  This is a mandatory field.
    357          */
    358         @NonNull
    359         public Builder setId(@NonNull String id) {
    360             mId = Preconditions.checkStringNotEmpty(id, "id");
    361             return this;
    362         }
    363 
    364         /**
    365          * Optionally sets the target activity.  If it's not set, and if the caller application
    366          * has multiple launcher icons, this shortcut will be shown on all those icons.
    367          * If it's set, this shortcut will be only shown on this activity.
    368          *
    369          * <p>The package name of the target activity must match the package name of the shortcut
    370          * publisher.
    371          *
    372          * <p>This has nothing to do with the activity that this shortcut will launch.  This is
    373          * a hint to the launcher app about which launcher icon to associate this shortcut with.
    374          */
    375         @NonNull
    376         public Builder setActivityComponent(@NonNull ComponentName activityComponent) {
    377             mActivityComponent = Preconditions.checkNotNull(activityComponent, "activityComponent");
    378             return this;
    379         }
    380 
    381         /**
    382          * Optionally sets an icon.
    383          *
    384          * <ul>
    385          *     <li>Tints set by {@link Icon#setTint} or {@link Icon#setTintList} are not supported.
    386          *     <li>Bitmaps and resources are supported, but "content:" URIs are not supported.
    387          * </ul>
    388          *
    389          * <p>For performance reasons, icons will <b>NOT</b> be available on instances
    390          * returned by {@link ShortcutManager} or {@link LauncherApps}.  Launcher applications
    391          * can use {@link ShortcutInfo#getIconResourceId()} if {@link #hasIconResource()} is true.
    392          * Otherwise, if {@link #hasIconFile()} is true, use
    393          * {@link LauncherApps#getShortcutIconFd} to load the image.
    394          */
    395         @NonNull
    396         public Builder setIcon(Icon icon) {
    397             mIcon = validateIcon(icon);
    398             return this;
    399         }
    400 
    401         /**
    402          * Sets the title of a shortcut.  This is a mandatory field.
    403          *
    404          * <p>This field is intended for a concise description of a shortcut displayed under
    405          * an icon.  The recommend max length is 10 characters.
    406          */
    407         @NonNull
    408         public Builder setTitle(@NonNull String title) {
    409             mTitle = Preconditions.checkStringNotEmpty(title, "title");
    410             return this;
    411         }
    412 
    413         /**
    414          * Sets the text of a shortcut.  This is an optional field.
    415          *
    416          * <p>This field is intended to be more descriptive than the shortcut title.
    417          * The recommend max length is 25 characters.
    418          */
    419         @NonNull
    420         public Builder setText(@NonNull String text) {
    421             mText = Preconditions.checkStringNotEmpty(text, "text");
    422             return this;
    423         }
    424 
    425         /**
    426          * Sets categories for a shortcut.  Launcher applications may use this information to
    427          * categorise shortcuts.
    428          *
    429          * @see #SHORTCUT_CATEGORY_CONVERSATION
    430          */
    431         @NonNull
    432         public Builder setCategories(Set<String> categories) {
    433             mCategories = categories;
    434             return this;
    435         }
    436 
    437         /**
    438          * Sets the intent of a shortcut.  This is a mandatory field.  The extras must only contain
    439          * persistable information.  (See {@link PersistableBundle}).
    440          */
    441         @NonNull
    442         public Builder setIntent(@NonNull Intent intent) {
    443             mIntent = Preconditions.checkNotNull(intent, "intent");
    444             return this;
    445         }
    446 
    447         /**
    448          * Optionally sets the weight of a shortcut, which will be used by the launcher for sorting.
    449          * The larger the weight, the more "important" a shortcut is.
    450          */
    451         @NonNull
    452         public Builder setWeight(int weight) {
    453             mWeight = weight;
    454             return this;
    455         }
    456 
    457         /**
    458          * Optional values that applications can set.  Applications can store any meta-data of
    459          * shortcuts in this, and retrieve later from {@link ShortcutInfo#getExtras()}.
    460          */
    461         @NonNull
    462         public Builder setExtras(@NonNull PersistableBundle extras) {
    463             mExtras = extras;
    464             return this;
    465         }
    466 
    467         /**
    468          * Creates a {@link ShortcutInfo} instance.
    469          */
    470         @NonNull
    471         public ShortcutInfo build() {
    472             return new ShortcutInfo(this);
    473         }
    474     }
    475 
    476     /**
    477      * Return the ID of the shortcut.
    478      */
    479     @NonNull
    480     public String getId() {
    481         return mId;
    482     }
    483 
    484     /**
    485      * Return the package name of the creator application.
    486      */
    487     @NonNull
    488     public String getPackageName() {
    489         return mPackageName;
    490     }
    491 
    492     /**
    493      * Return the target activity, which may be null, in which case the shortcut is not associated
    494      * with a specific activity.
    495      *
    496      * <p>This has nothing to do with the activity that this shortcut will launch.  This is
    497      * a hint to the launcher app that on which launcher icon this shortcut should be shown.
    498      *
    499      * @see Builder#setActivityComponent
    500      */
    501     @Nullable
    502     public ComponentName getActivityComponent() {
    503         return mActivityComponent;
    504     }
    505 
    506     /**
    507      * Icon.
    508      *
    509      * For performance reasons, this will <b>NOT</b> be available when an instance is returned
    510      * by {@link ShortcutManager} or {@link LauncherApps}.  A launcher application needs to use
    511      * other APIs in LauncherApps to fetch the bitmap.
    512      *
    513      * @hide
    514      */
    515     @Nullable
    516     public Icon getIcon() {
    517         return mIcon;
    518     }
    519 
    520     /**
    521      * Return the shortcut title.
    522      *
    523      * <p>All shortcuts must have a non-empty title, but this method will return null when
    524      * {@link #hasKeyFieldsOnly()} is true.
    525      */
    526     @Nullable
    527     public String getTitle() {
    528         return mTitle;
    529     }
    530 
    531     /**
    532      * Return the shortcut text.
    533      */
    534     @Nullable
    535     public String getText() {
    536         return mText;
    537     }
    538 
    539     /**
    540      * Return the categories.
    541      */
    542     @Nullable
    543     public Set<String> getCategories() {
    544         return mCategories;
    545     }
    546 
    547     /**
    548      * Return the intent.
    549      *
    550      * <p>All shortcuts must have an intent, but this method will return null when
    551      * {@link #hasKeyFieldsOnly()} is true.
    552      *
    553      * <p>Launcher apps <b>cannot</b> see the intent.  If a {@link ShortcutInfo} is obtained via
    554      * {@link LauncherApps}, then this method will always return null.  Launcher apps can only
    555      * start a shortcut intent with {@link LauncherApps#startShortcut}.
    556      */
    557     @Nullable
    558     public Intent getIntent() {
    559         if (mIntent == null) {
    560             return null;
    561         }
    562         final Intent intent = new Intent(mIntent);
    563         intent.replaceExtras(
    564                 mIntentPersistableExtras != null ? new Bundle(mIntentPersistableExtras) : null);
    565         return intent;
    566     }
    567 
    568     /**
    569      * Return "raw" intent, which is the original intent without the extras.
    570      * @hide
    571      */
    572     @Nullable
    573     public Intent getIntentNoExtras() {
    574         return mIntent;
    575     }
    576 
    577     /**
    578      * The extras in the intent.  We convert extras into {@link PersistableBundle} so we can
    579      * persist them.
    580      * @hide
    581      */
    582     @Nullable
    583     public PersistableBundle getIntentPersistableExtras() {
    584         return mIntentPersistableExtras;
    585     }
    586 
    587     /**
    588      * Return the weight of a shortcut, which will be used by Launcher for sorting.
    589      * The larger the weight, the more "important" a shortcut is.
    590      */
    591     public int getWeight() {
    592         return mWeight;
    593     }
    594 
    595     /**
    596      * Optional values that application can set.
    597      */
    598     @Nullable
    599     public PersistableBundle getExtras() {
    600         return mExtras;
    601     }
    602 
    603     /** @hide */
    604     public int getUserId() {
    605         return mUserId;
    606     }
    607 
    608     /**
    609      * {@link UserHandle} on which the publisher created shortcuts.
    610      */
    611     public UserHandle getUserHandle() {
    612         return UserHandle.of(mUserId);
    613     }
    614 
    615     /**
    616      * Last time when any of the fields was updated.
    617      */
    618     public long getLastChangedTimestamp() {
    619         return mLastChangedTimestamp;
    620     }
    621 
    622     /** @hide */
    623     @ShortcutFlags
    624     public int getFlags() {
    625         return mFlags;
    626     }
    627 
    628     /** @hide*/
    629     public void replaceFlags(@ShortcutFlags int flags) {
    630         mFlags = flags;
    631     }
    632 
    633     /** @hide*/
    634     public void addFlags(@ShortcutFlags int flags) {
    635         mFlags |= flags;
    636     }
    637 
    638     /** @hide*/
    639     public void clearFlags(@ShortcutFlags int flags) {
    640         mFlags &= ~flags;
    641     }
    642 
    643     /** @hide*/
    644     public boolean hasFlags(@ShortcutFlags int flags) {
    645         return (mFlags & flags) == flags;
    646     }
    647 
    648     /** Return whether a shortcut is dynamic. */
    649     public boolean isDynamic() {
    650         return hasFlags(FLAG_DYNAMIC);
    651     }
    652 
    653     /** Return whether a shortcut is pinned. */
    654     public boolean isPinned() {
    655         return hasFlags(FLAG_PINNED);
    656     }
    657 
    658     /**
    659      * Return whether a shortcut's icon is a resource in the owning package.
    660      *
    661      * @see LauncherApps#getShortcutIconResId(ShortcutInfo)
    662      */
    663     public boolean hasIconResource() {
    664         return hasFlags(FLAG_HAS_ICON_RES);
    665     }
    666 
    667     /**
    668      * Return whether a shortcut's icon is stored as a file.
    669      *
    670      * @see LauncherApps#getShortcutIconFd(ShortcutInfo)
    671      */
    672     public boolean hasIconFile() {
    673         return hasFlags(FLAG_HAS_ICON_FILE);
    674     }
    675 
    676     /**
    677      * Return whether a shortcut only contains "key" information only or not.  If true, only the
    678      * following fields are available.
    679      * <ul>
    680      *     <li>{@link #getId()}
    681      *     <li>{@link #getPackageName()}
    682      *     <li>{@link #getLastChangedTimestamp()}
    683      *     <li>{@link #isDynamic()}
    684      *     <li>{@link #isPinned()}
    685      *     <li>{@link #hasIconResource()}
    686      *     <li>{@link #hasIconFile()}
    687      * </ul>
    688      */
    689     public boolean hasKeyFieldsOnly() {
    690         return hasFlags(FLAG_KEY_FIELDS_ONLY);
    691     }
    692 
    693     /** @hide */
    694     public void updateTimestamp() {
    695         mLastChangedTimestamp = System.currentTimeMillis();
    696     }
    697 
    698     /** @hide */
    699     // VisibleForTesting
    700     public void setTimestamp(long value) {
    701         mLastChangedTimestamp = value;
    702     }
    703 
    704     /** @hide */
    705     public void clearIcon() {
    706         mIcon = null;
    707     }
    708 
    709     /** @hide */
    710     public void setIconResourceId(int iconResourceId) {
    711         mIconResourceId = iconResourceId;
    712     }
    713 
    714     /**
    715      * Get the resource ID for the icon, valid only when {@link #hasIconResource()} } is true.
    716      */
    717     public int getIconResourceId() {
    718         return mIconResourceId;
    719     }
    720 
    721     /** @hide */
    722     public String getBitmapPath() {
    723         return mBitmapPath;
    724     }
    725 
    726     /** @hide */
    727     public void setBitmapPath(String bitmapPath) {
    728         mBitmapPath = bitmapPath;
    729     }
    730 
    731     private ShortcutInfo(Parcel source) {
    732         final ClassLoader cl = getClass().getClassLoader();
    733 
    734         mUserId = source.readInt();
    735         mId = source.readString();
    736         mPackageName = source.readString();
    737         mActivityComponent = source.readParcelable(cl);
    738         mIcon = source.readParcelable(cl);
    739         mTitle = source.readString();
    740         mText = source.readString();
    741         mIntent = source.readParcelable(cl);
    742         mIntentPersistableExtras = source.readParcelable(cl);
    743         mWeight = source.readInt();
    744         mExtras = source.readParcelable(cl);
    745         mLastChangedTimestamp = source.readLong();
    746         mFlags = source.readInt();
    747         mIconResourceId = source.readInt();
    748         mBitmapPath = source.readString();
    749 
    750         int N = source.readInt();
    751         if (N == 0) {
    752             mCategories = null;
    753         } else {
    754             mCategories = new ArraySet<>(N);
    755             for (int i = 0; i < N; i++) {
    756                 mCategories.add(source.readString().intern());
    757             }
    758         }
    759     }
    760 
    761     @Override
    762     public void writeToParcel(Parcel dest, int flags) {
    763         dest.writeInt(mUserId);
    764         dest.writeString(mId);
    765         dest.writeString(mPackageName);
    766         dest.writeParcelable(mActivityComponent, flags);
    767         dest.writeParcelable(mIcon, flags);
    768         dest.writeString(mTitle);
    769         dest.writeString(mText);
    770 
    771         dest.writeParcelable(mIntent, flags);
    772         dest.writeParcelable(mIntentPersistableExtras, flags);
    773         dest.writeInt(mWeight);
    774         dest.writeParcelable(mExtras, flags);
    775         dest.writeLong(mLastChangedTimestamp);
    776         dest.writeInt(mFlags);
    777         dest.writeInt(mIconResourceId);
    778         dest.writeString(mBitmapPath);
    779 
    780         if (mCategories != null) {
    781             final int N = mCategories.size();
    782             dest.writeInt(N);
    783             for (int i = 0; i < N; i++) {
    784                 dest.writeString(mCategories.valueAt(i));
    785             }
    786         } else {
    787             dest.writeInt(0);
    788         }
    789     }
    790 
    791     public static final Creator<ShortcutInfo> CREATOR =
    792             new Creator<ShortcutInfo>() {
    793                 public ShortcutInfo createFromParcel(Parcel source) {
    794                     return new ShortcutInfo(source);
    795                 }
    796                 public ShortcutInfo[] newArray(int size) {
    797                     return new ShortcutInfo[size];
    798                 }
    799             };
    800 
    801     @Override
    802     public int describeContents() {
    803         return 0;
    804     }
    805 
    806     /**
    807      * Return a string representation, intended for logging.  Some fields will be retracted.
    808      */
    809     @Override
    810     public String toString() {
    811         return toStringInner(/* secure =*/ true, /* includeInternalData =*/ false);
    812     }
    813 
    814     /** @hide */
    815     public String toInsecureString() {
    816         return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true);
    817     }
    818 
    819     private String toStringInner(boolean secure, boolean includeInternalData) {
    820         final StringBuilder sb = new StringBuilder();
    821         sb.append("ShortcutInfo {");
    822 
    823         sb.append("id=");
    824         sb.append(secure ? "***" : mId);
    825 
    826         sb.append(", packageName=");
    827         sb.append(mPackageName);
    828 
    829         if (isDynamic()) {
    830             sb.append(", dynamic");
    831         }
    832         if (isPinned()) {
    833             sb.append(", pinned");
    834         }
    835 
    836         sb.append(", activity=");
    837         sb.append(mActivityComponent);
    838 
    839         sb.append(", title=");
    840         sb.append(secure ? "***" : mTitle);
    841 
    842         sb.append(", text=");
    843         sb.append(secure ? "***" : mText);
    844 
    845         sb.append(", categories=");
    846         sb.append(mCategories);
    847 
    848         sb.append(", icon=");
    849         sb.append(mIcon);
    850 
    851         sb.append(", weight=");
    852         sb.append(mWeight);
    853 
    854         sb.append(", timestamp=");
    855         sb.append(mLastChangedTimestamp);
    856 
    857         sb.append(", intent=");
    858         sb.append(mIntent);
    859 
    860         sb.append(", intentExtras=");
    861         sb.append(secure ? "***" : mIntentPersistableExtras);
    862 
    863         sb.append(", extras=");
    864         sb.append(mExtras);
    865 
    866         sb.append(", flags=");
    867         sb.append(mFlags);
    868 
    869         if (includeInternalData) {
    870 
    871             sb.append(", iconRes=");
    872             sb.append(mIconResourceId);
    873 
    874             sb.append(", bitmapPath=");
    875             sb.append(mBitmapPath);
    876         }
    877 
    878         sb.append("}");
    879         return sb.toString();
    880     }
    881 
    882     /** @hide */
    883     public ShortcutInfo(
    884             @UserIdInt int userId, String id, String packageName, ComponentName activityComponent,
    885             Icon icon, String title, String text, Set<String> categories, Intent intent,
    886             PersistableBundle intentPersistableExtras,
    887             int weight, PersistableBundle extras, long lastChangedTimestamp,
    888             int flags, int iconResId, String bitmapPath) {
    889         mUserId = userId;
    890         mId = id;
    891         mPackageName = packageName;
    892         mActivityComponent = activityComponent;
    893         mIcon = icon;
    894         mTitle = title;
    895         mText = text;
    896         mCategories = clone(categories);
    897         mIntent = intent;
    898         mIntentPersistableExtras = intentPersistableExtras;
    899         mWeight = weight;
    900         mExtras = extras;
    901         mLastChangedTimestamp = lastChangedTimestamp;
    902         mFlags = flags;
    903         mIconResourceId = iconResId;
    904         mBitmapPath = bitmapPath;
    905     }
    906 }
    907