Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial support for BCP47 tags #100

Merged
merged 6 commits into from Feb 25, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -26,9 +26,7 @@ public class ResConfigFlags {
public final short mnc;

public final char[] language;
public final char[] country;

public final short layoutDirection;
public final char[] region;

public final byte orientation;
public final byte touchscreen;
Expand All @@ -50,6 +48,9 @@ public class ResConfigFlags {
public final short screenWidthDp;
public final short screenHeightDp;

private final char[] localeScript;
private final char[] localeVariant;

public final boolean isInvalid;

private final String mQualifiers;
Expand All @@ -58,8 +59,7 @@ public ResConfigFlags() {
mcc = 0;
mnc = 0;
language = new char[] { '\00', '\00' };
country = new char[] { '\00', '\00' };
layoutDirection = SCREENLAYOUT_LAYOUTDIR_ANY;
region = new char[] { '\00', '\00' };
orientation = ORIENTATION_ANY;
touchscreen = TOUCHSCREEN_ANY;
density = DENSITY_DEFAULT;
Expand All @@ -74,17 +74,20 @@ public ResConfigFlags() {
smallestScreenWidthDp = 0;
screenWidthDp = 0;
screenHeightDp = 0;
localeScript = new char[] { '\00', '\00', '\00', '\00' };
localeVariant = new char[] { '\00', '\00', '\00', '\00', '\00', '\00', '\00', '\00' };
isInvalid = false;
mQualifiers = "";
}

public ResConfigFlags(short mcc, short mnc, char[] language,
char[] country, short layoutDirection, byte orientation,
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, boolean isInvalid) {
short screenHeightDp, char[] localeScript, char[] localeVariant,
boolean isInvalid) {
if (orientation < 0 || orientation > 3) {
LOGGER.warning("Invalid orientation value: " + orientation);
orientation = 0;
Expand Down Expand Up @@ -114,8 +117,7 @@ public ResConfigFlags(short mcc, short mnc, char[] language,
this.mcc = mcc;
this.mnc = mnc;
this.language = language;
this.country = country;
this.layoutDirection = layoutDirection;
this.region = region;
this.orientation = orientation;
this.touchscreen = touchscreen;
this.density = density;
Expand All @@ -130,6 +132,8 @@ public ResConfigFlags(short mcc, short mnc, char[] language,
this.smallestScreenWidthDp = smallestScreenWidthDp;
this.screenWidthDp = screenWidthDp;
this.screenHeightDp = screenHeightDp;
this.localeScript = localeScript;
this.localeVariant = localeVariant;
this.isInvalid = isInvalid;
mQualifiers = generateQualifiers();
}
Expand All @@ -155,12 +159,8 @@ private String generateQualifiers() {
ret.append("-mnc00");
}
}
if (language[0] != '\00') {
ret.append('-').append(language);
if (country[0] != '\00') {
ret.append("-r").append(country);
}
}
ret.append(getLocaleString());

switch (screenLayout & MASK_LAYOUTDIR) {
case SCREENLAYOUT_LAYOUTDIR_RTL:
ret.append("-ldrtl");
Expand Down Expand Up @@ -369,6 +369,51 @@ private short getNaturalSdkVersionRequirement() {
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 (language[0] != '\00' && localeScript.length == 0 && localeVariant.length == 0 &&
(region.length != 3 && language.length != 3) ||
(language.length == 3 && region.length == 2 && region[0] != '\00' &&
localeScript.length == 0 && localeVariant.length == 0)) {

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.length == 4) {
sb.append("+").append(localeScript);
}
if ((region.length == 2 || region.length == 3) && region[0] != '\00') {
sb.append("+").append(region);
}
if (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]";
Expand Down
Expand Up @@ -256,8 +256,8 @@ private ResConfigFlags readConfigFlags() throws IOException,
short mcc = mIn.readShort();
short mnc = mIn.readShort();

char[] language = new char[] { (char) mIn.readByte(), (char) mIn.readByte() };
char[] country = new char[] { (char) mIn.readByte(), (char) mIn.readByte() };
char[] language = this.unpackLanguageOrRegion(mIn.readByte(), mIn.readByte(), 'a');
char[] country = this.unpackLanguageOrRegion(mIn.readByte(), mIn.readByte(), '0');

byte orientation = mIn.readByte();
byte touchscreen = mIn.readByte();
Expand Down Expand Up @@ -291,9 +291,11 @@ private ResConfigFlags readConfigFlags() throws IOException,
screenHeightDp = mIn.readShort();
}

short layoutDirection = 0;
if (size >= 38) {
layoutDirection = mIn.readShort();
char[] localeScript = {'\00'};
char[] localeVariant = {'\00'};
if (size >= 48) {
localeScript = this.readScriptOrVariantChar(4).toCharArray();
localeVariant = this.readScriptOrVariantChar(8).toCharArray();
}

int exceedingSize = size - KNOWN_CONFIG_BYTES;
Expand All @@ -313,11 +315,40 @@ private ResConfigFlags readConfigFlags() throws IOException,
}
}

return new ResConfigFlags(mcc, mnc, language, country, layoutDirection,
return new ResConfigFlags(mcc, mnc, language, country,
orientation, touchscreen, density, keyboard, navigation,
inputFlags, screenWidth, screenHeight, sdkVersion,
screenLayout, uiMode, smallestScreenWidthDp, screenWidthDp,
screenHeightDp, isInvalid);
screenHeightDp, localeScript, localeVariant, isInvalid);
}

private char[] unpackLanguageOrRegion(byte in0, byte in1, char base) throws AndrolibException {
// 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;

// 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 readScriptOrVariantChar(int length) throws AndrolibException, IOException {
StringBuilder string = new StringBuilder(16);

while(length-- != 0) {
short ch = mIn.readByte();
if (ch == 0) {
break;
}
string.append((char) ch);
}
mIn.skipBytes(length);

return string.toString();
}

private void addMissingResSpecs() throws AndrolibException {
Expand Down Expand Up @@ -416,7 +447,7 @@ public FlagsOffset(int offset, int count) {
}

private static final Logger LOGGER = Logger.getLogger(ARSCDecoder.class.getName());
private static final int KNOWN_CONFIG_BYTES = 38;
private static final int KNOWN_CONFIG_BYTES = 48;

public static class ARSCData {

Expand Down
Expand Up @@ -182,6 +182,51 @@ public void anyDpiTest() throws BrutException, IOException {
compareValuesFiles("values-watch/strings.xml");
}

@Test
public void packed3CharsTest() throws BrutException, IOException {
compareValuesFiles("values-ast-rES/strings.xml");
}

@Test
public void rightToLeftTest() throws BrutException, IOException {
compareValuesFiles("values-ldrtl/strings.xml");
}

@Test
public void scriptBcp47Test() throws BrutException, IOException {
compareValuesFiles("values-b+en+Latn+US/strings.xml");
}

@Test
public void threeLetterLangBcp47Test() throws BrutException, IOException {
compareValuesFiles("values-b+ast/strings.xml");
}

@Test
public void twoLetterLangBcp47Test() throws BrutException, IOException {
compareValuesFiles("values-en-rUS/strings.xml");
}

@Test
public void variantBcp47Test() throws BrutException, IOException {
compareValuesFiles("values-b+en+US+POSIX/strings.xml");
}

@Test
public void fourpartBcp47Test() throws BrutException, IOException {
compareValuesFiles("values-b+ast+Latn+IT+AREVELA/strings.xml");
}

@Test
public void RegionLocaleBcp47Test() throws BrutException, IOException {
compareValuesFiles("values-b+en+Latn+419/strings.xml");
}

@Test
public void numericalRegionBcp47Test() throws BrutException, IOException {
compareValuesFiles("values-b+eng+419/strings.xml");
}

@Test
public void drawableNoDpiTest() throws BrutException, IOException {
compareResFolder("drawable-nodpi");
Expand Down
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="test1">test1</string>
</resources>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="test1">test1</string>
</resources>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="test1">test1</string>
</resources>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="test1">test1</string>
</resources>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="test1">test1</string>
</resources>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="test1">test1</string>
</resources>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="test1">test1</string>
</resources>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="test1">test1</string>
</resources>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="test1">test1</string>
</resources>