Home | History | Annotate | Download | only in pm
      1 /*
      2  * Copyright (C) 2007 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 
     17 package android.content.pm;
     18 
     19 import static java.lang.annotation.RetentionPolicy.SOURCE;
     20 
     21 import android.annotation.FloatRange;
     22 import android.annotation.IntDef;
     23 import android.annotation.NonNull;
     24 import android.annotation.SystemApi;
     25 import android.content.res.XmlResourceParser;
     26 import android.graphics.drawable.Drawable;
     27 import android.os.Bundle;
     28 import android.os.Parcel;
     29 import android.os.UserHandle;
     30 import android.text.Html;
     31 import android.text.TextPaint;
     32 import android.text.TextUtils;
     33 import android.util.Printer;
     34 import android.util.proto.ProtoOutputStream;
     35 
     36 import com.android.internal.util.Preconditions;
     37 
     38 import java.lang.annotation.Retention;
     39 import java.text.Collator;
     40 import java.util.BitSet;
     41 import java.util.Comparator;
     42 
     43 /**
     44  * Base class containing information common to all package items held by
     45  * the package manager.  This provides a very common basic set of attributes:
     46  * a label, icon, and meta-data.  This class is not intended
     47  * to be used by itself; it is simply here to share common definitions
     48  * between all items returned by the package manager.  As such, it does not
     49  * itself implement Parcelable, but does provide convenience methods to assist
     50  * in the implementation of Parcelable in subclasses.
     51  */
     52 public class PackageItemInfo {
     53     private static final int LINE_FEED_CODE_POINT = 10;
     54     private static final int NBSP_CODE_POINT = 160;
     55 
     56     /**
     57      * Flags for {@link #loadSafeLabel(PackageManager, float, int)}
     58      *
     59      * @hide
     60      */
     61     @Retention(SOURCE)
     62     @IntDef(flag = true, prefix = "SAFE_LABEL_FLAG_",
     63             value = {SAFE_LABEL_FLAG_TRIM, SAFE_LABEL_FLAG_SINGLE_LINE,
     64                     SAFE_LABEL_FLAG_FIRST_LINE})
     65     public @interface SafeLabelFlags {}
     66 
     67     /**
     68      * Remove {@link Character#isWhitespace(int) whitespace} and non-breaking spaces from the edges
     69      * of the label.
     70      *
     71      * @see #loadSafeLabel(PackageManager, float, int)
     72      * @hide
     73      */
     74     public static final int SAFE_LABEL_FLAG_TRIM = 0x1;
     75 
     76     /**
     77      * Force entire string into single line of text (no newlines). Cannot be set at the same time as
     78      * {@link #SAFE_LABEL_FLAG_FIRST_LINE}.
     79      *
     80      * @see #loadSafeLabel(PackageManager, float, int)
     81      * @hide
     82      */
     83     public static final int SAFE_LABEL_FLAG_SINGLE_LINE = 0x2;
     84 
     85     /**
     86      * Return only first line of text (truncate at first newline). Cannot be set at the same time as
     87      * {@link #SAFE_LABEL_FLAG_SINGLE_LINE}.
     88      *
     89      * @see #loadSafeLabel(PackageManager, float, int)
     90      * @hide
     91      */
     92     public static final int SAFE_LABEL_FLAG_FIRST_LINE = 0x4;
     93 
     94     private static final float MAX_LABEL_SIZE_PX = 500f;
     95     /** The maximum length of a safe label, in characters */
     96     private static final int MAX_SAFE_LABEL_LENGTH = 50000;
     97 
     98     private static volatile boolean sForceSafeLabels = false;
     99 
    100     /** {@hide} */
    101     public static void setForceSafeLabels(boolean forceSafeLabels) {
    102         sForceSafeLabels = forceSafeLabels;
    103     }
    104 
    105     /**
    106      * Public name of this item. From the "android:name" attribute.
    107      */
    108     public String name;
    109 
    110     /**
    111      * Name of the package that this item is in.
    112      */
    113     public String packageName;
    114 
    115     /**
    116      * A string resource identifier (in the package's resources) of this
    117      * component's label.  From the "label" attribute or, if not set, 0.
    118      */
    119     public int labelRes;
    120 
    121     /**
    122      * The string provided in the AndroidManifest file, if any.  You
    123      * probably don't want to use this.  You probably want
    124      * {@link PackageManager#getApplicationLabel}
    125      */
    126     public CharSequence nonLocalizedLabel;
    127 
    128     /**
    129      * A drawable resource identifier (in the package's resources) of this
    130      * component's icon.  From the "icon" attribute or, if not set, 0.
    131      */
    132     public int icon;
    133 
    134     /**
    135      * A drawable resource identifier (in the package's resources) of this
    136      * component's banner.  From the "banner" attribute or, if not set, 0.
    137      */
    138     public int banner;
    139 
    140     /**
    141      * A drawable resource identifier (in the package's resources) of this
    142      * component's logo. Logos may be larger/wider than icons and are
    143      * displayed by certain UI elements in place of a name or name/icon
    144      * combination. From the "logo" attribute or, if not set, 0.
    145      */
    146     public int logo;
    147 
    148     /**
    149      * Additional meta-data associated with this component.  This field
    150      * will only be filled in if you set the
    151      * {@link PackageManager#GET_META_DATA} flag when requesting the info.
    152      */
    153     public Bundle metaData;
    154 
    155     /**
    156      * If different of UserHandle.USER_NULL, The icon of this item will be the one of that user.
    157      * @hide
    158      */
    159     public int showUserIcon;
    160 
    161     public PackageItemInfo() {
    162         showUserIcon = UserHandle.USER_NULL;
    163     }
    164 
    165     public PackageItemInfo(PackageItemInfo orig) {
    166         name = orig.name;
    167         if (name != null) name = name.trim();
    168         packageName = orig.packageName;
    169         labelRes = orig.labelRes;
    170         nonLocalizedLabel = orig.nonLocalizedLabel;
    171         if (nonLocalizedLabel != null) nonLocalizedLabel = nonLocalizedLabel.toString().trim();
    172         icon = orig.icon;
    173         banner = orig.banner;
    174         logo = orig.logo;
    175         metaData = orig.metaData;
    176         showUserIcon = orig.showUserIcon;
    177     }
    178 
    179     /**
    180      * Retrieve the current textual label associated with this item.  This
    181      * will call back on the given PackageManager to load the label from
    182      * the application.
    183      *
    184      * @param pm A PackageManager from which the label can be loaded; usually
    185      * the PackageManager from which you originally retrieved this item.
    186      *
    187      * @return Returns a CharSequence containing the item's label.  If the
    188      * item does not have a label, its name is returned.
    189      */
    190     public @NonNull CharSequence loadLabel(@NonNull PackageManager pm) {
    191         if (sForceSafeLabels) {
    192             return loadSafeLabel(pm);
    193         } else {
    194             return loadUnsafeLabel(pm);
    195         }
    196     }
    197 
    198     /** {@hide} */
    199     public CharSequence loadUnsafeLabel(PackageManager pm) {
    200         if (nonLocalizedLabel != null) {
    201             return nonLocalizedLabel;
    202         }
    203         if (labelRes != 0) {
    204             CharSequence label = pm.getText(packageName, labelRes, getApplicationInfo());
    205             if (label != null) {
    206                 return label.toString().trim();
    207             }
    208         }
    209         if (name != null) {
    210             return name;
    211         }
    212         return packageName;
    213     }
    214 
    215     /**
    216      * Deprecated use loadSafeLabel(PackageManager, float, int) instead
    217      *
    218      * @hide
    219      */
    220     @SystemApi
    221     public @NonNull CharSequence loadSafeLabel(@NonNull PackageManager pm) {
    222         // loadLabel() always returns non-null
    223         String label = loadUnsafeLabel(pm).toString();
    224         // strip HTML tags to avoid <br> and other tags overwriting original message
    225         String labelStr = Html.fromHtml(label).toString();
    226 
    227         // If the label contains new line characters it may push the UI
    228         // down to hide a part of it. Labels shouldn't have new line
    229         // characters, so just truncate at the first time one is seen.
    230         final int labelLength = Math.min(labelStr.length(), MAX_SAFE_LABEL_LENGTH);
    231         final StringBuffer sb = new StringBuffer(labelLength);
    232         int offset = 0;
    233         while (offset < labelLength) {
    234             final int codePoint = labelStr.codePointAt(offset);
    235             final int type = Character.getType(codePoint);
    236             if (type == Character.LINE_SEPARATOR
    237                     || type == Character.CONTROL
    238                     || type == Character.PARAGRAPH_SEPARATOR) {
    239                 labelStr = labelStr.substring(0, offset);
    240                 break;
    241             }
    242             // replace all non-break space to " " in order to be trimmed
    243             final int charCount = Character.charCount(codePoint);
    244             if (type == Character.SPACE_SEPARATOR) {
    245                 sb.append(' ');
    246             } else {
    247                 sb.append(labelStr.charAt(offset));
    248                 if (charCount == 2) {
    249                     sb.append(labelStr.charAt(offset + 1));
    250                 }
    251             }
    252             offset += charCount;
    253         }
    254 
    255         labelStr = sb.toString().trim();
    256         if (labelStr.isEmpty()) {
    257             return packageName;
    258         }
    259         TextPaint paint = new TextPaint();
    260         paint.setTextSize(42);
    261 
    262         return TextUtils.ellipsize(labelStr, paint, MAX_LABEL_SIZE_PX,
    263                 TextUtils.TruncateAt.END);
    264     }
    265 
    266     private static boolean isNewline(int codePoint) {
    267         int type = Character.getType(codePoint);
    268         return type == Character.PARAGRAPH_SEPARATOR || type == Character.LINE_SEPARATOR
    269                 || codePoint == LINE_FEED_CODE_POINT;
    270     }
    271 
    272     private static boolean isWhiteSpace(int codePoint) {
    273         return Character.isWhitespace(codePoint) || codePoint == NBSP_CODE_POINT;
    274     }
    275 
    276     /**
    277      * A special string manipulation class. Just records removals and executes the when onString()
    278      * is called.
    279      */
    280     private static class StringWithRemovedChars {
    281         /** The original string */
    282         private final String mOriginal;
    283 
    284         /**
    285          * One bit per char in string. If bit is set, character needs to be removed. If whole
    286          * bit field is not initialized nothing needs to be removed.
    287          */
    288         private BitSet mRemovedChars;
    289 
    290         StringWithRemovedChars(@NonNull String original) {
    291             mOriginal = original;
    292         }
    293 
    294         /**
    295          * Mark all chars in a range {@code [firstRemoved - firstNonRemoved[} (not including
    296          * firstNonRemoved) as removed.
    297          */
    298         void removeRange(int firstRemoved, int firstNonRemoved) {
    299             if (mRemovedChars == null) {
    300                 mRemovedChars = new BitSet(mOriginal.length());
    301             }
    302 
    303             mRemovedChars.set(firstRemoved, firstNonRemoved);
    304         }
    305 
    306         /**
    307          * Remove all characters before {@code firstNonRemoved}.
    308          */
    309         void removeAllCharBefore(int firstNonRemoved) {
    310             if (mRemovedChars == null) {
    311                 mRemovedChars = new BitSet(mOriginal.length());
    312             }
    313 
    314             mRemovedChars.set(0, firstNonRemoved);
    315         }
    316 
    317         /**
    318          * Remove all characters after and including {@code firstRemoved}.
    319          */
    320         void removeAllCharAfter(int firstRemoved) {
    321             if (mRemovedChars == null) {
    322                 mRemovedChars = new BitSet(mOriginal.length());
    323             }
    324 
    325             mRemovedChars.set(firstRemoved, mOriginal.length());
    326         }
    327 
    328         @Override
    329         public String toString() {
    330             // Common case, no chars removed
    331             if (mRemovedChars == null) {
    332                 return mOriginal;
    333             }
    334 
    335             StringBuilder sb = new StringBuilder(mOriginal.length());
    336             for (int i = 0; i < mOriginal.length(); i++) {
    337                 if (!mRemovedChars.get(i)) {
    338                     sb.append(mOriginal.charAt(i));
    339                 }
    340             }
    341 
    342             return sb.toString();
    343         }
    344 
    345         /**
    346          * Return length or the original string
    347          */
    348         int length() {
    349             return mOriginal.length();
    350         }
    351 
    352         /**
    353          * Return if a certain {@code offset} of the original string is removed
    354          */
    355         boolean isRemoved(int offset) {
    356             return mRemovedChars != null && mRemovedChars.get(offset);
    357         }
    358 
    359         /**
    360          * Return codePoint of original string at a certain {@code offset}
    361          */
    362         int codePointAt(int offset) {
    363             return mOriginal.codePointAt(offset);
    364         }
    365     }
    366 
    367     /**
    368      * Load, clean up and truncate label before use.
    369      *
    370      * <p>This method is meant to remove common mistakes and nefarious formatting from strings that
    371      * are used in sensitive parts of the UI.
    372      *
    373      * <p>This method first treats the string like HTML and then ...
    374      * <ul>
    375      * <li>Removes new lines or truncates at first new line
    376      * <li>Trims the white-space off the end
    377      * <li>Truncates the string to a given length
    378      * </ul>
    379      * ... if specified.
    380      *
    381      * @param ellipsizeDip Assuming maximum length of the string (in dip), assuming font size 42.
    382      *                     This is roughly 50 characters for {@code ellipsizeDip == 1000}.<br />
    383      *                     Usually ellipsizing should be left to the view showing the string. If a
    384      *                     string is used as an input to another string, it might be useful to
    385      *                     control the length of the input string though. {@code 0} disables this
    386      *                     feature.
    387      * @return The safe label
    388      * @hide
    389      */
    390     public @NonNull CharSequence loadSafeLabel(@NonNull PackageManager pm,
    391             @FloatRange(from = 0) float ellipsizeDip, @SafeLabelFlags int flags) {
    392         boolean onlyKeepFirstLine = ((flags & SAFE_LABEL_FLAG_FIRST_LINE) != 0);
    393         boolean forceSingleLine = ((flags & SAFE_LABEL_FLAG_SINGLE_LINE) != 0);
    394         boolean trim = ((flags & SAFE_LABEL_FLAG_TRIM) != 0);
    395 
    396         Preconditions.checkNotNull(pm);
    397         Preconditions.checkArgument(ellipsizeDip >= 0);
    398         Preconditions.checkFlagsArgument(flags, SAFE_LABEL_FLAG_TRIM | SAFE_LABEL_FLAG_SINGLE_LINE
    399                 | SAFE_LABEL_FLAG_FIRST_LINE);
    400         Preconditions.checkArgument(!(onlyKeepFirstLine && forceSingleLine),
    401                 "Cannot set SAFE_LABEL_FLAG_SINGLE_LINE and SAFE_LABEL_FLAG_FIRST_LINE at the same "
    402                         + "time");
    403 
    404         // loadLabel() always returns non-null
    405         String label = loadUnsafeLabel(pm).toString();
    406 
    407         // Treat string as HTML. This
    408         // - converts HTML symbols: e.g. &szlig; -> 
    409         // - applies some HTML tags: e.g. <br> -> \n
    410         // - removes invalid characters such as \b
    411         // - removes html styling, such as <b>
    412         // - applies html formatting: e.g. a<p>b</p>c -> a\n\nb\n\nc
    413         // - replaces some html tags by "object replacement" markers: <img> -> \ufffc
    414         // - Removes leading white space
    415         // - Removes all trailing white space beside a single space
    416         // - Collapses double white space
    417         StringWithRemovedChars labelStr = new StringWithRemovedChars(
    418                 Html.fromHtml(label).toString());
    419 
    420         int firstNonWhiteSpace = -1;
    421         int firstTrailingWhiteSpace = -1;
    422 
    423         // Remove new lines (if requested) and control characters.
    424         int labelLength = labelStr.length();
    425         for (int offset = 0; offset < labelLength; ) {
    426             int codePoint = labelStr.codePointAt(offset);
    427             int type = Character.getType(codePoint);
    428             int codePointLen = Character.charCount(codePoint);
    429             boolean isNewline = isNewline(codePoint);
    430 
    431             if (offset > MAX_SAFE_LABEL_LENGTH || onlyKeepFirstLine && isNewline) {
    432                 labelStr.removeAllCharAfter(offset);
    433                 break;
    434             } else if (forceSingleLine && isNewline) {
    435                 labelStr.removeRange(offset, offset + codePointLen);
    436             } else if (type == Character.CONTROL && !isNewline) {
    437                 labelStr.removeRange(offset, offset + codePointLen);
    438             } else if (trim && !isWhiteSpace(codePoint)) {
    439                 // This is only executed if the code point is not removed
    440                 if (firstNonWhiteSpace == -1) {
    441                     firstNonWhiteSpace = offset;
    442                 }
    443                 firstTrailingWhiteSpace = offset + codePointLen;
    444             }
    445 
    446             offset += codePointLen;
    447         }
    448 
    449         if (trim) {
    450             // Remove leading and trailing white space
    451             if (firstNonWhiteSpace == -1) {
    452                 // No non whitespace found, remove all
    453                 labelStr.removeAllCharAfter(0);
    454             } else {
    455                 if (firstNonWhiteSpace > 0) {
    456                     labelStr.removeAllCharBefore(firstNonWhiteSpace);
    457                 }
    458                 if (firstTrailingWhiteSpace < labelLength) {
    459                     labelStr.removeAllCharAfter(firstTrailingWhiteSpace);
    460                 }
    461             }
    462         }
    463 
    464         if (ellipsizeDip == 0) {
    465             return labelStr.toString();
    466         } else {
    467             // Truncate
    468             final TextPaint paint = new TextPaint();
    469             paint.setTextSize(42);
    470 
    471             return TextUtils.ellipsize(labelStr.toString(), paint, ellipsizeDip,
    472                     TextUtils.TruncateAt.END);
    473         }
    474     }
    475 
    476     /**
    477      * Retrieve the current graphical icon associated with this item.  This
    478      * will call back on the given PackageManager to load the icon from
    479      * the application.
    480      *
    481      * @param pm A PackageManager from which the icon can be loaded; usually
    482      * the PackageManager from which you originally retrieved this item.
    483      *
    484      * @return Returns a Drawable containing the item's icon.  If the
    485      * item does not have an icon, the item's default icon is returned
    486      * such as the default activity icon.
    487      */
    488     public Drawable loadIcon(PackageManager pm) {
    489         return pm.loadItemIcon(this, getApplicationInfo());
    490     }
    491 
    492     /**
    493      * Retrieve the current graphical icon associated with this item without
    494      * the addition of a work badge if applicable.
    495      * This will call back on the given PackageManager to load the icon from
    496      * the application.
    497      *
    498      * @param pm A PackageManager from which the icon can be loaded; usually
    499      * the PackageManager from which you originally retrieved this item.
    500      *
    501      * @return Returns a Drawable containing the item's icon.  If the
    502      * item does not have an icon, the item's default icon is returned
    503      * such as the default activity icon.
    504      */
    505     public Drawable loadUnbadgedIcon(PackageManager pm) {
    506         return pm.loadUnbadgedItemIcon(this, getApplicationInfo());
    507     }
    508 
    509     /**
    510      * Retrieve the current graphical banner associated with this item.  This
    511      * will call back on the given PackageManager to load the banner from
    512      * the application.
    513      *
    514      * @param pm A PackageManager from which the banner can be loaded; usually
    515      * the PackageManager from which you originally retrieved this item.
    516      *
    517      * @return Returns a Drawable containing the item's banner.  If the item
    518      * does not have a banner, this method will return null.
    519      */
    520     public Drawable loadBanner(PackageManager pm) {
    521         if (banner != 0) {
    522             Drawable dr = pm.getDrawable(packageName, banner, getApplicationInfo());
    523             if (dr != null) {
    524                 return dr;
    525             }
    526         }
    527         return loadDefaultBanner(pm);
    528     }
    529 
    530     /**
    531      * Retrieve the default graphical icon associated with this item.
    532      *
    533      * @param pm A PackageManager from which the icon can be loaded; usually
    534      * the PackageManager from which you originally retrieved this item.
    535      *
    536      * @return Returns a Drawable containing the item's default icon
    537      * such as the default activity icon.
    538      *
    539      * @hide
    540      */
    541     public Drawable loadDefaultIcon(PackageManager pm) {
    542         return pm.getDefaultActivityIcon();
    543     }
    544 
    545     /**
    546      * Retrieve the default graphical banner associated with this item.
    547      *
    548      * @param pm A PackageManager from which the banner can be loaded; usually
    549      * the PackageManager from which you originally retrieved this item.
    550      *
    551      * @return Returns a Drawable containing the item's default banner
    552      * or null if no default logo is available.
    553      *
    554      * @hide
    555      */
    556     protected Drawable loadDefaultBanner(PackageManager pm) {
    557         return null;
    558     }
    559 
    560     /**
    561      * Retrieve the current graphical logo associated with this item. This
    562      * will call back on the given PackageManager to load the logo from
    563      * the application.
    564      *
    565      * @param pm A PackageManager from which the logo can be loaded; usually
    566      * the PackageManager from which you originally retrieved this item.
    567      *
    568      * @return Returns a Drawable containing the item's logo. If the item
    569      * does not have a logo, this method will return null.
    570      */
    571     public Drawable loadLogo(PackageManager pm) {
    572         if (logo != 0) {
    573             Drawable d = pm.getDrawable(packageName, logo, getApplicationInfo());
    574             if (d != null) {
    575                 return d;
    576             }
    577         }
    578         return loadDefaultLogo(pm);
    579     }
    580 
    581     /**
    582      * Retrieve the default graphical logo associated with this item.
    583      *
    584      * @param pm A PackageManager from which the logo can be loaded; usually
    585      * the PackageManager from which you originally retrieved this item.
    586      *
    587      * @return Returns a Drawable containing the item's default logo
    588      * or null if no default logo is available.
    589      *
    590      * @hide
    591      */
    592     protected Drawable loadDefaultLogo(PackageManager pm) {
    593         return null;
    594     }
    595 
    596     /**
    597      * Load an XML resource attached to the meta-data of this item.  This will
    598      * retrieved the name meta-data entry, and if defined call back on the
    599      * given PackageManager to load its XML file from the application.
    600      *
    601      * @param pm A PackageManager from which the XML can be loaded; usually
    602      * the PackageManager from which you originally retrieved this item.
    603      * @param name Name of the meta-date you would like to load.
    604      *
    605      * @return Returns an XmlPullParser you can use to parse the XML file
    606      * assigned as the given meta-data.  If the meta-data name is not defined
    607      * or the XML resource could not be found, null is returned.
    608      */
    609     public XmlResourceParser loadXmlMetaData(PackageManager pm, String name) {
    610         if (metaData != null) {
    611             int resid = metaData.getInt(name);
    612             if (resid != 0) {
    613                 return pm.getXml(packageName, resid, getApplicationInfo());
    614             }
    615         }
    616         return null;
    617     }
    618 
    619     /**
    620      * @hide Flag for dumping: include all details.
    621      */
    622     public static final int DUMP_FLAG_DETAILS = 1<<0;
    623 
    624     /**
    625      * @hide Flag for dumping: include nested ApplicationInfo.
    626      */
    627     public static final int DUMP_FLAG_APPLICATION = 1<<1;
    628 
    629     /**
    630      * @hide Flag for dumping: all flags to dump everything.
    631      */
    632     public static final int DUMP_FLAG_ALL = DUMP_FLAG_DETAILS | DUMP_FLAG_APPLICATION;
    633 
    634     protected void dumpFront(Printer pw, String prefix) {
    635         if (name != null) {
    636             pw.println(prefix + "name=" + name);
    637         }
    638         pw.println(prefix + "packageName=" + packageName);
    639         if (labelRes != 0 || nonLocalizedLabel != null || icon != 0 || banner != 0) {
    640             pw.println(prefix + "labelRes=0x" + Integer.toHexString(labelRes)
    641                     + " nonLocalizedLabel=" + nonLocalizedLabel
    642                     + " icon=0x" + Integer.toHexString(icon)
    643                     + " banner=0x" + Integer.toHexString(banner));
    644         }
    645     }
    646 
    647     protected void dumpBack(Printer pw, String prefix) {
    648         // no back here
    649     }
    650 
    651     public void writeToParcel(Parcel dest, int parcelableFlags) {
    652         dest.writeString(name);
    653         dest.writeString(packageName);
    654         dest.writeInt(labelRes);
    655         TextUtils.writeToParcel(nonLocalizedLabel, dest, parcelableFlags);
    656         dest.writeInt(icon);
    657         dest.writeInt(logo);
    658         dest.writeBundle(metaData);
    659         dest.writeInt(banner);
    660         dest.writeInt(showUserIcon);
    661     }
    662 
    663     /**
    664      * @hide
    665      */
    666     public void writeToProto(ProtoOutputStream proto, long fieldId) {
    667         long token = proto.start(fieldId);
    668         if (name != null) {
    669             proto.write(PackageItemInfoProto.NAME, name);
    670         }
    671         proto.write(PackageItemInfoProto.PACKAGE_NAME, packageName);
    672         if (labelRes != 0 || nonLocalizedLabel != null || icon != 0 || banner != 0) {
    673             proto.write(PackageItemInfoProto.LABEL_RES, labelRes);
    674             proto.write(PackageItemInfoProto.NON_LOCALIZED_LABEL, nonLocalizedLabel.toString());
    675             proto.write(PackageItemInfoProto.ICON, icon);
    676             proto.write(PackageItemInfoProto.BANNER, banner);
    677         }
    678         proto.end(token);
    679     }
    680 
    681     protected PackageItemInfo(Parcel source) {
    682         name = source.readString();
    683         packageName = source.readString();
    684         labelRes = source.readInt();
    685         nonLocalizedLabel
    686                 = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
    687         icon = source.readInt();
    688         logo = source.readInt();
    689         metaData = source.readBundle();
    690         banner = source.readInt();
    691         showUserIcon = source.readInt();
    692     }
    693 
    694     /**
    695      * Get the ApplicationInfo for the application to which this item belongs,
    696      * if available, otherwise returns null.
    697      *
    698      * @return Returns the ApplicationInfo of this item, or null if not known.
    699      *
    700      * @hide
    701      */
    702     protected ApplicationInfo getApplicationInfo() {
    703         return null;
    704     }
    705 
    706     public static class DisplayNameComparator
    707             implements Comparator<PackageItemInfo> {
    708         public DisplayNameComparator(PackageManager pm) {
    709             mPM = pm;
    710         }
    711 
    712         public final int compare(PackageItemInfo aa, PackageItemInfo ab) {
    713             CharSequence  sa = aa.loadLabel(mPM);
    714             if (sa == null) sa = aa.name;
    715             CharSequence  sb = ab.loadLabel(mPM);
    716             if (sb == null) sb = ab.name;
    717             return sCollator.compare(sa.toString(), sb.toString());
    718         }
    719 
    720         private final Collator   sCollator = Collator.getInstance();
    721         private PackageManager   mPM;
    722     }
    723 }
    724