Merge pull request #100 from iBotPeaches/bcp47-support

[WIP] Initial support for BCP47 tags
This commit is contained in:
Connor Tumbleson 2015-02-25 16:46:22 -06:00
commit 4638c06de4
12 changed files with 180 additions and 23 deletions

View File

@ -26,9 +26,7 @@ public class ResConfigFlags {
public final short mnc; public final short mnc;
public final char[] language; public final char[] language;
public final char[] country; public final char[] region;
public final short layoutDirection;
public final byte orientation; public final byte orientation;
public final byte touchscreen; public final byte touchscreen;
@ -50,6 +48,9 @@ public class ResConfigFlags {
public final short screenWidthDp; public final short screenWidthDp;
public final short screenHeightDp; public final short screenHeightDp;
private final char[] localeScript;
private final char[] localeVariant;
public final boolean isInvalid; public final boolean isInvalid;
private final String mQualifiers; private final String mQualifiers;
@ -58,8 +59,7 @@ public class ResConfigFlags {
mcc = 0; mcc = 0;
mnc = 0; mnc = 0;
language = new char[] { '\00', '\00' }; language = new char[] { '\00', '\00' };
country = new char[] { '\00', '\00' }; region = new char[] { '\00', '\00' };
layoutDirection = SCREENLAYOUT_LAYOUTDIR_ANY;
orientation = ORIENTATION_ANY; orientation = ORIENTATION_ANY;
touchscreen = TOUCHSCREEN_ANY; touchscreen = TOUCHSCREEN_ANY;
density = DENSITY_DEFAULT; density = DENSITY_DEFAULT;
@ -74,17 +74,20 @@ public class ResConfigFlags {
smallestScreenWidthDp = 0; smallestScreenWidthDp = 0;
screenWidthDp = 0; screenWidthDp = 0;
screenHeightDp = 0; screenHeightDp = 0;
localeScript = new char[] { '\00', '\00', '\00', '\00' };
localeVariant = new char[] { '\00', '\00', '\00', '\00', '\00', '\00', '\00', '\00' };
isInvalid = false; isInvalid = false;
mQualifiers = ""; mQualifiers = "";
} }
public ResConfigFlags(short mcc, short mnc, char[] language, 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 touchscreen, int density, byte keyboard, byte navigation,
byte inputFlags, short screenWidth, short screenHeight, byte inputFlags, short screenWidth, short screenHeight,
short sdkVersion, byte screenLayout, byte uiMode, short sdkVersion, byte screenLayout, byte uiMode,
short smallestScreenWidthDp, short screenWidthDp, short smallestScreenWidthDp, short screenWidthDp,
short screenHeightDp, boolean isInvalid) { short screenHeightDp, char[] localeScript, char[] localeVariant,
boolean isInvalid) {
if (orientation < 0 || orientation > 3) { if (orientation < 0 || orientation > 3) {
LOGGER.warning("Invalid orientation value: " + orientation); LOGGER.warning("Invalid orientation value: " + orientation);
orientation = 0; orientation = 0;
@ -114,8 +117,7 @@ public class ResConfigFlags {
this.mcc = mcc; this.mcc = mcc;
this.mnc = mnc; this.mnc = mnc;
this.language = language; this.language = language;
this.country = country; this.region = region;
this.layoutDirection = layoutDirection;
this.orientation = orientation; this.orientation = orientation;
this.touchscreen = touchscreen; this.touchscreen = touchscreen;
this.density = density; this.density = density;
@ -130,6 +132,8 @@ public class ResConfigFlags {
this.smallestScreenWidthDp = smallestScreenWidthDp; this.smallestScreenWidthDp = smallestScreenWidthDp;
this.screenWidthDp = screenWidthDp; this.screenWidthDp = screenWidthDp;
this.screenHeightDp = screenHeightDp; this.screenHeightDp = screenHeightDp;
this.localeScript = localeScript;
this.localeVariant = localeVariant;
this.isInvalid = isInvalid; this.isInvalid = isInvalid;
mQualifiers = generateQualifiers(); mQualifiers = generateQualifiers();
} }
@ -155,12 +159,8 @@ public class ResConfigFlags {
ret.append("-mnc00"); ret.append("-mnc00");
} }
} }
if (language[0] != '\00') { ret.append(getLocaleString());
ret.append('-').append(language);
if (country[0] != '\00') {
ret.append("-r").append(country);
}
}
switch (screenLayout & MASK_LAYOUTDIR) { switch (screenLayout & MASK_LAYOUTDIR) {
case SCREENLAYOUT_LAYOUTDIR_RTL: case SCREENLAYOUT_LAYOUTDIR_RTL:
ret.append("-ldrtl"); ret.append("-ldrtl");
@ -369,6 +369,51 @@ public class ResConfigFlags {
return 0; 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 @Override
public String toString() { public String toString() {
return !getQualifiers().equals("") ? getQualifiers() : "[DEFAULT]"; return !getQualifiers().equals("") ? getQualifiers() : "[DEFAULT]";

View File

@ -256,8 +256,8 @@ public class ARSCDecoder {
short mcc = mIn.readShort(); short mcc = mIn.readShort();
short mnc = mIn.readShort(); short mnc = mIn.readShort();
char[] language = new char[] { (char) mIn.readByte(), (char) mIn.readByte() }; char[] language = this.unpackLanguageOrRegion(mIn.readByte(), mIn.readByte(), 'a');
char[] country = new char[] { (char) mIn.readByte(), (char) mIn.readByte() }; char[] country = this.unpackLanguageOrRegion(mIn.readByte(), mIn.readByte(), '0');
byte orientation = mIn.readByte(); byte orientation = mIn.readByte();
byte touchscreen = mIn.readByte(); byte touchscreen = mIn.readByte();
@ -291,9 +291,11 @@ public class ARSCDecoder {
screenHeightDp = mIn.readShort(); screenHeightDp = mIn.readShort();
} }
short layoutDirection = 0; char[] localeScript = {'\00'};
if (size >= 38) { char[] localeVariant = {'\00'};
layoutDirection = mIn.readShort(); if (size >= 48) {
localeScript = this.readScriptOrVariantChar(4).toCharArray();
localeVariant = this.readScriptOrVariantChar(8).toCharArray();
} }
int exceedingSize = size - KNOWN_CONFIG_BYTES; int exceedingSize = size - KNOWN_CONFIG_BYTES;
@ -313,11 +315,40 @@ public class ARSCDecoder {
} }
} }
return new ResConfigFlags(mcc, mnc, language, country, layoutDirection, return new ResConfigFlags(mcc, mnc, language, country,
orientation, touchscreen, density, keyboard, navigation, orientation, touchscreen, density, keyboard, navigation,
inputFlags, screenWidth, screenHeight, sdkVersion, inputFlags, screenWidth, screenHeight, sdkVersion,
screenLayout, uiMode, smallestScreenWidthDp, screenWidthDp, 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 { private void addMissingResSpecs() throws AndrolibException {
@ -416,7 +447,7 @@ public class ARSCDecoder {
} }
private static final Logger LOGGER = Logger.getLogger(ARSCDecoder.class.getName()); 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 { public static class ARSCData {

View File

@ -182,6 +182,51 @@ public class BuildAndDecodeTest {
compareValuesFiles("values-watch/strings.xml"); 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 @Test
public void drawableNoDpiTest() throws BrutException, IOException { public void drawableNoDpiTest() throws BrutException, IOException {
compareResFolder("drawable-nodpi"); compareResFolder("drawable-nodpi");

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="test1">test1</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="test1">test1</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="test1">test1</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="test1">test1</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="test1">test1</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="test1">test1</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="test1">test1</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="test1">test1</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="test1">test1</string>
</resources>