1 package com.android.launcher3.compat; 2 3 import android.content.Context; 4 import android.icu.text.AlphabeticIndex; 5 import android.os.LocaleList; 6 import android.util.Log; 7 8 import com.android.launcher3.Utilities; 9 10 import java.util.Locale; 11 12 import androidx.annotation.NonNull; 13 14 public class AlphabeticIndexCompat { 15 private static final String TAG = "AlphabeticIndexCompat"; 16 17 private static final String MID_DOT = "\u2219"; 18 private final BaseIndex mBaseIndex; 19 private final String mDefaultMiscLabel; 20 AlphabeticIndexCompat(Context context)21 public AlphabeticIndexCompat(Context context) { 22 BaseIndex index = null; 23 24 try { 25 index = new AlphabeticIndexVN(context); 26 } catch (Exception e) { 27 Log.d(TAG, "Unable to load the system index", e); 28 } 29 30 mBaseIndex = index == null ? new BaseIndex() : index; 31 32 if (context.getResources().getConfiguration().locale 33 .getLanguage().equals(Locale.JAPANESE.getLanguage())) { 34 // Japanese character 他 ("misc") 35 mDefaultMiscLabel = "\u4ed6"; 36 // TODO(winsonc, omakoto): We need to handle Japanese sections better, especially the kanji 37 } else { 38 // Dot 39 mDefaultMiscLabel = MID_DOT; 40 } 41 } 42 43 /** 44 * Computes the section name for an given string {@param s}. 45 */ computeSectionName(@onNull CharSequence cs)46 public String computeSectionName(@NonNull CharSequence cs) { 47 String s = Utilities.trim(cs); 48 String sectionName = mBaseIndex.getBucketLabel(mBaseIndex.getBucketIndex(s)); 49 if (Utilities.trim(sectionName).isEmpty() && s.length() > 0) { 50 int c = s.codePointAt(0); 51 boolean startsWithDigit = Character.isDigit(c); 52 if (startsWithDigit) { 53 // Digit section 54 return "#"; 55 } else { 56 boolean startsWithLetter = Character.isLetter(c); 57 if (startsWithLetter) { 58 return mDefaultMiscLabel; 59 } else { 60 // In languages where these differ, this ensures that we differentiate 61 // between the misc section in the native language and a misc section 62 // for everything else. 63 return MID_DOT; 64 } 65 } 66 } 67 return sectionName; 68 } 69 70 /** 71 * Base class to support Alphabetic indexing if not supported by the framework. 72 * TODO(winsonc): disable for non-english locales 73 */ 74 private static class BaseIndex { 75 76 private static final String BUCKETS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-"; 77 private static final int UNKNOWN_BUCKET_INDEX = BUCKETS.length() - 1; 78 79 /** 80 * Returns the index of the bucket in which the given string should appear. 81 */ getBucketIndex(@onNull String s)82 protected int getBucketIndex(@NonNull String s) { 83 if (s.isEmpty()) { 84 return UNKNOWN_BUCKET_INDEX; 85 } 86 int index = BUCKETS.indexOf(s.substring(0, 1).toUpperCase()); 87 if (index != -1) { 88 return index; 89 } 90 return UNKNOWN_BUCKET_INDEX; 91 } 92 93 /** 94 * Returns the label for the bucket at the given index (as returned by getBucketIndex). 95 */ getBucketLabel(int index)96 protected String getBucketLabel(int index) { 97 return BUCKETS.substring(index, index + 1); 98 } 99 } 100 101 /** 102 * Implementation based on {@link AlphabeticIndex}. 103 */ 104 private static class AlphabeticIndexVN extends BaseIndex { 105 106 private final AlphabeticIndex.ImmutableIndex mAlphabeticIndex; 107 AlphabeticIndexVN(Context context)108 public AlphabeticIndexVN(Context context) { 109 LocaleList locales = context.getResources().getConfiguration().getLocales(); 110 int localeCount = locales.size(); 111 112 Locale primaryLocale = localeCount == 0 ? Locale.ENGLISH : locales.get(0); 113 AlphabeticIndex indexBuilder = new AlphabeticIndex(primaryLocale); 114 for (int i = 1; i < localeCount; i++) { 115 indexBuilder.addLabels(locales.get(i)); 116 } 117 indexBuilder.addLabels(Locale.ENGLISH); 118 119 mAlphabeticIndex = indexBuilder.buildImmutableIndex(); 120 } 121 122 /** 123 * Returns the index of the bucket in which {@param s} should appear. 124 */ getBucketIndex(String s)125 protected int getBucketIndex(String s) { 126 return mAlphabeticIndex.getBucketIndex(s); 127 } 128 129 /** 130 * Returns the label for the bucket at the given index 131 */ getBucketLabel(int index)132 protected String getBucketLabel(int index) { 133 return mAlphabeticIndex.getBucket(index).getLabel(); 134 } 135 } 136 } 137