Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 712a7f0c authored by Sunny Goyal's avatar Sunny Goyal Committed by Android (Google) Code Review
Browse files

Merge "Adding android.icu.text.AlphabeticIndex implementation for...

Merge "Adding android.icu.text.AlphabeticIndex implementation for AlphabeticIndexCompat as libcore.icu.AlphabeticIndex is no longer available in N" into ub-launcher3-calgary
parents c487bd34 a0a4abe8
Loading
Loading
Loading
Loading
+155 −102
Original line number Diff line number Diff line
package com.android.launcher3.compat;

import android.content.Context;
import android.content.res.Configuration;
import android.util.Log;

import com.android.launcher3.Utilities;

@@ -8,23 +10,79 @@ 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 {
public class AlphabeticIndexCompat {
    private static final String TAG = "AlphabeticIndexCompat";

    private static final String BUCKETS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-";
    private static final int UNKNOWN_BUCKET_INDEX = BUCKETS.length() - 1;
    private static final String MID_DOT = "\u2219";
    private final BaseIndex mBaseIndex;
    private final String mDefaultMiscLabel;

    public AlphabeticIndexCompat(Context context) {
        BaseIndex index = null;

    public BaseAlphabeticIndex() {}
        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;
        }
    }

    /**
     * Sets the max number of the label buckets in this index.
     * Computes the section name for an given string {@param s}.
     */
    public void setMaxLabelCount(int count) {
        // Not currently supported
    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.
@@ -49,124 +107,119 @@ class BaseAlphabeticIndex {
    }

    /**
 * Reflected libcore.icu.AlphabeticIndex implementation, falls back to the base alphabetic index.
     * Reflected libcore.icu.AlphabeticIndex implementation, falls back to the base
     * alphabetic index.
     */
public class AlphabeticIndexCompat extends BaseAlphabeticIndex {

    private static final String MID_DOT = "\u2219";
    private static class AlphabeticIndexV16 extends BaseIndex {

        private Object mAlphabeticIndex;
    private Method mAddLabelsMethod;
    private Method mSetMaxLabelCountMethod;
        private Method mGetBucketIndexMethod;
        private Method mGetBucketLabelMethod;
    private boolean mHasValidAlphabeticIndex;
    private String mDefaultMiscLabel;

    @SuppressWarnings({"unchecked", "rawtypes"})
    public AlphabeticIndexCompat(Context context) {
        super();
        try {
        public AlphabeticIndexV16(Context context) throws Exception {
            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
            mAlphabeticIndex = clazz.getConstructor(Locale.class).newInstance(curLocale);

            if (!curLocale.getLanguage().equals(Locale.ENGLISH.getLanguage())) {
                    mAddLabelsMethod.invoke(mAlphabeticIndex, Locale.ENGLISH);
                clazz.getDeclaredMethod("addLabels", Locale.class)
                        .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;

        /**
         * 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) {
            mHasValidAlphabeticIndex = false;
                e.printStackTrace();
            }
            return super.getBucketIndex(s);
        }

        /**
     * Sets the max number of the label buckets in this index.
     * (ICU 51 default is 99)
         * Returns the label for the bucket at the given index (as returned by getBucketIndex).
         */
    public void setMaxLabelCount(int count) {
        if (mHasValidAlphabeticIndex) {
        protected String getBucketLabel(int index) {
            try {
                mSetMaxLabelCountMethod.invoke(mAlphabeticIndex, count);
                return (String) mGetBucketLabelMethod.invoke(mAlphabeticIndex, index);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            super.setMaxLabelCount(count);
            return super.getBucketLabel(index);
        }
    }

    /**
     * Computes the section name for an given string {@param s}.
     * Reflected android.icu.text.AlphabeticIndex implementation, falls back to the base
     * alphabetic index.
     */
    public String computeSectionName(CharSequence cs) {
        String s = Utilities.trim(cs);
        String sectionName = getBucketLabel(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;
                }
            }
    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}});
            }
        return sectionName;
            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.
     * Function is synchronized because underlying routine walks an iterator
     * whose state is maintained inside the index object.
         */
        protected int getBucketIndex(String s) {
        if (mHasValidAlphabeticIndex) {
            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).
         * Returns the label for the bucket at the given index
         */
        protected String getBucketLabel(int index) {
        if (mHasValidAlphabeticIndex) {
            try {
                return (String) mGetBucketLabelMethod.invoke(mAlphabeticIndex, index);
                return (String) mGetLabelMethod.invoke(
                        mGetBucketMethod.invoke(mAlphabeticIndex, index));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
            return super.getBucketLabel(index);
        }
    }
}