/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.providers.contacts;
import java.lang.Character.UnicodeBlock;
import java.util.Collections;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
/**
* This utility class provides specialized handling for locale specific
* information: labels, name lookup keys.
* Modified to work with IBM ICU4J.
*/
public class ContactLocaleUtils {
public static final String TAG = "ContactLocale";
/**
* This class is the default implementation and should be the base class
* for other locales.
*
* sortKey: same as name
* nameLookupKeys: none
* labels: uses ICU AlphabeticIndex for labels and extends by labeling
* phone numbers "#". Eg English labels are: [A-Z], #, " "
*/
private static class ContactLocaleUtilsBase {
private static final String EMPTY_STRING = "";
private static final String NUMBER_STRING = "#";
public String getBucketLabel(String name) {
if (name.length() == 0)
return EMPTY_STRING;
boolean prefixIsNumeric = false;
final int length = name.length();
int offset = 0;
while (offset < length) {
int codePoint = Character.codePointAt(name, offset);
// Ignore standard phone number separators and identify any
// string that otherwise starts with a number.
if (Character.isDigit(codePoint)) {
prefixIsNumeric = true;
break;
} else if (!Character.isSpaceChar(codePoint) &&
codePoint != '+' && codePoint != '(' &&
codePoint != ')' && codePoint != '.' &&
codePoint != '-' && codePoint != '#') {
break;
}
offset += Character.charCount(codePoint);
}
if (prefixIsNumeric) {
return NUMBER_STRING;
}
return name.substring(0, name.offsetByCodePoints(0, 1));
}
}
/**
* Japanese specific locale overrides.
*
* sortKey: unchanged (same as name)
* nameLookupKeys: unchanged (none)
* labels: extends default labels by labeling unlabeled CJ characters
* with the Japanese character 他 ("misc"). Japanese labels are:
* あ, か, さ, た, な, は, ま, や, ら, わ, 他, [A-Z], #, " "
*/
private static class JapaneseContactUtils extends ContactLocaleUtilsBase {
// \u4ed6 is Japanese character 他 ("misc")
private static final String JAPANESE_MISC_LABEL = "\u4ed6";
public JapaneseContactUtils() {
super();
}
// Set of UnicodeBlocks for unified CJK (Chinese) characters and
// Japanese characters. This includes all code blocks that might
// contain a character used in Japanese (which is why unified CJK
// blocks are included but Korean Hangul and jamo are not).
private static final Set<Character.UnicodeBlock> CJ_BLOCKS;
static {
Set<UnicodeBlock> set = new HashSet<>();
set.add(UnicodeBlock.HIRAGANA);
set.add(UnicodeBlock.KATAKANA);
set.add(UnicodeBlock.KATAKANA_PHONETIC_EXTENSIONS);
set.add(UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS);
set.add(UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS);
set.add(UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A);
set.add(UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B);
set.add(UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION);
set.add(UnicodeBlock.CJK_RADICALS_SUPPLEMENT);
set.add(UnicodeBlock.CJK_COMPATIBILITY);
set.add(UnicodeBlock.CJK_COMPATIBILITY_FORMS);
set.add(UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS);
set.add(UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT);
CJ_BLOCKS = Collections.unmodifiableSet(set);
}
/**
* Helper routine to identify unlabeled Chinese or Japanese characters
* to put in a 'misc' bucket.
*
* @return true if the specified Unicode code point is Chinese or
* Japanese
*/
private static boolean isChineseOrJapanese(int codePoint) {
return CJ_BLOCKS.contains(UnicodeBlock.of(codePoint));
}
@Override
public String getBucketLabel(String name) {
if (!isChineseOrJapanese(Character.codePointAt(name, 0)))
return JAPANESE_MISC_LABEL;
return super.getBucketLabel(name);
}
}
/**
* Simplified Chinese specific locale overrides. Uses ICU Transliterator
* for generating pinyin transliteration.
*
* sortKey: unchanged (same as name)
* nameLookupKeys: adds additional name lookup keys
* - Chinese character's pinyin and pinyin's initial character.
* - Latin word and initial character.
* labels: unchanged
* Simplified Chinese labels are the same as English: [A-Z], #, " "
*/
private static class SimplifiedChineseContactUtils
extends ContactLocaleUtilsBase {
public SimplifiedChineseContactUtils() {
super();
}
}
private static final String JAPANESE_LANGUAGE = Locale.JAPANESE.getLanguage().toLowerCase();
private static ContactLocaleUtils sSingleton;
private final LocaleSet mLocales;
private final ContactLocaleUtilsBase mUtils;
private ContactLocaleUtils(LocaleSet locales) {
if (locales == null) {
mLocales = LocaleSet.getDefault();
} else {
mLocales = locales;
}
if (mLocales.isPrimaryLanguage(JAPANESE_LANGUAGE)) {
mUtils = new JapaneseContactUtils();
} else if (mLocales.isPrimaryLocaleSimplifiedChinese()) {
mUtils = new SimplifiedChineseContactUtils();
} else {
mUtils = new ContactLocaleUtilsBase();
}
}
public boolean isLocale(LocaleSet locales) {
return mLocales.equals(locales);
}
public static synchronized ContactLocaleUtils getInstance() {
if (sSingleton == null) {
sSingleton = new ContactLocaleUtils(LocaleSet.getDefault());
}
return sSingleton;
}
public static synchronized void setLocale(Locale locale) {
setLocales(new LocaleSet(locale));
}
public static synchronized void setLocales(LocaleSet locales) {
if (sSingleton == null || !sSingleton.isLocale(locales)) {
sSingleton = new ContactLocaleUtils(locales);
}
}
public String getLabel(String name) {
return mUtils.getBucketLabel(name);
}
}