1 package org.robolectric.res.android; 2 3 import static java.nio.charset.StandardCharsets.US_ASCII; 4 import static java.nio.charset.StandardCharsets.UTF_8; 5 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_ANY; 6 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_DEFAULT; 7 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_HIGH; 8 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_LOW; 9 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_MEDIUM; 10 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_NONE; 11 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_TV; 12 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_XHIGH; 13 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_XXHIGH; 14 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_XXXHIGH; 15 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_HDR_ANY; 16 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_HDR_NO; 17 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_HDR_YES; 18 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_KEYBOARD_ANY; 19 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_KEYSHIDDEN_ANY; 20 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_KEYSHIDDEN_NO; 21 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_KEYSHIDDEN_SOFT; 22 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_KEYSHIDDEN_YES; 23 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_LAYOUTDIR_ANY; 24 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_LAYOUTDIR_LTR; 25 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_LAYOUTDIR_RTL; 26 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_NAVHIDDEN_ANY; 27 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_NAVHIDDEN_NO; 28 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_NAVHIDDEN_YES; 29 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_NAVIGATION_ANY; 30 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_ORIENTATION_ANY; 31 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_ORIENTATION_LAND; 32 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_ORIENTATION_PORT; 33 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_ORIENTATION_SQUARE; 34 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENLONG_ANY; 35 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENLONG_NO; 36 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENLONG_YES; 37 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENROUND_ANY; 38 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENROUND_NO; 39 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENROUND_YES; 40 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENSIZE_ANY; 41 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENSIZE_LARGE; 42 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENSIZE_NORMAL; 43 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENSIZE_SMALL; 44 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENSIZE_XLARGE; 45 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_TOUCHSCREEN_ANY; 46 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_UI_MODE_NIGHT_ANY; 47 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_UI_MODE_TYPE_ANY; 48 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_UI_MODE_TYPE_NORMAL; 49 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_WIDE_COLOR_GAMUT_ANY; 50 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_WIDE_COLOR_GAMUT_NO; 51 import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_WIDE_COLOR_GAMUT_YES; 52 import static org.robolectric.res.android.LocaleData.localeDataCompareRegions; 53 import static org.robolectric.res.android.LocaleData.localeDataComputeScript; 54 import static org.robolectric.res.android.LocaleData.localeDataIsCloseToUsEnglish; 55 import static org.robolectric.res.android.ResTable.kDebugTableSuperNoisy; 56 import static org.robolectric.res.android.Util.ALOGI; 57 import static org.robolectric.res.android.Util.dtohl; 58 import static org.robolectric.res.android.Util.dtohs; 59 import static org.robolectric.res.android.Util.isTruthy; 60 61 import com.google.common.base.Charsets; 62 import com.google.common.base.Joiner; 63 import com.google.common.base.Preconditions; 64 import com.google.common.primitives.Bytes; 65 import com.google.common.primitives.UnsignedBytes; 66 import java.nio.ByteBuffer; 67 import java.util.Arrays; 68 import java.util.Collection; 69 import java.util.Collections; 70 import java.util.HashMap; 71 import java.util.LinkedHashMap; 72 import java.util.Map; 73 import javax.annotation.Nonnull; 74 75 /** 76 * Describes a particular resource configuration. 77 * 78 * Transliterated from: 79 * * https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/ResourceTypes.cpp 80 * * https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/include/ResourceTypes.h (struct ResTable_config) 81 * 82 * Changes from 8.0.0_r4 partially applied. 83 */ 84 @SuppressWarnings("NewApi") 85 public class ResTable_config { 86 87 // The most specific locale can consist of: 88 // 89 // - a 3 char language code 90 // - a 3 char region code prefixed by a 'r' 91 // - a 4 char script code prefixed by a 's' 92 // - a 8 char variant code prefixed by a 'v' 93 // 94 // each separated by a single char separator, which sums up to a total of 24 95 // chars, (25 include the string terminator) rounded up to 28 to be 4 byte 96 // aligned. 97 public static final int RESTABLE_MAX_LOCALE_LEN = 28; 98 99 /** The minimum size in bytes that this configuration must be to contain screen config info. */ 100 private static final int SCREEN_CONFIG_MIN_SIZE = 32; 101 102 /** The minimum size in bytes that this configuration must be to contain screen dp info. */ 103 private static final int SCREEN_DP_MIN_SIZE = 36; 104 105 /** The minimum size in bytes that this configuration must be to contain locale info. */ 106 private static final int LOCALE_MIN_SIZE = 48; 107 108 /** The minimum size in bytes that this config must be to contain the screenConfig extension. */ 109 private static final int SCREEN_CONFIG_EXTENSION_MIN_SIZE = 52; 110 111 public static final int SIZEOF = SCREEN_CONFIG_EXTENSION_MIN_SIZE; 112 113 // Codes for specially handled languages and regions 114 static final byte[] kEnglish = new byte[] {'e', 'n'}; // packed version of "en" 115 static final byte[] kUnitedStates = new byte[] {'U', 'S'}; // packed version of "US" 116 static final byte[] kFilipino = new byte[] {(byte)0xAD, 0x05}; // packed version of "fil" ported from C {'\xAD', '\x05'} 117 static final byte[] kTagalog = new byte[] {'t', 'l'}; // packed version of "tl" 118 createConfig(ByteBuffer buffer)119 static ResTable_config createConfig(ByteBuffer buffer) { 120 int startPosition = buffer.position(); // The starting buffer position to calculate bytes read. 121 int size = buffer.getInt(); 122 int mcc = buffer.getShort() & 0xFFFF; 123 int mnc = buffer.getShort() & 0xFFFF; 124 byte[] language = new byte[2]; 125 buffer.get(language); 126 byte[] region = new byte[2]; 127 buffer.get(region); 128 int orientation = UnsignedBytes.toInt(buffer.get()); 129 int touchscreen = UnsignedBytes.toInt(buffer.get()); 130 int density = buffer.getShort() & 0xFFFF; 131 int keyboard = UnsignedBytes.toInt(buffer.get()); 132 int navigation = UnsignedBytes.toInt(buffer.get()); 133 int inputFlags = UnsignedBytes.toInt(buffer.get()); 134 buffer.get(); // 1 byte of padding 135 int screenWidth = buffer.getShort() & 0xFFFF; 136 int screenHeight = buffer.getShort() & 0xFFFF; 137 int sdkVersion = buffer.getShort() & 0xFFFF; 138 int minorVersion = buffer.getShort() & 0xFFFF; 139 140 // At this point, the configuration's size needs to be taken into account as not all 141 // configurations have all values. 142 int screenLayout = 0; 143 int uiMode = 0; 144 int smallestScreenWidthDp = 0; 145 int screenWidthDp = 0; 146 int screenHeightDp = 0; 147 byte[] localeScript = new byte[4]; 148 byte[] localeVariant = new byte[8]; 149 byte screenLayout2 = 0; 150 byte screenConfigPad1 = 0; 151 short screenConfigPad2 = 0; 152 153 if (size >= SCREEN_CONFIG_MIN_SIZE) { 154 screenLayout = UnsignedBytes.toInt(buffer.get()); 155 uiMode = UnsignedBytes.toInt(buffer.get()); 156 smallestScreenWidthDp = buffer.getShort() & 0xFFFF; 157 } 158 159 if (size >= SCREEN_DP_MIN_SIZE) { 160 screenWidthDp = buffer.getShort() & 0xFFFF; 161 screenHeightDp = buffer.getShort() & 0xFFFF; 162 } 163 164 if (size >= LOCALE_MIN_SIZE) { 165 buffer.get(localeScript); 166 buffer.get(localeVariant); 167 } 168 169 if (size >= SCREEN_CONFIG_EXTENSION_MIN_SIZE) { 170 screenLayout2 = (byte) UnsignedBytes.toInt(buffer.get()); 171 screenConfigPad1 = buffer.get(); // Reserved padding 172 screenConfigPad2 = buffer.getShort(); // More reserved padding 173 } 174 175 // After parsing everything that's known, account for anything that's unknown. 176 int bytesRead = buffer.position() - startPosition; 177 byte[] unknown = new byte[size - bytesRead]; 178 buffer.get(unknown); 179 180 return new ResTable_config(size, mcc, mnc, language, region, orientation, 181 touchscreen, density, keyboard, navigation, inputFlags, screenWidth, screenHeight, 182 sdkVersion, minorVersion, screenLayout, uiMode, smallestScreenWidthDp, screenWidthDp, 183 screenHeightDp, localeScript, localeVariant, screenLayout2, screenConfigPad1, screenConfigPad2, unknown); 184 } 185 186 /** 187 * The different types of configs that can be present in a {@link ResTable_config}. 188 * 189 * The ordering of these types is roughly the same as {@code #isBetterThan}, but is not 190 * guaranteed to be the same. 191 */ 192 public enum Type { 193 MCC, 194 MNC, 195 LANGUAGE_STRING, 196 LOCALE_SCRIPT_STRING, 197 REGION_STRING, 198 LOCALE_VARIANT_STRING, 199 SCREEN_LAYOUT_DIRECTION, 200 SMALLEST_SCREEN_WIDTH_DP, 201 SCREEN_WIDTH_DP, 202 SCREEN_HEIGHT_DP, 203 SCREEN_LAYOUT_SIZE, 204 SCREEN_LAYOUT_LONG, 205 SCREEN_LAYOUT_ROUND, 206 COLOR_MODE_WIDE_COLOR_GAMUT, // NB: COLOR_GAMUT takes priority over HDR in #isBetterThan. 207 COLOR_MODE_HDR, 208 ORIENTATION, 209 UI_MODE_TYPE, 210 UI_MODE_NIGHT, 211 DENSITY_DPI, 212 TOUCHSCREEN, 213 KEYBOARD_HIDDEN, 214 KEYBOARD, 215 NAVIGATION_HIDDEN, 216 NAVIGATION, 217 SCREEN_SIZE, 218 SDK_VERSION 219 } 220 221 // screenLayout bits for layout direction. 222 // public static final int MASK_LAYOUTDIR = 0xC0; 223 public static final int SHIFT_LAYOUTDIR = 6; 224 public static final int LAYOUTDIR_ANY = ACONFIGURATION_LAYOUTDIR_ANY << SHIFT_LAYOUTDIR; 225 public static final int LAYOUTDIR_LTR = ACONFIGURATION_LAYOUTDIR_LTR << SHIFT_LAYOUTDIR; 226 public static final int LAYOUTDIR_RTL = ACONFIGURATION_LAYOUTDIR_RTL << SHIFT_LAYOUTDIR; 227 228 public static final int SCREENWIDTH_ANY = 0; 229 // public static final int MASK_SCREENSIZE = 0x0f; 230 public static final int SCREENSIZE_ANY = ACONFIGURATION_SCREENSIZE_ANY; 231 public static final int SCREENSIZE_SMALL = ACONFIGURATION_SCREENSIZE_SMALL; 232 public static final int SCREENSIZE_NORMAL = ACONFIGURATION_SCREENSIZE_NORMAL; 233 public static final int SCREENSIZE_LARGE = ACONFIGURATION_SCREENSIZE_LARGE; 234 public static final int SCREENSIZE_XLARGE = ACONFIGURATION_SCREENSIZE_XLARGE; 235 236 // uiMode bits for the mode type. 237 public static final int MASK_UI_MODE_TYPE = 0x0f; 238 public static final int UI_MODE_TYPE_ANY = ACONFIGURATION_UI_MODE_TYPE_ANY; 239 public static final int UI_MODE_TYPE_NORMAL = ACONFIGURATION_UI_MODE_TYPE_NORMAL; 240 241 // uiMode bits for the night switch; 242 public static final int MASK_UI_MODE_NIGHT = 0x30; 243 public static final int SHIFT_UI_MODE_NIGHT = 4; 244 public static final int UI_MODE_NIGHT_ANY = ACONFIGURATION_UI_MODE_NIGHT_ANY << SHIFT_UI_MODE_NIGHT; 245 246 public static final int DENSITY_DEFAULT = ACONFIGURATION_DENSITY_DEFAULT; 247 public static final int DENSITY_LOW = ACONFIGURATION_DENSITY_LOW; 248 public static final int DENSITY_MEDIUM = ACONFIGURATION_DENSITY_MEDIUM; 249 public static final int DENSITY_TV = ACONFIGURATION_DENSITY_TV; 250 public static final int DENSITY_HIGH = ACONFIGURATION_DENSITY_HIGH; 251 public static final int DENSITY_XHIGH = ACONFIGURATION_DENSITY_XHIGH; 252 public static final int DENSITY_XXHIGH = ACONFIGURATION_DENSITY_XXHIGH; 253 public static final int DENSITY_XXXHIGH = ACONFIGURATION_DENSITY_XXXHIGH; 254 public static final int DENSITY_ANY = ACONFIGURATION_DENSITY_ANY; 255 public static final int DENSITY_NONE = ACONFIGURATION_DENSITY_NONE; 256 257 public static final int TOUCHSCREEN_ANY = ACONFIGURATION_TOUCHSCREEN_ANY; 258 259 public static final int MASK_KEYSHIDDEN = 0x0003; 260 public static final byte KEYSHIDDEN_ANY = ACONFIGURATION_KEYSHIDDEN_ANY; 261 public static final byte KEYSHIDDEN_NO = ACONFIGURATION_KEYSHIDDEN_NO; 262 public static final byte KEYSHIDDEN_YES = ACONFIGURATION_KEYSHIDDEN_YES; 263 public static final byte KEYSHIDDEN_SOFT = ACONFIGURATION_KEYSHIDDEN_SOFT; 264 265 public static final int KEYBOARD_ANY = ACONFIGURATION_KEYBOARD_ANY; 266 267 public static final int MASK_NAVHIDDEN = 0x000c; 268 public static final int SHIFT_NAVHIDDEN = 2; 269 public static final byte NAVHIDDEN_ANY = ACONFIGURATION_NAVHIDDEN_ANY << SHIFT_NAVHIDDEN; 270 public static final byte NAVHIDDEN_NO = ACONFIGURATION_NAVHIDDEN_NO << SHIFT_NAVHIDDEN; 271 public static final byte NAVHIDDEN_YES = ACONFIGURATION_NAVHIDDEN_YES << SHIFT_NAVHIDDEN; 272 273 public static final int NAVIGATION_ANY = ACONFIGURATION_NAVIGATION_ANY; 274 275 public static final int SCREENHEIGHT_ANY = 0; 276 277 public static final int SDKVERSION_ANY = 0; 278 public static final int MINORVERSION_ANY = 0; 279 280 // from https://github.com/google/android-arscblamer/blob/master/java/com/google/devrel/gmscore/tools/apk/arsc/ResourceConfiguration.java 281 /** The below constants are from android.content.res.Configuration. */ 282 static final int COLOR_MODE_WIDE_COLOR_GAMUT_MASK = 0x03; 283 284 public static final int WIDE_COLOR_GAMUT_ANY = ACONFIGURATION_WIDE_COLOR_GAMUT_ANY; 285 public static final int WIDE_COLOR_GAMUT_NO = ACONFIGURATION_WIDE_COLOR_GAMUT_NO; 286 public static final int WIDE_COLOR_GAMUT_YES = ACONFIGURATION_WIDE_COLOR_GAMUT_YES; 287 public static final int MASK_WIDE_COLOR_GAMUT = 0x03; 288 static final int COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED = 0; 289 static final int COLOR_MODE_WIDE_COLOR_GAMUT_NO = 0x01; 290 static final int COLOR_MODE_WIDE_COLOR_GAMUT_YES = 0x02; 291 292 private static final Map<Integer, String> COLOR_MODE_WIDE_COLOR_GAMUT_VALUES; 293 294 static { 295 Map<Integer, String> map = new HashMap<>(); map.put(COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED, "")296 map.put(COLOR_MODE_WIDE_COLOR_GAMUT_UNDEFINED, ""); map.put(COLOR_MODE_WIDE_COLOR_GAMUT_NO, "nowidecg")297 map.put(COLOR_MODE_WIDE_COLOR_GAMUT_NO, "nowidecg"); map.put(COLOR_MODE_WIDE_COLOR_GAMUT_YES, "widecg")298 map.put(COLOR_MODE_WIDE_COLOR_GAMUT_YES, "widecg"); 299 COLOR_MODE_WIDE_COLOR_GAMUT_VALUES = Collections.unmodifiableMap(map); 300 } 301 302 public static final int HDR_ANY = ACONFIGURATION_HDR_ANY; 303 public static final int HDR_NO = ACONFIGURATION_HDR_NO << 2; 304 public static final int HDR_YES = ACONFIGURATION_HDR_YES << 2; 305 public static final int MASK_HDR = 0x0c; 306 static final int COLOR_MODE_HDR_MASK = 0x0C; 307 static final int COLOR_MODE_HDR_UNDEFINED = 0; 308 static final int COLOR_MODE_HDR_NO = 0x04; 309 static final int COLOR_MODE_HDR_YES = 0x08; 310 311 private static final Map<Integer, String> COLOR_MODE_HDR_VALUES; 312 313 static { 314 Map<Integer, String> map = new HashMap<>(); map.put(COLOR_MODE_HDR_UNDEFINED, "")315 map.put(COLOR_MODE_HDR_UNDEFINED, ""); map.put(COLOR_MODE_HDR_NO, "lowdr")316 map.put(COLOR_MODE_HDR_NO, "lowdr"); map.put(COLOR_MODE_HDR_YES, "highdr")317 map.put(COLOR_MODE_HDR_YES, "highdr"); 318 COLOR_MODE_HDR_VALUES = Collections.unmodifiableMap(map); 319 } 320 321 public static final int DENSITY_DPI_UNDEFINED = 0; 322 static final int DENSITY_DPI_LDPI = 120; 323 public static final int DENSITY_DPI_MDPI = 160; 324 static final int DENSITY_DPI_TVDPI = 213; 325 static final int DENSITY_DPI_HDPI = 240; 326 static final int DENSITY_DPI_XHDPI = 320; 327 static final int DENSITY_DPI_XXHDPI = 480; 328 static final int DENSITY_DPI_XXXHDPI = 640; 329 public static final int DENSITY_DPI_ANY = 0xFFFE; 330 public static final int DENSITY_DPI_NONE = 0xFFFF; 331 332 private static final Map<Integer, String> DENSITY_DPI_VALUES; 333 334 static { 335 Map<Integer, String> map = new HashMap<>(); map.put(DENSITY_DPI_UNDEFINED, "")336 map.put(DENSITY_DPI_UNDEFINED, ""); map.put(DENSITY_DPI_LDPI, "ldpi")337 map.put(DENSITY_DPI_LDPI, "ldpi"); map.put(DENSITY_DPI_MDPI, "mdpi")338 map.put(DENSITY_DPI_MDPI, "mdpi"); map.put(DENSITY_DPI_TVDPI, "tvdpi")339 map.put(DENSITY_DPI_TVDPI, "tvdpi"); map.put(DENSITY_DPI_HDPI, "hdpi")340 map.put(DENSITY_DPI_HDPI, "hdpi"); map.put(DENSITY_DPI_XHDPI, "xhdpi")341 map.put(DENSITY_DPI_XHDPI, "xhdpi"); map.put(DENSITY_DPI_XXHDPI, "xxhdpi")342 map.put(DENSITY_DPI_XXHDPI, "xxhdpi"); map.put(DENSITY_DPI_XXXHDPI, "xxxhdpi")343 map.put(DENSITY_DPI_XXXHDPI, "xxxhdpi"); map.put(DENSITY_DPI_ANY, "anydpi")344 map.put(DENSITY_DPI_ANY, "anydpi"); map.put(DENSITY_DPI_NONE, "nodpi")345 map.put(DENSITY_DPI_NONE, "nodpi"); 346 DENSITY_DPI_VALUES = Collections.unmodifiableMap(map); 347 } 348 349 static final int KEYBOARD_NOKEYS = 1; 350 static final int KEYBOARD_QWERTY = 2; 351 static final int KEYBOARD_12KEY = 3; 352 353 private static final Map<Integer, String> KEYBOARD_VALUES; 354 355 static { 356 Map<Integer, String> map = new HashMap<>(); map.put(KEYBOARD_NOKEYS, "nokeys")357 map.put(KEYBOARD_NOKEYS, "nokeys"); map.put(KEYBOARD_QWERTY, "qwerty")358 map.put(KEYBOARD_QWERTY, "qwerty"); map.put(KEYBOARD_12KEY, "12key")359 map.put(KEYBOARD_12KEY, "12key"); 360 KEYBOARD_VALUES = Collections.unmodifiableMap(map); 361 } 362 363 static final int KEYBOARDHIDDEN_MASK = 0x03; 364 static final int KEYBOARDHIDDEN_NO = 1; 365 static final int KEYBOARDHIDDEN_YES = 2; 366 static final int KEYBOARDHIDDEN_SOFT = 3; 367 368 private static final Map<Integer, String> KEYBOARDHIDDEN_VALUES; 369 370 static { 371 Map<Integer, String> map = new HashMap<>(); map.put(KEYBOARDHIDDEN_NO, "keysexposed")372 map.put(KEYBOARDHIDDEN_NO, "keysexposed"); map.put(KEYBOARDHIDDEN_YES, "keyshidden")373 map.put(KEYBOARDHIDDEN_YES, "keyshidden"); map.put(KEYBOARDHIDDEN_SOFT, "keyssoft")374 map.put(KEYBOARDHIDDEN_SOFT, "keyssoft"); 375 KEYBOARDHIDDEN_VALUES = Collections.unmodifiableMap(map); 376 } 377 378 static final int NAVIGATION_NONAV = 1; 379 static final int NAVIGATION_DPAD = 2; 380 static final int NAVIGATION_TRACKBALL = 3; 381 static final int NAVIGATION_WHEEL = 4; 382 383 private static final Map<Integer, String> NAVIGATION_VALUES; 384 385 static { 386 Map<Integer, String> map = new HashMap<>(); map.put(NAVIGATION_NONAV, "nonav")387 map.put(NAVIGATION_NONAV, "nonav"); map.put(NAVIGATION_DPAD, "dpad")388 map.put(NAVIGATION_DPAD, "dpad"); map.put(NAVIGATION_TRACKBALL, "trackball")389 map.put(NAVIGATION_TRACKBALL, "trackball"); map.put(NAVIGATION_WHEEL, "wheel")390 map.put(NAVIGATION_WHEEL, "wheel"); 391 NAVIGATION_VALUES = Collections.unmodifiableMap(map); 392 } 393 394 static final int NAVIGATIONHIDDEN_MASK = 0x0C; 395 static final int NAVIGATIONHIDDEN_NO = 0x04; 396 static final int NAVIGATIONHIDDEN_YES = 0x08; 397 398 private static final Map<Integer, String> NAVIGATIONHIDDEN_VALUES; 399 400 static { 401 Map<Integer, String> map = new HashMap<>(); map.put(NAVIGATIONHIDDEN_NO, "navexposed")402 map.put(NAVIGATIONHIDDEN_NO, "navexposed"); map.put(NAVIGATIONHIDDEN_YES, "navhidden")403 map.put(NAVIGATIONHIDDEN_YES, "navhidden"); 404 NAVIGATIONHIDDEN_VALUES = Collections.unmodifiableMap(map); 405 } 406 407 public static final int ORIENTATION_ANY = ACONFIGURATION_ORIENTATION_ANY; 408 public static final int ORIENTATION_PORT = ACONFIGURATION_ORIENTATION_PORT; 409 public static final int ORIENTATION_LAND = ACONFIGURATION_ORIENTATION_LAND; 410 public static final int ORIENTATION_SQUARE = ACONFIGURATION_ORIENTATION_SQUARE; 411 static final int ORIENTATION_PORTRAIT = 0x01; 412 static final int ORIENTATION_LANDSCAPE = 0x02; 413 414 private static final Map<Integer, String> ORIENTATION_VALUES; 415 416 static { 417 Map<Integer, String> map = new HashMap<>(); map.put(ORIENTATION_PORTRAIT, "port")418 map.put(ORIENTATION_PORTRAIT, "port"); map.put(ORIENTATION_LANDSCAPE, "land")419 map.put(ORIENTATION_LANDSCAPE, "land"); 420 ORIENTATION_VALUES = Collections.unmodifiableMap(map); 421 } 422 423 static final int SCREENLAYOUT_LAYOUTDIR_MASK = 0xC0; 424 static final int SCREENLAYOUT_LAYOUTDIR_LTR = 0x40; 425 static final int SCREENLAYOUT_LAYOUTDIR_RTL = 0x80; 426 427 private static final Map<Integer, String> SCREENLAYOUT_LAYOUTDIR_VALUES; 428 429 static { 430 Map<Integer, String> map = new HashMap<>(); map.put(SCREENLAYOUT_LAYOUTDIR_LTR, "ldltr")431 map.put(SCREENLAYOUT_LAYOUTDIR_LTR, "ldltr"); map.put(SCREENLAYOUT_LAYOUTDIR_RTL, "ldrtl")432 map.put(SCREENLAYOUT_LAYOUTDIR_RTL, "ldrtl"); 433 SCREENLAYOUT_LAYOUTDIR_VALUES = Collections.unmodifiableMap(map); 434 } 435 436 // screenLayout bits for wide/long screen variation. 437 public static final int MASK_SCREENLONG = 0x30; 438 public static final int SHIFT_SCREENLONG = 4; 439 public static final int SCREENLONG_ANY = ACONFIGURATION_SCREENLONG_ANY << SHIFT_SCREENLONG; 440 public static final int SCREENLONG_NO = ACONFIGURATION_SCREENLONG_NO << SHIFT_SCREENLONG; 441 public static final int SCREENLONG_YES = ACONFIGURATION_SCREENLONG_YES << SHIFT_SCREENLONG; 442 static final int SCREENLAYOUT_LONG_MASK = 0x30; 443 static final int SCREENLAYOUT_LONG_NO = 0x10; 444 static final int SCREENLAYOUT_LONG_YES = 0x20; 445 446 private static final Map<Integer, String> SCREENLAYOUT_LONG_VALUES; 447 448 static { 449 Map<Integer, String> map = new HashMap<>(); map.put(SCREENLAYOUT_LONG_NO, "notlong")450 map.put(SCREENLAYOUT_LONG_NO, "notlong"); map.put(SCREENLAYOUT_LONG_YES, "long")451 map.put(SCREENLAYOUT_LONG_YES, "long"); 452 SCREENLAYOUT_LONG_VALUES = Collections.unmodifiableMap(map); 453 } 454 455 // screenLayout2 bits for round/notround. 456 static final int MASK_SCREENROUND = 0x03; 457 public static final int SCREENROUND_ANY = ACONFIGURATION_SCREENROUND_ANY; 458 public static final int SCREENROUND_NO = ACONFIGURATION_SCREENROUND_NO; 459 public static final int SCREENROUND_YES = ACONFIGURATION_SCREENROUND_YES; 460 461 static final int SCREENLAYOUT_ROUND_MASK = 0x03; 462 static final int SCREENLAYOUT_ROUND_NO = 0x01; 463 static final int SCREENLAYOUT_ROUND_YES = 0x02; 464 465 private static final Map<Integer, String> SCREENLAYOUT_ROUND_VALUES; 466 467 static { 468 Map<Integer, String> map = new HashMap<>(); map.put(SCREENLAYOUT_ROUND_NO, "notround")469 map.put(SCREENLAYOUT_ROUND_NO, "notround"); map.put(SCREENLAYOUT_ROUND_YES, "round")470 map.put(SCREENLAYOUT_ROUND_YES, "round"); 471 SCREENLAYOUT_ROUND_VALUES = Collections.unmodifiableMap(map); 472 } 473 474 static final int SCREENLAYOUT_SIZE_MASK = 0x0F; 475 static final int SCREENLAYOUT_SIZE_SMALL = 0x01; 476 static final int SCREENLAYOUT_SIZE_NORMAL = 0x02; 477 static final int SCREENLAYOUT_SIZE_LARGE = 0x03; 478 static final int SCREENLAYOUT_SIZE_XLARGE = 0x04; 479 480 private static final Map<Integer, String> SCREENLAYOUT_SIZE_VALUES; 481 482 static { 483 Map<Integer, String> map = new HashMap<>(); map.put(SCREENLAYOUT_SIZE_SMALL, "small")484 map.put(SCREENLAYOUT_SIZE_SMALL, "small"); map.put(SCREENLAYOUT_SIZE_NORMAL, "normal")485 map.put(SCREENLAYOUT_SIZE_NORMAL, "normal"); map.put(SCREENLAYOUT_SIZE_LARGE, "large")486 map.put(SCREENLAYOUT_SIZE_LARGE, "large"); map.put(SCREENLAYOUT_SIZE_XLARGE, "xlarge")487 map.put(SCREENLAYOUT_SIZE_XLARGE, "xlarge"); 488 SCREENLAYOUT_SIZE_VALUES = Collections.unmodifiableMap(map); 489 } 490 491 static final int TOUCHSCREEN_NOTOUCH = 1; 492 @Deprecated static final int TOUCHSCREEN_STYLUS = 2; 493 public static final int TOUCHSCREEN_FINGER = 3; 494 495 private static final Map<Integer, String> TOUCHSCREEN_VALUES; 496 497 static { 498 Map<Integer, String> map = new HashMap<>(); map.put(TOUCHSCREEN_NOTOUCH, "notouch")499 map.put(TOUCHSCREEN_NOTOUCH, "notouch"); map.put(TOUCHSCREEN_FINGER, "finger")500 map.put(TOUCHSCREEN_FINGER, "finger"); 501 TOUCHSCREEN_VALUES = Collections.unmodifiableMap(map); 502 } 503 504 static final int UI_MODE_NIGHT_MASK = 0x30; 505 public static final int UI_MODE_NIGHT_NO = 0x10; 506 static final int UI_MODE_NIGHT_YES = 0x20; 507 508 private static final Map<Integer, String> UI_MODE_NIGHT_VALUES; 509 510 static { 511 Map<Integer, String> map = new HashMap<>(); map.put(UI_MODE_NIGHT_NO, "notnight")512 map.put(UI_MODE_NIGHT_NO, "notnight"); map.put(UI_MODE_NIGHT_YES, "night")513 map.put(UI_MODE_NIGHT_YES, "night"); 514 UI_MODE_NIGHT_VALUES = Collections.unmodifiableMap(map); 515 } 516 517 static final int UI_MODE_TYPE_MASK = 0x0F; 518 static final int UI_MODE_TYPE_DESK = 0x02; 519 static final int UI_MODE_TYPE_CAR = 0x03; 520 static final int UI_MODE_TYPE_TELEVISION = 0x04; 521 static final int UI_MODE_TYPE_APPLIANCE = 0x05; 522 static final int UI_MODE_TYPE_WATCH = 0x06; 523 static final int UI_MODE_TYPE_VR_HEADSET = 0x07; 524 525 private static final Map<Integer, String> UI_MODE_TYPE_VALUES; 526 527 static { 528 Map<Integer, String> map = new HashMap<>(); map.put(UI_MODE_TYPE_DESK, "desk")529 map.put(UI_MODE_TYPE_DESK, "desk"); map.put(UI_MODE_TYPE_CAR, "car")530 map.put(UI_MODE_TYPE_CAR, "car"); map.put(UI_MODE_TYPE_TELEVISION, "television")531 map.put(UI_MODE_TYPE_TELEVISION, "television"); map.put(UI_MODE_TYPE_APPLIANCE, "appliance")532 map.put(UI_MODE_TYPE_APPLIANCE, "appliance"); map.put(UI_MODE_TYPE_WATCH, "watch")533 map.put(UI_MODE_TYPE_WATCH, "watch"); map.put(UI_MODE_TYPE_VR_HEADSET, "vrheadset")534 map.put(UI_MODE_TYPE_VR_HEADSET, "vrheadset"); 535 UI_MODE_TYPE_VALUES = Collections.unmodifiableMap(map); 536 } 537 538 /** The number of bytes that this resource configuration takes up. */ 539 int size; 540 541 public int mcc; 542 public int mnc; 543 544 /** Returns a packed 2-byte language code. */ 545 @SuppressWarnings("mutable") 546 public final byte[] language; 547 548 /** Returns {@link #language} as an unpacked string representation. */ 549 @Nonnull languageString()550 public final String languageString() { 551 return unpackLanguage(); 552 } 553 554 /** Returns the {@link #localeScript} as a string. */ localeScriptString()555 public final String localeScriptString() { 556 return byteArrayToString(localeScript); 557 } 558 559 /** Returns the {@link #localeVariant} as a string. */ localeVariantString()560 public final String localeVariantString() { 561 return byteArrayToString(localeVariant); 562 } 563 byteArrayToString(byte[] data)564 private String byteArrayToString(byte[] data) { 565 int length = Bytes.indexOf(data, (byte) 0); 566 return new String(data, 0, length >= 0 ? length : data.length, Charsets.US_ASCII); 567 } 568 569 /** Returns the wide color gamut section of {@link #colorMode}. */ colorModeWideColorGamut()570 public final int colorModeWideColorGamut() { 571 return colorMode & COLOR_MODE_WIDE_COLOR_GAMUT_MASK; 572 } 573 574 /** Returns the HDR section of {@link #colorMode}. */ colorModeHdr()575 public final int colorModeHdr() { 576 return colorMode & COLOR_MODE_HDR_MASK; 577 } 578 579 /** Returns a packed 2-byte country code. */ 580 @SuppressWarnings("mutable") 581 public final byte[] country; 582 583 /** Returns {@link #country} as an unpacked string representation. */ 584 @Nonnull regionString()585 public final String regionString() { 586 return unpackRegion(); 587 } 588 scriptString()589 public final String scriptString() { 590 if (localeScript[0] != '\0') { 591 return new String(localeScript, Charsets.UTF_8); 592 } else { 593 return null; 594 } 595 } 596 597 public int orientation; 598 public int touchscreen; 599 public int density; 600 public int keyboard; 601 public int navigation; 602 public int inputFlags; 603 keyboardHidden()604 public final int keyboardHidden() { 605 return inputFlags & KEYBOARDHIDDEN_MASK; 606 } 607 keyboardHidden(int value)608 public final void keyboardHidden(int value) { 609 inputFlags = (inputFlags & ~KEYBOARDHIDDEN_MASK) | value; 610 } 611 navigationHidden()612 public final int navigationHidden() { 613 return (inputFlags & NAVIGATIONHIDDEN_MASK) >> 2; 614 } 615 navigationHidden(int value)616 public final void navigationHidden(int value) { 617 inputFlags = (inputFlags & ~NAVIGATIONHIDDEN_MASK) | value; 618 } 619 620 public int screenWidth; 621 public int screenHeight; 622 public int sdkVersion; 623 624 /** 625 * Returns a copy of this resource configuration with a different {@link #sdkVersion}, or this 626 * configuration if the {@code sdkVersion} is the same. 627 * 628 * @param sdkVersion The SDK version of the returned configuration. 629 * @return A copy of this configuration with the only difference being #sdkVersion. 630 */ withSdkVersion(int sdkVersion)631 public final ResTable_config withSdkVersion(int sdkVersion) { 632 if (sdkVersion == this.sdkVersion) { 633 return this; 634 } 635 return new ResTable_config(size, mcc, mnc, language, country, 636 orientation, touchscreen, density, keyboard, navigation, inputFlags, 637 screenWidth, screenHeight, sdkVersion, minorVersion, screenLayout, uiMode, 638 smallestScreenWidthDp, screenWidthDp, screenHeightDp, localeScript, localeVariant, 639 screenLayout2, colorMode, screenConfigPad2, unknown); 640 } 641 ResTable_config(int size, int mcc, int mnc, byte[] language, byte[] country, int orientation, int touchscreen, int density, int keyboard, int navigation, int inputFlags, int screenWidth, int screenHeight, int sdkVersion, int minorVersion, int screenLayout, int uiMode, int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, byte[] localeScript, byte[] localeVariant, byte screenLayout2, byte colorMode, short screenConfigPad2, byte[] unknown)642 public ResTable_config(int size, int mcc, int mnc, byte[] language, byte[] country, 643 int orientation, int touchscreen, int density, int keyboard, int navigation, int inputFlags, 644 int screenWidth, int screenHeight, int sdkVersion, int minorVersion, int screenLayout, 645 int uiMode, int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, 646 byte[] localeScript, byte[] localeVariant, byte screenLayout2, byte colorMode, 647 short screenConfigPad2, byte[] unknown) { 648 this.size = size; 649 this.mcc = mcc; 650 this.mnc = mnc; 651 this.language = language; 652 this.country = country; 653 this.orientation = orientation; 654 this.touchscreen = touchscreen; 655 this.density = density; 656 this.keyboard = keyboard; 657 this.navigation = navigation; 658 this.inputFlags = inputFlags; 659 this.screenWidth = screenWidth; 660 this.screenHeight = screenHeight; 661 this.sdkVersion = sdkVersion; 662 this.minorVersion = minorVersion; 663 this.screenLayout = screenLayout; 664 this.uiMode = uiMode; 665 this.smallestScreenWidthDp = smallestScreenWidthDp; 666 this.screenWidthDp = screenWidthDp; 667 this.screenHeightDp = screenHeightDp; 668 this.localeScript = localeScript; 669 this.localeVariant = localeVariant; 670 this.screenLayout2 = screenLayout2; 671 this.colorMode = colorMode; 672 this.screenConfigPad2 = screenConfigPad2; 673 this.unknown = unknown; 674 } 675 ResTable_config()676 public ResTable_config() { 677 this.language = new byte[2]; 678 this.country = new byte[2]; 679 this.localeScript = new byte[LocaleData.SCRIPT_LENGTH]; 680 this.localeVariant = new byte[8]; 681 } 682 683 public int minorVersion; 684 public int screenLayout; 685 screenLayoutDirection()686 public final int screenLayoutDirection() { 687 return screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK; 688 } 689 screenLayoutDirection(int value)690 public final void screenLayoutDirection(int value) { 691 screenLayout = (screenLayout & ~SCREENLAYOUT_LAYOUTDIR_MASK) | value; 692 } 693 screenLayoutSize()694 public final int screenLayoutSize() { 695 return screenLayout & SCREENLAYOUT_SIZE_MASK; 696 } 697 screenLayoutSize(int value)698 public final void screenLayoutSize(int value) { 699 screenLayout = (screenLayout & ~SCREENLAYOUT_SIZE_MASK) | value; 700 } 701 screenLayoutLong()702 public final int screenLayoutLong() { 703 return screenLayout & SCREENLAYOUT_LONG_MASK; 704 } 705 screenLayoutLong(int value)706 public final void screenLayoutLong(int value) { 707 screenLayout = (screenLayout & ~SCREENLAYOUT_LONG_MASK) | value; 708 } 709 screenLayoutRound()710 public final int screenLayoutRound() { 711 return screenLayout2 & SCREENLAYOUT_ROUND_MASK; 712 } 713 screenLayoutRound(int value)714 public final void screenLayoutRound(int value) { 715 screenLayout2 = (byte) ((screenLayout2 & ~SCREENLAYOUT_ROUND_MASK) | value); 716 } 717 718 public int uiMode; 719 uiModeType()720 public final int uiModeType() { 721 return uiMode & UI_MODE_TYPE_MASK; 722 } 723 uiModeType(int value)724 public final void uiModeType(int value) { 725 uiMode = (uiMode & ~UI_MODE_TYPE_MASK) | value; 726 } 727 uiModeNight()728 public final int uiModeNight() { 729 return uiMode & UI_MODE_NIGHT_MASK; 730 } 731 uiModeNight(int value)732 public final void uiModeNight(int value) { 733 uiMode = (uiMode & ~UI_MODE_NIGHT_MASK) | value; 734 } 735 736 public int smallestScreenWidthDp; 737 public int screenWidthDp; 738 public int screenHeightDp; 739 740 /** The ISO-15924 short name for the script corresponding to this configuration. */ 741 @SuppressWarnings("mutable") 742 public final byte[] localeScript; 743 744 /** A single BCP-47 variant subtag. */ 745 @SuppressWarnings("mutable") 746 public final byte[] localeVariant; 747 748 /** An extension to {@link #screenLayout}. Contains round/notround qualifier. */ 749 public byte screenLayout2; // Contains round/notround qualifier. 750 public byte colorMode; // Wide-gamut, HDR, etc. 751 public short screenConfigPad2; // Reserved padding. 752 753 /** Any remaining bytes in this resource configuration that are unaccounted for. */ 754 @SuppressWarnings("mutable") 755 public byte[] unknown; 756 757 758 /** 759 * // An extension of screenConfig. 760 union { 761 struct { 762 uint8_t screenLayout2; // Contains round/notround qualifier. 763 uint8_t screenConfigPad1; // Reserved padding. 764 uint16_t screenConfigPad2; // Reserved padding. 765 }; 766 uint32_t screenConfig2; 767 }; 768 */ screenConfig2()769 private int screenConfig2() { 770 return ((screenLayout2 & 0xff) << 24) | ((colorMode * 0xff) << 16) | (screenConfigPad2 & 0xffff); 771 } 772 773 // If false and localeScript is set, it means that the script of the locale 774 // was explicitly provided. 775 // 776 // If true, it means that localeScript was automatically computed. 777 // localeScript may still not be set in this case, which means that we 778 // tried but could not compute a script. 779 boolean localeScriptWasComputed; 780 781 // The value of BCP 47 Unicode extension for key 'nu' (numbering system). 782 // Varies in length from 3 to 8 chars. Zero-filled value. 783 byte[] localeNumberingSystem = new byte[8]; 784 785 // -------------------------------------------------------------------- 786 // -------------------------------------------------------------------- 787 // -------------------------------------------------------------------- 788 789 // void copyFromDeviceNoSwap(final ResTable_config o) { 790 // final int size = dtohl(o.size); 791 // if (size >= sizeof(ResTable_config)) { 792 // *this = o; 793 // } else { 794 // memcpy(this, &o, size); 795 // memset(((uint8_t*)this)+size, 0, sizeof(ResTable_config)-size); 796 // } 797 // } 798 799 @Nonnull unpackLanguageOrRegion(byte[] value, int base)800 private String unpackLanguageOrRegion(byte[] value, int base) { 801 Preconditions.checkState(value.length == 2, "Language or country value must be 2 bytes."); 802 if (value[0] == 0 && value[1] == 0) { 803 return ""; 804 } 805 if (isTruthy(UnsignedBytes.toInt(value[0]) & 0x80)) { 806 byte[] result = new byte[3]; 807 result[0] = (byte) (base + (value[1] & 0x1F)); 808 result[1] = (byte) (base + ((value[1] & 0xE0) >>> 5) + ((value[0] & 0x03) << 3)); 809 result[2] = (byte) (base + ((value[0] & 0x7C) >>> 2)); 810 return new String(result, US_ASCII); 811 } 812 return new String(value, US_ASCII); 813 } 814 packLanguageOrRegion(final String in, final byte base, final byte[] out)815 /* static */ void packLanguageOrRegion(final String in, final byte base, 816 final byte[] out) { 817 if (in == null) { 818 out[0] = 0; 819 out[1] = 0; 820 } else if (in.length() < 3 || in.charAt(2) == 0 || in.charAt(2) == '-') { 821 out[0] = (byte) in.charAt(0); 822 out[1] = (byte) in.charAt(1); 823 } else { 824 byte first = (byte) ((in.charAt(0) - base) & 0x007f); 825 byte second = (byte) ((in.charAt(1) - base) & 0x007f); 826 byte third = (byte) ((in.charAt(2) - base) & 0x007f); 827 828 out[0] = (byte) (0x80 | (third << 2) | (second >> 3)); 829 out[1] = (byte) ((second << 5) | first); 830 } 831 } 832 packLanguage(final String language)833 public void packLanguage(final String language) { 834 packLanguageOrRegion(language, (byte) 'a', this.language); 835 } 836 packRegion(final String region)837 public void packRegion(final String region) { 838 packLanguageOrRegion(region, (byte) '0', this.country); 839 } 840 841 @Nonnull unpackLanguage()842 private String unpackLanguage() { 843 return unpackLanguageOrRegion(language, 0x61); 844 } 845 unpackRegion()846 private String unpackRegion() { 847 return unpackLanguageOrRegion(country, 0x30); 848 } 849 850 // void copyFromDtoH(final ResTable_config o) { 851 // copyFromDeviceNoSwap(o); 852 // size = sizeof(ResTable_config); 853 // mcc = dtohs(mcc); 854 // mnc = dtohs(mnc); 855 // density = dtohs(density); 856 // screenWidth = dtohs(screenWidth); 857 // screenHeight = dtohs(screenHeight); 858 // sdkVersion = dtohs(sdkVersion); 859 // minorVersion = dtohs(minorVersion); 860 // smallestScreenWidthDp = dtohs(smallestScreenWidthDp); 861 // screenWidthDp = dtohs(screenWidthDp); 862 // screenHeightDp = dtohs(screenHeightDp); 863 // } 864 865 // void ResTable_config::copyFromDtoH(const ResTable_config& o) { fromDtoH(final ResTable_config o)866 static ResTable_config fromDtoH(final ResTable_config o) { 867 return new ResTable_config( 868 0 /*sizeof(ResTable_config)*/, 869 dtohs((short) o.mcc), 870 dtohs((short) o.mnc), 871 o.language, 872 o.country, 873 o.orientation, 874 o.touchscreen, 875 dtohl(o.density), 876 o.keyboard, 877 o.navigation, 878 o.inputFlags, 879 dtohs((short) o.screenWidth), 880 dtohs((short) o.screenHeight), 881 dtohs((short) o.sdkVersion), 882 dtohs((short) o.minorVersion), 883 o.screenLayout, 884 o.uiMode, 885 dtohs((short) o.smallestScreenWidthDp), 886 dtohs((short) o.screenWidthDp), 887 dtohs((short) o.screenHeightDp), 888 o.localeScript, 889 o.localeVariant, 890 o.screenLayout2, 891 o.colorMode, 892 o.screenConfigPad2, 893 o.unknown 894 ); 895 } 896 swapHtoD()897 void swapHtoD() { 898 // size = htodl(size); 899 // mcc = htods(mcc); 900 // mnc = htods(mnc); 901 // density = htods(density); 902 // screenWidth = htods(screenWidth); 903 // screenHeight = htods(screenHeight); 904 // sdkVersion = htods(sdkVersion); 905 // minorVersion = htods(minorVersion); 906 // smallestScreenWidthDp = htods(smallestScreenWidthDp); 907 // screenWidthDp = htods(screenWidthDp); 908 // screenHeightDp = htods(screenHeightDp); 909 } 910 compareLocales(final ResTable_config l, final ResTable_config r)911 static final int compareLocales(final ResTable_config l, final ResTable_config r) { 912 if (l.locale() != r.locale()) { 913 // NOTE: This is the old behaviour with respect to comparison orders. 914 // The diff value here doesn't make much sense (given our bit packing scheme) 915 // but it's stable, and that's all we need. 916 return (l.locale() > r.locale()) ? 1 : -1; 917 } 918 919 // The language & region are equal, so compare the scripts, variants and 920 // numbering systms in this order. Comparison of variants and numbering 921 // systems should happen very infrequently (if at all.) 922 // The comparison code relies on memcmp low-level optimizations that make it 923 // more efficient than strncmp. 924 final byte emptyScript[] = {'\0', '\0', '\0', '\0'}; 925 final byte[] lScript = l.localeScriptWasComputed ? emptyScript : l.localeScript; 926 final byte[] rScript = r.localeScriptWasComputed ? emptyScript : r.localeScript; 927 // int script = memcmp(lScript, rScript); 928 // if (script) { 929 // return script; 930 // } 931 int d = arrayCompare(lScript, rScript); 932 if (d != 0) return d; 933 934 int variant = arrayCompare(l.localeVariant, r.localeVariant); 935 if (isTruthy(variant)) { 936 return variant; 937 } 938 939 return arrayCompare(l.localeNumberingSystem, r.localeNumberingSystem); 940 } 941 arrayCompare(byte[] l, byte[] r)942 private static int arrayCompare(byte[] l, byte[] r) { 943 for (int i = 0; i < l.length; i++) { 944 byte l0 = l[i]; 945 byte r0 = r[i]; 946 int d = l0 - r0; 947 if (d != 0) return d; 948 } 949 return 0; 950 } 951 952 // Flags indicating a set of config values. These flag constants must 953 // match the corresponding ones in android.content.pm.ActivityInfo and 954 // attrs_manifest.xml. 955 private static final int CONFIG_MCC = AConfiguration.ACONFIGURATION_MCC; 956 private static final int CONFIG_MNC = AConfiguration.ACONFIGURATION_MNC; 957 private static final int CONFIG_LOCALE = AConfiguration.ACONFIGURATION_LOCALE; 958 private static final int CONFIG_TOUCHSCREEN = AConfiguration.ACONFIGURATION_TOUCHSCREEN; 959 private static final int CONFIG_KEYBOARD = AConfiguration.ACONFIGURATION_KEYBOARD; 960 private static final int CONFIG_KEYBOARD_HIDDEN = AConfiguration.ACONFIGURATION_KEYBOARD_HIDDEN; 961 private static final int CONFIG_NAVIGATION = AConfiguration.ACONFIGURATION_NAVIGATION; 962 private static final int CONFIG_ORIENTATION = AConfiguration.ACONFIGURATION_ORIENTATION; 963 private static final int CONFIG_DENSITY = AConfiguration.ACONFIGURATION_DENSITY; 964 private static final int CONFIG_SCREEN_SIZE = AConfiguration.ACONFIGURATION_SCREEN_SIZE; 965 private static final int CONFIG_SMALLEST_SCREEN_SIZE = AConfiguration.ACONFIGURATION_SMALLEST_SCREEN_SIZE; 966 private static final int CONFIG_VERSION = AConfiguration.ACONFIGURATION_VERSION; 967 private static final int CONFIG_SCREEN_LAYOUT = AConfiguration.ACONFIGURATION_SCREEN_LAYOUT; 968 private static final int CONFIG_UI_MODE = AConfiguration.ACONFIGURATION_UI_MODE; 969 private static final int CONFIG_LAYOUTDIR = AConfiguration.ACONFIGURATION_LAYOUTDIR; 970 private static final int CONFIG_SCREEN_ROUND = AConfiguration.ACONFIGURATION_SCREEN_ROUND; 971 private static final int CONFIG_COLOR_MODE = AConfiguration.ACONFIGURATION_COLOR_MODE; 972 973 // Compare two configuration, returning CONFIG_* flags set for each value 974 // that is different. diff(final ResTable_config o)975 int diff(final ResTable_config o) { 976 int diffs = 0; 977 if (mcc != o.mcc) diffs |= CONFIG_MCC; 978 if (mnc != o.mnc) diffs |= CONFIG_MNC; 979 if (orientation != o.orientation) diffs |= CONFIG_ORIENTATION; 980 if (density != o.density) diffs |= CONFIG_DENSITY; 981 if (touchscreen != o.touchscreen) diffs |= CONFIG_TOUCHSCREEN; 982 if (((inputFlags^o.inputFlags)&(MASK_KEYSHIDDEN|MASK_NAVHIDDEN)) != 0) 983 diffs |= CONFIG_KEYBOARD_HIDDEN; 984 if (keyboard != o.keyboard) diffs |= CONFIG_KEYBOARD; 985 if (navigation != o.navigation) diffs |= CONFIG_NAVIGATION; 986 if (screenSize() != o.screenSize()) diffs |= CONFIG_SCREEN_SIZE; 987 if (version() != o.version()) diffs |= CONFIG_VERSION; 988 if ((screenLayout & MASK_LAYOUTDIR) != (o.screenLayout & MASK_LAYOUTDIR)) diffs |= CONFIG_LAYOUTDIR; 989 if ((screenLayout & ~MASK_LAYOUTDIR) != (o.screenLayout & ~MASK_LAYOUTDIR)) diffs |= CONFIG_SCREEN_LAYOUT; 990 if ((screenLayout2 & MASK_SCREENROUND) != (o.screenLayout2 & MASK_SCREENROUND)) diffs |= CONFIG_SCREEN_ROUND; 991 if ((colorMode & MASK_WIDE_COLOR_GAMUT) != (o.colorMode & MASK_WIDE_COLOR_GAMUT)) diffs |= CONFIG_COLOR_MODE; 992 if ((colorMode & MASK_HDR) != (o.colorMode & MASK_HDR)) diffs |= CONFIG_COLOR_MODE; 993 if (uiMode != o.uiMode) diffs |= CONFIG_UI_MODE; 994 if (smallestScreenWidthDp != o.smallestScreenWidthDp) diffs |= CONFIG_SMALLEST_SCREEN_SIZE; 995 if (screenSizeDp() != o.screenSizeDp()) diffs |= CONFIG_SCREEN_SIZE; 996 997 int diff = compareLocales(this, o); 998 if (isTruthy(diff)) diffs |= CONFIG_LOCALE; 999 1000 return diffs; 1001 } 1002 1003 // There isn't a well specified "importance" order between variants and 1004 // scripts. We can't easily tell whether, say "en-Latn-US" is more or less 1005 // specific than "en-US-POSIX". 1006 // 1007 // We therefore arbitrarily decide to give priority to variants over 1008 // scripts since it seems more useful to do so. We will consider 1009 // "en-US-POSIX" to be more specific than "en-Latn-US". 1010 // 1011 // Unicode extension keywords are considered to be less important than 1012 // scripts and variants. getImportanceScoreOfLocale()1013 int getImportanceScoreOfLocale() { 1014 return (isTruthy(localeVariant[0]) ? 4 : 0) 1015 + (isTruthy(localeScript[0]) && !localeScriptWasComputed ? 2: 0) 1016 + (isTruthy(localeNumberingSystem[0]) ? 1: 0); 1017 } 1018 compare(final ResTable_config o)1019 int compare(final ResTable_config o) { 1020 if (imsi() != o.imsi()) { 1021 return (imsi() > o.imsi()) ? 1 : -1; 1022 } 1023 1024 int diff = compareLocales(this, o); 1025 if (diff < 0) { 1026 return -1; 1027 } 1028 if (diff > 0) { 1029 return 1; 1030 } 1031 1032 if (screenType() != o.screenType()) { 1033 return (screenType() > o.screenType()) ? 1 : -1; 1034 } 1035 if (input() != o.input()) { 1036 return (input() > o.input()) ? 1 : -1; 1037 } 1038 if (screenSize() != o.screenSize()) { 1039 return (screenSize() > o.screenSize()) ? 1 : -1; 1040 } 1041 if (version() != o.version()) { 1042 return (version() > o.version()) ? 1 : -1; 1043 } 1044 if (screenLayout != o.screenLayout) { 1045 return (screenLayout > o.screenLayout) ? 1 : -1; 1046 } 1047 if (screenLayout2 != o.screenLayout2) { 1048 return (screenLayout2 > o.screenLayout2) ? 1 : -1; 1049 } 1050 if (colorMode != o.colorMode) { 1051 return (colorMode > o.colorMode) ? 1 : -1; 1052 } 1053 if (uiMode != o.uiMode) { 1054 return (uiMode > o.uiMode) ? 1 : -1; 1055 } 1056 if (smallestScreenWidthDp != o.smallestScreenWidthDp) { 1057 return (smallestScreenWidthDp > o.smallestScreenWidthDp) ? 1 : -1; 1058 } 1059 if (screenSizeDp() != o.screenSizeDp()) { 1060 return (screenSizeDp() > o.screenSizeDp()) ? 1 : -1; 1061 } 1062 return 0; 1063 } 1064 1065 1066 /** Returns true if this is the default "any" configuration. */ isDefault()1067 public final boolean isDefault() { 1068 return mcc == 0 1069 && mnc == 0 1070 && isZeroes(language) 1071 && isZeroes(country) 1072 && orientation == 0 1073 && touchscreen == 0 1074 && density == 0 1075 && keyboard == 0 1076 && navigation == 0 1077 && inputFlags == 0 1078 && screenWidth == 0 1079 && screenHeight == 0 1080 && sdkVersion == 0 1081 && minorVersion == 0 1082 && screenLayout == 0 1083 && uiMode == 0 1084 && smallestScreenWidthDp == 0 1085 && screenWidthDp == 0 1086 && screenHeightDp == 0 1087 && isZeroes(localeScript) 1088 && isZeroes(localeVariant) 1089 && screenLayout2 == 0 1090 && colorMode == 0 1091 ; 1092 } 1093 isZeroes(byte[] bytes1)1094 private boolean isZeroes(byte[] bytes1) { 1095 for (byte b : bytes1) { 1096 if (b != 0) { 1097 return false; 1098 } 1099 } 1100 return true; 1101 } 1102 1103 @Override toString()1104 public final String toString() { 1105 if (isDefault()) { // Prevent the default configuration from returning the empty string 1106 return "default"; 1107 } 1108 Collection<String> parts = toStringParts().values(); 1109 parts.removeAll(Collections.singleton("")); 1110 return Joiner.on('-').join(parts); 1111 } 1112 1113 /** 1114 * Returns a map of the configuration parts for {@link #toString}. 1115 * 1116 * If a configuration part is not defined for this {@link ResTable_config}, its value 1117 * will be the empty string. 1118 */ toStringParts()1119 public final Map<Type, String> toStringParts() { 1120 Map<Type, String> result = new LinkedHashMap<>(); // Preserve order for #toString(). 1121 result.put(Type.MCC, mcc != 0 ? "mcc" + mcc : ""); 1122 result.put(Type.MNC, mnc != 0 ? "mnc" + mnc : ""); 1123 result.put(Type.LANGUAGE_STRING, languageString()); 1124 result.put(Type.LOCALE_SCRIPT_STRING, localeScriptString()); 1125 result.put(Type.REGION_STRING, !regionString().isEmpty() ? "r" + regionString() : ""); 1126 result.put(Type.LOCALE_VARIANT_STRING, localeVariantString()); 1127 result.put(Type.SCREEN_LAYOUT_DIRECTION, 1128 getOrDefault(SCREENLAYOUT_LAYOUTDIR_VALUES, screenLayoutDirection(), "")); 1129 result.put(Type.SMALLEST_SCREEN_WIDTH_DP, 1130 smallestScreenWidthDp != 0 ? "sw" + smallestScreenWidthDp + "dp" : ""); 1131 result.put(Type.SCREEN_WIDTH_DP, screenWidthDp != 0 ? "w" + screenWidthDp + "dp" : ""); 1132 result.put(Type.SCREEN_HEIGHT_DP, screenHeightDp != 0 ? "h" + screenHeightDp + "dp" : ""); 1133 result.put(Type.SCREEN_LAYOUT_SIZE, 1134 getOrDefault(SCREENLAYOUT_SIZE_VALUES, screenLayoutSize(), "")); 1135 result.put(Type.SCREEN_LAYOUT_LONG, 1136 getOrDefault(SCREENLAYOUT_LONG_VALUES, screenLayoutLong(), "")); 1137 result.put(Type.SCREEN_LAYOUT_ROUND, 1138 getOrDefault(SCREENLAYOUT_ROUND_VALUES, screenLayoutRound(), "")); 1139 result.put(Type.COLOR_MODE_HDR, getOrDefault(COLOR_MODE_HDR_VALUES, colorModeHdr(), "")); 1140 result.put( 1141 Type.COLOR_MODE_WIDE_COLOR_GAMUT, 1142 getOrDefault(COLOR_MODE_WIDE_COLOR_GAMUT_VALUES, colorModeWideColorGamut(), "")); 1143 result.put(Type.ORIENTATION, getOrDefault(ORIENTATION_VALUES, orientation, "")); 1144 result.put(Type.UI_MODE_TYPE, getOrDefault(UI_MODE_TYPE_VALUES, uiModeType(), "")); 1145 result.put(Type.UI_MODE_NIGHT, getOrDefault(UI_MODE_NIGHT_VALUES, uiModeNight(), "")); 1146 result.put(Type.DENSITY_DPI, getOrDefault(DENSITY_DPI_VALUES, density, density + "dpi")); 1147 result.put(Type.TOUCHSCREEN, getOrDefault(TOUCHSCREEN_VALUES, touchscreen, "")); 1148 result.put(Type.KEYBOARD_HIDDEN, getOrDefault(KEYBOARDHIDDEN_VALUES, keyboardHidden(), "")); 1149 result.put(Type.KEYBOARD, getOrDefault(KEYBOARD_VALUES, keyboard, "")); 1150 result.put(Type.NAVIGATION_HIDDEN, 1151 getOrDefault(NAVIGATIONHIDDEN_VALUES, navigationHidden(), "")); 1152 result.put(Type.NAVIGATION, getOrDefault(NAVIGATION_VALUES, navigation, "")); 1153 result.put(Type.SCREEN_SIZE, 1154 screenWidth != 0 || screenHeight != 0 ? screenWidth + "x" + screenHeight : ""); 1155 1156 String sdkVersion = ""; 1157 if (this.sdkVersion != 0) { 1158 sdkVersion = "v" + this.sdkVersion; 1159 if (minorVersion != 0) { 1160 sdkVersion += "." + minorVersion; 1161 } 1162 } 1163 result.put(Type.SDK_VERSION, sdkVersion); 1164 return result; 1165 } 1166 getOrDefault(Map<K, V> map, K key, V defaultValue)1167 private <K, V> V getOrDefault(Map<K, V> map, K key, V defaultValue) { 1168 // TODO(acornwall): Remove this when Java 8's Map#getOrDefault is available. 1169 // Null is not returned, even if the map contains a key whose value is null. This is intended. 1170 V value = map.get(key); 1171 return value != null ? value : defaultValue; 1172 } 1173 1174 1175 // constants for isBetterThan... 1176 public static final int MASK_LAYOUTDIR = SCREENLAYOUT_LAYOUTDIR_MASK; 1177 static final int MASK_SCREENSIZE = SCREENLAYOUT_SIZE_MASK; 1178 isBetterThan( ResTable_config o, ResTable_config requested)1179 public boolean isBetterThan( 1180 ResTable_config o, ResTable_config requested) { 1181 if (isTruthy(requested)) { 1182 if (isTruthy(imsi()) || isTruthy(o.imsi())) { 1183 if ((mcc != o.mcc) && isTruthy(requested.mcc)) { 1184 return (isTruthy(mcc)); 1185 } 1186 1187 if ((mnc != o.mnc) && isTruthy(requested.mnc)) { 1188 return (isTruthy(mnc)); 1189 } 1190 } 1191 1192 if (isLocaleBetterThan(o, requested)) { 1193 return true; 1194 } 1195 1196 if (isTruthy(screenLayout) || isTruthy(o.screenLayout)) { 1197 if (isTruthy((screenLayout^o.screenLayout) & MASK_LAYOUTDIR) 1198 && isTruthy(requested.screenLayout & MASK_LAYOUTDIR)) { 1199 int myLayoutDir = screenLayout & MASK_LAYOUTDIR; 1200 int oLayoutDir = o.screenLayout & MASK_LAYOUTDIR; 1201 return (myLayoutDir > oLayoutDir); 1202 } 1203 } 1204 1205 if (isTruthy(smallestScreenWidthDp) || isTruthy(o.smallestScreenWidthDp)) { 1206 // The configuration closest to the actual size is best. 1207 // We assume that larger configs have already been filtered 1208 // out at this point. That means we just want the largest one. 1209 if (smallestScreenWidthDp != o.smallestScreenWidthDp) { 1210 return smallestScreenWidthDp > o.smallestScreenWidthDp; 1211 } 1212 } 1213 1214 if (isTruthy(screenSizeDp()) || isTruthy(o.screenSizeDp())) { 1215 // "Better" is based on the sum of the difference between both 1216 // width and height from the requested dimensions. We are 1217 // assuming the invalid configs (with smaller dimens) have 1218 // already been filtered. Note that if a particular dimension 1219 // is unspecified, we will end up with a large value (the 1220 // difference between 0 and the requested dimension), which is 1221 // good since we will prefer a config that has specified a 1222 // dimension value. 1223 int myDelta = 0, otherDelta = 0; 1224 if (isTruthy(requested.screenWidthDp)) { 1225 myDelta += requested.screenWidthDp - screenWidthDp; 1226 otherDelta += requested.screenWidthDp - o.screenWidthDp; 1227 } 1228 if (isTruthy(requested.screenHeightDp)) { 1229 myDelta += requested.screenHeightDp - screenHeightDp; 1230 otherDelta += requested.screenHeightDp - o.screenHeightDp; 1231 } 1232 1233 if (myDelta != otherDelta) { 1234 return myDelta < otherDelta; 1235 } 1236 } 1237 1238 if (isTruthy(screenLayout) || isTruthy(o.screenLayout)) { 1239 if (isTruthy((screenLayout^o.screenLayout) & MASK_SCREENSIZE) 1240 && isTruthy(requested.screenLayout & MASK_SCREENSIZE)) { 1241 // A little backwards compatibility here: undefined is 1242 // considered equivalent to normal. But only if the 1243 // requested size is at least normal; otherwise, small 1244 // is better than the default. 1245 int mySL = (screenLayout & MASK_SCREENSIZE); 1246 int oSL = (o.screenLayout & MASK_SCREENSIZE); 1247 int fixedMySL = mySL; 1248 int fixedOSL = oSL; 1249 if ((requested.screenLayout & MASK_SCREENSIZE) >= SCREENSIZE_NORMAL) { 1250 if (fixedMySL == 0) fixedMySL = SCREENSIZE_NORMAL; 1251 if (fixedOSL == 0) fixedOSL = SCREENSIZE_NORMAL; 1252 } 1253 // For screen size, the best match is the one that is 1254 // closest to the requested screen size, but not over 1255 // (the not over part is dealt with in match() below). 1256 if (fixedMySL == fixedOSL) { 1257 // If the two are the same, but 'this' is actually 1258 // undefined, then the other is really a better match. 1259 if (mySL == 0) return false; 1260 return true; 1261 } 1262 if (fixedMySL != fixedOSL) { 1263 return fixedMySL > fixedOSL; 1264 } 1265 } 1266 if (((screenLayout^o.screenLayout) & MASK_SCREENLONG) != 0 1267 && isTruthy(requested.screenLayout & MASK_SCREENLONG)) { 1268 return isTruthy(screenLayout & MASK_SCREENLONG); 1269 } 1270 } 1271 1272 if (isTruthy(screenLayout2) || isTruthy(o.screenLayout2)) { 1273 if (((screenLayout2^o.screenLayout2) & MASK_SCREENROUND) != 0 && 1274 isTruthy(requested.screenLayout2 & MASK_SCREENROUND)) { 1275 return isTruthy(screenLayout2 & MASK_SCREENROUND); 1276 } 1277 } 1278 1279 if (isTruthy(colorMode) || isTruthy(o.colorMode)) { 1280 if (((colorMode^o.colorMode) & MASK_WIDE_COLOR_GAMUT) != 0 && 1281 isTruthy((requested.colorMode & MASK_WIDE_COLOR_GAMUT))) { 1282 return isTruthy(colorMode & MASK_WIDE_COLOR_GAMUT); 1283 } 1284 if (((colorMode^o.colorMode) & MASK_HDR) != 0 && 1285 isTruthy((requested.colorMode & MASK_HDR))) { 1286 return isTruthy(colorMode & MASK_HDR); 1287 } 1288 } 1289 1290 if ((orientation != o.orientation) && isTruthy(requested.orientation)) { 1291 return isTruthy(orientation); 1292 } 1293 1294 if (isTruthy(uiMode) || isTruthy(o.uiMode)) { 1295 if (((uiMode^o.uiMode) & MASK_UI_MODE_TYPE) != 0 1296 && isTruthy(requested.uiMode & MASK_UI_MODE_TYPE)) { 1297 return isTruthy(uiMode & MASK_UI_MODE_TYPE); 1298 } 1299 if (((uiMode^o.uiMode) & MASK_UI_MODE_NIGHT) != 0 1300 && isTruthy(requested.uiMode & MASK_UI_MODE_NIGHT)) { 1301 return isTruthy(uiMode & MASK_UI_MODE_NIGHT); 1302 } 1303 } 1304 1305 if (isTruthy(screenType()) || isTruthy(o.screenType())) { 1306 if (density != o.density) { 1307 // Use the system default density (DENSITY_MEDIUM, 160dpi) if none specified. 1308 final int thisDensity = isTruthy(density) ? density : DENSITY_MEDIUM; 1309 final int otherDensity = isTruthy(o.density) ? o.density : DENSITY_MEDIUM; 1310 1311 // We always prefer DENSITY_ANY over scaling a density bucket. 1312 if (thisDensity == DENSITY_ANY) { 1313 return true; 1314 } else if (otherDensity == DENSITY_ANY) { 1315 return false; 1316 } 1317 1318 int requestedDensity = requested.density; 1319 if (requested.density == 0 || 1320 requested.density == DENSITY_ANY) { 1321 requestedDensity = DENSITY_MEDIUM; 1322 } 1323 1324 // DENSITY_ANY is now dealt with. We should look to 1325 // pick a density bucket and potentially scale it. 1326 // Any density is potentially useful 1327 // because the system will scale it. Scaling down 1328 // is generally better than scaling up. 1329 int h = thisDensity; 1330 int l = otherDensity; 1331 boolean bImBigger = true; 1332 if (l > h) { 1333 int t = h; 1334 h = l; 1335 l = t; 1336 bImBigger = false; 1337 } 1338 1339 if (requestedDensity >= h) { 1340 // requested value higher than both l and h, give h 1341 return bImBigger; 1342 } 1343 if (l >= requestedDensity) { 1344 // requested value lower than both l and h, give l 1345 return !bImBigger; 1346 } 1347 // saying that scaling down is 2x better than up 1348 if (((2 * l) - requestedDensity) * h > requestedDensity * requestedDensity) { 1349 return !bImBigger; 1350 } else { 1351 return bImBigger; 1352 } 1353 } 1354 1355 if ((touchscreen != o.touchscreen) && isTruthy(requested.touchscreen)) { 1356 return isTruthy(touchscreen); 1357 } 1358 } 1359 1360 if (isTruthy(input()) || isTruthy(o.input())) { 1361 final int keysHidden = inputFlags & MASK_KEYSHIDDEN; 1362 final int oKeysHidden = o.inputFlags & MASK_KEYSHIDDEN; 1363 if (keysHidden != oKeysHidden) { 1364 final int reqKeysHidden = 1365 requested.inputFlags & MASK_KEYSHIDDEN; 1366 if (isTruthy(reqKeysHidden)) { 1367 1368 if (keysHidden == 0) return false; 1369 if (oKeysHidden == 0) return true; 1370 // For compatibility, we count KEYSHIDDEN_NO as being 1371 // the same as KEYSHIDDEN_SOFT. Here we disambiguate 1372 // these by making an exact match more specific. 1373 if (reqKeysHidden == keysHidden) return true; 1374 if (reqKeysHidden == oKeysHidden) return false; 1375 } 1376 } 1377 1378 final int navHidden = inputFlags & MASK_NAVHIDDEN; 1379 final int oNavHidden = o.inputFlags & MASK_NAVHIDDEN; 1380 if (navHidden != oNavHidden) { 1381 final int reqNavHidden = 1382 requested.inputFlags & MASK_NAVHIDDEN; 1383 if (isTruthy(reqNavHidden)) { 1384 1385 if (navHidden == 0) return false; 1386 if (oNavHidden == 0) return true; 1387 } 1388 } 1389 1390 if ((keyboard != o.keyboard) && isTruthy(requested.keyboard)) { 1391 return isTruthy(keyboard); 1392 } 1393 1394 if ((navigation != o.navigation) && isTruthy(requested.navigation)) { 1395 return isTruthy(navigation); 1396 } 1397 } 1398 1399 if (isTruthy(screenSize()) || isTruthy(o.screenSize())) { 1400 // "Better" is based on the sum of the difference between both 1401 // width and height from the requested dimensions. We are 1402 // assuming the invalid configs (with smaller sizes) have 1403 // already been filtered. Note that if a particular dimension 1404 // is unspecified, we will end up with a large value (the 1405 // difference between 0 and the requested dimension), which is 1406 // good since we will prefer a config that has specified a 1407 // size value. 1408 int myDelta = 0, otherDelta = 0; 1409 if (isTruthy(requested.screenWidth)) { 1410 myDelta += requested.screenWidth - screenWidth; 1411 otherDelta += requested.screenWidth - o.screenWidth; 1412 } 1413 if (isTruthy(requested.screenHeight)) { 1414 myDelta += requested.screenHeight - screenHeight; 1415 otherDelta += requested.screenHeight - o.screenHeight; 1416 } 1417 if (myDelta != otherDelta) { 1418 return myDelta < otherDelta; 1419 } 1420 } 1421 1422 if (isTruthy(version()) || isTruthy(o.version())) { 1423 if ((sdkVersion != o.sdkVersion) && isTruthy(requested.sdkVersion)) { 1424 return (sdkVersion > o.sdkVersion); 1425 } 1426 1427 if ((minorVersion != o.minorVersion) && 1428 isTruthy(requested.minorVersion)) { 1429 return isTruthy(minorVersion); 1430 } 1431 } 1432 1433 return false; 1434 } 1435 return isMoreSpecificThan(o); 1436 } 1437 1438 /* 1439 boolean match(final ResTable_config settings) { 1440 System.out.println(this + ".match(" + settings + ")"); 1441 boolean result = match_(settings); 1442 System.out.println(" -> " + result); 1443 return result; 1444 } 1445 */ 1446 match(final ResTable_config settings)1447 public boolean match(final ResTable_config settings) { 1448 if (imsi() != 0) { 1449 if (mcc != 0 && mcc != settings.mcc) { 1450 return false; 1451 } 1452 if (mnc != 0 && mnc != settings.mnc) { 1453 return false; 1454 } 1455 } 1456 if (locale() != 0) { 1457 // Don't consider country and variants when deciding matches. 1458 // (Theoretically, the variant can also affect the script. For 1459 // example, "ar-alalc97" probably implies the Latin script, but since 1460 // CLDR doesn't support getting likely scripts for that, we'll assume 1461 // the variant doesn't change the script.) 1462 // 1463 // If two configs differ only in their country and variant, 1464 // they can be weeded out in the isMoreSpecificThan test. 1465 if (!langsAreEquivalent(language, settings.language)) { 1466 return false; 1467 } 1468 1469 // For backward compatibility and supporting private-use locales, we 1470 // fall back to old behavior if we couldn't determine the script for 1471 // either of the desired locale or the provided locale. But if we could determine 1472 // the scripts, they should be the same for the locales to match. 1473 boolean countriesMustMatch = false; 1474 byte[] computed_script = new byte[4]; 1475 byte[] script = null; 1476 if (settings.localeScript[0] == '\0') { // could not determine the request's script 1477 countriesMustMatch = true; 1478 } else { 1479 if (localeScript[0] == '\0' && !localeScriptWasComputed) { 1480 // script was not provided or computed, so we try to compute it 1481 localeDataComputeScript(computed_script, language, country); 1482 if (computed_script[0] == '\0') { // we could not compute the script 1483 countriesMustMatch = true; 1484 } else { 1485 script = computed_script; 1486 } 1487 } else { // script was provided, so just use it 1488 script = localeScript; 1489 } 1490 } 1491 1492 if (countriesMustMatch) { 1493 if (country[0] != '\0' && !areIdentical(country, settings.country)) { 1494 return false; 1495 } 1496 } else { 1497 if (!Arrays.equals(script, settings.localeScript)) { 1498 return false; 1499 } 1500 } 1501 } 1502 1503 if (screenConfig() != 0) { 1504 final int layoutDir = screenLayout&MASK_LAYOUTDIR; 1505 final int setLayoutDir = settings.screenLayout&MASK_LAYOUTDIR; 1506 if (layoutDir != 0 && layoutDir != setLayoutDir) { 1507 return false; 1508 } 1509 1510 final int screenSize = screenLayout&MASK_SCREENSIZE; 1511 final int setScreenSize = settings.screenLayout&MASK_SCREENSIZE; 1512 // Any screen sizes for larger screens than the setting do not 1513 // match. 1514 if (screenSize != 0 && screenSize > setScreenSize) { 1515 return false; 1516 } 1517 1518 final int screenLong = screenLayout&MASK_SCREENLONG; 1519 final int setScreenLong = settings.screenLayout&MASK_SCREENLONG; 1520 if (screenLong != 0 && screenLong != setScreenLong) { 1521 return false; 1522 } 1523 1524 final int uiModeType = uiMode&MASK_UI_MODE_TYPE; 1525 final int setUiModeType = settings.uiMode&MASK_UI_MODE_TYPE; 1526 if (uiModeType != 0 && uiModeType != setUiModeType) { 1527 return false; 1528 } 1529 1530 final int uiModeNight = uiMode&MASK_UI_MODE_NIGHT; 1531 final int setUiModeNight = settings.uiMode&MASK_UI_MODE_NIGHT; 1532 if (uiModeNight != 0 && uiModeNight != setUiModeNight) { 1533 return false; 1534 } 1535 1536 if (smallestScreenWidthDp != 0 1537 && smallestScreenWidthDp > settings.smallestScreenWidthDp) { 1538 return false; 1539 } 1540 } 1541 1542 if (screenConfig2() != 0) { 1543 final int screenRound = screenLayout2 & MASK_SCREENROUND; 1544 final int setScreenRound = settings.screenLayout2 & MASK_SCREENROUND; 1545 if (screenRound != 0 && screenRound != setScreenRound) { 1546 return false; 1547 } 1548 } 1549 1550 final int hdr = colorMode & MASK_HDR; 1551 final int setHdr = settings.colorMode & MASK_HDR; 1552 if (hdr != 0 && hdr != setHdr) { 1553 return false; 1554 } 1555 1556 final int wideColorGamut = colorMode & MASK_WIDE_COLOR_GAMUT; 1557 final int setWideColorGamut = settings.colorMode & MASK_WIDE_COLOR_GAMUT; 1558 if (wideColorGamut != 0 && wideColorGamut != setWideColorGamut) { 1559 return false; 1560 } 1561 1562 if (screenSizeDp() != 0) { 1563 if (screenWidthDp != 0 && screenWidthDp > settings.screenWidthDp) { 1564 if (kDebugTableSuperNoisy) { 1565 ALOGI("Filtering out width %d in requested %d", screenWidthDp, 1566 settings.screenWidthDp); 1567 } 1568 return false; 1569 } 1570 if (screenHeightDp != 0 && screenHeightDp > settings.screenHeightDp) { 1571 if (kDebugTableSuperNoisy) { 1572 ALOGI("Filtering out height %d in requested %d", screenHeightDp, 1573 settings.screenHeightDp); 1574 } 1575 return false; 1576 } 1577 } 1578 if (screenType() != 0) { 1579 if (orientation != 0 && orientation != settings.orientation) { 1580 return false; 1581 } 1582 // density always matches - we can scale it. See isBetterThan 1583 if (touchscreen != 0 && touchscreen != settings.touchscreen) { 1584 return false; 1585 } 1586 } 1587 if (input() != 0) { 1588 final int keysHidden = inputFlags&MASK_KEYSHIDDEN; 1589 final int setKeysHidden = settings.inputFlags&MASK_KEYSHIDDEN; 1590 if (keysHidden != 0 && keysHidden != setKeysHidden) { 1591 // For compatibility, we count a request for KEYSHIDDEN_NO as also 1592 // matching the more recent KEYSHIDDEN_SOFT. Basically 1593 // KEYSHIDDEN_NO means there is some kind of keyboard available. 1594 if (kDebugTableSuperNoisy) { 1595 ALOGI("Matching keysHidden: have=%d, config=%d\n", keysHidden, setKeysHidden); 1596 } 1597 if (keysHidden != KEYSHIDDEN_NO || setKeysHidden != KEYSHIDDEN_SOFT) { 1598 if (kDebugTableSuperNoisy) { 1599 ALOGI("No match!"); 1600 } 1601 return false; 1602 } 1603 } 1604 final int navHidden = inputFlags&MASK_NAVHIDDEN; 1605 final int setNavHidden = settings.inputFlags&MASK_NAVHIDDEN; 1606 if (navHidden != 0 && navHidden != setNavHidden) { 1607 return false; 1608 } 1609 if (keyboard != 0 && keyboard != settings.keyboard) { 1610 return false; 1611 } 1612 if (navigation != 0 && navigation != settings.navigation) { 1613 return false; 1614 } 1615 } 1616 if (screenSize() != 0) { 1617 if (screenWidth != 0 && screenWidth > settings.screenWidth) { 1618 return false; 1619 } 1620 if (screenHeight != 0 && screenHeight > settings.screenHeight) { 1621 return false; 1622 } 1623 } 1624 if (version() != 0) { 1625 if (sdkVersion != 0 && sdkVersion > settings.sdkVersion) { 1626 return false; 1627 } 1628 if (minorVersion != 0 && minorVersion != settings.minorVersion) { 1629 return false; 1630 } 1631 } 1632 return true; 1633 } 1634 1635 // void appendDirLocale(String8& out) const { 1636 // if (!language[0]) { 1637 // return; 1638 // } 1639 // const bool scriptWasProvided = localeScript[0] != '\0' && !localeScriptWasComputed; 1640 // if (!scriptWasProvided && !localeVariant[0] && !localeNumberingSystem[0]) { 1641 // // Legacy format. 1642 // if (out.size() > 0) { 1643 // out.append("-"); 1644 // } 1645 // 1646 // char buf[4]; 1647 // size_t len = unpackLanguage(buf); 1648 // out.append(buf, len); 1649 // 1650 // if (country[0]) { 1651 // out.append("-r"); 1652 // len = unpackRegion(buf); 1653 // out.append(buf, len); 1654 // } 1655 // return; 1656 // } 1657 // 1658 // // We are writing the modified BCP 47 tag. 1659 // // It starts with 'b+' and uses '+' as a separator. 1660 // 1661 // if (out.size() > 0) { 1662 // out.append("-"); 1663 // } 1664 // out.append("b+"); 1665 // 1666 // char buf[4]; 1667 // size_t len = unpackLanguage(buf); 1668 // out.append(buf, len); 1669 // 1670 // if (scriptWasProvided) { 1671 // out.append("+"); 1672 // out.append(localeScript, sizeof(localeScript)); 1673 // } 1674 // 1675 // if (country[0]) { 1676 // out.append("+"); 1677 // len = unpackRegion(buf); 1678 // out.append(buf, len); 1679 // } 1680 // 1681 // if (localeVariant[0]) { 1682 // out.append("+"); 1683 // out.append(localeVariant, strnlen(localeVariant, sizeof(localeVariant))); 1684 // } 1685 // 1686 // if (localeNumberingSystem[0]) { 1687 // out.append("+u+nu+"); 1688 // out.append(localeNumberingSystem, 1689 // strnlen(localeNumberingSystem, sizeof(localeNumberingSystem))); 1690 // } 1691 // } 1692 1693 // returns string as return value instead of by mutating first arg 1694 // void ResTable_config::getBcp47Locale(char str[RESTABLE_MAX_LOCALE_LEN], bool canonicalize) const { getBcp47Locale(boolean canonicalize)1695 String getBcp47Locale(boolean canonicalize) { 1696 StringBuilder str = new StringBuilder(); 1697 1698 // This represents the "any" locale value, which has traditionally been 1699 // represented by the empty string. 1700 if (language[0] == '\0' && country[0] == '\0') { 1701 return ""; 1702 } 1703 1704 if (language[0] != '\0') { 1705 if (canonicalize && areIdentical(language, kTagalog)) { 1706 // Replace Tagalog with Filipino if we are canonicalizing 1707 str.setLength(0); 1708 str.append("fil");// 3-letter code for Filipino 1709 } else { 1710 str.append(unpackLanguage()); 1711 } 1712 } 1713 1714 if (isTruthy(localeScript[0]) && !localeScriptWasComputed) { 1715 if (str.length() > 0) { 1716 str.append('-'); 1717 } 1718 for (byte aLocaleScript : localeScript) { 1719 str.append((char) aLocaleScript); 1720 } 1721 } 1722 1723 if (country[0] != '\0') { 1724 if (str.length() > 0) { 1725 str.append('-'); 1726 } 1727 String regionStr = unpackRegion(); 1728 str.append(regionStr); 1729 } 1730 1731 if (isTruthy(localeVariant[0])) { 1732 if (str.length() > 0) { 1733 str.append('-'); 1734 } 1735 1736 for (byte aLocaleScript : localeVariant) { 1737 str.append((char) aLocaleScript); 1738 } 1739 } 1740 1741 // Add Unicode extension only if at least one other locale component is present 1742 if (localeNumberingSystem[0] != '\0' && str.length() > 0) { 1743 String NU_PREFIX = "-u-nu-"; 1744 str.append(NU_PREFIX); 1745 str.append(new String(localeNumberingSystem, UTF_8)); 1746 } 1747 1748 return str.toString(); 1749 } 1750 1751 enum State { 1752 BASE, UNICODE_EXTENSION, IGNORE_THE_REST 1753 } 1754 1755 enum UnicodeState { 1756 /* Initial state after the Unicode singleton is detected. Either a keyword 1757 * or an attribute is expected. */ 1758 NO_KEY, 1759 /* Unicode extension key (but not attribute) is expected. Next states: 1760 * NO_KEY, IGNORE_KEY or NUMBERING_SYSTEM. */ 1761 EXPECT_KEY, 1762 /* A key is detected, however it is not supported for now. Ignore its 1763 * value. Next states: IGNORE_KEY or NUMBERING_SYSTEM. */ 1764 IGNORE_KEY, 1765 /* Numbering system key was detected. Store its value in the configuration 1766 * localeNumberingSystem field. Next state: EXPECT_KEY */ 1767 NUMBERING_SYSTEM 1768 } 1769 1770 static class LocaleParserState { 1771 State parserState; 1772 UnicodeState unicodeState; 1773 1774 // LocaleParserState(): parserState(BASE), unicodeState(NO_KEY) {} LocaleParserState()1775 public LocaleParserState() { 1776 this.parserState = State.BASE; 1777 this.unicodeState = UnicodeState.NO_KEY; 1778 } 1779 } 1780 assignLocaleComponent(ResTable_config config, final String start, int size, LocaleParserState state)1781 static LocaleParserState assignLocaleComponent(ResTable_config config, 1782 final String start, int size, LocaleParserState state) { 1783 1784 /* It is assumed that this function is not invoked with state.parserState 1785 * set to IGNORE_THE_REST. The condition is checked by setBcp47Locale 1786 * function. */ 1787 1788 if (state.parserState == State.UNICODE_EXTENSION) { 1789 switch (size) { 1790 case 1: 1791 /* Other BCP 47 extensions are not supported at the moment */ 1792 state.parserState = State.IGNORE_THE_REST; 1793 break; 1794 case 2: 1795 if (state.unicodeState == UnicodeState.NO_KEY || 1796 state.unicodeState == UnicodeState.EXPECT_KEY) { 1797 /* Analyze Unicode extension key. Currently only 'nu' 1798 * (numbering system) is supported.*/ 1799 if ((start.charAt(0) == 'n' || start.charAt(0) == 'N') && 1800 (start.charAt(1) == 'u' || start.charAt(1) == 'U')) { 1801 state.unicodeState = UnicodeState.NUMBERING_SYSTEM; 1802 } else { 1803 state.unicodeState = UnicodeState.IGNORE_KEY; 1804 } 1805 } else { 1806 /* Keys are not allowed in other state allowed, ignore the rest. */ 1807 state.parserState = State.IGNORE_THE_REST; 1808 } 1809 break; 1810 case 3: 1811 case 4: 1812 case 5: 1813 case 6: 1814 case 7: 1815 case 8: 1816 switch (state.unicodeState) { 1817 case NUMBERING_SYSTEM: 1818 /* Accept only the first occurrence of the numbering system. */ 1819 if (config.localeNumberingSystem[0] == '\0') { 1820 for (int i = 0; i < size; ++i) { 1821 config.localeNumberingSystem[i] = (byte) Character.toLowerCase(start.charAt(i)); 1822 } 1823 state.unicodeState = UnicodeState.EXPECT_KEY; 1824 } else { 1825 state.parserState = State.IGNORE_THE_REST; 1826 } 1827 break; 1828 case IGNORE_KEY: 1829 /* Unsupported Unicode keyword. Ignore. */ 1830 state.unicodeState = UnicodeState.EXPECT_KEY; 1831 break; 1832 case EXPECT_KEY: 1833 /* A keyword followed by an attribute is not allowed. */ 1834 state.parserState = State.IGNORE_THE_REST; 1835 break; 1836 case NO_KEY: 1837 /* Extension attribute. Do nothing. */ 1838 break; 1839 } 1840 break; 1841 default: 1842 /* Unexpected field length - ignore the rest and treat as an error */ 1843 state.parserState = State.IGNORE_THE_REST; 1844 } 1845 return state; 1846 } 1847 1848 switch (size) { 1849 case 0: 1850 state.parserState = State.IGNORE_THE_REST; 1851 break; 1852 case 1: 1853 state.parserState = (start.charAt(0) == 'u' || start.charAt(0) == 'U') 1854 ? State.UNICODE_EXTENSION 1855 : State.IGNORE_THE_REST; 1856 break; 1857 case 2: 1858 case 3: 1859 if (isTruthy(config.language[0])) { 1860 config.packRegion(start); 1861 } else { 1862 config.packLanguage(start); 1863 } 1864 break; 1865 case 4: 1866 char start0 = start.charAt(0); 1867 if ('0' <= start0 && start0 <= '9') { 1868 // this is a variant, so fall through 1869 } else { 1870 config.localeScript[0] = (byte) Character.toUpperCase(start0); 1871 for (int i = 1; i < 4; ++i) { 1872 config.localeScript[i] = (byte) Character.toLowerCase(start.charAt(i)); 1873 } 1874 break; 1875 } 1876 // fall through 1877 case 5: 1878 case 6: 1879 case 7: 1880 case 8: 1881 for (int i = 0; i < size; ++i) { 1882 config.localeVariant[i] = (byte) Character.toLowerCase(start.charAt(i)); 1883 } 1884 break; 1885 default: 1886 state.parserState = State.IGNORE_THE_REST; 1887 } 1888 1889 return state; 1890 } 1891 setBcp47Locale(final String in)1892 public void setBcp47Locale(final String in) { 1893 clearLocale(); 1894 1895 int start = 0; 1896 LocaleParserState state = new LocaleParserState(); 1897 int separator; 1898 while ((separator = in.indexOf('-', start)) > 0) { 1899 final int size = separator - start; 1900 state = assignLocaleComponent(this, in.substring(start), size, state); 1901 if (state.parserState == State.IGNORE_THE_REST) { 1902 1903 System.err.println(String.format("Invalid BCP-47 locale string: %s", in)); 1904 break; 1905 } 1906 1907 start = (separator + 1); 1908 } 1909 1910 if (state.parserState != State.IGNORE_THE_REST) { 1911 final int size = in.length() - start; 1912 assignLocaleComponent(this, in.substring(start), size, state); 1913 } 1914 1915 localeScriptWasComputed = (localeScript[0] == '\0'); 1916 if (localeScriptWasComputed) { 1917 computeScript(); 1918 } 1919 } 1920 clearLocale()1921 void clearLocale() { 1922 // locale = 0; 1923 clear(language); 1924 clear(country); 1925 1926 localeScriptWasComputed = false; 1927 clear(localeScript); 1928 clear(localeVariant); 1929 } 1930 computeScript()1931 void computeScript() { 1932 localeDataComputeScript(localeScript, language, country); 1933 } 1934 clear(byte[] bytes)1935 private void clear(byte[] bytes) { 1936 for (int i = 0; i < bytes.length; i++) { 1937 bytes[i] = 0; 1938 } 1939 } 1940 1941 1942 /** 1943 * union { 1944 struct { 1945 // Mobile country code (from SIM). 0 means "any". 1946 uint16_t mcc; 1947 // Mobile network code (from SIM). 0 means "any". 1948 uint16_t mnc; 1949 }; 1950 uint32_t imsi; 1951 }; 1952 */ imsi()1953 private int imsi() { 1954 return ((mcc & 0xffff) << 16) | (mnc & 0xffff); 1955 } 1956 1957 /** 1958 * union { 1959 struct { 1960 uint16_t screenWidth; 1961 uint16_t screenHeight; 1962 }; 1963 uint32_t screenSize; 1964 }; 1965 */ screenSize()1966 private int screenSize() { 1967 return ((screenWidth & 0xffff) << 16) | (screenHeight & 0xffff); 1968 } 1969 1970 1971 /** 1972 union { 1973 struct { 1974 uint8_t screenLayout; 1975 uint8_t uiMode; 1976 uint16_t smallestScreenWidthDp; 1977 }; 1978 uint32_t screenConfig; 1979 }; 1980 */ screenConfig()1981 private int screenConfig() { 1982 return ((screenLayout & 0xff) << 24) | ((uiMode * 0xff) << 16) | (smallestScreenWidthDp & 0xffff); 1983 } 1984 1985 1986 /** 1987 * union { 1988 struct { 1989 uint16_t screenWidthDp; 1990 uint16_t screenHeightDp; 1991 }; 1992 uint32_t screenSizeDp; 1993 }; 1994 */ screenSizeDp()1995 private int screenSizeDp() { 1996 // screenWidthDp and screenHeightDp are really shorts... 1997 return (screenWidthDp & 0xffff) << 16 | (screenHeightDp & 0xffff); 1998 } 1999 2000 /** 2001 union { 2002 struct { 2003 uint8_t orientation; 2004 uint8_t touchscreen; 2005 uint16_t density; 2006 }; 2007 uint32_t screenType; 2008 }; 2009 */ screenType()2010 private int screenType() { 2011 return ((orientation & 0xff) << 24) | ((touchscreen & 0xff) << 16) | (density & 0xffff); 2012 } 2013 2014 /** 2015 * 2016 union { 2017 struct { 2018 uint8_t keyboard; 2019 uint8_t navigation; 2020 uint8_t inputFlags; 2021 uint8_t inputPad0; 2022 }; 2023 uint32_t input; 2024 }; 2025 */ input()2026 private int input() { 2027 // TODO is Pad Zeros? 2028 return ((keyboard & 0xff) << 24) | ((navigation & 0xff) << 16) | ((inputFlags & 0xff) << 8); 2029 } 2030 2031 /** 2032 * union { 2033 struct { 2034 uint16_t sdkVersion; 2035 // For now minorVersion must always be 0!!! Its meaning 2036 // is currently undefined. 2037 uint16_t minorVersion; 2038 }; 2039 uint32_t version; 2040 }; 2041 */ version()2042 private int version() { 2043 return ((sdkVersion & 0xffff) << 16) | (minorVersion & 0xffff); 2044 } 2045 2046 /** 2047 union { 2048 struct { 2049 // This field can take three different forms: 2050 // - \0\0 means "any". 2051 // 2052 // - Two 7 bit ascii values interpreted as ISO-639-1 language 2053 // codes ('fr', 'en' etc. etc.). The high bit for both bytes is 2054 // zero. 2055 // 2056 // - A single 16 bit little endian packed value representing an 2057 // ISO-639-2 3 letter language code. This will be of the form: 2058 // 2059 // {1, t, t, t, t, t, s, s, s, s, s, f, f, f, f, f} 2060 // 2061 // bit[0, 4] = first letter of the language code 2062 // bit[5, 9] = second letter of the language code 2063 // bit[10, 14] = third letter of the language code. 2064 // bit[15] = 1 always 2065 // 2066 // For backwards compatibility, languages that have unambiguous 2067 // two letter codes are represented in that format. 2068 // 2069 // The layout is always bigendian irrespective of the runtime 2070 // architecture. 2071 char language[2]; 2072 2073 // This field can take three different forms: 2074 // - \0\0 means "any". 2075 // 2076 // - Two 7 bit ascii values interpreted as 2 letter country 2077 // codes ('US', 'GB' etc.). The high bit for both bytes is zero. 2078 // 2079 // - An UN M.49 3 digit country code. For simplicity, these are packed 2080 // in the same manner as the language codes, though we should need 2081 // only 10 bits to represent them, instead of the 15. 2082 // 2083 // The layout is always bigendian irrespective of the runtime 2084 // architecture. 2085 char country[2]; 2086 }; 2087 uint32_t locale; 2088 }; 2089 */ locale()2090 int locale() { 2091 return ((language[0] & 0xff) << 24) | ((language[1] & 0xff) << 16) | ((country[0] & 0xff) << 8) | (country[1] & 0xff); 2092 } 2093 isLocaleBetterThan(ResTable_config o, ResTable_config requested)2094 private boolean isLocaleBetterThan(ResTable_config o, ResTable_config requested) { 2095 if (requested.locale() == 0) { 2096 // The request doesn't have a locale, so no resource is better 2097 // than the other. 2098 return false; 2099 } 2100 2101 if (locale() == 0 && o.locale() == 0) { 2102 // The locale part of both resources is empty, so none is better 2103 // than the other. 2104 return false; 2105 } 2106 2107 // Non-matching locales have been filtered out, so both resources 2108 // match the requested locale. 2109 // 2110 // Because of the locale-related checks in match() and the checks, we know 2111 // that: 2112 // 1) The resource languages are either empty or match the request; 2113 // and 2114 // 2) If the request's script is known, the resource scripts are either 2115 // unknown or match the request. 2116 2117 if (!langsAreEquivalent(language, o.language)) { 2118 // The languages of the two resources are not equivalent. If we are 2119 // here, we can only assume that the two resources matched the request 2120 // because one doesn't have a language and the other has a matching 2121 // language. 2122 // 2123 // We consider the one that has the language specified a better match. 2124 // 2125 // The exception is that we consider no-language resources a better match 2126 // for US English and similar locales than locales that are a descendant 2127 // of Internatinal English (en-001), since no-language resources are 2128 // where the US English resource have traditionally lived for most apps. 2129 if (areIdentical(requested.language, kEnglish)) { 2130 if (areIdentical(requested.country, kUnitedStates)) { 2131 // For US English itself, we consider a no-locale resource a 2132 // better match if the other resource has a country other than 2133 // US specified. 2134 if (language[0] != '\0') { 2135 return country[0] == '\0' || areIdentical(country, kUnitedStates); 2136 } else { 2137 return !(o.country[0] == '\0' || areIdentical(o.country, kUnitedStates)); 2138 } 2139 } else if (localeDataIsCloseToUsEnglish(requested.country)) { 2140 if (language[0] != '\0') { 2141 return localeDataIsCloseToUsEnglish(country); 2142 } else { 2143 return !localeDataIsCloseToUsEnglish(o.country); 2144 } 2145 } 2146 } 2147 return (language[0] != '\0'); 2148 } 2149 2150 // If we are here, both the resources have an equivalent non-empty language 2151 // to the request. 2152 // 2153 // Because the languages are equivalent, computeScript() always returns a 2154 // non-empty script for languages it knows about, and we have passed the 2155 // script checks in match(), the scripts are either all unknown or are all 2156 // the same. So we can't gain anything by checking the scripts. We need to 2157 // check the country and variant. 2158 2159 // See if any of the regions is better than the other. 2160 final int region_comparison = localeDataCompareRegions( 2161 country, o.country, 2162 requested.language, str(requested.localeScript), requested.country); 2163 if (region_comparison != 0) { 2164 return (region_comparison > 0); 2165 } 2166 2167 // The regions are the same. Try the variant. 2168 final boolean localeMatches = Arrays.equals(localeVariant, requested.localeVariant); 2169 final boolean otherMatches = Arrays.equals(o.localeVariant, requested.localeVariant); 2170 if (localeMatches != otherMatches) { 2171 return localeMatches; 2172 } 2173 2174 // The variants are the same, try numbering system. 2175 boolean localeNumsysMatches = arrayCompare(localeNumberingSystem, 2176 requested.localeNumberingSystem 2177 ) == 0; 2178 boolean otherNumsysMatches = arrayCompare(o.localeNumberingSystem, 2179 requested.localeNumberingSystem 2180 ) == 0; 2181 2182 if (localeNumsysMatches != otherNumsysMatches) { 2183 return localeNumsysMatches; 2184 } 2185 2186 // Finally, the languages, although equivalent, may still be different 2187 // (like for Tagalog and Filipino). Identical is better than just 2188 // equivalent. 2189 if (areIdentical(language, requested.language) 2190 && !areIdentical(o.language, requested.language)) { 2191 return true; 2192 } 2193 2194 return false; 2195 } 2196 str(byte[] country)2197 private String str(byte[] country) { 2198 return new String(country, UTF_8); 2199 } 2200 langsAreEquivalent(final byte[] lang1, final byte[] lang2)2201 private boolean langsAreEquivalent(final byte[] lang1, final byte[] lang2) { 2202 return areIdentical(lang1, lang2) || 2203 (areIdentical(lang1, kTagalog) && areIdentical(lang2, kFilipino)) || 2204 (areIdentical(lang1, kFilipino) && areIdentical(lang2, kTagalog)); 2205 } 2206 2207 // Checks if two language or country codes are identical areIdentical(final byte[] code1, final byte[] code2)2208 private boolean areIdentical(final byte[] code1, final byte[] code2) { 2209 return code1[0] == code2[0] && code1[1] == code2[1]; 2210 } 2211 isLocaleMoreSpecificThan(ResTable_config o)2212 int isLocaleMoreSpecificThan(ResTable_config o) { 2213 if (isTruthy(locale()) || isTruthy(o.locale())) { 2214 if (language[0] != o.language[0]) { 2215 if (!isTruthy(language[0])) return -1; 2216 if (!isTruthy(o.language[0])) return 1; 2217 } 2218 if (country[0] != o.country[0]) { 2219 if (!isTruthy(country[0])) return -1; 2220 if (!isTruthy(o.country[0])) return 1; 2221 } 2222 } 2223 return getImportanceScoreOfLocale() - o.getImportanceScoreOfLocale(); 2224 } 2225 isMoreSpecificThan(ResTable_config o)2226 private boolean isMoreSpecificThan(ResTable_config o) { 2227 // The order of the following tests defines the importance of one 2228 // configuration parameter over another. Those tests first are more 2229 // important, trumping any values in those following them. 2230 if (isTruthy(imsi()) || isTruthy(o.imsi())) { 2231 if (mcc != o.mcc) { 2232 if (!isTruthy(mcc)) return false; 2233 if (!isTruthy(o.mcc)) return true; 2234 } 2235 if (mnc != o.mnc) { 2236 if (!isTruthy(mnc)) return false; 2237 if (!isTruthy(o.mnc)) return true; 2238 } 2239 } 2240 if (isTruthy(locale()) || isTruthy(o.locale())) { 2241 int diff = isLocaleMoreSpecificThan(o); 2242 if (diff < 0) { 2243 return false; 2244 } 2245 if (diff > 0) { 2246 return true; 2247 } 2248 } 2249 if (isTruthy(screenLayout) || isTruthy(o.screenLayout)) { 2250 if (((screenLayout^o.screenLayout) & MASK_LAYOUTDIR) != 0) { 2251 if (!isTruthy((screenLayout & MASK_LAYOUTDIR))) return false; 2252 if (!isTruthy((o.screenLayout & MASK_LAYOUTDIR))) return true; 2253 } 2254 } 2255 if (isTruthy(smallestScreenWidthDp) || isTruthy(o.smallestScreenWidthDp)) { 2256 if (smallestScreenWidthDp != o.smallestScreenWidthDp) { 2257 if (!isTruthy(smallestScreenWidthDp)) return false; 2258 if (!isTruthy(o.smallestScreenWidthDp)) return true; 2259 } 2260 } 2261 if (isTruthy(screenSizeDp()) || isTruthy(o.screenSizeDp())) { 2262 if (screenWidthDp != o.screenWidthDp) { 2263 if (!isTruthy(screenWidthDp)) return false; 2264 if (!isTruthy(o.screenWidthDp)) return true; 2265 } 2266 if (screenHeightDp != o.screenHeightDp) { 2267 if (!isTruthy(screenHeightDp)) return false; 2268 if (!isTruthy(o.screenHeightDp)) return true; 2269 } 2270 } 2271 if (isTruthy(screenLayout) || isTruthy(o.screenLayout)) { 2272 if (((screenLayout^o.screenLayout) & MASK_SCREENSIZE) != 0) { 2273 if (!isTruthy((screenLayout & MASK_SCREENSIZE))) return false; 2274 if (!isTruthy((o.screenLayout & MASK_SCREENSIZE))) return true; 2275 } 2276 if (((screenLayout^o.screenLayout) & MASK_SCREENLONG) != 0) { 2277 if (!isTruthy((screenLayout & MASK_SCREENLONG))) return false; 2278 if (!isTruthy((o.screenLayout & MASK_SCREENLONG))) return true; 2279 } 2280 } 2281 if (isTruthy(screenLayout2) || isTruthy(o.screenLayout2)) { 2282 if (((screenLayout2^o.screenLayout2) & MASK_SCREENROUND) != 0) { 2283 if (!isTruthy((screenLayout2 & MASK_SCREENROUND))) return false; 2284 if (!isTruthy((o.screenLayout2 & MASK_SCREENROUND))) return true; 2285 } 2286 } 2287 2288 if (isTruthy(colorMode) || isTruthy(o.colorMode)) { 2289 if (((colorMode^o.colorMode) & MASK_HDR) != 0) { 2290 if (!isTruthy((colorMode & MASK_HDR))) return false; 2291 if (!isTruthy((o.colorMode & MASK_HDR))) return true; 2292 } 2293 if (((colorMode^o.colorMode) & MASK_WIDE_COLOR_GAMUT) != 0) { 2294 if (!isTruthy((colorMode & MASK_WIDE_COLOR_GAMUT))) return false; 2295 if (!isTruthy((o.colorMode & MASK_WIDE_COLOR_GAMUT))) return true; 2296 } 2297 } 2298 2299 if (orientation != o.orientation) { 2300 if (!isTruthy(orientation)) return false; 2301 if (!isTruthy(o.orientation)) return true; 2302 } 2303 if (isTruthy(uiMode) || isTruthy(o.uiMode)) { 2304 if (((uiMode^o.uiMode) & MASK_UI_MODE_TYPE) != 0) { 2305 if (!isTruthy((uiMode & MASK_UI_MODE_TYPE))) return false; 2306 if (!isTruthy((o.uiMode & MASK_UI_MODE_TYPE))) return true; 2307 } 2308 if (((uiMode^o.uiMode) & MASK_UI_MODE_NIGHT) != 0) { 2309 if (!isTruthy((uiMode & MASK_UI_MODE_NIGHT))) return false; 2310 if (!isTruthy((o.uiMode & MASK_UI_MODE_NIGHT))) return true; 2311 } 2312 } 2313 // density is never 'more specific' 2314 // as the default just equals 160 2315 if (touchscreen != o.touchscreen) { 2316 if (!isTruthy(touchscreen)) return false; 2317 if (!isTruthy(o.touchscreen)) return true; 2318 } 2319 if (isTruthy(input()) || isTruthy(o.input())) { 2320 if (((inputFlags^o.inputFlags) & MASK_KEYSHIDDEN) != 0) { 2321 if (!isTruthy((inputFlags & MASK_KEYSHIDDEN))) return false; 2322 if (!isTruthy((o.inputFlags & MASK_KEYSHIDDEN))) return true; 2323 } 2324 if (((inputFlags^o.inputFlags) & MASK_NAVHIDDEN) != 0) { 2325 if (!isTruthy((inputFlags & MASK_NAVHIDDEN))) return false; 2326 if (!isTruthy((o.inputFlags & MASK_NAVHIDDEN))) return true; 2327 } 2328 if (keyboard != o.keyboard) { 2329 if (!isTruthy(keyboard)) return false; 2330 if (!isTruthy(o.keyboard)) return true; 2331 } 2332 if (navigation != o.navigation) { 2333 if (!isTruthy(navigation)) return false; 2334 if (!isTruthy(o.navigation)) return true; 2335 } 2336 } 2337 if (isTruthy(screenSize()) || isTruthy(o.screenSize())) { 2338 if (screenWidth != o.screenWidth) { 2339 if (!isTruthy(screenWidth)) return false; 2340 if (!isTruthy(o.screenWidth)) return true; 2341 } 2342 if (screenHeight != o.screenHeight) { 2343 if (!isTruthy(screenHeight)) return false; 2344 if (!isTruthy(o.screenHeight)) return true; 2345 } 2346 } 2347 if (isTruthy(version()) || isTruthy(o.version())) { 2348 if (sdkVersion != o.sdkVersion) { 2349 if (!isTruthy(sdkVersion)) return false; 2350 if (!isTruthy(o.sdkVersion)) return true; 2351 } 2352 if (minorVersion != o.minorVersion) { 2353 if (!isTruthy(minorVersion)) return false; 2354 if (!isTruthy(o.minorVersion)) return true; 2355 } 2356 } 2357 return false; 2358 } 2359 }