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