Merge "Call new init function to use for testing, to turn off app ops." into jb-mr2-dev
diff --git a/src/com/android/providers/contacts/ContactLocaleUtils.java b/src/com/android/providers/contacts/ContactLocaleUtils.java
index 0e7b292..0284c01 100644
--- a/src/com/android/providers/contacts/ContactLocaleUtils.java
+++ b/src/com/android/providers/contacts/ContactLocaleUtils.java
@@ -17,78 +17,263 @@
 package com.android.providers.contacts;
 
 import android.provider.ContactsContract.FullNameStyle;
-import android.util.SparseArray;
+import android.util.Log;
 
 import com.android.providers.contacts.HanziToPinyin.Token;
 
+import java.lang.Character.UnicodeBlock;
+import java.util.Arrays;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import libcore.icu.AlphabeticIndex;
 
 /**
- * This utility class provides customized sort key and name lookup key according the locale.
+ * This utility class provides specialized handling for locale specific
+ * information: labels, name lookup keys.
  */
 public class ContactLocaleUtils {
+    public static final String TAG = "ContactLocale";
 
     /**
-     * This class is the default implementation.
-     * <p>
-     * It should be the base class for other locales' implementation.
+     * 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], #, " "
      */
-    public class ContactLocaleUtilsBase {
-        public String getSortKey(String displayName) {
-            return displayName;
+    private static class ContactLocaleUtilsBase {
+        private static final String EMPTY_STRING = "";
+        private static final String NUMBER_STRING = "#";
+
+        protected final AlphabeticIndex mAlphabeticIndex;
+        private final int mAlphabeticIndexBucketCount;
+        private final int mNumberBucketIndex;
+
+        public ContactLocaleUtilsBase(Locale locale) {
+            mAlphabeticIndex = new AlphabeticIndex(locale);
+            mAlphabeticIndex.addLabels(Locale.US);
+            // Force creation of lazy-init data structures.
+            mAlphabeticIndexBucketCount = mAlphabeticIndex.getBucketCount();
+            mNumberBucketIndex = mAlphabeticIndexBucketCount - 1;
         }
+
+        public String getSortKey(String name) {
+            return name;
+        }
+
+        /**
+         * Returns the bucket index for the specified string. AlphabeticIndex
+         * sorts strings into buckets numbered in order from 0 to N, where the
+         * exact value of N depends on how many representative index labels are
+         * used in a particular locale. This routine adds one additional bucket
+         * for phone numbers. It attempts to detect phone numbers and shifts
+         * the bucket indexes returned by AlphabeticIndex in order to make room
+         * for the new # bucket, so the returned range becomes 0 to N+1.
+         */
+        public int getBucketIndex(String name) {
+            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 mNumberBucketIndex;
+            }
+
+            final int bucket = mAlphabeticIndex.getBucketIndex(name);
+            if (bucket < 0) {
+                return -1;
+            }
+            if (bucket >= mNumberBucketIndex) {
+                return bucket + 1;
+            }
+            return bucket;
+        }
+
+        /**
+         * Returns the number of buckets in use (one more than AlphabeticIndex
+         * uses, because this class adds a bucket for phone numbers).
+         */
+        public int getBucketCount() {
+            return mAlphabeticIndexBucketCount + 1;
+        }
+
+        /**
+         * Returns the label for the specified bucket index if a valid index,
+         * otherwise returns an empty string. '#' is returned for the phone
+         * number bucket; for all others, the AlphabeticIndex label is returned.
+         */
+        public String getBucketLabel(int bucketIndex) {
+            if (bucketIndex < 0 || bucketIndex >= getBucketCount()) {
+                return EMPTY_STRING;
+            } else if (bucketIndex == mNumberBucketIndex) {
+                return NUMBER_STRING;
+            } else if (bucketIndex > mNumberBucketIndex) {
+                --bucketIndex;
+            }
+            return mAlphabeticIndex.getBucketLabel(bucketIndex);
+        }
+
         @SuppressWarnings("unused")
         public Iterator<String> getNameLookupKeys(String name) {
             return null;
         }
+
+        public ArrayList<String> getLabels() {
+            final int bucketCount = getBucketCount();
+            final ArrayList<String> labels = new ArrayList<String>(bucketCount);
+            for(int i = 0; i < bucketCount; ++i) {
+                labels.add(getBucketLabel(i));
+            }
+            return labels;
+        }
     }
 
     /**
-     * The classes to generate the Chinese style sort and search keys.
-     * <p>
-     * The sorting key is generated as each Chinese character' pinyin proceeding with
-     * space and character itself. If the character's pinyin unable to find, the character
-     * itself will be used.
-     * <p>
-     * The below additional name lookup keys will be generated.
-     * a. Chinese character's pinyin and pinyin's initial character.
-     * b. Latin word and the initial character for Latin word.
-     * The name lookup keys are generated to make sure the name can be found by from any
-     * initial character.
+     * 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 class ChineseContactUtils extends ContactLocaleUtilsBase {
+    private static class JapaneseContactUtils extends ContactLocaleUtilsBase {
+        // \u4ed6 is Japanese character 他 ("misc")
+        private static final String JAPANESE_MISC_LABEL = "\u4ed6";
+        private final int mMiscBucketIndex;
+
+        public JapaneseContactUtils(Locale locale) {
+            super(locale);
+            // Determine which bucket AlphabeticIndex is lumping unclassified
+            // Japanese characters into by looking up the bucket index for
+            // a representative Kanji/CJK unified ideograph (\u65e5 is the
+            // character '日').
+            mMiscBucketIndex = super.getBucketIndex("\u65e5");
+        }
+
+        // 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<UnicodeBlock>();
+            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));
+        }
+
+        /**
+         * Returns the bucket index for the specified string. Adds an
+         * additional 'misc' bucket for Kanji characters to the base class set.
+         */
         @Override
-        public String getSortKey(String displayName) {
-            ArrayList<Token> tokens = HanziToPinyin.getInstance().get(displayName);
-            if (tokens != null && tokens.size() > 0) {
-                StringBuilder sb = new StringBuilder();
-                for (Token token : tokens) {
-                    // Put Chinese character's pinyin, then proceed with the
-                    // character itself.
-                    if (Token.PINYIN == token.type) {
-                        if (sb.length() > 0) {
-                            sb.append(' ');
-                        }
-                        sb.append(token.target);
-                        sb.append(' ');
-                        sb.append(token.source);
-                    } else {
-                        if (sb.length() > 0) {
-                            sb.append(' ');
-                        }
-                        sb.append(token.source);
-                    }
-                }
-                return sb.toString();
+        public int getBucketIndex(String name) {
+            final int bucketIndex = super.getBucketIndex(name);
+            if ((bucketIndex == mMiscBucketIndex &&
+                 !isChineseOrJapanese(Character.codePointAt(name, 0))) ||
+                bucketIndex > mMiscBucketIndex) {
+                return bucketIndex + 1;
             }
-            return super.getSortKey(displayName);
+            return bucketIndex;
+        }
+
+        /**
+         * Returns the number of buckets in use (one more than the base class
+         * uses, because this class adds a bucket for Kanji).
+         */
+        @Override
+        public int getBucketCount() {
+            return super.getBucketCount() + 1;
+        }
+
+        /**
+         * Returns the label for the specified bucket index if a valid index,
+         * otherwise returns an empty string. '他' is returned for unclassified
+         * Kanji; for all others, the label determined by the base class is
+         * returned.
+         */
+        @Override
+        public String getBucketLabel(int bucketIndex) {
+            if (bucketIndex == mMiscBucketIndex) {
+                return JAPANESE_MISC_LABEL;
+            } else if (bucketIndex > mMiscBucketIndex) {
+                --bucketIndex;
+            }
+            return super.getBucketLabel(bucketIndex);
+        }
+    }
+
+    /**
+     * 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], #, " "
+     *     Traditional Chinese labels are stroke count, then English labels:
+     *         [1-18], [A-Z], #, " "
+     */
+    private static class ChineseContactUtils extends ContactLocaleUtilsBase {
+        public ChineseContactUtils(Locale locale) {
+            super(locale);
         }
 
         @Override
         public Iterator<String> getNameLookupKeys(String name) {
+            return getPinyinNameLookupKeys(name);
+        }
+
+        public static Iterator<String> getPinyinNameLookupKeys(String name) {
             // TODO : Reduce the object allocation.
             HashSet<String> keys = new HashSet<String>();
             ArrayList<Token> tokens = HanziToPinyin.getInstance().get(name);
@@ -96,9 +281,9 @@
             final StringBuilder keyPinyin = new StringBuilder();
             final StringBuilder keyInitial = new StringBuilder();
             // There is no space among the Chinese Characters, the variant name
-            // lookup key wouldn't work for Chinese. The keyOrignal is used to
+            // lookup key wouldn't work for Chinese. The keyOriginal is used to
             // build the lookup keys for itself.
-            final StringBuilder keyOrignal = new StringBuilder();
+            final StringBuilder keyOriginal = new StringBuilder();
             for (int i = tokenCount - 1; i >= 0; i--) {
                 final Token token = tokens.get(i);
                 if (Token.PINYIN == token.type) {
@@ -109,14 +294,14 @@
                     if (keyPinyin.length() > 0) {
                         keyPinyin.insert(0, ' ');
                     }
-                    if (keyOrignal.length() > 0) {
-                        keyOrignal.insert(0, ' ');
+                    if (keyOriginal.length() > 0) {
+                        keyOriginal.insert(0, ' ');
                     }
                     keyPinyin.insert(0, token.source);
                     keyInitial.insert(0, token.source.charAt(0));
                 }
-                keyOrignal.insert(0, token.source);
-                keys.add(keyOrignal.toString());
+                keyOriginal.insert(0, token.source);
+                keys.add(keyOriginal.toString());
                 keys.add(keyPinyin.toString());
                 keys.add(keyInitial.toString());
             }
@@ -124,89 +309,147 @@
         }
     }
 
+    /**
+     * Traditional Chinese specific locale overrides. Rewrites ICU labels
+     * to correct ICU 4.9 labels.
+     *
+     * TODO: remove once ICU is upgraded to 5.0 and labels are fixed
+     *
+     * sortKey: unchanged from base class (same as name)
+     * nameLookupKeys: unchanged from ChineseContactUtils
+     * labels: unchanged
+     *     Simplified Chinese labels are the same as English: [A-Z], #, " "
+     *     Traditional Chinese labels are stroke count, then English labels:
+     *         [1-18]劃, [A-Z], #, " "
+     */
+    private static class TraditionalChineseContactUtils
+        extends ChineseContactUtils {
+        // Remap ICU 4.9 labels to desired values
+        private static final Map<String, String> labelMap;
+        static {
+            Map<String, String> map = new HashMap<String, String>();
+            final List<String> oldLabels =
+                Arrays.asList("\u4E00", "\u4E01", "\u4E08", "\u4E0D",
+                              "\u4E14", "\u4E1E", "\u4E32", "\u4E26",
+                              "\u4EAD", "\u4E58", "\u4E7E", "\u5080",
+                              "\u4E82", "\u50CE", "\u50F5", "\u5110",
+                              "\u511F", "\u53E2", "\u5133", "\u56B4",
+                              "\u5137", "\u513B", "\u56CC", "\u56D1",
+                              "\u5EF3");
+            int strokeCount = 1;
+            for(String oldLabel : oldLabels) {
+                String newLabel = "" + strokeCount + "\u5283";
+                map.put(oldLabel, newLabel);
+                ++strokeCount;
+            }
+            labelMap = Collections.unmodifiableMap(map);
+        }
+
+        public TraditionalChineseContactUtils(Locale locale) {
+            super(locale);
+        }
+
+        @Override
+        public String getBucketLabel(int bucketIndex) {
+            final String label = super.getBucketLabel(bucketIndex);
+            final String remappedLabel = labelMap.get(label);
+            return remappedLabel != null ? remappedLabel : label;
+        }
+    }
+
     private static final String CHINESE_LANGUAGE = Locale.CHINESE.getLanguage().toLowerCase();
     private static final String JAPANESE_LANGUAGE = Locale.JAPANESE.getLanguage().toLowerCase();
     private static final String KOREAN_LANGUAGE = Locale.KOREAN.getLanguage().toLowerCase();
 
     private static ContactLocaleUtils sSingleton;
-    private final SparseArray<ContactLocaleUtilsBase> mUtils =
-            new SparseArray<ContactLocaleUtilsBase>();
 
-    private final ContactLocaleUtilsBase mBase = new ContactLocaleUtilsBase();
+    private final Locale mLocale;
+    private final String mLanguage;
+    private final ContactLocaleUtilsBase mUtils;
 
-    private String mLanguage;
-
-    private ContactLocaleUtils() {
-        setLocale(null);
-    }
-
-    public void setLocale(Locale currentLocale) {
-        if (currentLocale == null) {
-            mLanguage = Locale.getDefault().getLanguage().toLowerCase();
+    private ContactLocaleUtils(Locale locale) {
+        if (locale == null) {
+            mLocale = Locale.getDefault();
         } else {
-            mLanguage = currentLocale.getLanguage().toLowerCase();
+            mLocale = locale;
         }
-    }
-
-    public String getSortKey(String displayName, int nameStyle) {
-        return getForSort(Integer.valueOf(nameStyle)).getSortKey(displayName);
-    }
-
-    public Iterator<String> getNameLookupKeys(String name, int nameStyle) {
-        return getForNameLookup(Integer.valueOf(nameStyle)).getNameLookupKeys(name);
-    }
-
-    /**
-     *  Determine which utility should be used for generating NameLookupKey.
-     *  <p>
-     *  a. For Western style name, if the current language is Chinese, the
-     *     ChineseContactUtils should be used.
-     *  b. For Chinese and CJK style name if current language is neither Japanese or Korean,
-     *     the ChineseContactUtils should be used.
-     */
-    private ContactLocaleUtilsBase getForNameLookup(Integer nameStyle) {
-        int nameStyleInt = nameStyle.intValue();
-        Integer adjustedUtil = Integer.valueOf(getAdjustedStyle(nameStyleInt));
-        if (CHINESE_LANGUAGE.equals(mLanguage) && nameStyleInt == FullNameStyle.WESTERN) {
-            adjustedUtil = Integer.valueOf(FullNameStyle.CHINESE);
-        }
-        return get(adjustedUtil);
-    }
-
-    private synchronized ContactLocaleUtilsBase get(Integer nameStyle) {
-        ContactLocaleUtilsBase utils = mUtils.get(nameStyle);
-        if (utils == null) {
-            if (nameStyle.intValue() == FullNameStyle.CHINESE) {
-                utils = new ChineseContactUtils();
-                mUtils.put(nameStyle, utils);
+        mLanguage = mLocale.getLanguage().toLowerCase();
+        if (mLanguage.equals(JAPANESE_LANGUAGE)) {
+            mUtils = new JapaneseContactUtils(mLocale);
+        } else if (mLanguage.equals(CHINESE_LANGUAGE)) {
+            if (isLocale(Locale.TRADITIONAL_CHINESE)) {
+                mUtils = new TraditionalChineseContactUtils(mLocale);
+            } else {
+                mUtils = new ChineseContactUtils(mLocale);
             }
+        } else {
+            mUtils = new ContactLocaleUtilsBase(mLocale);
         }
-        return (utils == null) ? mBase : utils;
+        Log.i(TAG, "AddressBook Labels [" + mLocale.toString() + "]: "
+              + getLabels().toString());
     }
 
-    /**
-     *  Determine the which utility should be used for generating sort key.
-     *  <p>
-     *  For Chinese and CJK style name if current language is neither Japanese or Korean,
-     *  the ChineseContactUtils should be used.
-     */
-    private ContactLocaleUtilsBase getForSort(Integer nameStyle) {
-        return get(Integer.valueOf(getAdjustedStyle(nameStyle.intValue())));
+    public boolean isLocale(Locale locale) {
+        return mLocale.equals(locale);
     }
 
-    public static synchronized ContactLocaleUtils getIntance() {
+    public static synchronized ContactLocaleUtils getInstance() {
         if (sSingleton == null) {
-            sSingleton = new ContactLocaleUtils();
+            sSingleton = new ContactLocaleUtils(null);
         }
         return sSingleton;
     }
 
-    private int getAdjustedStyle(int nameStyle) {
-        if (nameStyle == FullNameStyle.CJK  && !JAPANESE_LANGUAGE.equals(mLanguage) &&
-                !KOREAN_LANGUAGE.equals(mLanguage)) {
-            return FullNameStyle.CHINESE;
-        } else {
-            return nameStyle;
+    public static synchronized void setLocale(Locale locale) {
+        if (sSingleton == null || !sSingleton.isLocale(locale)) {
+            sSingleton = new ContactLocaleUtils(locale);
         }
     }
+
+    public String getSortKey(String name, int nameStyle) {
+        return mUtils.getSortKey(name);
+    }
+
+    public int getBucketIndex(String name) {
+        return mUtils.getBucketIndex(name);
+    }
+
+    public int getBucketCount() {
+        return mUtils.getBucketCount();
+    }
+
+    public String getBucketLabel(int bucketIndex) {
+        return mUtils.getBucketLabel(bucketIndex);
+    }
+
+    public String getLabel(String name) {
+        return getBucketLabel(getBucketIndex(name));
+    }
+
+    public ArrayList<String> getLabels() {
+        return mUtils.getLabels();
+    }
+
+    /**
+     *  Determine which utility should be used for generating NameLookupKey.
+     *  (ie, whether we generate Pinyin lookup keys or not)
+     *
+     *  a. For unclassified CJK name, if current locale language is neither
+     *     Japanese nor Korean, use ChineseContactUtils.
+     *  b. If we're sure this is a Chinese name, always use ChineseContactUtils.
+     *  c. Otherwise, use whichever ContactUtils are appropriate for the locale
+     *     (so, Western names in Chinese locale will use ChineseContactUtils)
+     */
+    public Iterator<String> getNameLookupKeys(String name, int nameStyle) {
+        if (nameStyle == FullNameStyle.CJK &&
+            !JAPANESE_LANGUAGE.equals(mLanguage) &&
+            !KOREAN_LANGUAGE.equals(mLanguage)) {
+            return ChineseContactUtils.getPinyinNameLookupKeys(name);
+        }
+        if (nameStyle == FullNameStyle.CHINESE) {
+            return ChineseContactUtils.getPinyinNameLookupKeys(name);
+        }
+        return mUtils.getNameLookupKeys(name);
+    }
+
 }
diff --git a/src/com/android/providers/contacts/ContactsDatabaseHelper.java b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
index 478cd68..6cd0a70 100644
--- a/src/com/android/providers/contacts/ContactsDatabaseHelper.java
+++ b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
@@ -107,7 +107,7 @@
      *   700-799 Jelly Bean
      * </pre>
      */
-    static final int DATABASE_VERSION = 706;
+    static final int DATABASE_VERSION = 707;
 
     private static final String DATABASE_NAME = "contacts2.db";
     private static final String DATABASE_PRESENCE = "presence_db";
@@ -359,6 +359,10 @@
                 + Contacts.SEND_TO_VOICEMAIL;
         public static final String CONCRETE_LOOKUP_KEY = Tables.CONTACTS + "."
                 + Contacts.LOOKUP_KEY;
+        public static final String PHONEBOOK_LABEL_PRIMARY = "phonebook_label";
+        public static final String PHONEBOOK_BUCKET_PRIMARY = "phonebook_bucket";
+        public static final String PHONEBOOK_LABEL_ALTERNATIVE = "phonebook_label_alt";
+        public static final String PHONEBOOK_BUCKET_ALTERNATIVE = "phonebook_bucket_alt";
     }
 
     public interface RawContactsColumns {
@@ -403,7 +407,15 @@
         public static final String CONCRETE_CONTACT_ID =
                 Tables.RAW_CONTACTS + "." + RawContacts.CONTACT_ID;
         public static final String CONCRETE_NAME_VERIFIED =
-                Tables.RAW_CONTACTS + "." + RawContacts.NAME_VERIFIED;
+            Tables.RAW_CONTACTS + "." + RawContacts.NAME_VERIFIED;
+        public static final String PHONEBOOK_LABEL_PRIMARY =
+            ContactsColumns.PHONEBOOK_LABEL_PRIMARY;
+        public static final String PHONEBOOK_BUCKET_PRIMARY =
+            ContactsColumns.PHONEBOOK_BUCKET_PRIMARY;
+        public static final String PHONEBOOK_LABEL_ALTERNATIVE =
+            ContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE;
+        public static final String PHONEBOOK_BUCKET_ALTERNATIVE =
+            ContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE;
     }
 
     public interface ViewRawContactsColumns {
@@ -983,8 +995,12 @@
                 RawContacts.PHONETIC_NAME_STYLE + " TEXT," +
                 RawContacts.SORT_KEY_PRIMARY + " TEXT COLLATE " +
                         ContactsProvider2.PHONEBOOK_COLLATOR_NAME + "," +
+                RawContactsColumns.PHONEBOOK_LABEL_PRIMARY + " TEXT," +
+                RawContactsColumns.PHONEBOOK_BUCKET_PRIMARY + " INTEGER," +
                 RawContacts.SORT_KEY_ALTERNATIVE + " TEXT COLLATE " +
                         ContactsProvider2.PHONEBOOK_COLLATOR_NAME + "," +
+                RawContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE + " TEXT," +
+                RawContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE + " INTEGER," +
                 RawContacts.NAME_VERIFIED + " INTEGER NOT NULL DEFAULT 0," +
                 RawContacts.SYNC1 + " TEXT, " +
                 RawContacts.SYNC2 + " TEXT, " +
@@ -1612,8 +1628,16 @@
                         + " AS " + Contacts.PHONETIC_NAME_STYLE + ", "
                 + "name_raw_contact." + RawContacts.SORT_KEY_PRIMARY
                         + " AS " + Contacts.SORT_KEY_PRIMARY + ", "
+                + "name_raw_contact." + RawContactsColumns.PHONEBOOK_LABEL_PRIMARY
+                        + " AS " + ContactsColumns.PHONEBOOK_LABEL_PRIMARY + ", "
+                + "name_raw_contact." + RawContactsColumns.PHONEBOOK_BUCKET_PRIMARY
+                        + " AS " + ContactsColumns.PHONEBOOK_BUCKET_PRIMARY + ", "
                 + "name_raw_contact." + RawContacts.SORT_KEY_ALTERNATIVE
-                        + " AS " + Contacts.SORT_KEY_ALTERNATIVE;
+                        + " AS " + Contacts.SORT_KEY_ALTERNATIVE + ", "
+                + "name_raw_contact." + RawContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE
+                        + " AS " + ContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE + ", "
+                + "name_raw_contact." + RawContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE
+                        + " AS " + ContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE;
 
         String dataSelect = "SELECT "
                 + DataColumns.CONCRETE_ID + " AS " + Data._ID + ","
@@ -1670,7 +1694,11 @@
                 + RawContacts.PHONETIC_NAME  + ", "
                 + RawContacts.PHONETIC_NAME_STYLE  + ", "
                 + RawContacts.SORT_KEY_PRIMARY  + ", "
+                + RawContactsColumns.PHONEBOOK_LABEL_PRIMARY  + ", "
+                + RawContactsColumns.PHONEBOOK_BUCKET_PRIMARY  + ", "
                 + RawContacts.SORT_KEY_ALTERNATIVE + ", "
+                + RawContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE  + ", "
+                + RawContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE  + ", "
                 + dbForProfile() + " AS " + RawContacts.RAW_CONTACT_IS_USER_PROFILE + ", "
                 + rawContactOptionColumns + ", "
                 + syncColumns
@@ -2408,6 +2436,12 @@
             oldVersion = 706;
         }
 
+        if (oldVersion < 707) {
+            upgradeToVersion707(db);
+            upgradeViewsAndTriggers = true;
+            oldVersion = 707;
+        }
+
         if (upgradeViewsAndTriggers) {
             createContactsViews(db);
             createGroupsView(db);
@@ -2718,8 +2752,7 @@
                 sortKey = sortKeyAlternative = phoneticName;
             } else if (name.fullNameStyle == FullNameStyle.CHINESE ||
                     name.fullNameStyle == FullNameStyle.CJK) {
-                sortKey = sortKeyAlternative = ContactLocaleUtils.getIntance()
-                        .getSortKey(displayName, name.fullNameStyle);
+                sortKey = sortKeyAlternative = displayName;
             }
 
             if (sortKey == null) {
@@ -2776,20 +2809,7 @@
                 organizationUpdate.bindLong(2, dataId);
                 organizationUpdate.execute();
 
-                String sortKey = null;
-                if (phoneticName == null && company != null) {
-                    int nameStyle = splitter.guessFullNameStyle(company);
-                    nameStyle = splitter.getAdjustedFullNameStyle(nameStyle);
-                    if (nameStyle == FullNameStyle.CHINESE ||
-                            nameStyle == FullNameStyle.CJK ) {
-                        sortKey = ContactLocaleUtils.getIntance()
-                                .getSortKey(company, nameStyle);
-                    }
-                }
-
-                if (sortKey == null) {
-                    sortKey = company;
-                }
+                String sortKey = company;
 
                 updateRawContact205(rawContactUpdate, rawContactId, company,
                         company, phoneticNameStyle, phoneticName, sortKey, sortKey);
@@ -3810,6 +3830,17 @@
         }
     }
 
+    private void upgradeToVersion707(SQLiteDatabase db) {
+        db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS
+                + " ADD " + RawContactsColumns.PHONEBOOK_LABEL_PRIMARY + " TEXT;");
+        db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS
+                + " ADD " + RawContactsColumns.PHONEBOOK_BUCKET_PRIMARY + " INTEGER;");
+        db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS
+                + " ADD " + RawContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE + " TEXT;");
+        db.execSQL("ALTER TABLE " + Tables.RAW_CONTACTS
+                + " ADD " + RawContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE + " INTEGER;");
+    }
+
     public String extractHandleFromEmailAddress(String email) {
         Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(email);
         if (tokens.length == 0) {
@@ -5172,9 +5203,7 @@
             }
             if (displayNameStyle == FullNameStyle.CHINESE ||
                     displayNameStyle == FullNameStyle.CJK) {
-                sortKeyPrimary = sortKeyAlternative =
-                        ContactLocaleUtils.getIntance().getSortKey(
-                                sortNamePrimary, displayNameStyle);
+                sortKeyPrimary = sortKeyAlternative = sortNamePrimary;
             }
         }
 
@@ -5183,6 +5212,21 @@
             sortKeyAlternative = sortNameAlternative;
         }
 
+        String phonebookLabelPrimary = "";
+        String phonebookLabelAlternative = "";
+        int phonebookBucketPrimary = 0;
+        int phonebookBucketAlternative = 0;
+        ContactLocaleUtils localeUtils = ContactLocaleUtils.getInstance();
+
+        if (sortKeyPrimary != null) {
+            phonebookBucketPrimary = localeUtils.getBucketIndex(sortKeyPrimary);
+            phonebookLabelPrimary = localeUtils.getBucketLabel(phonebookBucketPrimary);
+        }
+        if (sortKeyAlternative != null) {
+            phonebookBucketAlternative = localeUtils.getBucketIndex(sortKeyAlternative);
+            phonebookLabelAlternative = localeUtils.getBucketLabel(phonebookBucketAlternative);
+        }
+
         if (mRawContactDisplayNameUpdate == null) {
             mRawContactDisplayNameUpdate = db.compileStatement(
                     "UPDATE " + Tables.RAW_CONTACTS +
@@ -5193,7 +5237,11 @@
                             RawContacts.PHONETIC_NAME + "=?," +
                             RawContacts.PHONETIC_NAME_STYLE + "=?," +
                             RawContacts.SORT_KEY_PRIMARY + "=?," +
-                            RawContacts.SORT_KEY_ALTERNATIVE + "=?" +
+                            RawContactsColumns.PHONEBOOK_LABEL_PRIMARY + "=?," +
+                            RawContactsColumns.PHONEBOOK_BUCKET_PRIMARY + "=?," +
+                            RawContacts.SORT_KEY_ALTERNATIVE + "=?," +
+                            RawContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE + "=?," +
+                            RawContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE + "=?" +
                     " WHERE " + RawContacts._ID + "=?");
         }
 
@@ -5203,8 +5251,12 @@
         bindString(mRawContactDisplayNameUpdate, 4, bestPhoneticName);
         mRawContactDisplayNameUpdate.bindLong(5, bestPhoneticNameStyle);
         bindString(mRawContactDisplayNameUpdate, 6, sortKeyPrimary);
-        bindString(mRawContactDisplayNameUpdate, 7, sortKeyAlternative);
-        mRawContactDisplayNameUpdate.bindLong(8, rawContactId);
+        bindString(mRawContactDisplayNameUpdate, 7, phonebookLabelPrimary);
+        mRawContactDisplayNameUpdate.bindLong(8, phonebookBucketPrimary);
+        bindString(mRawContactDisplayNameUpdate, 9, sortKeyAlternative);
+        bindString(mRawContactDisplayNameUpdate, 10, phonebookLabelAlternative);
+        mRawContactDisplayNameUpdate.bindLong(11, phonebookBucketAlternative);
+        mRawContactDisplayNameUpdate.bindLong(12, rawContactId);
         mRawContactDisplayNameUpdate.execute();
     }
 
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index d828458..1d88298 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -565,6 +565,10 @@
             .add(Contacts.SEND_TO_VOICEMAIL)
             .add(Contacts.SORT_KEY_ALTERNATIVE)
             .add(Contacts.SORT_KEY_PRIMARY)
+            .add(ContactsColumns.PHONEBOOK_LABEL_PRIMARY)
+            .add(ContactsColumns.PHONEBOOK_BUCKET_PRIMARY)
+            .add(ContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE)
+            .add(ContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE)
             .add(Contacts.STARRED)
             .add(Contacts.TIMES_CONTACTED)
             .add(Contacts.HAS_PHONE_NUMBER)
@@ -752,6 +756,10 @@
             .add(RawContacts.PHONETIC_NAME_STYLE)
             .add(RawContacts.SORT_KEY_PRIMARY)
             .add(RawContacts.SORT_KEY_ALTERNATIVE)
+            .add(RawContactsColumns.PHONEBOOK_LABEL_PRIMARY)
+            .add(RawContactsColumns.PHONEBOOK_BUCKET_PRIMARY)
+            .add(RawContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE)
+            .add(RawContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE)
             .add(RawContacts.TIMES_CONTACTED)
             .add(RawContacts.LAST_TIME_CONTACTED)
             .add(RawContacts.CUSTOM_RINGTONE)
@@ -1443,7 +1451,7 @@
         mNameLookupBuilder = new StructuredNameLookupBuilder(mNameSplitter);
         mPostalSplitter = new PostalSplitter(mCurrentLocale);
         mCommonNicknameCache = new CommonNicknameCache(mContactsHelper.getReadableDatabase());
-        ContactLocaleUtils.getIntance().setLocale(mCurrentLocale);
+        ContactLocaleUtils.setLocale(mCurrentLocale);
         mContactAggregator = new ContactAggregator(this, mContactsHelper,
                 createPhotoPriorityResolver(context), mNameSplitter, mCommonNicknameCache);
         mContactAggregator.setEnabled(SystemProperties.getBoolean(AGGREGATE_CONTACTS, true));
@@ -6011,8 +6019,10 @@
 
         qb.setStrict(true);
 
+        // Auto-rewrite SORT_KEY_{PRIMARY, ALTERNATIVE} sort orders.
+        String localizedSortOrder = getLocalizedSortOrder(sortOrder);
         Cursor cursor =
-                query(db, qb, projection, selection, selectionArgs, sortOrder, groupBy,
+                query(db, qb, projection, selection, selectionArgs, localizedSortOrder, groupBy,
                 having, limit, cancellationSignal);
 
         if (readBooleanQueryParameter(uri, ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, false)) {
@@ -6028,6 +6038,34 @@
     }
 
 
+    // Rewrites query sort orders using SORT_KEY_{PRIMARY, ALTERNATIVE}
+    // to use PHONEBOOK_BUCKET_{PRIMARY, ALTERNATIVE} as primary key; all
+    // other sort orders are returned unchanged. Preserves ordering
+    // (eg 'DESC') if present.
+    protected static String getLocalizedSortOrder(String sortOrder) {
+        String localizedSortOrder = sortOrder;
+        if (sortOrder != null) {
+            String sortKey;
+            String sortOrderSuffix = "";
+            int spaceIndex = sortOrder.indexOf(' ');
+            if (spaceIndex != -1) {
+                sortKey = sortOrder.substring(0, spaceIndex);
+                sortOrderSuffix = sortOrder.substring(spaceIndex);
+            } else {
+                sortKey = sortOrder;
+            }
+            if (TextUtils.equals(sortKey, Contacts.SORT_KEY_PRIMARY)) {
+                localizedSortOrder = ContactsColumns.PHONEBOOK_BUCKET_PRIMARY
+                    + sortOrderSuffix + ", " + sortOrder;
+            } else if (TextUtils.equals(sortKey, Contacts.SORT_KEY_ALTERNATIVE)) {
+                localizedSortOrder = ContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE
+                    + sortOrderSuffix + ", " + sortOrder;
+            }
+        }
+        return localizedSortOrder;
+    }
+
+
     private Cursor query(final SQLiteDatabase db, SQLiteQueryBuilder qb, String[] projection,
             String selection, String[] selectionArgs, String sortOrder, String groupBy,
             String having, String limit, CancellationSignal cancellationSignal) {
@@ -6122,7 +6160,7 @@
                 final long start = System.currentTimeMillis();
 
                 b = getFastScrollingIndexExtras(queryUri, db, qb, selection, selectionArgs,
-                        sortOrder, countExpression, cancellationSignal, getLocale());
+                        sortOrder, countExpression, cancellationSignal);
 
                 final long end = System.currentTimeMillis();
                 final int time = (int) (end - start);
@@ -6139,19 +6177,22 @@
 
     private static final class AddressBookIndexQuery {
         public static final String NAME = "name";
+        public static final String BUCKET = "bucket";
         public static final String LABEL = "label";
         public static final String COUNT = "count";
 
         public static final String[] COLUMNS = new String[] {
-                NAME, LABEL, COUNT
+            NAME, BUCKET, LABEL, COUNT
         };
 
         public static final int COLUMN_NAME = 0;
-        public static final int COLUMN_LABEL = 1;
-        public static final int COLUMN_COUNT = 2;
+        public static final int COLUMN_BUCKET = 1;
+        public static final int COLUMN_LABEL = 2;
+        public static final int COLUMN_COUNT = 3;
 
-        // PHONEBOOK collator registered in sqlite3_android.cpp
-        public static final String ORDER_BY = NAME + " COLLATE " + PHONEBOOK_COLLATOR_NAME;
+        public static final String GROUP_BY = BUCKET + ", " + LABEL;
+        public static final String ORDER_BY =
+            BUCKET + ", " +  NAME + " COLLATE " + PHONEBOOK_COLLATOR_NAME;
     }
 
     /**
@@ -6161,7 +6202,7 @@
     private static Bundle getFastScrollingIndexExtras(final Uri queryUri, final SQLiteDatabase db,
             final SQLiteQueryBuilder qb, final String selection, final String[] selectionArgs,
             final String sortOrder, String countExpression,
-            final CancellationSignal cancellationSignal, final Locale currentLocale) {
+            final CancellationSignal cancellationSignal) {
         String sortKey;
 
         // The sort order suffix could be something like "DESC".
@@ -6180,69 +6221,53 @@
             sortKey = Contacts.SORT_KEY_PRIMARY;
         }
 
+        String bucketKey;
+        String labelKey;
+        if (TextUtils.equals(sortKey, Contacts.SORT_KEY_PRIMARY)) {
+            bucketKey = ContactsColumns.PHONEBOOK_BUCKET_PRIMARY;
+            labelKey = ContactsColumns.PHONEBOOK_LABEL_PRIMARY;
+        } else if (TextUtils.equals(sortKey, Contacts.SORT_KEY_ALTERNATIVE)) {
+            bucketKey = ContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE;
+            labelKey = ContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE;
+        } else {
+            return null;
+        }
+
         HashMap<String, String> projectionMap = Maps.newHashMap();
         projectionMap.put(AddressBookIndexQuery.NAME,
                 sortKey + " AS " + AddressBookIndexQuery.NAME);
+        projectionMap.put(AddressBookIndexQuery.BUCKET,
+                bucketKey + " AS " + AddressBookIndexQuery.BUCKET);
+        projectionMap.put(AddressBookIndexQuery.LABEL,
+                labelKey + " AS " + AddressBookIndexQuery.LABEL);
 
         // If "what to count" is not specified, we just count all records.
         if (TextUtils.isEmpty(countExpression)) {
             countExpression = "*";
         }
 
-        /**
-         * Use the GET_PHONEBOOK_INDEX function, which is an android extension for SQLite3,
-         * to map the sort key to a character that is traditionally used in phonebooks to
-         * label its section.  For example, in Korean it will be the first consonant in the
-         * letter; for Japanese it will be Hiragana rather than Katakana. Note the label may
-         * be more than one character in some languages, such as "CH" in Czech.
-         */
-        projectionMap.put(AddressBookIndexQuery.LABEL,
-                "GET_PHONEBOOK_INDEX(" + sortKey + ",'" + currentLocale.toString() + "')"
-                        + " AS " + AddressBookIndexQuery.LABEL);
         projectionMap.put(AddressBookIndexQuery.COUNT,
                 "COUNT(" + countExpression + ") AS " + AddressBookIndexQuery.COUNT);
         qb.setProjectionMap(projectionMap);
+        String orderBy = AddressBookIndexQuery.BUCKET + sortOrderSuffix
+            + ", " + AddressBookIndexQuery.NAME + " COLLATE "
+            + PHONEBOOK_COLLATOR_NAME + sortOrderSuffix;
 
         Cursor indexCursor = qb.query(db, AddressBookIndexQuery.COLUMNS, selection, selectionArgs,
-                AddressBookIndexQuery.ORDER_BY, null /* having */,
-                AddressBookIndexQuery.ORDER_BY + sortOrderSuffix,
-                null, cancellationSignal);
+                AddressBookIndexQuery.GROUP_BY, null /* having */,
+                orderBy, null, cancellationSignal);
 
         try {
-            int groupCount = indexCursor.getCount();
-            String labels[] = new String[groupCount];
-            int counts[] = new int[groupCount];
-            int indexCount = 0;
-            String currentLabel = null;
+            int numLabels = indexCursor.getCount();
+            String labels[] = new String[numLabels];
+            int counts[] = new int[numLabels];
 
-            // Since GET_PHONEBOOK_INDEX is a many-to-1 function, we may end up
-            // with multiple entries for the same label.  The following code
-            // collapses those duplicates.
-            for (int i = 0; i < groupCount; i++) {
+            for (int i = 0; i < numLabels; i++) {
                 indexCursor.moveToNext();
-                String label = indexCursor.getString(AddressBookIndexQuery.COLUMN_LABEL);
-                if (label == null) {
-                    label = "";
-                }
-                int count = indexCursor.getInt(AddressBookIndexQuery.COLUMN_COUNT);
-                if (indexCount == 0 || !TextUtils.equals(label, currentLabel)) {
-                    labels[indexCount] = currentLabel = label;
-                    counts[indexCount] = count;
-                    indexCount++;
-                } else {
-                    counts[indexCount - 1] += count;
-                }
+                labels[i] = indexCursor.getString(AddressBookIndexQuery.COLUMN_LABEL);
+                counts[i] = indexCursor.getInt(AddressBookIndexQuery.COLUMN_COUNT);
             }
 
-            if (indexCount < groupCount) {
-                String[] newLabels = new String[indexCount];
-                System.arraycopy(labels, 0, newLabels, 0, indexCount);
-                labels = newLabels;
-
-                int[] newCounts = new int[indexCount];
-                System.arraycopy(counts, 0, newCounts, 0, indexCount);
-                counts = newCounts;
-            }
             return FastScrollingIndexCache.buildExtraBundle(labels, counts);
         } finally {
             indexCursor.close();
diff --git a/src/com/android/providers/contacts/NameLookupBuilder.java b/src/com/android/providers/contacts/NameLookupBuilder.java
index 8375b88..67dae46 100644
--- a/src/com/android/providers/contacts/NameLookupBuilder.java
+++ b/src/com/android/providers/contacts/NameLookupBuilder.java
@@ -321,7 +321,7 @@
 
     private void appendNameShorthandLookup(IndexBuilder builder, String name, int fullNameStyle) {
         Iterator<String> it =
-                ContactLocaleUtils.getIntance().getNameLookupKeys(name, fullNameStyle);
+                ContactLocaleUtils.getInstance().getNameLookupKeys(name, fullNameStyle);
         if (it != null) {
             while (it.hasNext()) {
                 builder.appendName(it.next());
diff --git a/tests/src/com/android/providers/contacts/ContactLocaleUtilsTest.java b/tests/src/com/android/providers/contacts/ContactLocaleUtilsTest.java
index cf18155..b43cff2 100644
--- a/tests/src/com/android/providers/contacts/ContactLocaleUtilsTest.java
+++ b/tests/src/com/android/providers/contacts/ContactLocaleUtilsTest.java
@@ -21,6 +21,7 @@
 import android.test.suitebuilder.annotation.SmallTest;
 
 import java.text.Collator;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -28,7 +29,11 @@
 
 @SmallTest
 public class ContactLocaleUtilsTest extends AndroidTestCase {
+    private static final String PHONE_NUMBER_1 = "+1 (650) 555-1212";
+    private static final String PHONE_NUMBER_2 = "650-555-1212";
     private static final String LATIN_NAME = "John Smith";
+    private static final String LATIN_NAME_2 = "John Paul Jones";
+    private static final String KANJI_NAME = "\u65e5";
     private static final String CHINESE_NAME = "\u675C\u9D51";
     private static final String CHINESE_LATIN_MIX_NAME_1 = "D\u675C\u9D51";
     private static final String CHINESE_LATIN_MIX_NAME_2 = "MARY \u675C\u9D51";
@@ -39,15 +44,87 @@
     private static final String[] CHINESE_LATIN_MIX_NAME_2_KEY = {"\u9D51", "\u675C\u9D51",
         "MARY \u675C\u9D51", "JUAN", "DUJUAN", "MARY DUJUAN", "J", "DJ", "MDJ"};
     private static final String[] LATIN_NAME_KEY = {"John Smith", "Smith", "JS", "S"};
+    private static final String[] LATIN_NAME_KEY_2 = {
+        "John Paul Jones", "Paul Jones", "Jones", "JPJ", "PJ", "J"};
+    private static final String[] LABELS_EN_US = {
+        "", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
+        "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
+        "#", ""};
+    private static final String[] LABELS_JA_JP = {
+        "", "\u3042", "\u304B", "\u3055", "\u305F", "\u306A", "\u306F",
+        "\u307E", "\u3084", "\u3089", "\u308F", "\u4ED6",
+        "", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
+        "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
+        "#", ""};
+    private static final String[] LABELS_ZH_TW = {
+        "", "1\u5283", "2\u5283", "3\u5283", "4\u5283", "5\u5283", "6\u5283",
+        "7\u5283", "8\u5283", "9\u5283", "10\u5283", "11\u5283", "12\u5283",
+        "13\u5283", "14\u5283", "15\u5283", "16\u5283", "17\u5283", "18\u5283",
+        "19\u5283", "20\u5283", "21\u5283", "22\u5283", "23\u5283", "24\u5283",
+        "25\u5283",
+        "", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
+        "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
+        "#", ""};
+    private static final String[] LABELS_KO = {
+        "",  "\u1100", "\u1102", "\u1103", "\u1105", "\u1106", "\u1107",
+        "\u1109", "\u110B", "\u110C", "\u110E", "\u110F", "\u1110", "\u1111",
+        "\u1112",
+        "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
+        "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
+        "#", ""};
 
+    private static final String JAPANESE_MISC = "\u4ed6";
 
-    private ContactLocaleUtils mContactLocaleUtils = ContactLocaleUtils.getIntance();
+    private String getLabel(String name) {
+        ContactLocaleUtils utils = ContactLocaleUtils.getInstance();
+        int bucketIndex = utils.getBucketIndex(name);
+        return utils.getBucketLabel(bucketIndex);
+    }
 
-    public void testContactLocaleUtilsBase() throws Exception {
-        assertEquals(mContactLocaleUtils.getSortKey(LATIN_NAME, FullNameStyle.UNDEFINED),
-                LATIN_NAME);
-        assertNull(mContactLocaleUtils.getNameLookupKeys(LATIN_NAME,
-                FullNameStyle.UNDEFINED));
+    private Iterator<String> getNameLookupKeys(String name, int nameStyle) {
+        ContactLocaleUtils utils = ContactLocaleUtils.getInstance();
+        return utils.getNameLookupKeys(name, nameStyle);
+    }
+
+    private ArrayList<String> getLabels() {
+        ContactLocaleUtils utils = ContactLocaleUtils.getInstance();
+        return utils.getLabels();
+    }
+
+    public void testEnglishContactLocaleUtils() throws Exception {
+        ContactLocaleUtils.setLocale(Locale.ENGLISH);
+        assertEquals("#", getLabel(PHONE_NUMBER_1));
+        assertEquals("#", getLabel(PHONE_NUMBER_2));
+        assertEquals("J", getLabel(LATIN_NAME));
+        assertEquals("", getLabel(CHINESE_NAME));
+        assertEquals("D", getLabel(CHINESE_LATIN_MIX_NAME_1));
+        assertEquals("B", getLabel("Bob Smith"));
+
+        assertNull(getNameLookupKeys(LATIN_NAME, FullNameStyle.UNDEFINED));
+        verifyLabels(getLabels(), LABELS_EN_US);
+    }
+
+    public void testJapaneseContactLocaleUtils() throws Exception {
+        if (!hasJapaneseCollator()) {
+            return;
+        }
+
+        ContactLocaleUtils.setLocale(Locale.JAPAN);
+        assertEquals("#", getLabel(PHONE_NUMBER_1));
+        assertEquals("#", getLabel(PHONE_NUMBER_2));
+        assertEquals(JAPANESE_MISC, getLabel(KANJI_NAME));
+        assertEquals("J", getLabel(LATIN_NAME));
+        assertEquals(JAPANESE_MISC, getLabel(CHINESE_NAME));
+        assertEquals("D", getLabel(CHINESE_LATIN_MIX_NAME_1));
+
+        assertNull(getNameLookupKeys(CHINESE_NAME, FullNameStyle.CJK));
+        Iterator<String> keys = getNameLookupKeys(CHINESE_NAME,
+                FullNameStyle.CHINESE);
+        verifyKeys(keys, CHINESE_NAME_KEY);
+
+        // Following two tests are broken with ICU 4.9
+        verifyLabels(getLabels(), LABELS_JA_JP);
+        assertEquals("B", getLabel("Bob Smith"));
     }
 
     public void testChineseContactLocaleUtils() throws Exception {
@@ -55,56 +132,68 @@
             return;
         }
 
-        assertTrue(mContactLocaleUtils.getSortKey(CHINESE_NAME,
-                FullNameStyle.CHINESE).equalsIgnoreCase("DU \u675C JUAN \u9D51"));
+        ContactLocaleUtils.setLocale(Locale.SIMPLIFIED_CHINESE);
+        assertEquals("#", getLabel(PHONE_NUMBER_1));
+        assertEquals("#", getLabel(PHONE_NUMBER_2));
+        assertEquals("J", getLabel(LATIN_NAME));
+        assertEquals("D", getLabel(CHINESE_NAME));
+        assertEquals("D", getLabel(CHINESE_LATIN_MIX_NAME_1));
+        assertEquals("B", getLabel("Bob Smith"));
+        verifyLabels(getLabels(), LABELS_EN_US);
 
-        assertTrue(mContactLocaleUtils.getSortKey(CHINESE_LATIN_MIX_NAME_1,
-                FullNameStyle.CHINESE).equalsIgnoreCase("d DU \u675C JUAN \u9D51"));
+        ContactLocaleUtils.setLocale(Locale.TRADITIONAL_CHINESE);
+        assertEquals("#", getLabel(PHONE_NUMBER_1));
+        assertEquals("#", getLabel(PHONE_NUMBER_2));
+        assertEquals("J", getLabel(LATIN_NAME));
+        assertEquals("12\u5283", getLabel(CHINESE_NAME));
+        assertEquals("D", getLabel(CHINESE_LATIN_MIX_NAME_1));
 
-        assertTrue(mContactLocaleUtils.getSortKey(CHINESE_LATIN_MIX_NAME_2,
-                FullNameStyle.CHINESE).equalsIgnoreCase("mary DU \u675C JUAN \u9D51"));
-
-        Iterator<String> keys = mContactLocaleUtils.getNameLookupKeys(CHINESE_NAME,
+        ContactLocaleUtils.setLocale(Locale.SIMPLIFIED_CHINESE);
+        Iterator<String> keys = getNameLookupKeys(CHINESE_NAME,
                 FullNameStyle.CHINESE);
         verifyKeys(keys, CHINESE_NAME_KEY);
 
-        keys = mContactLocaleUtils.getNameLookupKeys(CHINESE_LATIN_MIX_NAME_1,
-                FullNameStyle.CHINESE);
+        keys = getNameLookupKeys(CHINESE_LATIN_MIX_NAME_1, FullNameStyle.CHINESE);
         verifyKeys(keys, CHINESE_LATIN_MIX_NAME_1_KEY);
 
-        keys = mContactLocaleUtils.getNameLookupKeys(CHINESE_LATIN_MIX_NAME_2,
-                FullNameStyle.CHINESE);
+        keys = getNameLookupKeys(CHINESE_LATIN_MIX_NAME_2, FullNameStyle.CHINESE);
         verifyKeys(keys, CHINESE_LATIN_MIX_NAME_2_KEY);
+
+        // Following test broken with ICU 4.9
+        ContactLocaleUtils.setLocale(Locale.TRADITIONAL_CHINESE);
+        verifyLabels(getLabels(), LABELS_ZH_TW);
+        assertEquals("B", getLabel("Bob Smith"));
     }
 
     public void testChineseStyleNameWithDifferentLocale() throws Exception {
         if (!hasChineseCollator()) {
             return;
         }
-        mContactLocaleUtils.setLocale(Locale.ENGLISH);
-        assertEquals("DU \u675C JUAN \u9D51",
-                mContactLocaleUtils.getSortKey(CHINESE_NAME, FullNameStyle.CHINESE).toUpperCase());
-        assertEquals("DU \u675C JUAN \u9D51",
-                mContactLocaleUtils.getSortKey(CHINESE_NAME, FullNameStyle.CJK).toUpperCase());
-        mContactLocaleUtils.setLocale(Locale.CHINESE);
-        assertEquals("DU \u675C JUAN \u9D51",
-                mContactLocaleUtils.getSortKey(CHINESE_NAME, FullNameStyle.CHINESE).toUpperCase());
-        assertEquals("DU \u675C JUAN \u9D51",
-                mContactLocaleUtils.getSortKey(CHINESE_NAME, FullNameStyle.CJK).toUpperCase());
-        assertEquals(LATIN_NAME, mContactLocaleUtils.getSortKey(LATIN_NAME, FullNameStyle.WESTERN));
 
-        mContactLocaleUtils.setLocale(Locale.ENGLISH);
-        Iterator<String> keys = mContactLocaleUtils.getNameLookupKeys(CHINESE_NAME,
+        ContactLocaleUtils.setLocale(Locale.ENGLISH);
+        Iterator<String> keys = getNameLookupKeys(CHINESE_NAME,
                 FullNameStyle.CHINESE);
         verifyKeys(keys, CHINESE_NAME_KEY);
-        keys = mContactLocaleUtils.getNameLookupKeys(CHINESE_NAME, FullNameStyle.CJK);
+        keys = getNameLookupKeys(CHINESE_NAME, FullNameStyle.CJK);
         verifyKeys(keys, CHINESE_NAME_KEY);
-        mContactLocaleUtils.setLocale(Locale.CHINESE);
-        keys = mContactLocaleUtils.getNameLookupKeys(CHINESE_NAME, FullNameStyle.CJK);
-        verifyKeys(keys, CHINESE_NAME_KEY);
-        keys = mContactLocaleUtils.getNameLookupKeys(LATIN_NAME, FullNameStyle.WESTERN);
-        verifyKeys(keys, LATIN_NAME_KEY);
 
+        ContactLocaleUtils.setLocale(Locale.CHINESE);
+        keys = getNameLookupKeys(CHINESE_NAME, FullNameStyle.CJK);
+        verifyKeys(keys, CHINESE_NAME_KEY);
+        keys = getNameLookupKeys(LATIN_NAME, FullNameStyle.WESTERN);
+        verifyKeys(keys, LATIN_NAME_KEY);
+        keys = getNameLookupKeys(LATIN_NAME_2, FullNameStyle.WESTERN);
+        verifyKeys(keys, LATIN_NAME_KEY_2);
+
+        ContactLocaleUtils.setLocale(Locale.TRADITIONAL_CHINESE);
+        keys = getNameLookupKeys(CHINESE_NAME, FullNameStyle.CJK);
+        verifyKeys(keys, CHINESE_NAME_KEY);
+    }
+
+    public void testKoreanContactLocaleUtils() throws Exception {
+        ContactLocaleUtils.setLocale(Locale.KOREA);
+        assertEquals("B", getLabel("Bob Smith"));
+        verifyLabels(getLabels(), LABELS_KO);
     }
 
     private void verifyKeys(final Iterator<String> resultKeys, final String[] expectedKeys)
@@ -113,7 +202,14 @@
         while (resultKeys.hasNext()) {
             allKeys.add(resultKeys.next());
         }
-        assertEquals(allKeys, new HashSet<String>(Arrays.asList(expectedKeys)));
+        assertEquals(new HashSet<String>(Arrays.asList(expectedKeys)), allKeys);
+    }
+
+    private void verifyLabels(final ArrayList<String> resultLabels,
+                              final String[] expectedLabels)
+            throws Exception {
+        assertEquals(new ArrayList<String>(Arrays.asList(expectedLabels)),
+                     resultLabels);
     }
 
     private boolean hasChineseCollator() {
@@ -125,4 +221,14 @@
         }
         return false;
     }
+
+    private boolean hasJapaneseCollator() {
+        final Locale locale[] = Collator.getAvailableLocales();
+        for (int i = 0; i < locale.length; i++) {
+            if (locale[i].equals(Locale.JAPAN)) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
index 1011ae2..9bcf54c 100644
--- a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
@@ -69,9 +69,11 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.providers.contacts.ContactsDatabaseHelper;
 import com.android.providers.contacts.ContactsDatabaseHelper.AggregationExceptionColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.ContactsColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.DataUsageStatColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.DbProperties;
 import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
 import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
 import com.android.providers.contacts.tests.R;
 import com.google.android.collect.Lists;
@@ -112,6 +114,10 @@
                 Contacts.PHONETIC_NAME_STYLE,
                 Contacts.SORT_KEY_PRIMARY,
                 Contacts.SORT_KEY_ALTERNATIVE,
+                ContactsColumns.PHONEBOOK_LABEL_PRIMARY,
+                ContactsColumns.PHONEBOOK_BUCKET_PRIMARY,
+                ContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE,
+                ContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE,
                 Contacts.LAST_TIME_CONTACTED,
                 Contacts.TIMES_CONTACTED,
                 Contacts.STARRED,
@@ -146,6 +152,10 @@
                 Contacts.PHONETIC_NAME_STYLE,
                 Contacts.SORT_KEY_PRIMARY,
                 Contacts.SORT_KEY_ALTERNATIVE,
+                ContactsColumns.PHONEBOOK_LABEL_PRIMARY,
+                ContactsColumns.PHONEBOOK_BUCKET_PRIMARY,
+                ContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE,
+                ContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE,
                 Contacts.LAST_TIME_CONTACTED,
                 Contacts.TIMES_CONTACTED,
                 Contacts.STARRED,
@@ -184,6 +194,10 @@
                 Contacts.PHONETIC_NAME_STYLE,
                 Contacts.SORT_KEY_PRIMARY,
                 Contacts.SORT_KEY_ALTERNATIVE,
+                ContactsColumns.PHONEBOOK_LABEL_PRIMARY,
+                ContactsColumns.PHONEBOOK_BUCKET_PRIMARY,
+                ContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE,
+                ContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE,
                 Contacts.LAST_TIME_CONTACTED,
                 Contacts.TIMES_CONTACTED,
                 Contacts.STARRED,
@@ -224,6 +238,10 @@
                 Contacts.PHONETIC_NAME_STYLE,
                 Contacts.SORT_KEY_PRIMARY,
                 Contacts.SORT_KEY_ALTERNATIVE,
+                ContactsColumns.PHONEBOOK_LABEL_PRIMARY,
+                ContactsColumns.PHONEBOOK_BUCKET_PRIMARY,
+                ContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE,
+                ContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE,
                 Contacts.LAST_TIME_CONTACTED,
                 Contacts.TIMES_CONTACTED,
                 Contacts.STARRED,
@@ -271,6 +289,10 @@
                 RawContacts.NAME_VERIFIED,
                 RawContacts.SORT_KEY_PRIMARY,
                 RawContacts.SORT_KEY_ALTERNATIVE,
+                RawContactsColumns.PHONEBOOK_LABEL_PRIMARY,
+                RawContactsColumns.PHONEBOOK_BUCKET_PRIMARY,
+                RawContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE,
+                RawContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE,
                 RawContacts.TIMES_CONTACTED,
                 RawContacts.LAST_TIME_CONTACTED,
                 RawContacts.CUSTOM_RINGTONE,
@@ -337,6 +359,10 @@
                 Contacts.PHONETIC_NAME_STYLE,
                 Contacts.SORT_KEY_PRIMARY,
                 Contacts.SORT_KEY_ALTERNATIVE,
+                ContactsColumns.PHONEBOOK_LABEL_PRIMARY,
+                ContactsColumns.PHONEBOOK_BUCKET_PRIMARY,
+                ContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE,
+                ContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE,
                 Contacts.LAST_TIME_CONTACTED,
                 Contacts.TIMES_CONTACTED,
                 Contacts.STARRED,
@@ -406,6 +432,10 @@
                 Contacts.PHONETIC_NAME_STYLE,
                 Contacts.SORT_KEY_PRIMARY,
                 Contacts.SORT_KEY_ALTERNATIVE,
+                ContactsColumns.PHONEBOOK_LABEL_PRIMARY,
+                ContactsColumns.PHONEBOOK_BUCKET_PRIMARY,
+                ContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE,
+                ContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE,
                 Contacts.LAST_TIME_CONTACTED,
                 Contacts.TIMES_CONTACTED,
                 Contacts.STARRED,
@@ -490,6 +520,10 @@
                 Contacts.PHONETIC_NAME_STYLE,
                 Contacts.SORT_KEY_PRIMARY,
                 Contacts.SORT_KEY_ALTERNATIVE,
+                ContactsColumns.PHONEBOOK_LABEL_PRIMARY,
+                ContactsColumns.PHONEBOOK_BUCKET_PRIMARY,
+                ContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE,
+                ContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE,
                 Contacts.LAST_TIME_CONTACTED,
                 Contacts.TIMES_CONTACTED,
                 Contacts.STARRED,
@@ -1212,7 +1246,6 @@
         // Sanity test. Run tests for "Chilled Guacamole" again and see nothing changes
         // after the Sip address being inserted.
         assertStoredValues(filterUri2, values);
-        assertStoredValues(filterUri3, values);
         assertEquals(0, getCount(filterUri4, null, null));
         assertEquals(0, getCount(filterUri5, null, null));
         assertStoredValues(filterUri6, new ContentValues[] {values1, values2, values3} );
@@ -3034,6 +3067,7 @@
     }
 
     public void testContactWithoutPhoneticName() {
+        ContactLocaleUtils.setLocale(Locale.ENGLISH);
         final long rawContactId = createRawContact(null);
 
         ContentValues values = new ContentValues();
@@ -3051,7 +3085,9 @@
         values.putNull(RawContacts.PHONETIC_NAME);
         values.put(RawContacts.PHONETIC_NAME_STYLE, PhoneticNameStyle.UNDEFINED);
         values.put(RawContacts.SORT_KEY_PRIMARY, "John K. Doe, Jr.");
+        values.put(RawContactsColumns.PHONEBOOK_LABEL_PRIMARY, "J");
         values.put(RawContacts.SORT_KEY_ALTERNATIVE, "Doe, John K., Jr.");
+        values.put(RawContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE, "D");
 
         Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
         assertStoredValues(rawContactUri, values);
@@ -3063,7 +3099,9 @@
         values.putNull(Contacts.PHONETIC_NAME);
         values.put(Contacts.PHONETIC_NAME_STYLE, PhoneticNameStyle.UNDEFINED);
         values.put(Contacts.SORT_KEY_PRIMARY, "John K. Doe, Jr.");
+        values.put(ContactsColumns.PHONEBOOK_LABEL_PRIMARY, "J");
         values.put(Contacts.SORT_KEY_ALTERNATIVE, "Doe, John K., Jr.");
+        values.put(ContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE, "D");
 
         Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI,
                 queryContactId(rawContactId));
@@ -3074,15 +3112,15 @@
     }
 
     public void testContactWithChineseName() {
-
-        // Only run this test when Chinese collation is supported
-        if (!Arrays.asList(Collator.getAvailableLocales()).contains(Locale.CHINA)) {
+        if (!hasChineseCollator()) {
             return;
         }
+        ContactLocaleUtils.setLocale(Locale.SIMPLIFIED_CHINESE);
 
         long rawContactId = createRawContact(null);
 
         ContentValues values = new ContentValues();
+        // "DUAN \u6BB5 XIAO \u5C0F TAO \u6D9B"
         values.put(StructuredName.DISPLAY_NAME, "\u6BB5\u5C0F\u6D9B");
         Uri dataUri = insertStructuredName(rawContactId, values);
 
@@ -3092,8 +3130,10 @@
         values.put(RawContacts.DISPLAY_NAME_ALTERNATIVE, "\u6BB5\u5C0F\u6D9B");
         values.putNull(RawContacts.PHONETIC_NAME);
         values.put(RawContacts.PHONETIC_NAME_STYLE, PhoneticNameStyle.UNDEFINED);
-        values.put(RawContacts.SORT_KEY_PRIMARY, "DUAN \u6BB5 XIAO \u5C0F TAO \u6D9B");
-        values.put(RawContacts.SORT_KEY_ALTERNATIVE, "DUAN \u6BB5 XIAO \u5C0F TAO \u6D9B");
+        values.put(RawContacts.SORT_KEY_PRIMARY, "\u6BB5\u5C0F\u6D9B");
+        values.put(RawContactsColumns.PHONEBOOK_LABEL_PRIMARY, "D");
+        values.put(RawContacts.SORT_KEY_ALTERNATIVE, "\u6BB5\u5C0F\u6D9B");
+        values.put(RawContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE, "D");
 
         Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
         assertStoredValues(rawContactUri, values);
@@ -3104,8 +3144,10 @@
         values.put(Contacts.DISPLAY_NAME_ALTERNATIVE, "\u6BB5\u5C0F\u6D9B");
         values.putNull(Contacts.PHONETIC_NAME);
         values.put(Contacts.PHONETIC_NAME_STYLE, PhoneticNameStyle.UNDEFINED);
-        values.put(Contacts.SORT_KEY_PRIMARY, "DUAN \u6BB5 XIAO \u5C0F TAO \u6D9B");
-        values.put(Contacts.SORT_KEY_ALTERNATIVE, "DUAN \u6BB5 XIAO \u5C0F TAO \u6D9B");
+        values.put(Contacts.SORT_KEY_PRIMARY, "\u6BB5\u5C0F\u6D9B");
+        values.put(ContactsColumns.PHONEBOOK_LABEL_PRIMARY, "D");
+        values.put(Contacts.SORT_KEY_ALTERNATIVE, "\u6BB5\u5C0F\u6D9B");
+        values.put(ContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE, "D");
 
         Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI,
                 queryContactId(rawContactId));
@@ -3116,6 +3158,10 @@
     }
 
     public void testContactWithJapaneseName() {
+        if (!hasJapaneseCollator()) {
+            return;
+        }
+        ContactLocaleUtils.setLocale(Locale.JAPAN);
         long rawContactId = createRawContact(null);
 
         ContentValues values = new ContentValues();
@@ -3131,6 +3177,8 @@
         values.put(RawContacts.PHONETIC_NAME_STYLE, PhoneticNameStyle.JAPANESE);
         values.put(RawContacts.SORT_KEY_PRIMARY, "\u304B\u3044\u304F\u3046");
         values.put(RawContacts.SORT_KEY_ALTERNATIVE, "\u304B\u3044\u304F\u3046");
+        values.put(RawContactsColumns.PHONEBOOK_LABEL_PRIMARY, "\u304B");
+        values.put(RawContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE, "\u304B");
 
         Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
         assertStoredValues(rawContactUri, values);
@@ -3143,6 +3191,8 @@
         values.put(Contacts.PHONETIC_NAME_STYLE, PhoneticNameStyle.JAPANESE);
         values.put(Contacts.SORT_KEY_PRIMARY, "\u304B\u3044\u304F\u3046");
         values.put(Contacts.SORT_KEY_ALTERNATIVE, "\u304B\u3044\u304F\u3046");
+        values.put(ContactsColumns.PHONEBOOK_LABEL_PRIMARY, "\u304B");
+        values.put(ContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE, "\u304B");
 
         Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI,
                 queryContactId(rawContactId));
@@ -3230,10 +3280,16 @@
         values.put(Contacts.PHONETIC_NAME_STYLE, PhoneticNameStyle.UNDEFINED);
         values.put(Contacts.SORT_KEY_PRIMARY, "Monsters Inc");
         values.put(Contacts.SORT_KEY_ALTERNATIVE, "Monsters Inc");
+        values.put(ContactsColumns.PHONEBOOK_LABEL_PRIMARY, "M");
+        values.put(ContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE, "M");
         assertStoredValues(uri, values);
     }
 
     public void testDisplayNameFromOrganizationWithJapanesePhoneticName() {
+        if (!hasJapaneseCollator()) {
+            return;
+        }
+        ContactLocaleUtils.setLocale(Locale.JAPAN);
         long rawContactId = createRawContact();
         long contactId = queryContactId(rawContactId);
         ContentValues values = new ContentValues();
@@ -3252,22 +3308,16 @@
         values.put(Contacts.PHONETIC_NAME_STYLE, PhoneticNameStyle.JAPANESE);
         values.put(Contacts.SORT_KEY_PRIMARY, "\u30C9\u30B3\u30E2");
         values.put(Contacts.SORT_KEY_ALTERNATIVE, "\u30C9\u30B3\u30E2");
+        values.put(ContactsColumns.PHONEBOOK_LABEL_PRIMARY, "\u305F");
+        values.put(ContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE, "\u305F");
         assertStoredValues(uri, values);
     }
 
     public void testDisplayNameFromOrganizationWithChineseName() {
-        boolean hasChineseCollator = false;
-        final Locale locale[] = Collator.getAvailableLocales();
-        for (int i = 0; i < locale.length; i++) {
-            if (locale[i].equals(Locale.CHINA)) {
-                hasChineseCollator = true;
-                break;
-            }
-        }
-
-        if (!hasChineseCollator) {
+        if (!hasChineseCollator()) {
             return;
         }
+        ContactLocaleUtils.setLocale(Locale.SIMPLIFIED_CHINESE);
 
         long rawContactId = createRawContact();
         long contactId = queryContactId(rawContactId);
@@ -3284,8 +3334,10 @@
         values.put(Contacts.DISPLAY_NAME, "\u4E2D\u56FD\u7535\u4FE1");
         values.putNull(Contacts.PHONETIC_NAME);
         values.put(Contacts.PHONETIC_NAME_STYLE, PhoneticNameStyle.UNDEFINED);
-        values.put(Contacts.SORT_KEY_PRIMARY, "ZHONG \u4E2D GUO \u56FD DIAN \u7535 XIN \u4FE1");
-        values.put(Contacts.SORT_KEY_ALTERNATIVE, "ZHONG \u4E2D GUO \u56FD DIAN \u7535 XIN \u4FE1");
+        values.put(Contacts.SORT_KEY_PRIMARY, "\u4E2D\u56FD\u7535\u4FE1");
+        values.put(ContactsColumns.PHONEBOOK_LABEL_PRIMARY, "Z");
+        values.put(Contacts.SORT_KEY_ALTERNATIVE, "\u4E2D\u56FD\u7535\u4FE1");
+        values.put(ContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE, "Z");
         assertStoredValues(uri, values);
     }
 
@@ -6186,6 +6238,23 @@
         testChangingPrimary(true, true);
     }
 
+    public void testContactSortOrder() {
+        assertEquals(ContactsColumns.PHONEBOOK_BUCKET_PRIMARY + ", "
+                     + Contacts.SORT_KEY_PRIMARY,
+                     ContactsProvider2.getLocalizedSortOrder(Contacts.SORT_KEY_PRIMARY));
+        assertEquals(ContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE + ", "
+                     + Contacts.SORT_KEY_ALTERNATIVE,
+                     ContactsProvider2.getLocalizedSortOrder(Contacts.SORT_KEY_ALTERNATIVE));
+        assertEquals(ContactsColumns.PHONEBOOK_BUCKET_PRIMARY + " DESC, "
+                     + Contacts.SORT_KEY_PRIMARY + " DESC",
+                     ContactsProvider2.getLocalizedSortOrder(Contacts.SORT_KEY_PRIMARY + " DESC"));
+        String suffix = " COLLATE LOCALIZED DESC";
+        assertEquals(ContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE + suffix
+                     + ", " + Contacts.SORT_KEY_ALTERNATIVE + suffix,
+                     ContactsProvider2.getLocalizedSortOrder(Contacts.SORT_KEY_ALTERNATIVE
+                                                             + suffix));
+    }
+
     public void testContactCounts() {
         Uri uri = Contacts.CONTENT_URI.buildUpon()
                 .appendQueryParameter(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true").build();
@@ -6201,7 +6270,7 @@
 
         Cursor cursor = mResolver.query(uri,
                 new String[]{Contacts.DISPLAY_NAME},
-                null, null, Contacts.SORT_KEY_PRIMARY + " COLLATE LOCALIZED");
+                null, null, Contacts.SORT_KEY_PRIMARY);
 
         assertFirstLetterValues(cursor, "", "B", "J", "M", "R", "T");
         assertFirstLetterCounts(cursor,    1,   1,   1,   2,   2,   1);
@@ -7266,4 +7335,24 @@
                 .appendQueryParameter(DataUsageFeedback.USAGE_TYPE, usageType)
                 .build(), new ContentValues(), null, null);
     }
+
+    private boolean hasChineseCollator() {
+        final Locale locale[] = Collator.getAvailableLocales();
+        for (int i = 0; i < locale.length; i++) {
+            if (locale[i].equals(Locale.CHINA)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean hasJapaneseCollator() {
+        final Locale locale[] = Collator.getAvailableLocales();
+        for (int i = 0; i < locale.length; i++) {
+            if (locale[i].equals(Locale.JAPAN)) {
+                return true;
+            }
+        }
+        return false;
+    }
 }