Home | History | Annotate | Download | only in compat
      1 package com.android.launcher3.compat;
      2 
      3 import android.content.Context;
      4 import android.content.res.Configuration;
      5 import android.util.Log;
      6 
      7 import com.android.launcher3.Utilities;
      8 
      9 import java.lang.reflect.Method;
     10 import java.util.Locale;
     11 
     12 public class AlphabeticIndexCompat {
     13     private static final String TAG = "AlphabeticIndexCompat";
     14 
     15     private static final String MID_DOT = "\u2219";
     16     private final BaseIndex mBaseIndex;
     17     private final String mDefaultMiscLabel;
     18 
     19     public AlphabeticIndexCompat(Context context) {
     20         BaseIndex index = null;
     21 
     22         try {
     23             if (Utilities.ATLEAST_N) {
     24                 index = new AlphabeticIndexVN(context);
     25             }
     26         } catch (Exception e) {
     27             Log.d(TAG, "Unable to load the system index", e);
     28         }
     29         if (index == null) {
     30             try {
     31                 index = new AlphabeticIndexV16(context);
     32             } catch (Exception e) {
     33                 Log.d(TAG, "Unable to load the system index", e);
     34             }
     35         }
     36 
     37         mBaseIndex = index == null ? new BaseIndex() : index;
     38 
     39         if (context.getResources().getConfiguration().locale
     40                 .getLanguage().equals(Locale.JAPANESE.getLanguage())) {
     41             // Japanese character  ("misc")
     42             mDefaultMiscLabel = "\u4ed6";
     43             // TODO(winsonc, omakoto): We need to handle Japanese sections better, especially the kanji
     44         } else {
     45             // Dot
     46             mDefaultMiscLabel = MID_DOT;
     47         }
     48     }
     49 
     50     /**
     51      * Computes the section name for an given string {@param s}.
     52      */
     53     public String computeSectionName(CharSequence cs) {
     54         String s = Utilities.trim(cs);
     55         String sectionName = mBaseIndex.getBucketLabel(mBaseIndex.getBucketIndex(s));
     56         if (Utilities.trim(sectionName).isEmpty() && s.length() > 0) {
     57             int c = s.codePointAt(0);
     58             boolean startsWithDigit = Character.isDigit(c);
     59             if (startsWithDigit) {
     60                 // Digit section
     61                 return "#";
     62             } else {
     63                 boolean startsWithLetter = Character.isLetter(c);
     64                 if (startsWithLetter) {
     65                     return mDefaultMiscLabel;
     66                 } else {
     67                     // In languages where these differ, this ensures that we differentiate
     68                     // between the misc section in the native language and a misc section
     69                     // for everything else.
     70                     return MID_DOT;
     71                 }
     72             }
     73         }
     74         return sectionName;
     75     }
     76 
     77     /**
     78      * Base class to support Alphabetic indexing if not supported by the framework.
     79      * TODO(winsonc): disable for non-english locales
     80      */
     81     private static class BaseIndex {
     82 
     83         private static final String BUCKETS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-";
     84         private static final int UNKNOWN_BUCKET_INDEX = BUCKETS.length() - 1;
     85 
     86         /**
     87          * Returns the index of the bucket in which the given string should appear.
     88          */
     89         protected int getBucketIndex(String s) {
     90             if (s.isEmpty()) {
     91                 return UNKNOWN_BUCKET_INDEX;
     92             }
     93             int index = BUCKETS.indexOf(s.substring(0, 1).toUpperCase());
     94             if (index != -1) {
     95                 return index;
     96             }
     97             return UNKNOWN_BUCKET_INDEX;
     98         }
     99 
    100         /**
    101          * Returns the label for the bucket at the given index (as returned by getBucketIndex).
    102          */
    103         protected String getBucketLabel(int index) {
    104             return BUCKETS.substring(index, index + 1);
    105         }
    106     }
    107 
    108     /**
    109      * Reflected libcore.icu.AlphabeticIndex implementation, falls back to the base
    110      * alphabetic index.
    111      */
    112     private static class AlphabeticIndexV16 extends BaseIndex {
    113 
    114         private Object mAlphabeticIndex;
    115         private Method mGetBucketIndexMethod;
    116         private Method mGetBucketLabelMethod;
    117 
    118         public AlphabeticIndexV16(Context context) throws Exception {
    119             Locale curLocale = context.getResources().getConfiguration().locale;
    120             Class clazz = Class.forName("libcore.icu.AlphabeticIndex");
    121             mGetBucketIndexMethod = clazz.getDeclaredMethod("getBucketIndex", String.class);
    122             mGetBucketLabelMethod = clazz.getDeclaredMethod("getBucketLabel", int.class);
    123             mAlphabeticIndex = clazz.getConstructor(Locale.class).newInstance(curLocale);
    124 
    125             if (!curLocale.getLanguage().equals(Locale.ENGLISH.getLanguage())) {
    126                 clazz.getDeclaredMethod("addLabels", Locale.class)
    127                         .invoke(mAlphabeticIndex, Locale.ENGLISH);
    128             }
    129         }
    130 
    131         /**
    132          * Returns the index of the bucket in which {@param s} should appear.
    133          * Function is synchronized because underlying routine walks an iterator
    134          * whose state is maintained inside the index object.
    135          */
    136         protected int getBucketIndex(String s) {
    137             try {
    138                 return (Integer) mGetBucketIndexMethod.invoke(mAlphabeticIndex, s);
    139             } catch (Exception e) {
    140                 e.printStackTrace();
    141             }
    142             return super.getBucketIndex(s);
    143         }
    144 
    145         /**
    146          * Returns the label for the bucket at the given index (as returned by getBucketIndex).
    147          */
    148         protected String getBucketLabel(int index) {
    149             try {
    150                 return (String) mGetBucketLabelMethod.invoke(mAlphabeticIndex, index);
    151             } catch (Exception e) {
    152                 e.printStackTrace();
    153             }
    154             return super.getBucketLabel(index);
    155         }
    156     }
    157 
    158     /**
    159      * Reflected android.icu.text.AlphabeticIndex implementation, falls back to the base
    160      * alphabetic index.
    161      */
    162     private static class AlphabeticIndexVN extends BaseIndex {
    163 
    164         private Object mAlphabeticIndex;
    165         private Method mGetBucketIndexMethod;
    166 
    167         private Method mGetBucketMethod;
    168         private Method mGetLabelMethod;
    169 
    170         public AlphabeticIndexVN(Context context) throws Exception {
    171             // TODO: Replace this with locale list once available.
    172             Object locales = Configuration.class.getDeclaredMethod("getLocales").invoke(
    173                     context.getResources().getConfiguration());
    174             int localeCount = (Integer) locales.getClass().getDeclaredMethod("size").invoke(locales);
    175             Method localeGetter = locales.getClass().getDeclaredMethod("get", int.class);
    176             Locale primaryLocale = localeCount == 0 ? Locale.ENGLISH :
    177                     (Locale) localeGetter.invoke(locales, 0);
    178 
    179             Class clazz = Class.forName("android.icu.text.AlphabeticIndex");
    180             mAlphabeticIndex = clazz.getConstructor(Locale.class).newInstance(primaryLocale);
    181 
    182             Method addLocales = clazz.getDeclaredMethod("addLabels", Locale[].class);
    183             for (int i = 1; i < localeCount; i++) {
    184                 Locale l = (Locale) localeGetter.invoke(locales, i);
    185                 addLocales.invoke(mAlphabeticIndex, new Object[]{ new Locale[] {l}});
    186             }
    187             addLocales.invoke(mAlphabeticIndex, new Object[]{ new Locale[] {Locale.ENGLISH}});
    188 
    189             mAlphabeticIndex = mAlphabeticIndex.getClass()
    190                     .getDeclaredMethod("buildImmutableIndex")
    191                     .invoke(mAlphabeticIndex);
    192 
    193             mGetBucketIndexMethod = mAlphabeticIndex.getClass().getDeclaredMethod(
    194                     "getBucketIndex", CharSequence.class);
    195             mGetBucketMethod = mAlphabeticIndex.getClass().getDeclaredMethod("getBucket", int.class);
    196             mGetLabelMethod = mGetBucketMethod.getReturnType().getDeclaredMethod("getLabel");
    197         }
    198 
    199         /**
    200          * Returns the index of the bucket in which {@param s} should appear.
    201          */
    202         protected int getBucketIndex(String s) {
    203             try {
    204                 return (Integer) mGetBucketIndexMethod.invoke(mAlphabeticIndex, s);
    205             } catch (Exception e) {
    206                 e.printStackTrace();
    207             }
    208             return super.getBucketIndex(s);
    209         }
    210 
    211         /**
    212          * Returns the label for the bucket at the given index
    213          */
    214         protected String getBucketLabel(int index) {
    215             try {
    216                 return (String) mGetLabelMethod.invoke(
    217                         mGetBucketMethod.invoke(mAlphabeticIndex, index));
    218             } catch (Exception e) {
    219                 e.printStackTrace();
    220             }
    221             return super.getBucketLabel(index);
    222         }
    223     }
    224 }
    225