2 * Copyright (C) 2010 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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
17 package org.lineageos.eleven.locale;
19 import android.icu.text.AlphabeticIndex;
20 import android.support.annotation.VisibleForTesting;
21 import android.util.Log;
23 import java.util.ArrayList;
24 import java.util.Iterator;
25 import java.util.Locale;
28 * This utility class provides specialized handling for locale specific
29 * information: labels, name lookup keys.
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
40 public class LocaleUtils {
41 public static final String TAG = "MusicLocale";
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");
52 * This class is the default implementation and should be the base class
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], #, " "
60 private static class LocaleUtilsBase {
61 private static final String EMPTY_STRING = "";
62 private static final String NUMBER_STRING = "#";
64 protected final AlphabeticIndex.ImmutableIndex mAlphabeticIndex;
65 private final int mAlphabeticIndexBucketCount;
66 private final int mNumberBucketIndex;
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
81 final Locale secondaryLocale = locales.getSecondaryLocale();
82 AlphabeticIndex ai = new AlphabeticIndex(locales.getPrimaryLocale())
83 .setMaxLabelCount(300);
84 if (secondaryLocale != null) {
85 ai.addLabels(secondaryLocale);
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;
101 public String getSortKey(String name) {
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.
114 public int getBucketIndex(String name) {
118 boolean prefixIsNumeric = false;
119 final int length = name.length();
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;
128 } else if (!Character.isSpaceChar(codePoint) &&
129 codePoint != '+' && codePoint != '(' &&
130 codePoint != ')' && codePoint != '.' &&
131 codePoint != '-' && codePoint != '#') {
134 offset += Character.charCount(codePoint);
136 if (prefixIsNumeric) {
137 return mNumberBucketIndex;
140 final int bucket = mAlphabeticIndex.getBucketIndex(name);
144 if (bucket >= mNumberBucketIndex) {
151 * Returns the number of buckets in use (one more than AlphabeticIndex
152 * uses, because this class adds a bucket for phone numbers).
154 public int getBucketCount() {
155 return mAlphabeticIndexBucketCount + 1;
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.
163 public String getBucketLabel(int bucketIndex) {
164 if (bucketIndex < 0 || bucketIndex >= getBucketCount()) {
166 } else if (bucketIndex == mNumberBucketIndex) {
167 return NUMBER_STRING;
168 } else if (bucketIndex > mNumberBucketIndex) {
171 return mAlphabeticIndex.getBucket(bucketIndex).getLabel();
174 @SuppressWarnings("unused")
175 public Iterator<String> getNameLookupKeys(String name, int nameStyle) {
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));
189 private static LocaleUtils sSingleton;
191 private final LocaleSet mLocales;
192 private final LocaleUtilsBase mUtils;
194 private LocaleUtils(LocaleSet locales) {
195 if (locales == null) {
196 mLocales = LocaleSet.getDefault();
201 mUtils = new LocaleUtilsBase(mLocales);
202 Log.i(TAG, "AddressBook Labels [" + mLocales.toString() + "]: "
203 + getLabels().toString());
206 public boolean isLocale(LocaleSet locales) {
207 return mLocales.equals(locales);
210 public static synchronized LocaleUtils getInstance() {
211 if (sSingleton == null) {
212 sSingleton = new LocaleUtils(LocaleSet.getDefault());
218 public static synchronized void setLocale(Locale locale) {
219 setLocales(new LocaleSet(locale));
222 public static synchronized void setLocales(LocaleSet locales) {
223 if (sSingleton == null || !sSingleton.isLocale(locales)) {
224 sSingleton = new LocaleUtils(locales);
228 public String getSortKey(String name, int nameStyle) {
229 return mUtils.getSortKey(name);
232 public int getBucketIndex(String name) {
233 return mUtils.getBucketIndex(name);
236 public int getBucketCount() {
237 return mUtils.getBucketCount();
240 public String getBucketLabel(int bucketIndex) {
241 return mUtils.getBucketLabel(bucketIndex);
244 public String getLabel(String name) {
245 return getBucketLabel(getBucketIndex(name));
248 public ArrayList<String> getLabels() {
249 return mUtils.getLabels();