| package org.robolectric.res.android; |
| |
| import static java.nio.charset.StandardCharsets.US_ASCII; |
| import static java.nio.charset.StandardCharsets.UTF_8; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_ANY; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_DEFAULT; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_HIGH; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_LOW; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_MEDIUM; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_NONE; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_TV; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_XHIGH; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_XXHIGH; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_DENSITY_XXXHIGH; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_KEYBOARD_12KEY; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_KEYBOARD_ANY; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_KEYBOARD_NOKEYS; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_KEYBOARD_QWERTY; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_KEYSHIDDEN_ANY; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_KEYSHIDDEN_NO; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_KEYSHIDDEN_SOFT; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_KEYSHIDDEN_YES; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_LAYOUTDIR_ANY; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_LAYOUTDIR_LTR; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_LAYOUTDIR_RTL; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_NAVHIDDEN_ANY; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_NAVHIDDEN_NO; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_NAVHIDDEN_YES; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_NAVIGATION_ANY; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_NAVIGATION_DPAD; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_NAVIGATION_NONAV; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_NAVIGATION_TRACKBALL; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_NAVIGATION_WHEEL; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_ORIENTATION_ANY; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_ORIENTATION_LAND; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_ORIENTATION_PORT; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_ORIENTATION_SQUARE; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENLONG_ANY; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENLONG_NO; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENLONG_YES; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENROUND_ANY; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENROUND_NO; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENROUND_YES; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENSIZE_ANY; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENSIZE_LARGE; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENSIZE_NORMAL; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENSIZE_SMALL; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_SCREENSIZE_XLARGE; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_TOUCHSCREEN_ANY; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_TOUCHSCREEN_FINGER; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_TOUCHSCREEN_NOTOUCH; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_TOUCHSCREEN_STYLUS; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_UI_MODE_NIGHT_ANY; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_UI_MODE_NIGHT_NO; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_UI_MODE_NIGHT_YES; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_UI_MODE_TYPE_ANY; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_UI_MODE_TYPE_APPLIANCE; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_UI_MODE_TYPE_CAR; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_UI_MODE_TYPE_DESK; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_UI_MODE_TYPE_NORMAL; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_UI_MODE_TYPE_TELEVISION; |
| import static org.robolectric.res.android.AConfiguration.ACONFIGURATION_UI_MODE_TYPE_WATCH; |
| import static org.robolectric.res.android.LocaleData.localeDataComputeScript; |
| import static org.robolectric.res.android.ResTable.kDebugTableSuperNoisy; |
| import static org.robolectric.res.android.Util.ALOGI; |
| import static org.robolectric.res.android.Util.dtohs; |
| import static org.robolectric.res.android.Util.isTruthy; |
| |
| import com.google.common.base.Joiner; |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.primitives.UnsignedBytes; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.LinkedHashMap; |
| import java.util.Map; |
| |
| /** |
| * Describes a particular resource configuration. |
| * |
| * <p>frameworks/base/include/androidfw/ResourceTypes.h (struct ResTable_config) |
| */ |
| public class ResTableConfig { |
| |
| // Codes for specially handled languages and regions |
| static final byte[] kEnglish = new byte[] {'e', 'n'}; // packed version of "en" |
| static final byte[] kUnitedStates = new byte[] {'U', 'S'}; // packed version of "US" |
| static final byte[] kFilipino = new byte[] {(byte)0xAD, 0x05}; // packed version of "fil" ported from C {'\xAD', '\x05'} |
| static final byte[] kTagalog = new byte[] {'t', 'l'}; // packed version of "tl" |
| |
| /** The different types of configs that can be present in a {@link ResTableConfig}. */ |
| public enum Type { |
| MCC, |
| MNC, |
| LANGUAGE_STRING, |
| REGION_STRING, |
| SCREEN_LAYOUT_DIRECTION, |
| SMALLEST_SCREEN_WIDTH_DP, |
| SCREEN_WIDTH_DP, |
| SCREEN_HEIGHT_DP, |
| SCREEN_LAYOUT_SIZE, |
| SCREEN_LAYOUT_LONG, |
| SCREEN_LAYOUT_ROUND, |
| ORIENTATION, |
| UI_MODE_TYPE, |
| UI_MODE_NIGHT, |
| DENSITY_DPI, |
| TOUCHSCREEN, |
| KEYBOARD_HIDDEN, |
| KEYBOARD, |
| NAVIGATION_HIDDEN, |
| NAVIGATION, |
| SDK_VERSION |
| } |
| |
| // screenLayout bits for layout direction. |
| // public static final int MASK_LAYOUTDIR = 0xC0; |
| public static final int SHIFT_LAYOUTDIR = 6; |
| public static final int LAYOUTDIR_ANY = ACONFIGURATION_LAYOUTDIR_ANY << SHIFT_LAYOUTDIR; |
| public static final int LAYOUTDIR_LTR = ACONFIGURATION_LAYOUTDIR_LTR << SHIFT_LAYOUTDIR; |
| public static final int LAYOUTDIR_RTL = ACONFIGURATION_LAYOUTDIR_RTL << SHIFT_LAYOUTDIR; |
| |
| public static final int SCREENWIDTH_ANY = 0; |
| // public static final int MASK_SCREENSIZE = 0x0f; |
| public static final int SCREENSIZE_ANY = ACONFIGURATION_SCREENSIZE_ANY; |
| public static final int SCREENSIZE_SMALL = ACONFIGURATION_SCREENSIZE_SMALL; |
| // public static final int SCREENSIZE_NORMAL = ACONFIGURATION_SCREENSIZE_NORMAL; |
| public static final int SCREENSIZE_LARGE = ACONFIGURATION_SCREENSIZE_LARGE; |
| public static final int SCREENSIZE_XLARGE = ACONFIGURATION_SCREENSIZE_XLARGE; |
| |
| // uiMode bits for the mode type. |
| public static final int MASK_UI_MODE_TYPE = 0x0f; |
| public static final int UI_MODE_TYPE_ANY = ACONFIGURATION_UI_MODE_TYPE_ANY; |
| public static final int UI_MODE_TYPE_NORMAL = ACONFIGURATION_UI_MODE_TYPE_NORMAL; |
| public static final int UI_MODE_TYPE_DESK = ACONFIGURATION_UI_MODE_TYPE_DESK; |
| public static final int UI_MODE_TYPE_CAR = ACONFIGURATION_UI_MODE_TYPE_CAR; |
| public static final int UI_MODE_TYPE_TELEVISION = ACONFIGURATION_UI_MODE_TYPE_TELEVISION; |
| public static final int UI_MODE_TYPE_APPLIANCE = ACONFIGURATION_UI_MODE_TYPE_APPLIANCE; |
| public static final int UI_MODE_TYPE_WATCH = ACONFIGURATION_UI_MODE_TYPE_WATCH; |
| |
| // uiMode bits for the night switch; |
| public static final int MASK_UI_MODE_NIGHT = 0x30; |
| public static final int SHIFT_UI_MODE_NIGHT = 4; |
| public static final int UI_MODE_NIGHT_ANY = ACONFIGURATION_UI_MODE_NIGHT_ANY << SHIFT_UI_MODE_NIGHT; |
| public static final int UI_MODE_NIGHT_NO = ACONFIGURATION_UI_MODE_NIGHT_NO << SHIFT_UI_MODE_NIGHT; |
| public static final int UI_MODE_NIGHT_YES = ACONFIGURATION_UI_MODE_NIGHT_YES << SHIFT_UI_MODE_NIGHT; |
| |
| public static final int DENSITY_DEFAULT = ACONFIGURATION_DENSITY_DEFAULT; |
| public static final int DENSITY_LOW = ACONFIGURATION_DENSITY_LOW; |
| public static final int DENSITY_MEDIUM = ACONFIGURATION_DENSITY_MEDIUM; |
| public static final int DENSITY_TV = ACONFIGURATION_DENSITY_TV; |
| public static final int DENSITY_HIGH = ACONFIGURATION_DENSITY_HIGH; |
| public static final int DENSITY_XHIGH = ACONFIGURATION_DENSITY_XHIGH; |
| public static final int DENSITY_XXHIGH = ACONFIGURATION_DENSITY_XXHIGH; |
| public static final int DENSITY_XXXHIGH = ACONFIGURATION_DENSITY_XXXHIGH; |
| public static final int DENSITY_ANY = ACONFIGURATION_DENSITY_ANY; |
| public static final int DENSITY_NONE = ACONFIGURATION_DENSITY_NONE; |
| |
| public static final int TOUCHSCREEN_ANY = ACONFIGURATION_TOUCHSCREEN_ANY; |
| public static final int TOUCHSCREEN_NOTOUCH = ACONFIGURATION_TOUCHSCREEN_NOTOUCH; |
| public static final int TOUCHSCREEN_STYLUS = ACONFIGURATION_TOUCHSCREEN_STYLUS; |
| public static final int TOUCHSCREEN_FINGER = ACONFIGURATION_TOUCHSCREEN_FINGER; |
| |
| public static final int MASK_KEYSHIDDEN = 0x0003; |
| public static final byte KEYSHIDDEN_ANY = ACONFIGURATION_KEYSHIDDEN_ANY; |
| public static final byte KEYSHIDDEN_NO = ACONFIGURATION_KEYSHIDDEN_NO; |
| public static final byte KEYSHIDDEN_YES = ACONFIGURATION_KEYSHIDDEN_YES; |
| public static final byte KEYSHIDDEN_SOFT = ACONFIGURATION_KEYSHIDDEN_SOFT; |
| |
| public static final int KEYBOARD_ANY = ACONFIGURATION_KEYBOARD_ANY; |
| public static final int KEYBOARD_NOKEYS = ACONFIGURATION_KEYBOARD_NOKEYS; |
| public static final int KEYBOARD_QWERTY = ACONFIGURATION_KEYBOARD_QWERTY; |
| public static final int KEYBOARD_12KEY = ACONFIGURATION_KEYBOARD_12KEY; |
| |
| public static final int MASK_NAVHIDDEN = 0x000c; |
| public static final int SHIFT_NAVHIDDEN = 2; |
| public static final byte NAVHIDDEN_ANY = ACONFIGURATION_NAVHIDDEN_ANY << SHIFT_NAVHIDDEN; |
| public static final byte NAVHIDDEN_NO = ACONFIGURATION_NAVHIDDEN_NO << SHIFT_NAVHIDDEN; |
| public static final byte NAVHIDDEN_YES = ACONFIGURATION_NAVHIDDEN_YES << SHIFT_NAVHIDDEN; |
| |
| public static final int NAVIGATION_ANY = ACONFIGURATION_NAVIGATION_ANY; |
| public static final int NAVIGATION_NONAV = ACONFIGURATION_NAVIGATION_NONAV; |
| public static final int NAVIGATION_DPAD = ACONFIGURATION_NAVIGATION_DPAD; |
| public static final int NAVIGATION_TRACKBALL = ACONFIGURATION_NAVIGATION_TRACKBALL; |
| public static final int NAVIGATION_WHEEL = ACONFIGURATION_NAVIGATION_WHEEL; |
| |
| public static final int SCREENHEIGHT_ANY = 0; |
| |
| public static final int SDKVERSION_ANY = 0; |
| public static final int MINORVERSION_ANY = 0; |
| |
| /** The below constants are from android.content.res.Configuration. */ |
| private static final int DENSITY_DPI_UNDEFINED = 0; |
| private static final int DENSITY_DPI_LDPI = 120; |
| private static final int DENSITY_DPI_MDPI = 160; |
| private static final int DENSITY_DPI_TVDPI = 213; |
| private static final int DENSITY_DPI_HDPI = 240; |
| private static final int DENSITY_DPI_XHDPI = 320; |
| private static final int DENSITY_DPI_XXHDPI = 480; |
| private static final int DENSITY_DPI_XXXHDPI = 640; |
| private static final int DENSITY_DPI_ANY = 0xFFFE; |
| private static final int DENSITY_DPI_NONE = 0xFFFF; |
| private static final Map<Integer, String> DENSITY_DPI_VALUES = |
| ImmutableMap.<Integer, String>builder() |
| .put(DENSITY_DPI_UNDEFINED, "") |
| .put(DENSITY_DPI_LDPI, "ldpi") |
| .put(DENSITY_DPI_MDPI, "mdpi") |
| .put(DENSITY_DPI_TVDPI, "tvdpi") |
| .put(DENSITY_DPI_HDPI, "hdpi") |
| .put(DENSITY_DPI_XHDPI, "xhdpi") |
| .put(DENSITY_DPI_XXHDPI, "xxhdpi") |
| .put(DENSITY_DPI_XXXHDPI, "xxxhdpi") |
| .put(DENSITY_DPI_ANY, "anydpi") |
| .put(DENSITY_DPI_NONE, "nodpi") |
| .build(); |
| |
| private static final Map<Integer, String> KEYBOARD_VALUES = ImmutableMap.of( |
| KEYBOARD_NOKEYS, "nokeys", |
| KEYBOARD_QWERTY, "qwerty", |
| KEYBOARD_12KEY, "12key"); |
| |
| private static final int KEYBOARDHIDDEN_MASK = 0x03; |
| private static final int KEYBOARDHIDDEN_NO = 1; |
| private static final int KEYBOARDHIDDEN_YES = 2; |
| private static final int KEYBOARDHIDDEN_SOFT = 3; |
| private static final Map<Integer, String> KEYBOARDHIDDEN_VALUES = ImmutableMap.of( |
| KEYBOARDHIDDEN_NO, "keysexposed", |
| KEYBOARDHIDDEN_YES, "keyshidden", |
| KEYBOARDHIDDEN_SOFT, "keyssoft"); |
| |
| private static final Map<Integer, String> NAVIGATION_VALUES = ImmutableMap.of( |
| NAVIGATION_NONAV, "nonav", |
| NAVIGATION_DPAD, "dpad", |
| NAVIGATION_TRACKBALL, "trackball", |
| NAVIGATION_WHEEL, "wheel"); |
| |
| private static final int NAVIGATIONHIDDEN_MASK = 0x0C; |
| private static final int NAVIGATIONHIDDEN_NO = 0x04; |
| private static final int NAVIGATIONHIDDEN_YES = 0x08; |
| private static final Map<Integer, String> NAVIGATIONHIDDEN_VALUES = ImmutableMap.of( |
| NAVIGATIONHIDDEN_NO, "navexposed", |
| NAVIGATIONHIDDEN_YES, "navhidden"); |
| |
| private static final int ORIENTATION_PORTRAIT = 0x01; |
| private static final int ORIENTATION_LANDSCAPE = 0x02; |
| private static final Map<Integer, String> ORIENTATION_VALUES = ImmutableMap.of( |
| ORIENTATION_PORTRAIT, "port", |
| ORIENTATION_LANDSCAPE, "land"); |
| |
| private static final int SCREENLAYOUT_LAYOUTDIR_MASK = 0xC0; |
| static final int SCREENLAYOUT_LAYOUTDIR_LTR = 0x40; |
| static final int SCREENLAYOUT_LAYOUTDIR_RTL = 0x80; |
| private static final Map<Integer, String> SCREENLAYOUT_LAYOUTDIR_VALUES = ImmutableMap.of( |
| SCREENLAYOUT_LAYOUTDIR_LTR, "ldltr", |
| SCREENLAYOUT_LAYOUTDIR_RTL, "ldrtl"); |
| |
| private static final int SCREENLAYOUT_LONG_MASK = 0x30; |
| private static final int SCREENLAYOUT_LONG_NO = 0x10; |
| private static final int SCREENLAYOUT_LONG_YES = 0x20; |
| private static final Map<Integer, String> SCREENLAYOUT_LONG_VALUES = ImmutableMap.of( |
| SCREENLAYOUT_LONG_NO, "notlong", |
| SCREENLAYOUT_LONG_YES, "long"); |
| |
| private static final int SCREENLAYOUT_ROUND_MASK = 0x0300; |
| private static final int SCREENLAYOUT_ROUND_NO = 0x0100; |
| private static final int SCREENLAYOUT_ROUND_YES = 0x0200; |
| private static final Map<Integer, String> SCREENLAYOUT_ROUND_VALUES = ImmutableMap.of( |
| SCREENLAYOUT_ROUND_NO, "notround", |
| SCREENLAYOUT_ROUND_YES, "round"); |
| |
| private static final int SCREENLAYOUT_SIZE_MASK = 0x0F; |
| private static final int SCREENLAYOUT_SIZE_SMALL = 0x01; |
| private static final int SCREENLAYOUT_SIZE_NORMAL = 0x02; |
| private static final int SCREENLAYOUT_SIZE_LARGE = 0x03; |
| private static final int SCREENLAYOUT_SIZE_XLARGE = 0x04; |
| private static final Map<Integer, String> SCREENLAYOUT_SIZE_VALUES = ImmutableMap.of( |
| SCREENLAYOUT_SIZE_SMALL, "small", |
| SCREENLAYOUT_SIZE_NORMAL, "normal", |
| SCREENLAYOUT_SIZE_LARGE, "large", |
| SCREENLAYOUT_SIZE_XLARGE, "xlarge"); |
| |
| private static final Map<Integer, String> TOUCHSCREEN_VALUES = ImmutableMap.of( |
| TOUCHSCREEN_NOTOUCH, "notouch", |
| TOUCHSCREEN_FINGER, "finger"); |
| |
| private static final int UI_MODE_NIGHT_MASK = 0x30; |
| private static final Map<Integer, String> UI_MODE_NIGHT_VALUES = ImmutableMap.of( |
| UI_MODE_NIGHT_NO, "notnight", |
| UI_MODE_NIGHT_YES, "night"); |
| |
| private static final int UI_MODE_TYPE_MASK = 0x0F; |
| private static final Map<Integer, String> UI_MODE_TYPE_VALUES = ImmutableMap.of( |
| UI_MODE_TYPE_DESK, "desk", |
| UI_MODE_TYPE_CAR, "car", |
| UI_MODE_TYPE_TELEVISION, "television", |
| UI_MODE_TYPE_APPLIANCE, "appliance", |
| UI_MODE_TYPE_WATCH, "watch"); |
| |
| // screenLayout bits for wide/long screen variation. |
| public static final int MASK_SCREENLONG = 0x30; |
| public static final int SHIFT_SCREENLONG = 4; |
| public static final int SCREENLONG_ANY = ACONFIGURATION_SCREENLONG_ANY << SHIFT_SCREENLONG; |
| public static final int SCREENLONG_NO = ACONFIGURATION_SCREENLONG_NO << SHIFT_SCREENLONG; |
| public static final int SCREENLONG_YES = ACONFIGURATION_SCREENLONG_YES << SHIFT_SCREENLONG; |
| |
| // screenLayout2 bits for round/notround. |
| public static final int MASK_SCREENROUND = 0x03; |
| public static final int SCREENROUND_ANY = ACONFIGURATION_SCREENROUND_ANY; |
| public static final int SCREENROUND_NO = ACONFIGURATION_SCREENROUND_NO; |
| public static final int SCREENROUND_YES = ACONFIGURATION_SCREENROUND_YES; |
| |
| public static final int ORIENTATION_ANY = ACONFIGURATION_ORIENTATION_ANY; |
| public static final int ORIENTATION_PORT = ACONFIGURATION_ORIENTATION_PORT; |
| public static final int ORIENTATION_LAND = ACONFIGURATION_ORIENTATION_LAND; |
| public static final int ORIENTATION_SQUARE = ACONFIGURATION_ORIENTATION_SQUARE; |
| |
| /** The number of bytes that this resource configuration takes up. */ |
| int size; |
| |
| int mcc; |
| int mnc; |
| |
| /** Returns a packed 2-byte language code. */ |
| @SuppressWarnings("mutable") |
| private final byte[] language; |
| |
| /** Returns {@link #language} as an unpacked string representation. */ |
| public final String languageString() { |
| return unpackLanguage(); |
| } |
| |
| /** Returns a packed 2-byte country code. */ |
| @SuppressWarnings("mutable") |
| private final byte[] country; |
| |
| /** Returns {@link #country} as an unpacked string representation. */ |
| public final String regionString() { |
| return unpackRegion(); |
| } |
| |
| public int orientation; |
| int touchscreen; |
| public int density; |
| int keyboard; |
| int navigation; |
| int inputFlags; |
| |
| public final int keyboardHidden() { |
| return inputFlags & KEYBOARDHIDDEN_MASK; |
| } |
| |
| public final int navigationHidden() { |
| return inputFlags & NAVIGATIONHIDDEN_MASK; |
| } |
| |
| int screenWidth; |
| int screenHeight; |
| int sdkVersion; |
| |
| /** |
| * Returns a copy of this resource configuration with a different {@link #sdkVersion}, or this |
| * configuration if the {@code sdkVersion} is the same. |
| * |
| * @param sdkVersion The SDK version of the returned configuration. |
| * @return A copy of this configuration with the only difference being #sdkVersion. |
| */ |
| public final ResTableConfig withSdkVersion(int sdkVersion) { |
| if (sdkVersion == this.sdkVersion) { |
| return this; |
| } |
| return new ResTableConfig(size, mcc, mnc, language, country, |
| orientation, touchscreen, density, keyboard, navigation, inputFlags, |
| screenWidth, screenHeight, sdkVersion, minorVersion, screenLayout, uiMode, |
| smallestScreenWidthDp, screenWidthDp, screenHeightDp, localeScript, localeVariant, |
| screenLayout2, screenConfigPad1, screenConfigPad2, unknown); |
| } |
| |
| public ResTableConfig(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 screenConfigPad1, |
| short screenConfigPad2, byte[] unknown) { |
| this.size = size; |
| this.mcc = mcc; |
| this.mnc = mnc; |
| this.language = language; |
| this.country = country; |
| this.orientation = orientation; |
| this.touchscreen = touchscreen; |
| this.density = density; |
| this.keyboard = keyboard; |
| this.navigation = navigation; |
| this.inputFlags = inputFlags; |
| this.screenWidth = screenWidth; |
| this.screenHeight = screenHeight; |
| this.sdkVersion = sdkVersion; |
| this.minorVersion = minorVersion; |
| this.screenLayout = screenLayout; |
| this.uiMode = uiMode; |
| this.smallestScreenWidthDp = smallestScreenWidthDp; |
| this.screenWidthDp = screenWidthDp; |
| this.screenHeightDp = screenHeightDp; |
| this.localeScript = localeScript; |
| this.localeVariant = localeVariant; |
| this.screenLayout2 = screenLayout2; |
| this.screenConfigPad1 = screenConfigPad1; |
| this.screenConfigPad2 = screenConfigPad2; |
| this.unknown = unknown; |
| } |
| |
| public ResTableConfig() { |
| this.language = new byte[2]; |
| this.country = new byte[2]; |
| this.localeScript = new byte[LocaleData.SCRIPT_LENGTH]; |
| this.localeVariant = new byte[2]; |
| } |
| |
| int minorVersion; |
| int screenLayout; |
| |
| public final int screenLayoutDirection() { |
| return screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK; |
| } |
| |
| public final int screenLayoutSize() { |
| return screenLayout & SCREENLAYOUT_SIZE_MASK; |
| } |
| |
| public final int screenLayoutLong() { |
| return screenLayout & SCREENLAYOUT_LONG_MASK; |
| } |
| |
| public final int screenLayoutRound() { |
| return screenLayout & SCREENLAYOUT_ROUND_MASK; |
| } |
| |
| int uiMode; |
| |
| public final int uiModeType() { |
| return uiMode & UI_MODE_TYPE_MASK; |
| } |
| |
| public final int uiModeNight() { |
| return uiMode & UI_MODE_NIGHT_MASK; |
| } |
| |
| public int smallestScreenWidthDp; |
| public int screenWidthDp; |
| int screenHeightDp; |
| |
| /** The ISO-15924 short name for the script corresponding to this configuration. */ |
| @SuppressWarnings("mutable") |
| final byte[] localeScript; |
| |
| /** A single BCP-47 variant subtag. */ |
| @SuppressWarnings("mutable") |
| final byte[] localeVariant; |
| |
| /** An extension to {@link #screenLayout}. Contains round/notround qualifier. */ |
| byte screenLayout2; |
| byte screenConfigPad1; |
| short screenConfigPad2; |
| |
| /** Any remaining bytes in this resource configuration that are unaccounted for. */ |
| @SuppressWarnings("mutable") |
| byte[] unknown; |
| |
| |
| /** |
| * // An extension of screenConfig. |
| union { |
| struct { |
| uint8_t screenLayout2; // Contains round/notround qualifier. |
| uint8_t screenConfigPad1; // Reserved padding. |
| uint16_t screenConfigPad2; // Reserved padding. |
| }; |
| uint32_t screenConfig2; |
| }; |
| */ |
| private int screenConfig2() { |
| return (screenLayout2 & 0xff << 24) | (screenConfigPad1 * 0xff << 16) | screenConfigPad2 & 0xffff; |
| } |
| |
| // If false and localeScript is set, it means that the script of the locale |
| // was explicitly provided. |
| // |
| // If true, it means that localeScript was automatically computed. |
| // localeScript may still not be set in this case, which means that we |
| // tried but could not compute a script. |
| boolean localeScriptWasComputed; |
| |
| // -------------------------------------------------------------------- |
| // -------------------------------------------------------------------- |
| // -------------------------------------------------------------------- |
| |
| // void copyFromDeviceNoSwap(final ResTableConfig o) { |
| // final int size = dtohl(o.size); |
| // if (size >= sizeof(ResTable_config)) { |
| // *this = o; |
| // } else { |
| // memcpy(this, &o, size); |
| // memset(((uint8_t*)this)+size, 0, sizeof(ResTable_config)-size); |
| // } |
| // } |
| |
| private String unpackLanguageOrRegion(byte[] value, int base) { |
| Preconditions.checkState(value.length == 2, "Language or country value must be 2 bytes."); |
| if (value[0] == 0 && value[1] == 0) { |
| return ""; |
| } |
| if (isTruthy(UnsignedBytes.toInt(value[0]) & 0x80)) { |
| byte[] result = new byte[3]; |
| result[0] = (byte) (base + (value[1] & 0x1F)); |
| result[1] = (byte) (base + ((value[1] & 0xE0) >>> 5) + ((value[0] & 0x03) << 3)); |
| result[2] = (byte) (base + ((value[0] & 0x7C) >>> 2)); |
| return new String(result, US_ASCII); |
| } |
| return new String(value, US_ASCII); |
| } |
| |
| /* static */ void packLanguageOrRegion(final byte[] in, final byte base, |
| final byte out[]) { |
| if (in.length < 3 || in[2] == 0 || in[2] == '-') { |
| out[0] = in[0]; |
| out[1] = in[1]; |
| } else { |
| byte first = (byte) ((in[0] - base) & 0x007f); |
| byte second = (byte) ((in[1] - base) & 0x007f); |
| byte third = (byte) ((in[2] - base) & 0x007f); |
| |
| out[0] = (byte) (0x80 | (third << 2) | (second >> 3)); |
| out[1] = (byte) ((second << 5) | first); |
| } |
| } |
| |
| void packLanguage(final byte[] language) { |
| packLanguageOrRegion(language, (byte) 'a', this.language); |
| } |
| |
| void packLanguage(final String language) { |
| byte[] bytes = language == null ? new byte[2] : language.getBytes(); |
| packLanguageOrRegion(bytes, (byte) 'a', this.language); |
| } |
| |
| void packRegion(final byte[] region) { |
| packLanguageOrRegion(region, (byte) '0', this.country); |
| } |
| |
| void packRegion(final String region) { |
| byte[] bytes = region == null ? new byte[2] : region.getBytes(); |
| packLanguageOrRegion(bytes, (byte) '0', this.country); |
| } |
| |
| private String unpackLanguage() { |
| return unpackLanguageOrRegion(language, 0x61); |
| } |
| |
| private String unpackRegion() { |
| return unpackLanguageOrRegion(country, 0x30); |
| } |
| |
| // void copyFromDtoH(final ResTableConfig o) { |
| // copyFromDeviceNoSwap(o); |
| // size = sizeof(ResTable_config); |
| // mcc = dtohs(mcc); |
| // mnc = dtohs(mnc); |
| // density = dtohs(density); |
| // screenWidth = dtohs(screenWidth); |
| // screenHeight = dtohs(screenHeight); |
| // sdkVersion = dtohs(sdkVersion); |
| // minorVersion = dtohs(minorVersion); |
| // smallestScreenWidthDp = dtohs(smallestScreenWidthDp); |
| // screenWidthDp = dtohs(screenWidthDp); |
| // screenHeightDp = dtohs(screenHeightDp); |
| // } |
| |
| // void ResTable_config::copyFromDtoH(const ResTable_config& o) { |
| static ResTableConfig fromDtoH(final ResTableConfig o) { |
| return new ResTableConfig( |
| 0 /*sizeof(ResTable_config)*/, |
| dtohs((short) o.mcc), |
| dtohs((short) o.mnc), |
| o.language, |
| o.country, |
| o.orientation, |
| o.touchscreen, |
| dtohs((short) o.density), |
| o.keyboard, |
| o.navigation, |
| o.inputFlags, |
| dtohs((short) o.screenWidth), |
| dtohs((short) o.screenHeight), |
| dtohs((short) o.sdkVersion), |
| dtohs((short) o.minorVersion), |
| o.screenLayout, |
| o.uiMode, |
| dtohs((short) o.smallestScreenWidthDp), |
| dtohs((short) o.screenWidthDp), |
| dtohs((short) o.screenHeightDp), |
| o.localeScript, |
| o.localeVariant, |
| o.screenLayout2, |
| o.screenConfigPad1, |
| o.screenConfigPad2, |
| o.unknown |
| ); |
| } |
| |
| void swapHtoD() { |
| // size = htodl(size); |
| // mcc = htods(mcc); |
| // mnc = htods(mnc); |
| // density = htods(density); |
| // screenWidth = htods(screenWidth); |
| // screenHeight = htods(screenHeight); |
| // sdkVersion = htods(sdkVersion); |
| // minorVersion = htods(minorVersion); |
| // smallestScreenWidthDp = htods(smallestScreenWidthDp); |
| // screenWidthDp = htods(screenWidthDp); |
| // screenHeightDp = htods(screenHeightDp); |
| } |
| |
| static final int compareLocales(final ResTableConfig l, final ResTableConfig r) { |
| if (l.locale() != r.locale()) { |
| // NOTE: This is the old behaviour with respect to comparison orders. |
| // The diff value here doesn't make much sense (given our bit packing scheme) |
| // but it's stable, and that's all we need. |
| return l.locale() - r.locale(); |
| } |
| |
| // The language & region are equal, so compare the scripts and variants. |
| final byte emptyScript[] = {'\0', '\0', '\0', '\0'}; |
| final byte[] lScript = l.localeScriptWasComputed ? emptyScript : l.localeScript; |
| final byte[] rScript = r.localeScriptWasComputed ? emptyScript : r.localeScript; |
| // int script = memcmp(lScript, rScript); |
| // if (script) { |
| // return script; |
| // } |
| int d = arrayCompare(lScript, rScript); |
| if (d != 0) return d; |
| |
| // The language, region and script are equal, so compare variants. |
| // |
| // This should happen very infrequently (if at all.) |
| return arrayCompare(l.localeVariant, r.localeVariant); |
| } |
| |
| private static int arrayCompare(byte[] l, byte[] r) { |
| for (int i = 0; i < l.length; i++) { |
| byte l0 = l[i]; |
| byte r0 = r[i]; |
| int d = l0 - r0; |
| if (d != 0) return d; |
| } |
| return 0; |
| } |
| |
| int compare(final ResTableConfig o) { |
| int diff = imsi() - o.imsi(); |
| if (diff != 0) return diff; |
| diff = compareLocales(this, o); |
| if (diff != 0) return diff; |
| diff = (screenType() - o.screenType()); |
| if (diff != 0) return diff; |
| diff = (input() - o.input()); |
| if (diff != 0) return diff; |
| diff = (screenSize() - o.screenSize()); |
| if (diff != 0) return diff; |
| diff = (version() - o.version()); |
| if (diff != 0) return diff; |
| diff = (screenLayout - o.screenLayout); |
| if (diff != 0) return diff; |
| diff = (screenLayout2 - o.screenLayout2); |
| if (diff != 0) return diff; |
| diff = (uiMode - o.uiMode); |
| if (diff != 0) return diff; |
| diff = (smallestScreenWidthDp - o.smallestScreenWidthDp); |
| if (diff != 0) return diff; |
| diff = (screenSizeDp() - o.screenSizeDp()); |
| return diff; |
| } |
| |
| |
| /** Returns true if this is the default "any" configuration. */ |
| public final boolean isDefault() { |
| return mcc == 0 |
| && mnc == 0 |
| && Arrays.equals(language, new byte[2]) |
| && Arrays.equals(country, new byte[2]) |
| && orientation == 0 |
| && touchscreen == 0 |
| && density == 0 |
| && keyboard == 0 |
| && navigation == 0 |
| && inputFlags == 0 |
| && screenWidth == 0 |
| && screenHeight == 0 |
| && sdkVersion == 0 |
| && minorVersion == 0 |
| && screenLayout == 0 |
| && uiMode == 0 |
| && smallestScreenWidthDp == 0 |
| && screenWidthDp == 0 |
| && screenHeightDp == 0 |
| && Arrays.equals(localeScript, new byte[4]) |
| && Arrays.equals(localeVariant, new byte[8]) |
| && screenLayout2 == 0; |
| } |
| |
| @Override |
| public final String toString() { |
| if (isDefault()) { // Prevent the default configuration from returning the empty string |
| return "default"; |
| } |
| Collection<String> parts = toStringParts().values(); |
| parts.removeAll(Collections.singleton("")); |
| return Joiner.on('-').join(parts); |
| } |
| |
| /** |
| * Returns a map of the configuration parts for {@link #toString}. |
| * |
| * <p>If a configuration part is not defined for this {@link ResTableConfig}, its value |
| * will be the empty string. |
| */ |
| public final Map<Type, String> toStringParts() { |
| Map<Type, String> result = new LinkedHashMap<>(); // Preserve order for #toString(). |
| result.put(Type.MCC, isTruthy(mcc) ? "mcc" + mcc : ""); |
| result.put(Type.MNC, isTruthy(mnc) ? "mnc" + mnc : ""); |
| result.put(Type.LANGUAGE_STRING, !languageString().isEmpty() ? "" + languageString() : ""); |
| result.put(Type.REGION_STRING, !regionString().isEmpty() ? "r" + regionString() : ""); |
| result.put(Type.SCREEN_LAYOUT_DIRECTION, |
| getOrDefault(SCREENLAYOUT_LAYOUTDIR_VALUES, screenLayoutDirection(), "")); |
| result.put(Type.SMALLEST_SCREEN_WIDTH_DP, |
| isTruthy(smallestScreenWidthDp) ? "sw" + smallestScreenWidthDp + "dp" : ""); |
| result.put(Type.SCREEN_WIDTH_DP, isTruthy(screenWidthDp) ? "w" + screenWidthDp + "dp" : ""); |
| result.put(Type.SCREEN_HEIGHT_DP, isTruthy(screenHeightDp) ? "h" + screenHeightDp + "dp" : ""); |
| result.put(Type.SCREEN_LAYOUT_SIZE, |
| getOrDefault(SCREENLAYOUT_SIZE_VALUES, screenLayoutSize(), "")); |
| result.put(Type.SCREEN_LAYOUT_LONG, |
| getOrDefault(SCREENLAYOUT_LONG_VALUES, screenLayoutLong(), "")); |
| result.put(Type.SCREEN_LAYOUT_ROUND, |
| getOrDefault(SCREENLAYOUT_ROUND_VALUES, screenLayoutRound(), "")); |
| result.put(Type.ORIENTATION, getOrDefault(ORIENTATION_VALUES, orientation, "")); |
| result.put(Type.UI_MODE_TYPE, getOrDefault(UI_MODE_TYPE_VALUES, uiModeType(), "")); |
| result.put(Type.UI_MODE_NIGHT, getOrDefault(UI_MODE_NIGHT_VALUES, uiModeNight(), "")); |
| result.put(Type.DENSITY_DPI, getOrDefault(DENSITY_DPI_VALUES, density, density + "dpi")); |
| result.put(Type.TOUCHSCREEN, getOrDefault(TOUCHSCREEN_VALUES, touchscreen, "")); |
| result.put(Type.KEYBOARD_HIDDEN, getOrDefault(KEYBOARDHIDDEN_VALUES, keyboardHidden(), "")); |
| result.put(Type.KEYBOARD, getOrDefault(KEYBOARD_VALUES, keyboard, "")); |
| result.put(Type.NAVIGATION_HIDDEN, |
| getOrDefault(NAVIGATIONHIDDEN_VALUES, navigationHidden(), "")); |
| result.put(Type.NAVIGATION, getOrDefault(NAVIGATION_VALUES, navigation, "")); |
| result.put(Type.SDK_VERSION, isTruthy(sdkVersion) ? "v" + sdkVersion : ""); |
| return result; |
| } |
| |
| private <K, V> V getOrDefault(Map<K, V> map, K key, V defaultValue) { |
| // TODO(acornwall): Remove this when Java 8's Map#getOrDefault is available. |
| // Null is not returned, even if the map contains a key whose value is null. This is intended. |
| V value = map.get(key); |
| return value != null ? value : defaultValue; |
| } |
| |
| |
| // constants for isBetterThan... |
| public static final int MASK_LAYOUTDIR = SCREENLAYOUT_LAYOUTDIR_MASK; |
| static final int MASK_SCREENSIZE = SCREENLAYOUT_SIZE_MASK; |
| static final int SCREENSIZE_NORMAL = ACONFIGURATION_SCREENSIZE_NORMAL; |
| |
| |
| |
| // transliterated from https://android.googlesource.com/platform/frameworks/base/+/android-7.1.1_r13/libs/androidfw/ResourceTypes.cpp |
| |
| /** |
| * Is {@code requested} a better match to this {@link ResTableConfig} object than {@code o} |
| */ |
| public boolean isBetterThan(ResTableConfig o, ResTableConfig requested) { |
| if (isTruthy(requested)) { |
| if (isTruthy(imsi()) || isTruthy(o.imsi())) { |
| if ((mcc != o.mcc) && isTruthy(requested.mcc)) { |
| return (isTruthy(mcc)); |
| } |
| |
| if ((mnc != o.mnc) && isTruthy(requested.mnc)) { |
| return (isTruthy(mnc)); |
| } |
| } |
| |
| if (isLocaleBetterThan(o, requested)) { |
| return true; |
| } |
| |
| if (isTruthy(screenLayout) || isTruthy(o.screenLayout)) { |
| if (isTruthy((screenLayout^o.screenLayout) & MASK_LAYOUTDIR) |
| && isTruthy(requested.screenLayout & MASK_LAYOUTDIR)) { |
| int myLayoutDir = screenLayout & MASK_LAYOUTDIR; |
| int oLayoutDir = o.screenLayout & MASK_LAYOUTDIR; |
| return (myLayoutDir > oLayoutDir); |
| } |
| } |
| |
| if (isTruthy(smallestScreenWidthDp) || isTruthy(o.smallestScreenWidthDp)) { |
| // The configuration closest to the actual size is best. |
| // We assume that larger configs have already been filtered |
| // out at this point. That means we just want the largest one. |
| if (smallestScreenWidthDp != o.smallestScreenWidthDp) { |
| return smallestScreenWidthDp > o.smallestScreenWidthDp; |
| } |
| } |
| |
| if (isTruthy(screenSizeDp()) || isTruthy(o.screenSizeDp())) { |
| // "Better" is based on the sum of the difference between both |
| // width and height from the requested dimensions. We are |
| // assuming the invalid configs (with smaller dimens) have |
| // already been filtered. Note that if a particular dimension |
| // is unspecified, we will end up with a large value (the |
| // difference between 0 and the requested dimension), which is |
| // good since we will prefer a config that has specified a |
| // dimension value. |
| int myDelta = 0, otherDelta = 0; |
| if (isTruthy(requested.screenWidthDp)) { |
| myDelta += requested.screenWidthDp - screenWidthDp; |
| otherDelta += requested.screenWidthDp - o.screenWidthDp; |
| } |
| if (isTruthy(requested.screenHeightDp)) { |
| myDelta += requested.screenHeightDp - screenHeightDp; |
| otherDelta += requested.screenHeightDp - o.screenHeightDp; |
| } |
| |
| if (myDelta != otherDelta) { |
| return myDelta < otherDelta; |
| } |
| } |
| |
| if (isTruthy(screenLayout) || isTruthy(o.screenLayout)) { |
| if (isTruthy((screenLayout^o.screenLayout) & MASK_SCREENSIZE) |
| && isTruthy(requested.screenLayout & MASK_SCREENSIZE)) { |
| // A little backwards compatibility here: undefined is |
| // considered equivalent to normal. But only if the |
| // requested size is at least normal; otherwise, small |
| // is better than the default. |
| int mySL = (screenLayout & MASK_SCREENSIZE); |
| int oSL = (o.screenLayout & MASK_SCREENSIZE); |
| int fixedMySL = mySL; |
| int fixedOSL = oSL; |
| if ((requested.screenLayout & MASK_SCREENSIZE) >= SCREENSIZE_NORMAL) { |
| if (fixedMySL == 0) fixedMySL = SCREENSIZE_NORMAL; |
| if (fixedOSL == 0) fixedOSL = SCREENSIZE_NORMAL; |
| } |
| // For screen size, the best match is the one that is |
| // closest to the requested screen size, but not over |
| // (the not over part is dealt with in match() below). |
| if (fixedMySL == fixedOSL) { |
| // If the two are the same, but 'this' is actually |
| // undefined, then the other is really a better match. |
| if (mySL == 0) return false; |
| return true; |
| } |
| if (fixedMySL != fixedOSL) { |
| return fixedMySL > fixedOSL; |
| } |
| } |
| if (((screenLayout^o.screenLayout) & MASK_SCREENLONG) != 0 |
| && isTruthy(requested.screenLayout & MASK_SCREENLONG)) { |
| return isTruthy(screenLayout & MASK_SCREENLONG); |
| } |
| } |
| |
| if (isTruthy(screenLayout2) || isTruthy(o.screenLayout2)) { |
| if (((screenLayout2^o.screenLayout2) & MASK_SCREENROUND) != 0 && |
| isTruthy(requested.screenLayout2 & MASK_SCREENROUND)) { |
| return isTruthy(screenLayout2 & MASK_SCREENROUND); |
| } |
| } |
| |
| if ((orientation != o.orientation) && isTruthy(requested.orientation)) { |
| return isTruthy(orientation); |
| } |
| |
| if (isTruthy(uiMode) || isTruthy(o.uiMode)) { |
| if (((uiMode^o.uiMode) & MASK_UI_MODE_TYPE) != 0 |
| && isTruthy(requested.uiMode & MASK_UI_MODE_TYPE)) { |
| return isTruthy(uiMode & MASK_UI_MODE_TYPE); |
| } |
| if (((uiMode^o.uiMode) & MASK_UI_MODE_NIGHT) != 0 |
| && isTruthy(requested.uiMode & MASK_UI_MODE_NIGHT)) { |
| return isTruthy(uiMode & MASK_UI_MODE_NIGHT); |
| } |
| } |
| |
| if (isTruthy(screenType()) || isTruthy(o.screenType())) { |
| if (density != o.density) { |
| // Use the system default density (DENSITY_MEDIUM, 160dpi) if none specified. |
| final int thisDensity = isTruthy(density) ? density : DENSITY_MEDIUM; |
| final int otherDensity = isTruthy(o.density) ? o.density : DENSITY_MEDIUM; |
| |
| // We always prefer DENSITY_ANY over scaling a density bucket. |
| if (thisDensity == DENSITY_ANY) { |
| return true; |
| } else if (otherDensity == DENSITY_ANY) { |
| return false; |
| } |
| |
| int requestedDensity = requested.density; |
| if (requested.density == 0 || |
| requested.density == DENSITY_ANY) { |
| requestedDensity = DENSITY_MEDIUM; |
| } |
| |
| // DENSITY_ANY is now dealt with. We should look to |
| // pick a density bucket and potentially scale it. |
| // Any density is potentially useful |
| // because the system will scale it. Scaling down |
| // is generally better than scaling up. |
| int h = thisDensity; |
| int l = otherDensity; |
| boolean bImBigger = true; |
| if (l > h) { |
| int t = h; |
| h = l; |
| l = t; |
| bImBigger = false; |
| } |
| |
| if (requestedDensity >= h) { |
| // requested value higher than both l and h, give h |
| return bImBigger; |
| } |
| if (l >= requestedDensity) { |
| // requested value lower than both l and h, give l |
| return !bImBigger; |
| } |
| // saying that scaling down is 2x better than up |
| if (((2 * l) - requestedDensity) * h > requestedDensity * requestedDensity) { |
| return !bImBigger; |
| } else { |
| return bImBigger; |
| } |
| } |
| |
| if ((touchscreen != o.touchscreen) && isTruthy(requested.touchscreen)) { |
| return isTruthy(touchscreen); |
| } |
| } |
| |
| if (isTruthy(input()) || isTruthy(o.input())) { |
| final int keysHidden = inputFlags & MASK_KEYSHIDDEN; |
| final int oKeysHidden = o.inputFlags & MASK_KEYSHIDDEN; |
| if (keysHidden != oKeysHidden) { |
| final int reqKeysHidden = |
| requested.inputFlags & MASK_KEYSHIDDEN; |
| if (isTruthy(reqKeysHidden)) { |
| |
| if (keysHidden == 0) return false; |
| if (oKeysHidden == 0) return true; |
| // For compatibility, we count KEYSHIDDEN_NO as being |
| // the same as KEYSHIDDEN_SOFT. Here we disambiguate |
| // these by making an exact match more specific. |
| if (reqKeysHidden == keysHidden) return true; |
| if (reqKeysHidden == oKeysHidden) return false; |
| } |
| } |
| |
| final int navHidden = inputFlags & MASK_NAVHIDDEN; |
| final int oNavHidden = o.inputFlags & MASK_NAVHIDDEN; |
| if (navHidden != oNavHidden) { |
| final int reqNavHidden = |
| requested.inputFlags & MASK_NAVHIDDEN; |
| if (isTruthy(reqNavHidden)) { |
| |
| if (navHidden == 0) return false; |
| if (oNavHidden == 0) return true; |
| } |
| } |
| |
| if ((keyboard != o.keyboard) && isTruthy(requested.keyboard)) { |
| return isTruthy(keyboard); |
| } |
| |
| if ((navigation != o.navigation) && isTruthy(requested.navigation)) { |
| return isTruthy(navigation); |
| } |
| } |
| |
| if (isTruthy(screenSize()) || isTruthy(o.screenSize())) { |
| // "Better" is based on the sum of the difference between both |
| // width and height from the requested dimensions. We are |
| // assuming the invalid configs (with smaller sizes) have |
| // already been filtered. Note that if a particular dimension |
| // is unspecified, we will end up with a large value (the |
| // difference between 0 and the requested dimension), which is |
| // good since we will prefer a config that has specified a |
| // size value. |
| int myDelta = 0, otherDelta = 0; |
| if (isTruthy(requested.screenWidth)) { |
| myDelta += requested.screenWidth - screenWidth; |
| otherDelta += requested.screenWidth - o.screenWidth; |
| } |
| if (isTruthy(requested.screenHeight)) { |
| myDelta += requested.screenHeight - screenHeight; |
| otherDelta += requested.screenHeight - o.screenHeight; |
| } |
| if (myDelta != otherDelta) { |
| return myDelta < otherDelta; |
| } |
| } |
| |
| if (isTruthy(version()) || isTruthy(o.version())) { |
| if ((sdkVersion != o.sdkVersion) && isTruthy(requested.sdkVersion)) { |
| return (sdkVersion > o.sdkVersion); |
| } |
| |
| if ((minorVersion != o.minorVersion) && |
| isTruthy(requested.minorVersion)) { |
| return isTruthy(minorVersion); |
| } |
| } |
| |
| return false; |
| } |
| return isMoreSpecificThan(o); |
| } |
| |
| boolean match(final ResTableConfig settings) { |
| if (imsi() != 0) { |
| if (mcc != 0 && mcc != settings.mcc) { |
| return false; |
| } |
| if (mnc != 0 && mnc != settings.mnc) { |
| return false; |
| } |
| } |
| if (locale() != 0) { |
| // Don't consider country and variants when deciding matches. |
| // (Theoretically, the variant can also affect the script. For |
| // example, "ar-alalc97" probably implies the Latin script, but since |
| // CLDR doesn't support getting likely scripts for that, we'll assume |
| // the variant doesn't change the script.) |
| // |
| // If two configs differ only in their country and variant, |
| // they can be weeded out in the isMoreSpecificThan test. |
| if (language[0] != settings.language[0] || language[1] != settings.language[1]) { |
| return false; |
| } |
| |
| // For backward compatibility and supporting private-use locales, we |
| // fall back to old behavior if we couldn't determine the script for |
| // either of the desired locale or the provided locale. But if we could determine |
| // the scripts, they should be the same for the locales to match. |
| boolean countriesMustMatch = false; |
| byte[] computed_script = new byte[4]; |
| byte[] script = null; |
| if (settings.localeScript[0] == '\0') { // could not determine the request's script |
| countriesMustMatch = true; |
| } else { |
| if (localeScript[0] == '\0' && !localeScriptWasComputed) { |
| // script was not provided or computed, so we try to compute it |
| localeDataComputeScript(computed_script, language, country); |
| if (computed_script[0] == '\0') { // we could not compute the script |
| countriesMustMatch = true; |
| } else { |
| script = computed_script; |
| } |
| } else { // script was provided, so just use it |
| script = localeScript; |
| } |
| } |
| |
| if (countriesMustMatch) { |
| if (country[0] != '\0' |
| && (country[0] != settings.country[0] |
| || country[1] != settings.country[1])) { |
| return false; |
| } |
| } else { |
| if (Arrays.equals(script, settings.localeScript)) { |
| return false; |
| } |
| } |
| } |
| |
| if (screenConfig() != 0) { |
| final int layoutDir = screenLayout&MASK_LAYOUTDIR; |
| final int setLayoutDir = settings.screenLayout&MASK_LAYOUTDIR; |
| if (layoutDir != 0 && layoutDir != setLayoutDir) { |
| return false; |
| } |
| |
| final int screenSize = screenLayout&MASK_SCREENSIZE; |
| final int setScreenSize = settings.screenLayout&MASK_SCREENSIZE; |
| // Any screen sizes for larger screens than the setting do not |
| // match. |
| if (screenSize != 0 && screenSize > setScreenSize) { |
| return false; |
| } |
| |
| final int screenLong = screenLayout&MASK_SCREENLONG; |
| final int setScreenLong = settings.screenLayout&MASK_SCREENLONG; |
| if (screenLong != 0 && screenLong != setScreenLong) { |
| return false; |
| } |
| |
| final int uiModeType = uiMode&MASK_UI_MODE_TYPE; |
| final int setUiModeType = settings.uiMode&MASK_UI_MODE_TYPE; |
| if (uiModeType != 0 && uiModeType != setUiModeType) { |
| return false; |
| } |
| |
| final int uiModeNight = uiMode&MASK_UI_MODE_NIGHT; |
| final int setUiModeNight = settings.uiMode&MASK_UI_MODE_NIGHT; |
| if (uiModeNight != 0 && uiModeNight != setUiModeNight) { |
| return false; |
| } |
| |
| if (smallestScreenWidthDp != 0 |
| && smallestScreenWidthDp > settings.smallestScreenWidthDp) { |
| return false; |
| } |
| } |
| |
| if (screenConfig2() != 0) { |
| final int screenRound = screenLayout2 & MASK_SCREENROUND; |
| final int setScreenRound = settings.screenLayout2 & MASK_SCREENROUND; |
| if (screenRound != 0 && screenRound != setScreenRound) { |
| return false; |
| } |
| } |
| |
| if (screenSizeDp() != 0) { |
| if (screenWidthDp != 0 && screenWidthDp > settings.screenWidthDp) { |
| if (kDebugTableSuperNoisy) { |
| ALOGI("Filtering out width %d in requested %d", screenWidthDp, |
| settings.screenWidthDp); |
| } |
| return false; |
| } |
| if (screenHeightDp != 0 && screenHeightDp > settings.screenHeightDp) { |
| if (kDebugTableSuperNoisy) { |
| ALOGI("Filtering out height %d in requested %d", screenHeightDp, |
| settings.screenHeightDp); |
| } |
| return false; |
| } |
| } |
| if (screenType() != 0) { |
| if (orientation != 0 && orientation != settings.orientation) { |
| return false; |
| } |
| // density always matches - we can scale it. See isBetterThan |
| if (touchscreen != 0 && touchscreen != settings.touchscreen) { |
| return false; |
| } |
| } |
| if (input() != 0) { |
| final int keysHidden = inputFlags&MASK_KEYSHIDDEN; |
| final int setKeysHidden = settings.inputFlags&MASK_KEYSHIDDEN; |
| if (keysHidden != 0 && keysHidden != setKeysHidden) { |
| // For compatibility, we count a request for KEYSHIDDEN_NO as also |
| // matching the more recent KEYSHIDDEN_SOFT. Basically |
| // KEYSHIDDEN_NO means there is some kind of keyboard available. |
| if (kDebugTableSuperNoisy) { |
| ALOGI("Matching keysHidden: have=%d, config=%d\n", keysHidden, setKeysHidden); |
| } |
| if (keysHidden != KEYSHIDDEN_NO || setKeysHidden != KEYSHIDDEN_SOFT) { |
| if (kDebugTableSuperNoisy) { |
| ALOGI("No match!"); |
| } |
| return false; |
| } |
| } |
| final int navHidden = inputFlags&MASK_NAVHIDDEN; |
| final int setNavHidden = settings.inputFlags&MASK_NAVHIDDEN; |
| if (navHidden != 0 && navHidden != setNavHidden) { |
| return false; |
| } |
| if (keyboard != 0 && keyboard != settings.keyboard) { |
| return false; |
| } |
| if (navigation != 0 && navigation != settings.navigation) { |
| return false; |
| } |
| } |
| if (screenSize() != 0) { |
| if (screenWidth != 0 && screenWidth > settings.screenWidth) { |
| return false; |
| } |
| if (screenHeight != 0 && screenHeight > settings.screenHeight) { |
| return false; |
| } |
| } |
| if (version() != 0) { |
| if (sdkVersion != 0 && sdkVersion > settings.sdkVersion) { |
| return false; |
| } |
| if (minorVersion != 0 && minorVersion != settings.minorVersion) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * union { |
| struct { |
| // Mobile country code (from SIM). 0 means "any". |
| uint16_t mcc; |
| // Mobile network code (from SIM). 0 means "any". |
| uint16_t mnc; |
| }; |
| uint32_t imsi; |
| }; |
| */ |
| private int imsi() { |
| return (mcc & 0xffff) << 16 | (mnc & 0xffff); |
| } |
| |
| /** |
| * union { |
| struct { |
| uint16_t screenWidth; |
| uint16_t screenHeight; |
| }; |
| uint32_t screenSize; |
| }; |
| */ |
| private int screenSize() { |
| return (screenWidth & 0xffff) << 16 | (screenHeight & 0xffff); |
| } |
| |
| |
| /** |
| union { |
| struct { |
| uint8_t screenLayout; |
| uint8_t uiMode; |
| uint16_t smallestScreenWidthDp; |
| }; |
| uint32_t screenConfig; |
| }; |
| */ |
| private int screenConfig() { |
| return (screenLayout & 0xff << 24) | (uiMode * 0xff << 16) | smallestScreenWidthDp & 0xffff; |
| } |
| |
| |
| /** |
| * union { |
| struct { |
| uint16_t screenWidthDp; |
| uint16_t screenHeightDp; |
| }; |
| uint32_t screenSizeDp; |
| }; |
| */ |
| private int screenSizeDp() { |
| // screenWidthDp and screenHeightDp are really shorts... |
| return (screenWidthDp & 0xffff) << 16 | (screenHeightDp & 0xffff); |
| } |
| |
| /** |
| union { |
| struct { |
| uint8_t orientation; |
| uint8_t touchscreen; |
| uint16_t density; |
| }; |
| uint32_t screenType; |
| }; |
| */ |
| private int screenType() { |
| return (orientation & 0xff << 24) | (touchscreen * 0xff << 16) | density & 0xffff; |
| } |
| |
| /** |
| * |
| union { |
| struct { |
| uint8_t keyboard; |
| uint8_t navigation; |
| uint8_t inputFlags; |
| uint8_t inputPad0; |
| }; |
| uint32_t input; |
| }; |
| */ |
| private int input() { |
| // TODO is Pad Zeros? |
| return (keyboard & 0xff << 24) | (navigation & 0xff << 16) | (inputFlags & 0xff << 8); |
| } |
| |
| /** |
| * union { |
| struct { |
| uint16_t sdkVersion; |
| // For now minorVersion must always be 0!!! Its meaning |
| // is currently undefined. |
| uint16_t minorVersion; |
| }; |
| uint32_t version; |
| }; |
| */ |
| private int version() { |
| return (sdkVersion & 0xffff) << 16 | (minorVersion & 0xffff); |
| } |
| |
| /** |
| union { |
| struct { |
| // This field can take three different forms: |
| // - \0\0 means "any". |
| // |
| // - Two 7 bit ascii values interpreted as ISO-639-1 language |
| // codes ('fr', 'en' etc. etc.). The high bit for both bytes is |
| // zero. |
| // |
| // - A single 16 bit little endian packed value representing an |
| // ISO-639-2 3 letter language code. This will be of the form: |
| // |
| // {1, t, t, t, t, t, s, s, s, s, s, f, f, f, f, f} |
| // |
| // bit[0, 4] = first letter of the language code |
| // bit[5, 9] = second letter of the language code |
| // bit[10, 14] = third letter of the language code. |
| // bit[15] = 1 always |
| // |
| // For backwards compatibility, languages that have unambiguous |
| // two letter codes are represented in that format. |
| // |
| // The layout is always bigendian irrespective of the runtime |
| // architecture. |
| char language[2]; |
| |
| // This field can take three different forms: |
| // - \0\0 means "any". |
| // |
| // - Two 7 bit ascii values interpreted as 2 letter country |
| // codes ('US', 'GB' etc.). The high bit for both bytes is zero. |
| // |
| // - An UN M.49 3 digit country code. For simplicity, these are packed |
| // in the same manner as the language codes, though we should need |
| // only 10 bits to represent them, instead of the 15. |
| // |
| // The layout is always bigendian irrespective of the runtime |
| // architecture. |
| char country[2]; |
| }; |
| uint32_t locale; |
| }; |
| */ |
| int locale() { |
| return (language[0] & 0xff << 24) | (language[1] * 0xff << 16) | (country[0] & 0xffff << 8) | (country[1] & 0xffff); |
| } |
| |
| private boolean isLocaleBetterThan(ResTableConfig o, ResTableConfig requested) { |
| if (requested.locale() == 0) { |
| // The request doesn't have a locale, so no resource is better |
| // than the other. |
| return false; |
| } |
| |
| if (locale() == 0 && o.locale() == 0) { |
| // The locale part of both resources is empty, so none is better |
| // than the other. |
| return false; |
| } |
| |
| // Non-matching locales have been filtered out, so both resources |
| // match the requested locale. |
| // |
| // Because of the locale-related checks in match() and the checks, we know |
| // that: |
| // 1) The resource languages are either empty or match the request; |
| // and |
| // 2) If the request's script is known, the resource scripts are either |
| // unknown or match the request. |
| |
| if (!langsAreEquivalent(language, o.language)) { |
| // The languages of the two resources are not equivalent. If we are |
| // here, we can only assume that the two resources matched the request |
| // because one doesn't have a language and the other has a matching |
| // language. |
| // |
| // We consider the one that has the language specified a better match. |
| // |
| // The exception is that we consider no-language resources a better match |
| // for US English and similar locales than locales that are a descendant |
| // of Internatinal English (en-001), since no-language resources are |
| // where the US English resource have traditionally lived for most apps. |
| if (areIdentical(requested.language, kEnglish)) { |
| if (areIdentical(requested.country, kUnitedStates)) { |
| // For US English itself, we consider a no-locale resource a |
| // better match if the other resource has a country other than |
| // US specified. |
| if (language[0] != '\0') { |
| return country[0] == '\0' || areIdentical(country, kUnitedStates); |
| } else { |
| return !(o.country[0] == '\0' || areIdentical(o.country, kUnitedStates)); |
| } |
| } else if (LocaleData.localeDataIsCloseToUsEnglish(requested.country)) { |
| if (language[0] != '\0') { |
| return LocaleData.localeDataIsCloseToUsEnglish(country); |
| } else { |
| return !LocaleData.localeDataIsCloseToUsEnglish(o.country); |
| } |
| } |
| } |
| return (language[0] != '\0'); |
| } |
| |
| // If we are here, both the resources have an equivalent non-empty language |
| // to the request. |
| // |
| // Because the languages are equivalent, computeScript() always returns a |
| // non-empty script for languages it knows about, and we have passed the |
| // script checks in match(), the scripts are either all unknown or are all |
| // the same. So we can't gain anything by checking the scripts. We need to |
| // check the country and variant. |
| |
| // See if any of the regions is better than the other. |
| final int region_comparison = LocaleData.localeDataCompareRegions( |
| country, o.country, |
| requested.language, str(requested.localeScript), requested.country); |
| if (region_comparison != 0) { |
| return (region_comparison > 0); |
| } |
| |
| // The regions are the same. Try the variant. |
| final boolean localeMatches = Arrays.equals(localeVariant, requested.localeVariant); |
| final boolean otherMatches = Arrays.equals(o.localeVariant, requested.localeVariant); |
| if (localeMatches != otherMatches) { |
| return localeMatches; |
| } |
| |
| // Finally, the languages, although equivalent, may still be different |
| // (like for Tagalog and Filipino). Identical is better than just |
| // equivalent. |
| if (areIdentical(language, requested.language) |
| && !areIdentical(o.language, requested.language)) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| private String str(byte[] country) { |
| return new String(country, UTF_8); |
| } |
| |
| private boolean langsAreEquivalent(final byte[] lang1, final byte[] lang2) { |
| return areIdentical(lang1, lang2) || |
| (areIdentical(lang1, kTagalog) && areIdentical(lang2, kFilipino)) || |
| (areIdentical(lang1, kFilipino) && areIdentical(lang2, kTagalog)); |
| } |
| |
| // Checks if two language or country codes are identical |
| private boolean areIdentical(final byte[] code1, final byte[] code2) { |
| return code1[0] == code2[0] && code1[1] == code2[1]; |
| } |
| |
| // TODO Convert from C |
| private boolean isMoreSpecificThan(ResTableConfig o) { |
| return false; |
| } |
| |
| } |
| |