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