• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.launcher3.compat;
2 
3 import android.content.Context;
4 import android.icu.text.AlphabeticIndex;
5 import android.os.LocaleList;
6 import android.util.Log;
7 
8 import com.android.launcher3.Utilities;
9 
10 import java.util.Locale;
11 
12 import androidx.annotation.NonNull;
13 
14 public class AlphabeticIndexCompat {
15     private static final String TAG = "AlphabeticIndexCompat";
16 
17     private static final String MID_DOT = "\u2219";
18     private final BaseIndex mBaseIndex;
19     private final String mDefaultMiscLabel;
20 
AlphabeticIndexCompat(Context context)21     public AlphabeticIndexCompat(Context context) {
22         BaseIndex index = null;
23 
24         try {
25             index = new AlphabeticIndexVN(context);
26         } catch (Exception e) {
27             Log.d(TAG, "Unable to load the system index", e);
28         }
29 
30         mBaseIndex = index == null ? new BaseIndex() : index;
31 
32         if (context.getResources().getConfiguration().locale
33                 .getLanguage().equals(Locale.JAPANESE.getLanguage())) {
34             // Japanese character 他 ("misc")
35             mDefaultMiscLabel = "\u4ed6";
36             // TODO(winsonc, omakoto): We need to handle Japanese sections better, especially the kanji
37         } else {
38             // Dot
39             mDefaultMiscLabel = MID_DOT;
40         }
41     }
42 
43     /**
44      * Computes the section name for an given string {@param s}.
45      */
computeSectionName(@onNull CharSequence cs)46     public String computeSectionName(@NonNull CharSequence cs) {
47         String s = Utilities.trim(cs);
48         String sectionName = mBaseIndex.getBucketLabel(mBaseIndex.getBucketIndex(s));
49         if (Utilities.trim(sectionName).isEmpty() && s.length() > 0) {
50             int c = s.codePointAt(0);
51             boolean startsWithDigit = Character.isDigit(c);
52             if (startsWithDigit) {
53                 // Digit section
54                 return "#";
55             } else {
56                 boolean startsWithLetter = Character.isLetter(c);
57                 if (startsWithLetter) {
58                     return mDefaultMiscLabel;
59                 } else {
60                     // In languages where these differ, this ensures that we differentiate
61                     // between the misc section in the native language and a misc section
62                     // for everything else.
63                     return MID_DOT;
64                 }
65             }
66         }
67         return sectionName;
68     }
69 
70     /**
71      * Base class to support Alphabetic indexing if not supported by the framework.
72      * TODO(winsonc): disable for non-english locales
73      */
74     private static class BaseIndex {
75 
76         private static final String BUCKETS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-";
77         private static final int UNKNOWN_BUCKET_INDEX = BUCKETS.length() - 1;
78 
79         /**
80          * Returns the index of the bucket in which the given string should appear.
81          */
getBucketIndex(@onNull String s)82         protected int getBucketIndex(@NonNull String s) {
83             if (s.isEmpty()) {
84                 return UNKNOWN_BUCKET_INDEX;
85             }
86             int index = BUCKETS.indexOf(s.substring(0, 1).toUpperCase());
87             if (index != -1) {
88                 return index;
89             }
90             return UNKNOWN_BUCKET_INDEX;
91         }
92 
93         /**
94          * Returns the label for the bucket at the given index (as returned by getBucketIndex).
95          */
getBucketLabel(int index)96         protected String getBucketLabel(int index) {
97             return BUCKETS.substring(index, index + 1);
98         }
99     }
100 
101     /**
102      * Implementation based on {@link AlphabeticIndex}.
103      */
104     private static class AlphabeticIndexVN extends BaseIndex {
105 
106         private final AlphabeticIndex.ImmutableIndex mAlphabeticIndex;
107 
AlphabeticIndexVN(Context context)108         public AlphabeticIndexVN(Context context) {
109             LocaleList locales = context.getResources().getConfiguration().getLocales();
110             int localeCount = locales.size();
111 
112             Locale primaryLocale = localeCount == 0 ? Locale.ENGLISH : locales.get(0);
113             AlphabeticIndex indexBuilder = new AlphabeticIndex(primaryLocale);
114             for (int i = 1; i < localeCount; i++) {
115                 indexBuilder.addLabels(locales.get(i));
116             }
117             indexBuilder.addLabels(Locale.ENGLISH);
118 
119             mAlphabeticIndex = indexBuilder.buildImmutableIndex();
120         }
121 
122         /**
123          * Returns the index of the bucket in which {@param s} should appear.
124          */
getBucketIndex(String s)125         protected int getBucketIndex(String s) {
126             return mAlphabeticIndex.getBucketIndex(s);
127         }
128 
129         /**
130          * Returns the label for the bucket at the given index
131          */
getBucketLabel(int index)132         protected String getBucketLabel(int index) {
133             return mAlphabeticIndex.getBucket(index).getLabel();
134         }
135     }
136 }
137