Adding android.icu.text.AlphabeticIndex implementation for AlphabeticIndexCompat
as libcore.icu.AlphabeticIndex is no longer available in N

Bug: 28795251
Change-Id: I3b168dfb451c0eac9b64c6559a51d2b1b8c578b9
diff --git a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
index ec1fb66..13ed1a2 100644
--- a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
+++ b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
@@ -1,113 +1,49 @@
 package com.android.launcher3.compat;
 
 import android.content.Context;
+import android.content.res.Configuration;
+import android.util.Log;
+
 import com.android.launcher3.Utilities;
 
-import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
 import java.util.Locale;
 
-/**
- * Fallback class to support Alphabetic indexing if not supported by the framework.
- * TODO(winsonc): disable for non-english locales
- */
-class BaseAlphabeticIndex {
-
-    private static final String BUCKETS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-";
-    private static final int UNKNOWN_BUCKET_INDEX = BUCKETS.length() - 1;
-
-    public BaseAlphabeticIndex() {}
-
-    /**
-     * Sets the max number of the label buckets in this index.
-     */
-    public void setMaxLabelCount(int count) {
-        // Not currently supported
-    }
-
-    /**
-     * Returns the index of the bucket in which the given string should appear.
-     */
-    protected int getBucketIndex(String s) {
-        if (s.isEmpty()) {
-            return UNKNOWN_BUCKET_INDEX;
-        }
-        int index = BUCKETS.indexOf(s.substring(0, 1).toUpperCase());
-        if (index != -1) {
-            return index;
-        }
-        return UNKNOWN_BUCKET_INDEX;
-    }
-
-    /**
-     * Returns the label for the bucket at the given index (as returned by getBucketIndex).
-     */
-    protected String getBucketLabel(int index) {
-        return BUCKETS.substring(index, index + 1);
-    }
-}
-
-/**
- * Reflected libcore.icu.AlphabeticIndex implementation, falls back to the base alphabetic index.
- */
-public class AlphabeticIndexCompat extends BaseAlphabeticIndex {
+public class AlphabeticIndexCompat {
+    private static final String TAG = "AlphabeticIndexCompat";
 
     private static final String MID_DOT = "\u2219";
-
-    private Object mAlphabeticIndex;
-    private Method mAddLabelsMethod;
-    private Method mSetMaxLabelCountMethod;
-    private Method mGetBucketIndexMethod;
-    private Method mGetBucketLabelMethod;
-    private boolean mHasValidAlphabeticIndex;
-    private String mDefaultMiscLabel;
+    private final BaseIndex mBaseIndex;
+    private final String mDefaultMiscLabel;
 
     public AlphabeticIndexCompat(Context context) {
-        super();
-        try {
-            Locale curLocale = context.getResources().getConfiguration().locale;
-            Class clazz = Class.forName("libcore.icu.AlphabeticIndex");
-            Constructor ctor = clazz.getConstructor(Locale.class);
-            mAddLabelsMethod = clazz.getDeclaredMethod("addLabels", Locale.class);
-            mSetMaxLabelCountMethod = clazz.getDeclaredMethod("setMaxLabelCount", int.class);
-            mGetBucketIndexMethod = clazz.getDeclaredMethod("getBucketIndex", String.class);
-            mGetBucketLabelMethod = clazz.getDeclaredMethod("getBucketLabel", int.class);
-            mAlphabeticIndex = ctor.newInstance(curLocale);
-            try {
-                // Ensure we always have some base English locale buckets
-                if (!curLocale.getLanguage().equals(Locale.ENGLISH.getLanguage())) {
-                    mAddLabelsMethod.invoke(mAlphabeticIndex, Locale.ENGLISH);
-                }
-            } catch (Exception e) {
-                e.printStackTrace();
-            }
-            if (curLocale.getLanguage().equals(Locale.JAPANESE.getLanguage())) {
-                // Japanese character 他 ("misc")
-                mDefaultMiscLabel = "\u4ed6";
-                // TODO(winsonc, omakoto): We need to handle Japanese sections better, especially the kanji
-            } else {
-                // Dot
-                mDefaultMiscLabel = MID_DOT;
-            }
-            mHasValidAlphabeticIndex = true;
-        } catch (Exception e) {
-            mHasValidAlphabeticIndex = false;
-        }
-    }
+        BaseIndex index = null;
 
-    /**
-     * Sets the max number of the label buckets in this index.
-     * (ICU 51 default is 99)
-     */
-    public void setMaxLabelCount(int count) {
-        if (mHasValidAlphabeticIndex) {
-            try {
-                mSetMaxLabelCountMethod.invoke(mAlphabeticIndex, count);
-            } catch (Exception e) {
-                e.printStackTrace();
+        try {
+            if (Utilities.isNycOrAbove()) {
+                index = new AlphabeticIndexVN(context);
             }
+        } catch (Exception e) {
+            Log.d(TAG, "Unable to load the system index", e);
+        }
+        if (index == null) {
+            try {
+                index = new AlphabeticIndexV16(context);
+            } catch (Exception e) {
+                Log.d(TAG, "Unable to load the system index", e);
+            }
+        }
+
+        mBaseIndex = index == null ? new BaseIndex() : index;
+
+        if (context.getResources().getConfiguration().locale
+                .getLanguage().equals(Locale.JAPANESE.getLanguage())) {
+            // Japanese character 他 ("misc")
+            mDefaultMiscLabel = "\u4ed6";
+            // TODO(winsonc, omakoto): We need to handle Japanese sections better, especially the kanji
         } else {
-            super.setMaxLabelCount(count);
+            // Dot
+            mDefaultMiscLabel = MID_DOT;
         }
     }
 
@@ -116,7 +52,7 @@
      */
     public String computeSectionName(CharSequence cs) {
         String s = Utilities.trim(cs);
-        String sectionName = getBucketLabel(getBucketIndex(s));
+        String sectionName = mBaseIndex.getBucketLabel(mBaseIndex.getBucketIndex(s));
         if (Utilities.trim(sectionName).isEmpty() && s.length() > 0) {
             int c = s.codePointAt(0);
             boolean startsWithDigit = Character.isDigit(c);
@@ -139,32 +75,150 @@
     }
 
     /**
-     * Returns the index of the bucket in which {@param s} should appear.
-     * Function is synchronized because underlying routine walks an iterator
-     * whose state is maintained inside the index object.
+     * Base class to support Alphabetic indexing if not supported by the framework.
+     * TODO(winsonc): disable for non-english locales
      */
-    protected int getBucketIndex(String s) {
-        if (mHasValidAlphabeticIndex) {
+    private static class BaseIndex {
+
+        private static final String BUCKETS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-";
+        private static final int UNKNOWN_BUCKET_INDEX = BUCKETS.length() - 1;
+
+        /**
+         * Returns the index of the bucket in which the given string should appear.
+         */
+        protected int getBucketIndex(String s) {
+            if (s.isEmpty()) {
+                return UNKNOWN_BUCKET_INDEX;
+            }
+            int index = BUCKETS.indexOf(s.substring(0, 1).toUpperCase());
+            if (index != -1) {
+                return index;
+            }
+            return UNKNOWN_BUCKET_INDEX;
+        }
+
+        /**
+         * Returns the label for the bucket at the given index (as returned by getBucketIndex).
+         */
+        protected String getBucketLabel(int index) {
+            return BUCKETS.substring(index, index + 1);
+        }
+    }
+
+    /**
+     * Reflected libcore.icu.AlphabeticIndex implementation, falls back to the base
+     * alphabetic index.
+     */
+    private static class AlphabeticIndexV16 extends BaseIndex {
+
+        private Object mAlphabeticIndex;
+        private Method mGetBucketIndexMethod;
+        private Method mGetBucketLabelMethod;
+
+        public AlphabeticIndexV16(Context context) throws Exception {
+            Locale curLocale = context.getResources().getConfiguration().locale;
+            Class clazz = Class.forName("libcore.icu.AlphabeticIndex");
+            mGetBucketIndexMethod = clazz.getDeclaredMethod("getBucketIndex", String.class);
+            mGetBucketLabelMethod = clazz.getDeclaredMethod("getBucketLabel", int.class);
+            mAlphabeticIndex = clazz.getConstructor(Locale.class).newInstance(curLocale);
+
+            if (!curLocale.getLanguage().equals(Locale.ENGLISH.getLanguage())) {
+                clazz.getDeclaredMethod("addLabels", Locale.class)
+                        .invoke(mAlphabeticIndex, Locale.ENGLISH);
+            }
+        }
+
+        /**
+         * Returns the index of the bucket in which {@param s} should appear.
+         * Function is synchronized because underlying routine walks an iterator
+         * whose state is maintained inside the index object.
+         */
+        protected int getBucketIndex(String s) {
             try {
                 return (Integer) mGetBucketIndexMethod.invoke(mAlphabeticIndex, s);
             } catch (Exception e) {
                 e.printStackTrace();
             }
+            return super.getBucketIndex(s);
         }
-        return super.getBucketIndex(s);
-    }
 
-    /**
-     * Returns the label for the bucket at the given index (as returned by getBucketIndex).
-     */
-    protected String getBucketLabel(int index) {
-        if (mHasValidAlphabeticIndex) {
+        /**
+         * Returns the label for the bucket at the given index (as returned by getBucketIndex).
+         */
+        protected String getBucketLabel(int index) {
             try {
                 return (String) mGetBucketLabelMethod.invoke(mAlphabeticIndex, index);
             } catch (Exception e) {
                 e.printStackTrace();
             }
+            return super.getBucketLabel(index);
         }
-        return super.getBucketLabel(index);
+    }
+
+    /**
+     * Reflected android.icu.text.AlphabeticIndex implementation, falls back to the base
+     * alphabetic index.
+     */
+    private static class AlphabeticIndexVN extends BaseIndex {
+
+        private Object mAlphabeticIndex;
+        private Method mGetBucketIndexMethod;
+
+        private Method mGetBucketMethod;
+        private Method mGetLabelMethod;
+
+        public AlphabeticIndexVN(Context context) throws Exception {
+            // TODO: Replace this with locale list once available.
+            Object locales = Configuration.class.getDeclaredMethod("getLocales").invoke(
+                    context.getResources().getConfiguration());
+            int localeCount = (Integer) locales.getClass().getDeclaredMethod("size").invoke(locales);
+            Method localeGetter = locales.getClass().getDeclaredMethod("get", int.class);
+            Locale primaryLocale = localeCount == 0 ? Locale.ENGLISH :
+                    (Locale) localeGetter.invoke(locales, 0);
+
+            Class clazz = Class.forName("android.icu.text.AlphabeticIndex");
+            mAlphabeticIndex = clazz.getConstructor(Locale.class).newInstance(primaryLocale);
+
+            Method addLocales = clazz.getDeclaredMethod("addLabels", Locale[].class);
+            for (int i = 1; i < localeCount; i++) {
+                Locale l = (Locale) localeGetter.invoke(locales, i);
+                addLocales.invoke(mAlphabeticIndex, new Object[]{ new Locale[] {l}});
+            }
+            addLocales.invoke(mAlphabeticIndex, new Object[]{ new Locale[] {Locale.ENGLISH}});
+
+            mAlphabeticIndex = mAlphabeticIndex.getClass()
+                    .getDeclaredMethod("buildImmutableIndex")
+                    .invoke(mAlphabeticIndex);
+
+            mGetBucketIndexMethod = mAlphabeticIndex.getClass().getDeclaredMethod(
+                    "getBucketIndex", CharSequence.class);
+            mGetBucketMethod = mAlphabeticIndex.getClass().getDeclaredMethod("getBucket", int.class);
+            mGetLabelMethod = mGetBucketMethod.getReturnType().getDeclaredMethod("getLabel");
+        }
+
+        /**
+         * Returns the index of the bucket in which {@param s} should appear.
+         */
+        protected int getBucketIndex(String s) {
+            try {
+                return (Integer) mGetBucketIndexMethod.invoke(mAlphabeticIndex, s);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+            return super.getBucketIndex(s);
+        }
+
+        /**
+         * Returns the label for the bucket at the given index
+         */
+        protected String getBucketLabel(int index) {
+            try {
+                return (String) mGetLabelMethod.invoke(
+                        mGetBucketMethod.invoke(mAlphabeticIndex, index));
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+            return super.getBucketLabel(index);
+        }
     }
 }