OSDN Git Service

Get rid of icu4j dependency
[android-x86/packages-apps-Eleven.git] / src / org / lineageos / eleven / locale / LocaleUtils.java
1 /*
2  * Copyright (C) 2010 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 org.lineageos.eleven.locale;
18
19 import android.icu.text.AlphabeticIndex;
20 import android.support.annotation.VisibleForTesting;
21 import android.util.Log;
22
23 import java.util.ArrayList;
24 import java.util.Iterator;
25 import java.util.Locale;
26
27 /**
28  * This utility class provides specialized handling for locale specific
29  * information: labels, name lookup keys.
30  *
31  * This class has been modified from ContactLocaleUtils.java for now to rip out
32  * Chinese/Japanese specific Alphabetic Indexers because the MediaProvider's sort
33  * is using a Collator sort which can result in confusing behavior, so for now we will
34  * simplify and batch up those results until we later support our own internal databases
35  * An example of what This is, if we have songs "Able", "Xylophone" and "上" in
36  * simplified chinese language The media provider would give it to us in that order sorted,
37  * but the ICU lib would return "A", "X", "S".  Unless we write our own db or do our own sort
38  * there is no good easy solution
39  */
40 public class LocaleUtils {
41     public static final String TAG = "MusicLocale";
42
43     public static final Locale LOCALE_ARABIC = new Locale("ar");
44     public static final Locale LOCALE_GREEK = new Locale("el");
45     public static final Locale LOCALE_HEBREW = new Locale("he");
46     // Serbian and Ukrainian labels are complementary supersets of Russian
47     public static final Locale LOCALE_SERBIAN = new Locale("sr");
48     public static final Locale LOCALE_UKRAINIAN = new Locale("uk");
49     public static final Locale LOCALE_THAI = new Locale("th");
50
51     /**
52      * This class is the default implementation and should be the base class
53      * for other locales.
54      *
55      * sortKey: same as name
56      * nameLookupKeys: none
57      * labels: uses ICU AlphabeticIndex for labels and extends by labeling
58      *     phone numbers "#".  Eg English labels are: [A-Z], #, " "
59      */
60     private static class LocaleUtilsBase {
61         private static final String EMPTY_STRING = "";
62         private static final String NUMBER_STRING = "#";
63
64         protected final AlphabeticIndex.ImmutableIndex mAlphabeticIndex;
65         private final int mAlphabeticIndexBucketCount;
66         private final int mNumberBucketIndex;
67
68         public LocaleUtilsBase(LocaleSet locales) {
69             // AlphabeticIndex.getBucketLabel() uses a binary search across
70             // the entire label set so care should be taken about growing this
71             // set too large. The following set determines for which locales
72             // we will show labels other than your primary locale. General rules
73             // of thumb for adding a locale: should be a supported locale; and
74             // should not be included if from a name it is not deterministic
75             // which way to label it (so eg Chinese cannot be added because
76             // the labeling of a Chinese character varies between Simplified,
77             // Traditional, and Japanese locales). Use English only for all
78             // Latin based alphabets. Ukrainian and Serbian are chosen for
79             // Cyrillic because their alphabets are complementary supersets
80             // of Russian.
81             final Locale secondaryLocale = locales.getSecondaryLocale();
82             AlphabeticIndex ai = new AlphabeticIndex(locales.getPrimaryLocale())
83                 .setMaxLabelCount(300);
84             if (secondaryLocale != null) {
85                 ai.addLabels(secondaryLocale);
86             }
87             mAlphabeticIndex = ai.addLabels(Locale.ENGLISH)
88                 .addLabels(Locale.JAPANESE)
89                 .addLabels(Locale.KOREAN)
90                 .addLabels(LOCALE_THAI)
91                 .addLabels(LOCALE_ARABIC)
92                 .addLabels(LOCALE_HEBREW)
93                 .addLabels(LOCALE_GREEK)
94                 .addLabels(LOCALE_UKRAINIAN)
95                 .addLabels(LOCALE_SERBIAN)
96                 .buildImmutableIndex();
97             mAlphabeticIndexBucketCount = mAlphabeticIndex.getBucketCount();
98             mNumberBucketIndex = mAlphabeticIndexBucketCount - 1;
99         }
100
101         public String getSortKey(String name) {
102             return name;
103         }
104
105         /**
106          * Returns the bucket index for the specified string. AlphabeticIndex
107          * sorts strings into buckets numbered in order from 0 to N, where the
108          * exact value of N depends on how many representative index labels are
109          * used in a particular locale. This routine adds one additional bucket
110          * for phone numbers. It attempts to detect phone numbers and shifts
111          * the bucket indexes returned by AlphabeticIndex in order to make room
112          * for the new # bucket, so the returned range becomes 0 to N+1.
113          */
114         public int getBucketIndex(String name) {
115             if (name == null) {
116                 return -1;
117             }
118             boolean prefixIsNumeric = false;
119             final int length = name.length();
120             int offset = 0;
121             while (offset < length) {
122                 int codePoint = Character.codePointAt(name, offset);
123                 // Ignore standard phone number separators and identify any
124                 // string that otherwise starts with a number.
125                 if (Character.isDigit(codePoint)) {
126                     prefixIsNumeric = true;
127                     break;
128                 } else if (!Character.isSpaceChar(codePoint) &&
129                            codePoint != '+' && codePoint != '(' &&
130                            codePoint != ')' && codePoint != '.' &&
131                            codePoint != '-' && codePoint != '#') {
132                     break;
133                 }
134                 offset += Character.charCount(codePoint);
135             }
136             if (prefixIsNumeric) {
137                 return mNumberBucketIndex;
138             }
139
140             final int bucket = mAlphabeticIndex.getBucketIndex(name);
141             if (bucket < 0) {
142                 return -1;
143             }
144             if (bucket >= mNumberBucketIndex) {
145                 return bucket + 1;
146             }
147             return bucket;
148         }
149
150         /**
151          * Returns the number of buckets in use (one more than AlphabeticIndex
152          * uses, because this class adds a bucket for phone numbers).
153          */
154         public int getBucketCount() {
155             return mAlphabeticIndexBucketCount + 1;
156         }
157
158         /**
159          * Returns the label for the specified bucket index if a valid index,
160          * otherwise returns an empty string. '#' is returned for the phone
161          * number bucket; for all others, the AlphabeticIndex label is returned.
162          */
163         public String getBucketLabel(int bucketIndex) {
164             if (bucketIndex < 0 || bucketIndex >= getBucketCount()) {
165                 return EMPTY_STRING;
166             } else if (bucketIndex == mNumberBucketIndex) {
167                 return NUMBER_STRING;
168             } else if (bucketIndex > mNumberBucketIndex) {
169                 --bucketIndex;
170             }
171             return mAlphabeticIndex.getBucket(bucketIndex).getLabel();
172         }
173
174         @SuppressWarnings("unused")
175         public Iterator<String> getNameLookupKeys(String name, int nameStyle) {
176             return null;
177         }
178
179         public ArrayList<String> getLabels() {
180             final int bucketCount = getBucketCount();
181             final ArrayList<String> labels = new ArrayList<String>(bucketCount);
182             for(int i = 0; i < bucketCount; ++i) {
183                 labels.add(getBucketLabel(i));
184             }
185             return labels;
186         }
187     }
188
189     private static LocaleUtils sSingleton;
190
191     private final LocaleSet mLocales;
192     private final LocaleUtilsBase mUtils;
193
194     private LocaleUtils(LocaleSet locales) {
195         if (locales == null) {
196             mLocales = LocaleSet.getDefault();
197         } else {
198             mLocales = locales;
199         }
200
201         mUtils = new LocaleUtilsBase(mLocales);
202         Log.i(TAG, "AddressBook Labels [" + mLocales.toString() + "]: "
203                 + getLabels().toString());
204     }
205
206     public boolean isLocale(LocaleSet locales) {
207         return mLocales.equals(locales);
208     }
209
210     public static synchronized LocaleUtils getInstance() {
211         if (sSingleton == null) {
212             sSingleton = new LocaleUtils(LocaleSet.getDefault());
213         }
214         return sSingleton;
215     }
216
217     @VisibleForTesting
218     public static synchronized void setLocale(Locale locale) {
219         setLocales(new LocaleSet(locale));
220     }
221
222     public static synchronized void setLocales(LocaleSet locales) {
223         if (sSingleton == null || !sSingleton.isLocale(locales)) {
224             sSingleton = new LocaleUtils(locales);
225         }
226     }
227
228     public String getSortKey(String name, int nameStyle) {
229         return mUtils.getSortKey(name);
230     }
231
232     public int getBucketIndex(String name) {
233         return mUtils.getBucketIndex(name);
234     }
235
236     public int getBucketCount() {
237         return mUtils.getBucketCount();
238     }
239
240     public String getBucketLabel(int bucketIndex) {
241         return mUtils.getBucketLabel(bucketIndex);
242     }
243
244     public String getLabel(String name) {
245         return getBucketLabel(getBucketIndex(name));
246     }
247
248     public ArrayList<String> getLabels() {
249         return mUtils.getLabels();
250     }
251 }