| package org.robolectric.res.android; |
| |
| import static java.nio.charset.StandardCharsets.US_ASCII; |
| |
| 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.nio.ByteBuffer; |
| 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. */ |
| public class ResourceConfiguration { |
| |
| /** The different types of configs that can be present in a {@link ResourceConfiguration}. */ |
| 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 |
| } |
| |
| /** 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 int KEYBOARD_NOKEYS = 1; |
| private static final int KEYBOARD_QWERTY = 2; |
| private static final int KEYBOARD_12KEY = 3; |
| 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 int NAVIGATION_NONAV = 1; |
| private static final int NAVIGATION_DPAD = 2; |
| private static final int NAVIGATION_TRACKBALL = 3; |
| private static final int NAVIGATION_WHEEL = 4; |
| 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; |
| private static final int SCREENLAYOUT_LAYOUTDIR_LTR = 0x40; |
| private 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 int TOUCHSCREEN_NOTOUCH = 1; |
| private static final int TOUCHSCREEN_FINGER = 3; |
| 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 int UI_MODE_NIGHT_NO = 0x10; |
| private static final int UI_MODE_NIGHT_YES = 0x20; |
| 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 int UI_MODE_TYPE_DESK = 0x02; |
| private static final int UI_MODE_TYPE_CAR = 0x03; |
| private static final int UI_MODE_TYPE_TELEVISION = 0x04; |
| private static final int UI_MODE_TYPE_APPLIANCE = 0x05; |
| private static final int UI_MODE_TYPE_WATCH = 0x06; |
| 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"); |
| |
| /** The minimum size in bytes that this configuration must be to contain screen config info. */ |
| private static final int SCREEN_CONFIG_MIN_SIZE = 32; |
| |
| /** The minimum size in bytes that this configuration must be to contain screen dp info. */ |
| private static final int SCREEN_DP_MIN_SIZE = 36; |
| |
| /** The minimum size in bytes that this configuration must be to contain locale info. */ |
| private static final int LOCALE_MIN_SIZE = 48; |
| |
| /** The minimum size in bytes that this config must be to contain the screenConfig extension. */ |
| private static final int SCREEN_CONFIG_EXTENSION_MIN_SIZE = 52; |
| |
| /** The number of bytes that this resource configuration takes up. */ |
| private final int size; |
| |
| private final int mcc; |
| private final 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 region code. */ |
| @SuppressWarnings("mutable") |
| private final byte[] region; |
| |
| /** Returns {@link #region} as an unpacked string representation. */ |
| public final String regionString() { |
| return unpackRegion(); |
| } |
| |
| private final int orientation; |
| private final int touchscreen; |
| private final int density; |
| private final int keyboard; |
| private final int navigation; |
| private final int inputFlags; |
| |
| public final int keyboardHidden() { |
| return inputFlags & KEYBOARDHIDDEN_MASK; |
| } |
| |
| public final int navigationHidden() { |
| return inputFlags & NAVIGATIONHIDDEN_MASK; |
| } |
| |
| private final int screenWidth; |
| private final int screenHeight; |
| private final 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 ResourceConfiguration withSdkVersion(int sdkVersion) { |
| if (sdkVersion == this.sdkVersion) { |
| return this; |
| } |
| return new ResourceConfiguration(size, mcc, mnc, language, region, |
| orientation, touchscreen, density, keyboard, navigation, inputFlags, |
| screenWidth, screenHeight, sdkVersion, minorVersion, screenLayout, uiMode, |
| smallestScreenWidthDp, screenWidthDp, screenHeightDp, localeScript, localeVariant, |
| screenLayout2, unknown); |
| } |
| |
| public ResourceConfiguration(int size, int mcc, int mnc, byte[] language, byte[] region, |
| 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, int screenLayout2, byte[] unknown) { |
| this.size = size; |
| this.mcc = mcc; |
| this.mnc = mnc; |
| this.language = language; |
| this.region = region; |
| 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.unknown = unknown; |
| } |
| |
| private final int minorVersion; |
| private final 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; |
| } |
| |
| private final int uiMode; |
| |
| public final int uiModeType() { |
| return uiMode & UI_MODE_TYPE_MASK; |
| } |
| |
| public final int uiModeNight() { |
| return uiMode & UI_MODE_NIGHT_MASK; |
| } |
| |
| private final int smallestScreenWidthDp; |
| private final int screenWidthDp; |
| private final int screenHeightDp; |
| |
| /** The ISO-15924 short name for the script corresponding to this configuration. */ |
| @SuppressWarnings("mutable") |
| private final byte[] localeScript; |
| |
| /** A single BCP-47 variant subtag. */ |
| @SuppressWarnings("mutable") |
| private final byte[] localeVariant; |
| |
| /** An extension to {@link #screenLayout}. Contains round/notround qualifier. */ |
| private final int screenLayout2; |
| |
| /** Any remaining bytes in this resource configuration that are unaccounted for. */ |
| @SuppressWarnings("mutable") |
| private final byte[] unknown; |
| |
| public static ResourceConfiguration create(ByteBuffer buffer) { |
| int startPosition = buffer.position(); // The starting buffer position to calculate bytes read. |
| int size = buffer.getInt(); |
| int mcc = buffer.getShort() & 0xFFFF; |
| int mnc = buffer.getShort() & 0xFFFF; |
| byte[] language = new byte[2]; |
| buffer.get(language); |
| byte[] region = new byte[2]; |
| buffer.get(region); |
| int orientation = UnsignedBytes.toInt(buffer.get()); |
| int touchscreen = UnsignedBytes.toInt(buffer.get()); |
| int density = buffer.getShort() & 0xFFFF; |
| int keyboard = UnsignedBytes.toInt(buffer.get()); |
| int navigation = UnsignedBytes.toInt(buffer.get()); |
| int inputFlags = UnsignedBytes.toInt(buffer.get()); |
| buffer.get(); // 1 byte of padding |
| int screenWidth = buffer.getShort() & 0xFFFF; |
| int screenHeight = buffer.getShort() & 0xFFFF; |
| int sdkVersion = buffer.getShort() & 0xFFFF; |
| int minorVersion = buffer.getShort() & 0xFFFF; |
| |
| // At this point, the configuration's size needs to be taken into account as not all |
| // configurations have all values. |
| int screenLayout = 0; |
| int uiMode = 0; |
| int smallestScreenWidthDp = 0; |
| int screenWidthDp = 0; |
| int screenHeightDp = 0; |
| byte[] localeScript = new byte[4]; |
| byte[] localeVariant = new byte[8]; |
| int screenLayout2 = 0; |
| |
| if (size >= SCREEN_CONFIG_MIN_SIZE) { |
| screenLayout = UnsignedBytes.toInt(buffer.get()); |
| uiMode = UnsignedBytes.toInt(buffer.get()); |
| smallestScreenWidthDp = buffer.getShort() & 0xFFFF; |
| } |
| |
| if (size >= SCREEN_DP_MIN_SIZE) { |
| screenWidthDp = buffer.getShort() & 0xFFFF; |
| screenHeightDp = buffer.getShort() & 0xFFFF; |
| } |
| |
| if (size >= LOCALE_MIN_SIZE) { |
| buffer.get(localeScript); |
| buffer.get(localeVariant); |
| } |
| |
| if (size >= SCREEN_CONFIG_EXTENSION_MIN_SIZE) { |
| screenLayout2 = UnsignedBytes.toInt(buffer.get()); |
| buffer.get(); // Reserved padding |
| buffer.getShort(); // More reserved padding |
| } |
| |
| // After parsing everything that's known, account for anything that's unknown. |
| int bytesRead = buffer.position() - startPosition; |
| byte[] unknown = new byte[size - bytesRead]; |
| buffer.get(unknown); |
| |
| return new ResourceConfiguration(size, mcc, mnc, language, region, orientation, |
| touchscreen, density, keyboard, navigation, inputFlags, screenWidth, screenHeight, |
| sdkVersion, minorVersion, screenLayout, uiMode, smallestScreenWidthDp, screenWidthDp, |
| screenHeightDp, localeScript, localeVariant, screenLayout2, unknown); |
| } |
| |
| private String unpackLanguage() { |
| return unpackLanguageOrRegion(language, 0x61); |
| } |
| |
| private String unpackRegion() { |
| return unpackLanguageOrRegion(region, 0x30); |
| } |
| |
| private String unpackLanguageOrRegion(byte[] value, int base) { |
| Preconditions.checkState(value.length == 2, "Language or region value must be 2 bytes."); |
| if (value[0] == 0 && value[1] == 0) { |
| return ""; |
| } |
| if ((UnsignedBytes.toInt(value[0]) & 0x80) != 0) { |
| 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); |
| } |
| |
| /** 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(region, 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 ResourceConfiguration}, 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, mcc != 0 ? "mcc" + mcc : ""); |
| result.put(Type.MNC, mnc != 0 ? "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, |
| smallestScreenWidthDp != 0 ? "sw" + smallestScreenWidthDp + "dp" : ""); |
| result.put(Type.SCREEN_WIDTH_DP, screenWidthDp != 0 ? "w" + screenWidthDp + "dp" : ""); |
| result.put(Type.SCREEN_HEIGHT_DP, screenHeightDp != 0 ? "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, sdkVersion != 0 ? "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... |
| private static final int MASK_LAYOUTDIR = SCREENLAYOUT_LAYOUTDIR_MASK; |
| private static final int MASK_SCREENSIZE = SCREENLAYOUT_SIZE_MASK; |
| private static final int ACONFIGURATION_SCREENSIZE_NORMAL = SCREENLAYOUT_SIZE_NORMAL; |
| private static final int SCREENSIZE_NORMAL = ACONFIGURATION_SCREENSIZE_NORMAL; |
| private static final int MASK_SCREENLONG = SCREENLAYOUT_LONG_MASK; |
| private static final int MASK_SCREENROUND = SCREENLAYOUT_ROUND_MASK; |
| private static final int MASK_UI_MODE_TYPE = UI_MODE_TYPE_MASK; |
| private static final int MASK_UI_MODE_NIGHT = UI_MODE_NIGHT_MASK; |
| private static final int ACONFIGURATION_DENSITY_MEDIUM = DENSITY_DPI_MDPI; |
| private static final int DENSITY_MEDIUM = ACONFIGURATION_DENSITY_MEDIUM; |
| private static final int ACONFIGURATION_DENSITY_ANY = DENSITY_DPI_ANY; |
| private static final int DENSITY_ANY = ACONFIGURATION_DENSITY_ANY; |
| private static final int MASK_KEYSHIDDEN = 0x0003; |
| public static final int MASK_NAVHIDDEN = 0x000c; |
| |
| |
| boolean isBetterThan(ResourceConfiguration o, ResourceConfiguration requested) { |
| if (requested != null) { |
| if (imsi() != 0 || o.imsi() != 0) { |
| if ((mcc != o.mcc) && requested.mcc != 0) { |
| return (mcc != 0); |
| } |
| |
| if ((mnc != o.mnc) && requested.mnc != 0) { |
| return (mnc != 0); |
| } |
| } |
| |
| if (isLocaleBetterThan(o, requested)) { |
| return true; |
| } |
| |
| if (screenLayout != 0 || o.screenLayout != 0) { |
| if (((screenLayout^o.screenLayout) & MASK_LAYOUTDIR) != 0 |
| && (requested.screenLayout & MASK_LAYOUTDIR) != 0) { |
| int myLayoutDir = screenLayout & MASK_LAYOUTDIR; |
| int oLayoutDir = o.screenLayout & MASK_LAYOUTDIR; |
| return (myLayoutDir > oLayoutDir); |
| } |
| } |
| |
| if (smallestScreenWidthDp != 0 || o.smallestScreenWidthDp != 0) { |
| // 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 (screenSizeDp() != 0 || o.screenSizeDp() != 0) { |
| // "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 (requested.screenWidthDp != 0) { |
| myDelta += requested.screenWidthDp - screenWidthDp; |
| otherDelta += requested.screenWidthDp - o.screenWidthDp; |
| } |
| if (requested.screenHeightDp != 0) { |
| myDelta += requested.screenHeightDp - screenHeightDp; |
| otherDelta += requested.screenHeightDp - o.screenHeightDp; |
| } |
| |
| if (myDelta != otherDelta) { |
| return myDelta < otherDelta; |
| } |
| } |
| |
| if (screenLayout != 0 || o.screenLayout != 0) { |
| if (((screenLayout^o.screenLayout) & MASK_SCREENSIZE) != 0 |
| && (requested.screenLayout & MASK_SCREENSIZE) != 0) { |
| // 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 |
| && (requested.screenLayout & MASK_SCREENLONG) != 0) { |
| return (screenLayout & MASK_SCREENLONG) != 0; |
| } |
| } |
| |
| if (screenLayout2 != 0 || o.screenLayout2 != 0) { |
| if (((screenLayout2^o.screenLayout2) & MASK_SCREENROUND) != 0 && |
| (requested.screenLayout2 & MASK_SCREENROUND) != 0) { |
| return (screenLayout2 & MASK_SCREENROUND) != 0; |
| } |
| } |
| |
| if ((orientation != o.orientation) && requested.orientation != 0) { |
| return (orientation) != 0; |
| } |
| |
| if (uiMode != 0 || o.uiMode != 0) { |
| if (((uiMode^o.uiMode) & MASK_UI_MODE_TYPE) != 0 |
| && (requested.uiMode & MASK_UI_MODE_TYPE) != 0) { |
| return (uiMode & MASK_UI_MODE_TYPE) != 0; |
| } |
| if (((uiMode^o.uiMode) & MASK_UI_MODE_NIGHT) != 0 |
| && (requested.uiMode & MASK_UI_MODE_NIGHT) != 0) { |
| return (uiMode & MASK_UI_MODE_NIGHT) != 0; |
| } |
| } |
| |
| if (screenType() != 0 || o.screenType() != 0) { |
| if (density != o.density) { |
| // Use the system default density (DENSITY_MEDIUM, 160dpi) if none specified. |
| final int thisDensity = density != 0 ? density : DENSITY_MEDIUM; |
| final int otherDensity = o.density != 0 ? 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) && requested.touchscreen != 0) { |
| return (touchscreen) != 0; |
| } |
| } |
| |
| if (input() != 0 || o.input() != 0) { |
| final int keysHidden = inputFlags & MASK_KEYSHIDDEN; |
| final int oKeysHidden = o.inputFlags & MASK_KEYSHIDDEN; |
| if (keysHidden != oKeysHidden) { |
| final int reqKeysHidden = |
| requested.inputFlags & MASK_KEYSHIDDEN; |
| if (reqKeysHidden != 0) { |
| |
| 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 (reqNavHidden != 0) { |
| |
| if (navHidden == 0) return false; |
| if (oNavHidden == 0) return true; |
| } |
| } |
| |
| if ((keyboard != o.keyboard) && requested.keyboard != 0) { |
| return (keyboard) != 0; |
| } |
| |
| if ((navigation != o.navigation) && requested.navigation != 0) { |
| return (navigation) != 0; |
| } |
| } |
| |
| if (screenSize() != 0 || o.screenSize() != 0) { |
| // "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 (requested.screenWidth != 0) { |
| myDelta += requested.screenWidth - screenWidth; |
| otherDelta += requested.screenWidth - o.screenWidth; |
| } |
| if (requested.screenHeight != 0) { |
| myDelta += requested.screenHeight - screenHeight; |
| otherDelta += requested.screenHeight - o.screenHeight; |
| } |
| if (myDelta != otherDelta) { |
| return myDelta < otherDelta; |
| } |
| } |
| |
| if (version() != 0 || o.version() != 0) { |
| if ((sdkVersion != o.sdkVersion) && requested.sdkVersion != 0) { |
| return (sdkVersion > o.sdkVersion); |
| } |
| |
| if ((minorVersion != o.minorVersion) && |
| requested.minorVersion != 0) { |
| return (minorVersion) != 0; |
| } |
| } |
| |
| return false; |
| } |
| return isMoreSpecificThan(o); |
| } |
| |
| /** |
| * 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 { |
| 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); |
| } |
| |
| // TODO Convert from C |
| private boolean isLocaleBetterThan(ResourceConfiguration o, ResourceConfiguration requested) { |
| return false; |
| } |
| |
| // TODO Convert from C |
| private boolean isMoreSpecificThan(ResourceConfiguration o) { |
| return false; |
| } |
| |
| } |
| |