PackageItemInfo.java revision 56c1b4f7047da711d53f6299692d127fc03c1a2d
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
17package android.content.pm;
18
19import static java.lang.annotation.RetentionPolicy.SOURCE;
20
21import android.annotation.FloatRange;
22import android.annotation.IntDef;
23import android.annotation.NonNull;
24import android.annotation.SystemApi;
25import android.content.res.XmlResourceParser;
26import android.graphics.drawable.Drawable;
27import android.os.Bundle;
28import android.os.Parcel;
29import android.os.UserHandle;
30import android.text.Html;
31import android.text.TextPaint;
32import android.text.TextUtils;
33import android.util.Printer;
34import android.util.proto.ProtoOutputStream;
35
36import com.android.internal.util.Preconditions;
37
38import java.lang.annotation.Retention;
39import java.text.Collator;
40import java.util.BitSet;
41import 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 */
52public 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