1 package org.robolectric.res.android; 2 3 import static org.robolectric.res.android.LocaleDataTables.LIKELY_SCRIPTS; 4 import static org.robolectric.res.android.LocaleDataTables.MAX_PARENT_DEPTH; 5 import static org.robolectric.res.android.LocaleDataTables.REPRESENTATIVE_LOCALES; 6 import static org.robolectric.res.android.LocaleDataTables.SCRIPT_CODES; 7 import static org.robolectric.res.android.LocaleDataTables.SCRIPT_PARENTS; 8 9 import java.util.Arrays; 10 import java.util.Map; 11 12 // transliterated from 13 // https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/LocaleData.cpp 14 public class LocaleData { 15 packLocale(final byte[] language, final byte[] region)16 private static int packLocale(final byte[] language, final byte[] region) { 17 return ((language[0] & 0xff) << 24) 18 | ((language[1] & 0xff) << 16) 19 | ((region[0] & 0xff) << 8) 20 | (region[1] & 0xff); 21 } 22 dropRegion(int packed_locale)23 private static int dropRegion(int packed_locale) { 24 return packed_locale & 0xFFFF0000; 25 } 26 hasRegion(int packed_locale)27 private static boolean hasRegion(int packed_locale) { 28 return (packed_locale & 0x0000FFFF) != 0; 29 } 30 31 static final int SCRIPT_LENGTH = 4; 32 private static final int PACKED_ROOT = 0; // to represent the root locale 33 findParent(int packed_locale, final String script)34 private static int findParent(int packed_locale, final String script) { 35 if (hasRegion(packed_locale)) { 36 for (Map.Entry<String, Map<Integer, Integer>> entry : SCRIPT_PARENTS.entrySet()) { 37 if (script.equals(entry.getKey())) { 38 Map<Integer, Integer> map = entry.getValue(); 39 Integer lookup_result = map.get(packed_locale); 40 if (lookup_result != null) { 41 return lookup_result; 42 } 43 break; 44 } 45 } 46 return dropRegion(packed_locale); 47 } 48 return PACKED_ROOT; 49 } 50 51 // Find the ancestors of a locale, and fill 'out' with it (assumes out has enough 52 // space). If any of the members of stop_list was seen, write it in the 53 // output but stop afterwards. 54 // 55 // This also outputs the index of the last written ancestor in the stop_list 56 // to stop_list_index, which will be -1 if it is not found in the stop_list. 57 // 58 // Returns the number of ancestors written in the output, which is always 59 // at least one. 60 // 61 // (If 'out' is null, we do everything the same way but we simply don't write 62 // any results in 'out'.) findAncestors( int[] out, Ref<Long> stop_list_index, int packed_locale, final String script, final int[] stop_list, int stop_set_length)63 static int findAncestors( 64 int[] out, 65 Ref<Long> stop_list_index, 66 int packed_locale, 67 final String script, 68 final int[] stop_list, 69 int stop_set_length) { 70 int ancestor = packed_locale; 71 int count = 0; 72 do { 73 if (out != null) { 74 out[count] = ancestor; 75 } 76 count++; 77 for (int i = 0; i < stop_set_length; i++) { 78 if (stop_list[i] == ancestor) { 79 stop_list_index.set((long) i); 80 return count; 81 } 82 } 83 ancestor = findParent(ancestor, script); 84 } while (ancestor != PACKED_ROOT); 85 stop_list_index.set((long) -1); 86 return count; 87 } 88 findDistance( int supported, final String script, final int[] request_ancestors, int request_ancestors_count)89 static int findDistance( 90 int supported, 91 final String script, 92 final int[] request_ancestors, 93 int request_ancestors_count) { 94 final Ref<Long> request_ancestors_indexRef = new Ref<>(null); 95 final int supported_ancestor_count = 96 findAncestors( 97 null, 98 request_ancestors_indexRef, 99 supported, 100 script, 101 request_ancestors, 102 request_ancestors_count); 103 // Since both locales share the same root, there will always be a shared 104 // ancestor, so the distance in the parent tree is the sum of the distance 105 // of 'supported' to the lowest common ancestor (number of ancestors 106 // written for 'supported' minus 1) plus the distance of 'request' to the 107 // lowest common ancestor (the index of the ancestor in request_ancestors). 108 return (int) (supported_ancestor_count + request_ancestors_indexRef.get() - 1); 109 } 110 isRepresentative(int language_and_region, final String script)111 static boolean isRepresentative(int language_and_region, final String script) { 112 final long packed_locale = 113 ((((long) language_and_region) << 32) 114 | (((long) script.charAt(0) & 0xff) << 24) 115 | (((long) script.charAt(1) & 0xff) << 16) 116 | (((long) script.charAt(2) & 0xff) << 8) 117 | ((long) script.charAt(3) & 0xff)); 118 return REPRESENTATIVE_LOCALES.contains(packed_locale); 119 } 120 121 private static final int US_SPANISH = 0x65735553; // es-US 122 private static final int MEXICAN_SPANISH = 0x65734D58; // es-MX 123 private static final int LATIN_AMERICAN_SPANISH = 0x6573A424; // es-419 124 125 // The two locales es-US and es-MX are treated as special fallbacks for es-419. 126 // If there is no es-419, they are considered its equivalent. isSpecialSpanish(int language_and_region)127 private static boolean isSpecialSpanish(int language_and_region) { 128 return (language_and_region == US_SPANISH || language_and_region == MEXICAN_SPANISH); 129 } 130 localeDataCompareRegions( final byte[] left_region, final byte[] right_region, final byte[] requested_language, final String requested_script, final byte[] requested_region)131 static int localeDataCompareRegions( 132 final byte[] left_region, 133 final byte[] right_region, 134 final byte[] requested_language, 135 final String requested_script, 136 final byte[] requested_region) { 137 if (left_region[0] == right_region[0] && left_region[1] == right_region[1]) { 138 return 0; 139 } 140 int left = packLocale(requested_language, left_region); 141 int right = packLocale(requested_language, right_region); 142 final int request = packLocale(requested_language, requested_region); 143 144 // If one and only one of the two locales is a special Spanish locale, we 145 // replace it with es-419. We don't do the replacement if the other locale 146 // is already es-419, or both locales are special Spanish locales (when 147 // es-US is being compared to es-MX). 148 final boolean leftIsSpecialSpanish = isSpecialSpanish(left); 149 final boolean rightIsSpecialSpanish = isSpecialSpanish(right); 150 if (leftIsSpecialSpanish && !rightIsSpecialSpanish && right != LATIN_AMERICAN_SPANISH) { 151 left = LATIN_AMERICAN_SPANISH; 152 } else if (rightIsSpecialSpanish && !leftIsSpecialSpanish && left != LATIN_AMERICAN_SPANISH) { 153 right = LATIN_AMERICAN_SPANISH; 154 } 155 156 int[] request_ancestors = new int[MAX_PARENT_DEPTH + 1]; 157 final Ref<Long> left_right_indexRef = new Ref<Long>(null); 158 // Find the parents of the request, but stop as soon as we saw left or right 159 final int left_and_right[] = {left, right}; 160 final int ancestor_count = 161 findAncestors( 162 request_ancestors, left_right_indexRef, 163 request, requested_script, 164 left_and_right, sizeof(left_and_right)); 165 if (left_right_indexRef.get() == 0) { // We saw left earlier 166 return 1; 167 } 168 if (left_right_indexRef.get() == 1) { // We saw right earlier 169 return -1; 170 } 171 // If we are here, neither left nor right are an ancestor of the 172 // request. This means that all the ancestors have been computed and 173 // the last ancestor is just the language by itself. We will use the 174 // distance in the parent tree for determining the better match. 175 final int left_distance = 176 findDistance(left, requested_script, request_ancestors, ancestor_count); 177 final int right_distance = 178 findDistance(right, requested_script, request_ancestors, ancestor_count); 179 if (left_distance != right_distance) { 180 return (int) right_distance - (int) left_distance; // smaller distance is better 181 } 182 // If we are here, left and right are equidistant from the request. We will 183 // try and see if any of them is a representative locale. 184 final boolean left_is_representative = isRepresentative(left, requested_script); 185 final boolean right_is_representative = isRepresentative(right, requested_script); 186 if (left_is_representative != right_is_representative) { 187 return (left_is_representative ? 1 : 0) - (right_is_representative ? 1 : 0); 188 } 189 // We have no way of figuring out which locale is a better match. For 190 // the sake of stability, we consider the locale with the lower region 191 // code (in dictionary order) better, with two-letter codes before 192 // three-digit codes (since two-letter codes are more specific). 193 return right - left; 194 } 195 localeDataComputeScript(byte[] out, final byte[] language, final byte[] region)196 static void localeDataComputeScript(byte[] out, final byte[] language, final byte[] region) { 197 if (language[0] == '\0') { 198 // memset(out, '\0', SCRIPT_LENGTH); 199 Arrays.fill(out, (byte) 0); 200 return; 201 } 202 int lookup_key = packLocale(language, region); 203 Byte lookup_result = LIKELY_SCRIPTS.get(lookup_key); 204 if (lookup_result == null) { 205 // We couldn't find the locale. Let's try without the region 206 if (region[0] != '\0') { 207 lookup_key = dropRegion(lookup_key); 208 lookup_result = LIKELY_SCRIPTS.get(lookup_key); 209 if (lookup_result != null) { 210 // memcpy(out, SCRIPT_CODES[lookup_result.second], SCRIPT_LENGTH); 211 System.arraycopy(SCRIPT_CODES[lookup_result], 0, out, 0, SCRIPT_LENGTH); 212 return; 213 } 214 } 215 // We don't know anything about the locale 216 // memset(out, '\0', SCRIPT_LENGTH); 217 Arrays.fill(out, (byte) 0); 218 return; 219 } else { 220 // We found the locale. 221 // memcpy(out, SCRIPT_CODES[lookup_result.second], SCRIPT_LENGTH); 222 System.arraycopy(SCRIPT_CODES[lookup_result], 0, out, 0, SCRIPT_LENGTH); 223 } 224 } 225 226 static final int[] ENGLISH_STOP_LIST = { 227 0x656E0000, // en 228 0x656E8400, // en-001 229 }; 230 231 static final byte[] ENGLISH_CHARS = {'e', 'n'}; 232 233 static final String LATIN_CHARS = "Latn"; 234 localeDataIsCloseToUsEnglish(final byte[] region)235 static boolean localeDataIsCloseToUsEnglish(final byte[] region) { 236 final int locale = packLocale(ENGLISH_CHARS, region); 237 final Ref<Long> stop_list_indexRef = new Ref<>(null); 238 findAncestors(null, stop_list_indexRef, locale, LATIN_CHARS, ENGLISH_STOP_LIST, 2); 239 // A locale is like US English if we see "en" before "en-001" in its ancestor list. 240 return stop_list_indexRef.get() == 0; // 'en' is first in ENGLISH_STOP_LIST 241 } 242 sizeof(int[] array)243 private static int sizeof(int[] array) { 244 return array.length; 245 } 246 } 247