diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ParserStream.java b/jadx-core/src/main/java/jadx/core/xmlgen/ParserStream.java index fc6cb27319c..10e37eb3635 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/ParserStream.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ParserStream.java @@ -1,5 +1,6 @@ package jadx.core.xmlgen; +import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; @@ -143,6 +144,23 @@ public void reset() throws IOException { input.reset(); } + public void readFully(byte[] b) throws IOException { + readFully(b, 0, b.length); + } + + public void readFully(byte[] b, int off, int len) throws IOException { + readPos += len; + if (len < 0) + throw new IndexOutOfBoundsException(); + int n = 0; + while (n < len) { + int count = input.read(b, off + n, len - n); + if (count < 0) + throw new EOFException(); + n += count; + } + } + @Override public String toString() { return "pos: 0x" + Long.toHexString(readPos); diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java b/jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java index dd04ae6c132..8c109c2ca78 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.io.InputStream; +import java.math.BigInteger; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -21,6 +22,8 @@ public class ResTableParser extends CommonBinaryParser { private static final Logger LOG = LoggerFactory.getLogger(ResTableParser.class); + private static final int KNOWN_CONFIG_BYTES = 56; + private static final class PackageChunk { private final int id; private final String name; @@ -194,6 +197,11 @@ private void parseTypeChunk(long start, PackageChunk pkg) throws IOException { EntryConfig config = parseConfig(); + if (config.isInvalid) { + String typeName = pkg.getTypeStrings()[id - 1]; + LOG.warn("Invalid config flags detected: " + typeName + config.getQualifiers()); + } + int[] entryIndexes = new int[entryCount]; for (int i = 0; i < entryCount; i++) { entryIndexes[i] = is.readInt32(); @@ -248,134 +256,134 @@ private RawValue parseValue() throws IOException { } private EntryConfig parseConfig() throws IOException { - long start = is.getPos(); int size = is.readInt32(); + int read = 28; - EntryConfig config = new EntryConfig(); - - is.readInt16(); //mcc - is.readInt16(); //mnc + if (size < 28) { + throw new IOException("Config size < 28"); + } - config.setLanguage(parseLocale()); - config.setCountry(parseLocale()); + boolean isInvalid = false; - int orientation = is.readInt8(); - int touchscreen = is.readInt8(); - int density = is.readInt16(); + short mcc = (short) is.readInt16(); + short mnc = (short) is.readInt16(); - if (density != 0) { - config.setDensity(parseDensity(density)); - } + char[] language = unpackLocaleOrRegion((byte) is.readInt8(), (byte) is.readInt8(), 'a'); + char[] country = unpackLocaleOrRegion((byte) is.readInt8(), (byte) is.readInt8(), '0'); - is.readInt8(); // keyboard - is.readInt8(); // navigation - is.readInt8(); // inputFlags - is.readInt8(); // inputPad0 + byte orientation = (byte) is.readInt8(); + byte touchscreen = (byte) is.readInt8(); - int screenWidth = is.readInt16(); - int screenHeight = is.readInt16(); + int density = is.readInt16(); - if (screenWidth != 0 && screenHeight != 0) { - config.setScreenSize(screenWidth + "x" + screenHeight); + byte keyboard = (byte) is.readInt8(); + byte navigation = (byte) is.readInt8(); + byte inputFlags = (byte) is.readInt8(); + /* inputPad0 */is.readInt8(); + + short screenWidth = (short) is.readInt16(); + short screenHeight = (short) is.readInt16(); + + short sdkVersion = (short) is.readInt16(); + /* minorVersion, now must always be 0 */is.readInt16(); + + byte screenLayout = 0; + byte uiMode = 0; + short smallestScreenWidthDp = 0; + if (size >= 32) { + screenLayout = (byte) is.readInt8(); + uiMode = (byte) is.readInt8(); + smallestScreenWidthDp = (short) is.readInt16(); + read = 32; } - int sdkVersion = is.readInt16(); - - if (sdkVersion != 0) { - config.setSdkVersion("v" + sdkVersion); + short screenWidthDp = 0; + short screenHeightDp = 0; + if (size >= 36) { + screenWidthDp = (short) is.readInt16(); + screenHeightDp = (short) is.readInt16(); + read = 36; } - int minorVersion = is.readInt16(); - - int screenLayout = is.readInt8(); - int uiMode = is.readInt8(); - int smallestScreenWidthDp = is.readInt16(); - - int screenWidthDp = is.readInt16(); - int screenHeightDp = is.readInt16(); - - if (screenLayout != 0) { - config.setScreenLayout(parseScreenLayout(screenLayout)); + char[] localeScript = null; + char[] localeVariant = null; + if (size >= 48) { + localeScript = readScriptOrVariantChar(4).toCharArray(); + localeVariant = readScriptOrVariantChar(8).toCharArray(); + read = 48; } - if (smallestScreenWidthDp != 0) { - config.setSmallestScreenWidthDp("sw" + smallestScreenWidthDp + "dp"); + byte screenLayout2 = 0; + byte colorMode = 0; + if (size >= 52) { + screenLayout2 = (byte) is.readInt8(); + colorMode = (byte) is.readInt8(); + is.readInt16(); // reserved padding + read = 52; } - if (orientation != 0) { - config.setOrientation(parseOrientation(orientation)); + if (size >= 56) { + is.readInt32(); + read = 56; } - if (screenWidthDp != 0) { - config.setScreenWidthDp("w" + screenWidthDp + "dp"); + int exceedingSize = size - KNOWN_CONFIG_BYTES; + if (exceedingSize > 0) { + byte[] buf = new byte[exceedingSize]; + read += exceedingSize; + is.readFully(buf); + BigInteger exceedingBI = new BigInteger(1, buf); + + if (exceedingBI.equals(BigInteger.ZERO)) { + LOG.info(String + .format("Config flags size > %d, but exceeding bytes are all zero, so it should be ok.", + KNOWN_CONFIG_BYTES)); + } else { + LOG.warn(String.format("Config flags size > %d. Size = %d. Exceeding bytes: 0x%X.", + KNOWN_CONFIG_BYTES, size, exceedingBI)); + isInvalid = true; + } } - if (screenHeightDp != 0) { - config.setScreenHeightDp("h" + screenHeightDp + "dp"); + int remainingSize = size - read; + if (remainingSize > 0) { + is.skip(remainingSize); } - is.skipToPos(start + size, "Skip config parsing"); - return config; + return new EntryConfig(mcc, mnc, language, country, + orientation, touchscreen, density, keyboard, navigation, + inputFlags, screenWidth, screenHeight, sdkVersion, + screenLayout, uiMode, smallestScreenWidthDp, screenWidthDp, + screenHeightDp, localeScript, localeVariant, screenLayout2, + colorMode, isInvalid, size); } - private String parseOrientation(int orientation) { - if (orientation == 1) { - return "port"; - } else if (orientation == 2) { - return "land"; - } else { - return "o" + orientation; - } - } + private char[] unpackLocaleOrRegion(byte in0, byte in1, char base) throws IOException { + // check high bit, if so we have a packed 3 letter code + if (((in0 >> 7) & 1) == 1) { + int first = in1 & 0x1F; + int second = ((in1 & 0xE0) >> 5) + ((in0 & 0x03) << 3); + int third = (in0 & 0x7C) >> 2; - private String parseScreenLayout(int screenLayout) { - switch (screenLayout) { - case 1: - return "small"; - case 2: - return "normal"; - case 3: - return "large"; - case 4: - return "xlarge"; - case 64: - return "ldltr"; - case 128: - return "ldrtl"; - default: - return "sl" + screenLayout; + // since this function handles languages & regions, we add the value(s) to the base char + // which is usually 'a' or '0' depending on language or region. + return new char[] { (char) (first + base), (char) (second + base), (char) (third + base) }; } + return new char[] { (char) in0, (char) in1 }; } - private String parseDensity(int density) { - if (density == 120) { - return "ldpi"; - } else if (density == 160) { - return "mdpi"; - } else if (density == 240) { - return "hdpi"; - } else if (density == 320) { - return "xhdpi"; - } else if (density == 480) { - return "xxhdpi"; - } else if (density == 640) { - return "xxxhdpi"; - } else { - return density + "dpi"; - } - } + private String readScriptOrVariantChar(int length) throws IOException { + StringBuilder string = new StringBuilder(16); - private String parseLocale() throws IOException { - int b1 = is.readInt8(); - int b2 = is.readInt8(); - String str = null; - if (b1 != 0 && b2 != 0) { - if ((b1 & 0x80) == 0) { - str = new String(new char[]{(char) b1, (char) b2}); - } else { - LOG.warn("TODO: parse locale: 0x{}{}", Integer.toHexString(b1), Integer.toHexString(b2)); + while(length-- != 0) { + short ch = (short) is.readInt8(); + if (ch == 0) { + break; } + string.append((char) ch); } - return str; + is.skip(length); + + return string.toString(); } } diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ResXmlGen.java b/jadx-core/src/main/java/jadx/core/xmlgen/ResXmlGen.java index a23768495b8..fec14a11344 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/ResXmlGen.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ResXmlGen.java @@ -202,10 +202,10 @@ private void addSimpleValue(CodeWriter cw, String typeName, String itemTag, Stri private String getFileName(ResourceEntry ri) { StringBuilder sb = new StringBuilder(); - String locale = ri.getConfig().getLocale(); + String qualifiers = ri.getConfig().getQualifiers(); sb.append("res/values"); - if (!locale.isEmpty()) { - sb.append('-').append(locale); + if (!qualifiers.isEmpty()) { + sb.append(qualifiers); } sb.append('/'); sb.append(ri.getTypeName()); diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/entry/EntryConfig.java b/jadx-core/src/main/java/jadx/core/xmlgen/entry/EntryConfig.java index d19b84666dd..bd5ca80fc21 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/entry/EntryConfig.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/entry/EntryConfig.java @@ -1,159 +1,649 @@ +/** + * Copyright (C) 2018 Ryszard Wiśniewski + * Copyright (C) 2018 Connor Tumbleson + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package jadx.core.xmlgen.entry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Original source code can be found here + */ + public class EntryConfig { - private String language; - private String country; - private String density; - private String screenSize; - private String sdkVersion; - private String screenLayout; - private String smallestScreenWidthDp; - private String orientation; - private String screenWidthDp; - private String screenHeightDp; - - public String getLanguage() { - return language; - } - - public void setLanguage(String language) { - this.language = language; - } - - public String getCountry() { - return country; - } - - public void setCountry(String country) { - this.country = country; - } - - public String getLocale() { - StringBuilder sb = new StringBuilder(); - if (screenSize != null) { - if (sb.length() != 0) { - sb.append("-"); - } - sb.append(screenSize); - } else if (screenHeightDp != null) { - if (sb.length() != 0) { - sb.append("-"); - } - sb.append(screenHeightDp); - } else if (screenWidthDp != null) { - if (sb.length() != 0) { - sb.append("-"); - } - sb.append(screenWidthDp); - } else if (screenLayout != null) { - if (sb.length() != 0) { - sb.append("-"); - } - sb.append(screenLayout); - } else if (smallestScreenWidthDp != null) { - if (sb.length() != 0) { - sb.append("-"); - } - sb.append(smallestScreenWidthDp); - } else if (density != null) { - sb.append(density); - } - if (language != null) { - if (sb.length() != 0) { - sb.append("-"); - } - sb.append(language); - } - if (country != null) { - sb.append("-r").append(country); - } - if (orientation != null) { - if (sb.length() != 0) { - sb.append("-"); - } - sb.append(orientation); - } - if (sdkVersion != null) { - if (sb.length() != 0) { - sb.append("-"); - } - sb.append(sdkVersion); - } - return sb.toString(); - } - - public String getDensity() { - return density; - } - - public void setDensity(String density) { - this.density = density; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(getLocale()); - if (sb.length() != 0) { - sb.insert(0, " ["); - sb.append(']'); - } - return sb.toString(); - } - - public void setScreenSize(String screenSize) { - this.screenSize = screenSize; - } - - public String getScreenSize() { - return screenSize; - } - - public void setSdkVersion(String sdkVersion) { - this.sdkVersion = sdkVersion; - } - - public String getSdkVersion() { - return sdkVersion; - } - - public void setScreenLayout(String screenLayout) { - this.screenLayout = screenLayout; - } - - public String getScreenLayout() { - return screenLayout; - } - - public void setSmallestScreenWidthDp(String smallestScreenWidthDp) { - this.smallestScreenWidthDp = smallestScreenWidthDp; - } - - public String getSmallestScreenWidthDp() { - return smallestScreenWidthDp; - } - - public void setOrientation(String orientation) { - this.orientation = orientation; - } - - public String getOrientation() { - return orientation; - } - - public void setScreenWidthDp(String screenWidthDp) { - this.screenWidthDp = screenWidthDp; - } - - public String getScreenWidthDp() { - return screenWidthDp; - } - - public void setScreenHeightDp(String screenHeightDp) { - this.screenHeightDp = screenHeightDp; - } - - public String getScreenHeightDp() { - return screenHeightDp; - } + public final short mcc; + public final short mnc; + + public final char[] language; + public final char[] region; + + public final byte orientation; + public final byte touchscreen; + public final int density; + + public final byte keyboard; + public final byte navigation; + public final byte inputFlags; + + public final short screenWidth; + public final short screenHeight; + + public final short sdkVersion; + + public final byte screenLayout; + public final byte uiMode; + public final short smallestScreenWidthDp; + + public final short screenWidthDp; + public final short screenHeightDp; + + private final char[] localeScript; + private final char[] localeVariant; + + private final byte screenLayout2; + private final byte colorMode; + + public final boolean isInvalid; + + private final String mQualifiers; + + private final int size; + + public EntryConfig() { + mcc = 0; + mnc = 0; + language = new char[]{'\00', '\00'}; + region = new char[]{'\00', '\00'}; + orientation = ORIENTATION_ANY; + touchscreen = TOUCHSCREEN_ANY; + density = DENSITY_DEFAULT; + keyboard = KEYBOARD_ANY; + navigation = NAVIGATION_ANY; + inputFlags = KEYSHIDDEN_ANY | NAVHIDDEN_ANY; + screenWidth = 0; + screenHeight = 0; + sdkVersion = 0; + screenLayout = SCREENLONG_ANY | SCREENSIZE_ANY; + uiMode = UI_MODE_TYPE_ANY | UI_MODE_NIGHT_ANY; + smallestScreenWidthDp = 0; + screenWidthDp = 0; + screenHeightDp = 0; + localeScript = null; + localeVariant = null; + screenLayout2 = 0; + colorMode = COLOR_WIDE_UNDEFINED; + isInvalid = false; + mQualifiers = ""; + size = 0; + } + + public EntryConfig(short mcc, short mnc, char[] language, + char[] region, byte orientation, + byte touchscreen, int density, byte keyboard, byte navigation, + byte inputFlags, short screenWidth, short screenHeight, + short sdkVersion, byte screenLayout, byte uiMode, + short smallestScreenWidthDp, short screenWidthDp, + short screenHeightDp, char[] localeScript, char[] localeVariant, + byte screenLayout2, byte colorMode, boolean isInvalid, int size) { + if (orientation < 0 || orientation > 3) { + LOG.warn("Invalid orientation value: " + orientation); + orientation = 0; + isInvalid = true; + } + if (touchscreen < 0 || touchscreen > 3) { + LOG.warn("Invalid touchscreen value: " + touchscreen); + touchscreen = 0; + isInvalid = true; + } + if (density < -1) { + LOG.warn("Invalid density value: " + density); + density = 0; + isInvalid = true; + } + if (keyboard < 0 || keyboard > 3) { + LOG.warn("Invalid keyboard value: " + keyboard); + keyboard = 0; + isInvalid = true; + } + if (navigation < 0 || navigation > 4) { + LOG.warn("Invalid navigation value: " + navigation); + navigation = 0; + isInvalid = true; + } + + if (localeScript != null && localeScript.length != 0) { + if (localeScript[0] == '\00') { + localeScript = null; + } + } else { + localeScript = null; + } + + if (localeVariant != null && localeVariant.length != 0) { + if (localeVariant[0] == '\00') { + localeVariant = null; + } + } else { + localeVariant = null; + } + + 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.screenLayout = screenLayout; + this.uiMode = uiMode; + this.smallestScreenWidthDp = smallestScreenWidthDp; + this.screenWidthDp = screenWidthDp; + this.screenHeightDp = screenHeightDp; + this.localeScript = localeScript; + this.localeVariant = localeVariant; + this.screenLayout2 = screenLayout2; + this.colorMode = colorMode; + this.isInvalid = isInvalid; + this.size = size; + mQualifiers = generateQualifiers(); + } + + public String getQualifiers() { + return mQualifiers; + } + + private String generateQualifiers() { + StringBuilder ret = new StringBuilder(); + if (mcc != 0) { + ret.append("-mcc").append(String.format("%03d", mcc)); + if (mnc != MNC_ZERO) { + if (mnc != 0) { + ret.append("-mnc"); + if (size <= 32) { + if (mnc > 0 && mnc < 10) { + ret.append(String.format("%02d", mnc)); + } else { + ret.append(String.format("%03d", mnc)); + } + } else { + ret.append(mnc); + } + } + } else { + ret.append("-mnc00"); + } + } else { + if (mnc != 0) { + ret.append("-mnc").append(mnc); + } + } + ret.append(getLocaleString()); + + switch (screenLayout & MASK_LAYOUTDIR) { + case SCREENLAYOUT_LAYOUTDIR_RTL: + ret.append("-ldrtl"); + break; + case SCREENLAYOUT_LAYOUTDIR_LTR: + ret.append("-ldltr"); + break; + } + if (smallestScreenWidthDp != 0) { + ret.append("-sw").append(smallestScreenWidthDp).append("dp"); + } + if (screenWidthDp != 0) { + ret.append("-w").append(screenWidthDp).append("dp"); + } + if (screenHeightDp != 0) { + ret.append("-h").append(screenHeightDp).append("dp"); + } + switch (screenLayout & MASK_SCREENSIZE) { + case SCREENSIZE_SMALL: + ret.append("-small"); + break; + case SCREENSIZE_NORMAL: + ret.append("-normal"); + break; + case SCREENSIZE_LARGE: + ret.append("-large"); + break; + case SCREENSIZE_XLARGE: + ret.append("-xlarge"); + break; + } + switch (screenLayout & MASK_SCREENLONG) { + case SCREENLONG_YES: + ret.append("-long"); + break; + case SCREENLONG_NO: + ret.append("-notlong"); + break; + } + switch (screenLayout2 & MASK_SCREENROUND) { + case SCREENLAYOUT_ROUND_NO: + ret.append("-notround"); + break; + case SCREENLAYOUT_ROUND_YES: + ret.append("-round"); + break; + } + switch (colorMode & COLOR_HDR_MASK) { + case COLOR_HDR_YES: + ret.append("-highdr"); + break; + case COLOR_HDR_NO: + ret.append("-lowdr"); + break; + } + switch (colorMode & COLOR_WIDE_MASK) { + case COLOR_WIDE_YES: + ret.append("-widecg"); + break; + case COLOR_WIDE_NO: + ret.append("-nowidecg"); + break; + } + switch (orientation) { + case ORIENTATION_PORT: + ret.append("-port"); + break; + case ORIENTATION_LAND: + ret.append("-land"); + break; + case ORIENTATION_SQUARE: + ret.append("-square"); + break; + } + switch (uiMode & MASK_UI_MODE_TYPE) { + case UI_MODE_TYPE_CAR: + ret.append("-car"); + break; + case UI_MODE_TYPE_DESK: + ret.append("-desk"); + break; + case UI_MODE_TYPE_TELEVISION: + ret.append("-television"); + break; + case UI_MODE_TYPE_SMALLUI: + ret.append("-smallui"); + break; + case UI_MODE_TYPE_MEDIUMUI: + ret.append("-mediumui"); + break; + case UI_MODE_TYPE_LARGEUI: + ret.append("-largeui"); + break; + case UI_MODE_TYPE_GODZILLAUI: + ret.append("-godzillaui"); + break; + case UI_MODE_TYPE_HUGEUI: + ret.append("-hugeui"); + break; + case UI_MODE_TYPE_APPLIANCE: + ret.append("-appliance"); + break; + case UI_MODE_TYPE_WATCH: + ret.append("-watch"); + break; + case UI_MODE_TYPE_VR_HEADSET: + ret.append("-vrheadset"); + break; + } + switch (uiMode & MASK_UI_MODE_NIGHT) { + case UI_MODE_NIGHT_YES: + ret.append("-night"); + break; + case UI_MODE_NIGHT_NO: + ret.append("-notnight"); + break; + } + switch (density) { + case DENSITY_DEFAULT: + break; + case DENSITY_LOW: + ret.append("-ldpi"); + break; + case DENSITY_MEDIUM: + ret.append("-mdpi"); + break; + case DENSITY_HIGH: + ret.append("-hdpi"); + break; + case DENSITY_TV: + ret.append("-tvdpi"); + break; + case DENSITY_XHIGH: + ret.append("-xhdpi"); + break; + case DENSITY_XXHIGH: + ret.append("-xxhdpi"); + break; + case DENSITY_XXXHIGH: + ret.append("-xxxhdpi"); + break; + case DENSITY_ANY: + ret.append("-anydpi"); + break; + case DENSITY_NONE: + ret.append("-nodpi"); + break; + default: + ret.append('-').append(density).append("dpi"); + } + switch (touchscreen) { + case TOUCHSCREEN_NOTOUCH: + ret.append("-notouch"); + break; + case TOUCHSCREEN_STYLUS: + ret.append("-stylus"); + break; + case TOUCHSCREEN_FINGER: + ret.append("-finger"); + break; + } + switch (inputFlags & MASK_KEYSHIDDEN) { + case KEYSHIDDEN_NO: + ret.append("-keysexposed"); + break; + case KEYSHIDDEN_YES: + ret.append("-keyshidden"); + break; + case KEYSHIDDEN_SOFT: + ret.append("-keyssoft"); + break; + } + switch (keyboard) { + case KEYBOARD_NOKEYS: + ret.append("-nokeys"); + break; + case KEYBOARD_QWERTY: + ret.append("-qwerty"); + break; + case KEYBOARD_12KEY: + ret.append("-12key"); + break; + } + switch (inputFlags & MASK_NAVHIDDEN) { + case NAVHIDDEN_NO: + ret.append("-navexposed"); + break; + case NAVHIDDEN_YES: + ret.append("-navhidden"); + break; + } + switch (navigation) { + case NAVIGATION_NONAV: + ret.append("-nonav"); + break; + case NAVIGATION_DPAD: + ret.append("-dpad"); + break; + case NAVIGATION_TRACKBALL: + ret.append("-trackball"); + break; + case NAVIGATION_WHEEL: + ret.append("-wheel"); + break; + } + if (screenWidth != 0 && screenHeight != 0) { + if (screenWidth > screenHeight) { + ret.append(String.format("-%dx%d", screenWidth, screenHeight)); + } else { + ret.append(String.format("-%dx%d", screenHeight, screenWidth)); + } + } + if (sdkVersion > 0 && sdkVersion >= getNaturalSdkVersionRequirement()) { + ret.append("-v").append(sdkVersion); + } + if (isInvalid) { + ret.append("-ERR").append(sErrCounter++); + } + + return ret.toString(); + } + + private short getNaturalSdkVersionRequirement() { + if ((uiMode & MASK_UI_MODE_TYPE) == UI_MODE_TYPE_VR_HEADSET || (colorMode & COLOR_WIDE_MASK) != 0 || ((colorMode & COLOR_HDR_MASK) != 0)) { + return SDK_OREO; + } + if ((screenLayout2 & MASK_SCREENROUND) != 0) { + return SDK_MNC; + } + if (density == DENSITY_ANY) { + return SDK_LOLLIPOP; + } + if (smallestScreenWidthDp != 0 || screenWidthDp != 0 || screenHeightDp != 0) { + return SDK_HONEYCOMB_MR2; + } + if ((uiMode & (MASK_UI_MODE_TYPE | MASK_UI_MODE_NIGHT)) != UI_MODE_NIGHT_ANY) { + return SDK_FROYO; + } + if ((screenLayout & (MASK_SCREENSIZE | MASK_SCREENLONG)) != SCREENSIZE_ANY || density != DENSITY_DEFAULT) { + return SDK_DONUT; + } + return 0; + } + + private String getLocaleString() { + StringBuilder sb = new StringBuilder(); + + // check for old style non BCP47 tags + // allows values-xx-rXX, values-xx, values-xxx-rXX + // denies values-xxx, anything else + if (localeVariant == null && localeScript == null && (region[0] != '\00' || language[0] != '\00') && + region.length != 3) { + sb.append("-").append(language); + if (region[0] != '\00') { + sb.append("-r").append(region); + } + } else { // BCP47 + if (language[0] == '\00' && region[0] == '\00') { + return sb.toString(); // early return, no language or region + } + sb.append("-b+"); + if (language[0] != '\00') { + sb.append(language); + } + if (localeScript != null && localeScript.length == 4) { + sb.append("+").append(localeScript); + } + if ((region.length == 2 || region.length == 3) && region[0] != '\00') { + sb.append("+").append(region); + } + if (localeVariant != null && localeVariant.length >= 5) { + sb.append("+").append(toUpper(localeVariant)); + } + } + return sb.toString(); + } + + private String toUpper(char[] character) { + StringBuilder sb = new StringBuilder(); + for (char ch : character) { + sb.append(Character.toUpperCase(ch)); + } + return sb.toString(); + } + + + @Override + public String toString() { + return !getQualifiers().equals("") ? getQualifiers() : "[DEFAULT]"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final EntryConfig other = (EntryConfig) obj; + return this.mQualifiers.equals(other.mQualifiers); + } + + @Override + public int hashCode() { + int hash = 17; + hash = 31 * hash + this.mQualifiers.hashCode(); + return hash; + } + + // TODO: Dirty static hack. This counter should be a part of ResPackage, + // but it would be hard right now and this feature is very rarely used. + private static int sErrCounter = 0; + + public final static byte SDK_BASE = 1; + public final static byte SDK_BASE_1_1 = 2; + public final static byte SDK_CUPCAKE = 3; + public final static byte SDK_DONUT = 4; + public final static byte SDK_ECLAIR = 5; + public final static byte SDK_ECLAIR_0_1 = 6; + public final static byte SDK_ECLAIR_MR1 = 7; + public final static byte SDK_FROYO = 8; + public final static byte SDK_GINGERBREAD = 9; + public final static byte SDK_GINGERBREAD_MR1 = 10; + public final static byte SDK_HONEYCOMB = 11; + public final static byte SDK_HONEYCOMB_MR1 = 12; + public final static byte SDK_HONEYCOMB_MR2 = 13; + public final static byte SDK_ICE_CREAM_SANDWICH = 14; + public final static byte SDK_ICE_CREAM_SANDWICH_MR1 = 15; + public final static byte SDK_JELLY_BEAN = 16; + public final static byte SDK_JELLY_BEAN_MR1 = 17; + public final static byte SDK_JELLY_BEAN_MR2 = 18; + public final static byte SDK_KITKAT = 19; + public final static byte SDK_LOLLIPOP = 21; + public final static byte SDK_LOLLIPOP_MR1 = 22; + public final static byte SDK_MNC = 23; + public final static byte SDK_NOUGAT = 24; + public final static byte SDK_NOUGAT_MR1 = 25; + public final static byte SDK_OREO = 26; + public final static byte SDK_OREO_MR1 = 27; + public final static byte SDK_P = 28; + + public final static byte ORIENTATION_ANY = 0; + public final static byte ORIENTATION_PORT = 1; + public final static byte ORIENTATION_LAND = 2; + public final static byte ORIENTATION_SQUARE = 3; + + public final static byte TOUCHSCREEN_ANY = 0; + public final static byte TOUCHSCREEN_NOTOUCH = 1; + public final static byte TOUCHSCREEN_STYLUS = 2; + public final static byte TOUCHSCREEN_FINGER = 3; + + public final static int DENSITY_DEFAULT = 0; + public final static int DENSITY_LOW = 120; + public final static int DENSITY_MEDIUM = 160; + public final static int DENSITY_400 = 190; + public final static int DENSITY_TV = 213; + public final static int DENSITY_HIGH = 240; + public final static int DENSITY_XHIGH = 320; + public final static int DENSITY_XXHIGH = 480; + public final static int DENSITY_XXXHIGH = 640; + public final static int DENSITY_ANY = 0xFFFE; + public final static int DENSITY_NONE = 0xFFFF; + + public final static int MNC_ZERO = -1; + + public final static short MASK_LAYOUTDIR = 0xc0; + public final static short SCREENLAYOUT_LAYOUTDIR_ANY = 0x00; + public final static short SCREENLAYOUT_LAYOUTDIR_LTR = 0x40; + public final static short SCREENLAYOUT_LAYOUTDIR_RTL = 0x80; + public final static short SCREENLAYOUT_LAYOUTDIR_SHIFT = 0x06; + + public final static short MASK_SCREENROUND = 0x03; + public final static short SCREENLAYOUT_ROUND_ANY = 0; + public final static short SCREENLAYOUT_ROUND_NO = 0x1; + public final static short SCREENLAYOUT_ROUND_YES = 0x2; + + public final static byte KEYBOARD_ANY = 0; + public final static byte KEYBOARD_NOKEYS = 1; + public final static byte KEYBOARD_QWERTY = 2; + public final static byte KEYBOARD_12KEY = 3; + + public final static byte NAVIGATION_ANY = 0; + public final static byte NAVIGATION_NONAV = 1; + public final static byte NAVIGATION_DPAD = 2; + public final static byte NAVIGATION_TRACKBALL = 3; + public final static byte NAVIGATION_WHEEL = 4; + + public final static byte MASK_KEYSHIDDEN = 0x3; + public final static byte KEYSHIDDEN_ANY = 0x0; + public final static byte KEYSHIDDEN_NO = 0x1; + public final static byte KEYSHIDDEN_YES = 0x2; + public final static byte KEYSHIDDEN_SOFT = 0x3; + + public final static byte MASK_NAVHIDDEN = 0xc; + public final static byte NAVHIDDEN_ANY = 0x0; + public final static byte NAVHIDDEN_NO = 0x4; + public final static byte NAVHIDDEN_YES = 0x8; + + public final static byte MASK_SCREENSIZE = 0x0f; + public final static byte SCREENSIZE_ANY = 0x00; + public final static byte SCREENSIZE_SMALL = 0x01; + public final static byte SCREENSIZE_NORMAL = 0x02; + public final static byte SCREENSIZE_LARGE = 0x03; + public final static byte SCREENSIZE_XLARGE = 0x04; + + public final static byte MASK_SCREENLONG = 0x30; + public final static byte SCREENLONG_ANY = 0x00; + public final static byte SCREENLONG_NO = 0x10; + public final static byte SCREENLONG_YES = 0x20; + + public final static byte MASK_UI_MODE_TYPE = 0x0f; + public final static byte UI_MODE_TYPE_ANY = 0x00; + public final static byte UI_MODE_TYPE_NORMAL = 0x01; + public final static byte UI_MODE_TYPE_DESK = 0x02; + public final static byte UI_MODE_TYPE_CAR = 0x03; + public final static byte UI_MODE_TYPE_TELEVISION = 0x04; + public final static byte UI_MODE_TYPE_APPLIANCE = 0x05; + public final static byte UI_MODE_TYPE_WATCH = 0x06; + public final static byte UI_MODE_TYPE_VR_HEADSET = 0x07; + + // start - miui + public final static byte UI_MODE_TYPE_GODZILLAUI = 0x0b; + public final static byte UI_MODE_TYPE_SMALLUI = 0x0c; + public final static byte UI_MODE_TYPE_MEDIUMUI = 0x0d; + public final static byte UI_MODE_TYPE_LARGEUI = 0x0e; + public final static byte UI_MODE_TYPE_HUGEUI = 0x0f; + // end - miui + + public final static byte MASK_UI_MODE_NIGHT = 0x30; + public final static byte UI_MODE_NIGHT_ANY = 0x00; + public final static byte UI_MODE_NIGHT_NO = 0x10; + public final static byte UI_MODE_NIGHT_YES = 0x20; + + public final static byte COLOR_HDR_MASK = 0xC; + public final static byte COLOR_HDR_NO = 0x4; + public final static byte COLOR_HDR_SHIFT = 0x2; + public final static byte COLOR_HDR_UNDEFINED = 0x0; + public final static byte COLOR_HDR_YES = 0x8; + + public final static byte COLOR_UNDEFINED = 0x0; + + public final static byte COLOR_WIDE_UNDEFINED = 0x0; + public final static byte COLOR_WIDE_NO = 0x1; + public final static byte COLOR_WIDE_YES = 0x2; + public final static byte COLOR_WIDE_MASK = 0x3; + + private static final Logger LOG = LoggerFactory.getLogger(EntryConfig.class); }