1package com.android.launcher3.compat;
2
3import android.content.Context;
4import com.android.launcher3.Utilities;
5
6import java.lang.reflect.Constructor;
7import java.lang.reflect.Method;
8import java.util.Locale;
9
10/**
11 * Fallback class to support Alphabetic indexing if not supported by the framework.
12 * TODO(winsonc): disable for non-english locales
13 */
14class BaseAlphabeticIndex {
15
16    private static final String BUCKETS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-";
17    private static final int UNKNOWN_BUCKET_INDEX = BUCKETS.length() - 1;
18
19    public BaseAlphabeticIndex() {}
20
21    /**
22     * Sets the max number of the label buckets in this index.
23     */
24    public void setMaxLabelCount(int count) {
25        // Not currently supported
26    }
27
28    /**
29     * Returns the index of the bucket in which the given string should appear.
30     */
31    protected int getBucketIndex(String s) {
32        if (s.isEmpty()) {
33            return UNKNOWN_BUCKET_INDEX;
34        }
35        int index = BUCKETS.indexOf(s.substring(0, 1).toUpperCase());
36        if (index != -1) {
37            return index;
38        }
39        return UNKNOWN_BUCKET_INDEX;
40    }
41
42    /**
43     * Returns the label for the bucket at the given index (as returned by getBucketIndex).
44     */
45    protected String getBucketLabel(int index) {
46        return BUCKETS.substring(index, index + 1);
47    }
48}
49
50/**
51 * Reflected libcore.icu.AlphabeticIndex implementation, falls back to the base alphabetic index.
52 */
53public class AlphabeticIndexCompat extends BaseAlphabeticIndex {
54
55    private static final String MID_DOT = "\u2219";
56
57    private Object mAlphabeticIndex;
58    private Method mAddLabelsMethod;
59    private Method mSetMaxLabelCountMethod;
60    private Method mGetBucketIndexMethod;
61    private Method mGetBucketLabelMethod;
62    private boolean mHasValidAlphabeticIndex;
63    private String mDefaultMiscLabel;
64
65    public AlphabeticIndexCompat(Context context) {
66        super();
67        try {
68            Locale curLocale = context.getResources().getConfiguration().locale;
69            Class clazz = Class.forName("libcore.icu.AlphabeticIndex");
70            Constructor ctor = clazz.getConstructor(Locale.class);
71            mAddLabelsMethod = clazz.getDeclaredMethod("addLabels", Locale.class);
72            mSetMaxLabelCountMethod = clazz.getDeclaredMethod("setMaxLabelCount", int.class);
73            mGetBucketIndexMethod = clazz.getDeclaredMethod("getBucketIndex", String.class);
74            mGetBucketLabelMethod = clazz.getDeclaredMethod("getBucketLabel", int.class);
75            mAlphabeticIndex = ctor.newInstance(curLocale);
76            try {
77                // Ensure we always have some base English locale buckets
78                if (!curLocale.getLanguage().equals(Locale.ENGLISH.getLanguage())) {
79                    mAddLabelsMethod.invoke(mAlphabeticIndex, Locale.ENGLISH);
80                }
81            } catch (Exception e) {
82                e.printStackTrace();
83            }
84            if (curLocale.getLanguage().equals(Locale.JAPANESE.getLanguage())) {
85                // Japanese character 他 ("misc")
86                mDefaultMiscLabel = "\u4ed6";
87                // TODO(winsonc, omakoto): We need to handle Japanese sections better, especially the kanji
88            } else {
89                // Dot
90                mDefaultMiscLabel = MID_DOT;
91            }
92            mHasValidAlphabeticIndex = true;
93        } catch (Exception e) {
94            mHasValidAlphabeticIndex = false;
95        }
96    }
97
98    /**
99     * Sets the max number of the label buckets in this index.
100     * (ICU 51 default is 99)
101     */
102    public void setMaxLabelCount(int count) {
103        if (mHasValidAlphabeticIndex) {
104            try {
105                mSetMaxLabelCountMethod.invoke(mAlphabeticIndex, count);
106            } catch (Exception e) {
107                e.printStackTrace();
108            }
109        } else {
110            super.setMaxLabelCount(count);
111        }
112    }
113
114    /**
115     * Computes the section name for an given string {@param s}.
116     */
117    public String computeSectionName(CharSequence cs) {
118        String s = Utilities.trim(cs);
119        String sectionName = getBucketLabel(getBucketIndex(s));
120        if (Utilities.trim(sectionName).isEmpty() && s.length() > 0) {
121            int c = s.codePointAt(0);
122            boolean startsWithDigit = Character.isDigit(c);
123            if (startsWithDigit) {
124                // Digit section
125                return "#";
126            } else {
127                boolean startsWithLetter = Character.isLetter(c);
128                if (startsWithLetter) {
129                    return mDefaultMiscLabel;
130                } else {
131                    // In languages where these differ, this ensures that we differentiate
132                    // between the misc section in the native language and a misc section
133                    // for everything else.
134                    return MID_DOT;
135                }
136            }
137        }
138        return sectionName;
139    }
140
141    /**
142     * Returns the index of the bucket in which {@param s} should appear.
143     * Function is synchronized because underlying routine walks an iterator
144     * whose state is maintained inside the index object.
145     */
146    protected int getBucketIndex(String s) {
147        if (mHasValidAlphabeticIndex) {
148            try {
149                return (Integer) mGetBucketIndexMethod.invoke(mAlphabeticIndex, s);
150            } catch (Exception e) {
151                e.printStackTrace();
152            }
153        }
154        return super.getBucketIndex(s);
155    }
156
157    /**
158     * Returns the label for the bucket at the given index (as returned by getBucketIndex).
159     */
160    protected String getBucketLabel(int index) {
161        if (mHasValidAlphabeticIndex) {
162            try {
163                return (String) mGetBucketLabelMethod.invoke(mAlphabeticIndex, index);
164            } catch (Exception e) {
165                e.printStackTrace();
166            }
167        }
168        return super.getBucketLabel(index);
169    }
170}
171