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; public class AlphabeticIndexCompat { private static final String TAG = "AlphabeticIndexCompat"; private static final String MID_DOT = "\u2219"; private final BaseIndex mBaseIndex; private final String mDefaultMiscLabel; public AlphabeticIndexCompat(Context context) { BaseIndex index = null; 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 { // Dot mDefaultMiscLabel = MID_DOT; } } /** * Computes the section name for an given string {@param s}. */ public String computeSectionName(CharSequence cs) { String s = Utilities.trim(cs); 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); if (startsWithDigit) { // Digit section return "#"; } else { boolean startsWithLetter = Character.isLetter(c); if (startsWithLetter) { return mDefaultMiscLabel; } else { // In languages where these differ, this ensures that we differentiate // between the misc section in the native language and a misc section // for everything else. return MID_DOT; } } } return sectionName; } /** * Base class to support Alphabetic indexing if not supported by the framework. * TODO(winsonc): disable for non-english locales */ 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); } /** * 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); } } /** * 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); } } }