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
17package com.android.providers.contacts;
18
19import android.provider.ContactsContract.FullNameStyle;
20import android.util.SparseArray;
21
22import com.android.providers.contacts.HanziToPinyin.Token;
23
24import java.util.ArrayList;
25import java.util.HashSet;
26import java.util.Iterator;
27import java.util.Locale;
28
29/**
30 * This utility class provides customized sort key and name lookup key according the locale.
31 */
32public class ContactLocaleUtils {
33
34    /**
35     * This class is the default implementation.
36     * <p>
37     * It should be the base class for other locales' implementation.
38     */
39    public class ContactLocaleUtilsBase {
40        public String getSortKey(String displayName) {
41            return displayName;
42        }
43        @SuppressWarnings("unused")
44        public Iterator<String> getNameLookupKeys(String name) {
45            return null;
46        }
47    }
48
49    /**
50     * The classes to generate the Chinese style sort and search keys.
51     * <p>
52     * The sorting key is generated as each Chinese character' pinyin proceeding with
53     * space and character itself. If the character's pinyin unable to find, the character
54     * itself will be used.
55     * <p>
56     * The below additional name lookup keys will be generated.
57     * a. Chinese character's pinyin and pinyin's initial character.
58     * b. Latin word and the initial character for Latin word.
59     * The name lookup keys are generated to make sure the name can be found by from any
60     * initial character.
61     */
62    private class ChineseContactUtils extends ContactLocaleUtilsBase {
63        @Override
64        public String getSortKey(String displayName) {
65            ArrayList<Token> tokens = HanziToPinyin.getInstance().get(displayName);
66            if (tokens != null && tokens.size() > 0) {
67                StringBuilder sb = new StringBuilder();
68                for (Token token : tokens) {
69                    // Put Chinese character's pinyin, then proceed with the
70                    // character itself.
71                    if (Token.PINYIN == token.type) {
72                        if (sb.length() > 0) {
73                            sb.append(' ');
74                        }
75                        sb.append(token.target);
76                        sb.append(' ');
77                        sb.append(token.source);
78                    } else {
79                        if (sb.length() > 0) {
80                            sb.append(' ');
81                        }
82                        sb.append(token.source);
83                    }
84                }
85                return sb.toString();
86            }
87            return super.getSortKey(displayName);
88        }
89
90        @Override
91        public Iterator<String> getNameLookupKeys(String name) {
92            // TODO : Reduce the object allocation.
93            HashSet<String> keys = new HashSet<String>();
94            ArrayList<Token> tokens = HanziToPinyin.getInstance().get(name);
95            final int tokenCount = tokens.size();
96            final StringBuilder keyPinyin = new StringBuilder();
97            final StringBuilder keyInitial = new StringBuilder();
98            // There is no space among the Chinese Characters, the variant name
99            // lookup key wouldn't work for Chinese. The keyOrignal is used to
100            // build the lookup keys for itself.
101            final StringBuilder keyOrignal = new StringBuilder();
102            for (int i = tokenCount - 1; i >= 0; i--) {
103                final Token token = tokens.get(i);
104                if (Token.PINYIN == token.type) {
105                    keyPinyin.insert(0, token.target);
106                    keyInitial.insert(0, token.target.charAt(0));
107                } else if (Token.LATIN == token.type) {
108                    // Avoid adding space at the end of String.
109                    if (keyPinyin.length() > 0) {
110                        keyPinyin.insert(0, ' ');
111                    }
112                    if (keyOrignal.length() > 0) {
113                        keyOrignal.insert(0, ' ');
114                    }
115                    keyPinyin.insert(0, token.source);
116                    keyInitial.insert(0, token.source.charAt(0));
117                }
118                keyOrignal.insert(0, token.source);
119                keys.add(keyOrignal.toString());
120                keys.add(keyPinyin.toString());
121                keys.add(keyInitial.toString());
122            }
123            return keys.iterator();
124        }
125    }
126
127    private static final String CHINESE_LANGUAGE = Locale.CHINESE.getLanguage().toLowerCase();
128    private static final String JAPANESE_LANGUAGE = Locale.JAPANESE.getLanguage().toLowerCase();
129    private static final String KOREAN_LANGUAGE = Locale.KOREAN.getLanguage().toLowerCase();
130
131    private static ContactLocaleUtils sSingleton;
132    private final SparseArray<ContactLocaleUtilsBase> mUtils =
133            new SparseArray<ContactLocaleUtilsBase>();
134
135    private final ContactLocaleUtilsBase mBase = new ContactLocaleUtilsBase();
136
137    private String mLanguage;
138
139    private ContactLocaleUtils() {
140        setLocale(null);
141    }
142
143    public void setLocale(Locale currentLocale) {
144        if (currentLocale == null) {
145            mLanguage = Locale.getDefault().getLanguage().toLowerCase();
146        } else {
147            mLanguage = currentLocale.getLanguage().toLowerCase();
148        }
149    }
150
151    public String getSortKey(String displayName, int nameStyle) {
152        return getForSort(Integer.valueOf(nameStyle)).getSortKey(displayName);
153    }
154
155    public Iterator<String> getNameLookupKeys(String name, int nameStyle) {
156        return getForNameLookup(Integer.valueOf(nameStyle)).getNameLookupKeys(name);
157    }
158
159    /**
160     *  Determine which utility should be used for generating NameLookupKey.
161     *  <p>
162     *  a. For Western style name, if the current language is Chinese, the
163     *     ChineseContactUtils should be used.
164     *  b. For Chinese and CJK style name if current language is neither Japanese or Korean,
165     *     the ChineseContactUtils should be used.
166     */
167    private ContactLocaleUtilsBase getForNameLookup(Integer nameStyle) {
168        int nameStyleInt = nameStyle.intValue();
169        Integer adjustedUtil = Integer.valueOf(getAdjustedStyle(nameStyleInt));
170        if (CHINESE_LANGUAGE.equals(mLanguage) && nameStyleInt == FullNameStyle.WESTERN) {
171            adjustedUtil = Integer.valueOf(FullNameStyle.CHINESE);
172        }
173        return get(adjustedUtil);
174    }
175
176    private synchronized ContactLocaleUtilsBase get(Integer nameStyle) {
177        ContactLocaleUtilsBase utils = mUtils.get(nameStyle);
178        if (utils == null) {
179            if (nameStyle.intValue() == FullNameStyle.CHINESE) {
180                utils = new ChineseContactUtils();
181                mUtils.put(nameStyle, utils);
182            }
183        }
184        return (utils == null) ? mBase : utils;
185    }
186
187    /**
188     *  Determine the which utility should be used for generating sort key.
189     *  <p>
190     *  For Chinese and CJK style name if current language is neither Japanese or Korean,
191     *  the ChineseContactUtils should be used.
192     */
193    private ContactLocaleUtilsBase getForSort(Integer nameStyle) {
194        return get(Integer.valueOf(getAdjustedStyle(nameStyle.intValue())));
195    }
196
197    public static synchronized ContactLocaleUtils getIntance() {
198        if (sSingleton == null) {
199            sSingleton = new ContactLocaleUtils();
200        }
201        return sSingleton;
202    }
203
204    private int getAdjustedStyle(int nameStyle) {
205        if (nameStyle == FullNameStyle.CJK  && !JAPANESE_LANGUAGE.equals(mLanguage) &&
206                !KOREAN_LANGUAGE.equals(mLanguage)) {
207            return FullNameStyle.CHINESE;
208        } else {
209            return nameStyle;
210        }
211    }
212}
213