299e488ee2
On certain devices running Android M with unknown vendor modifications. Setting the localeScriptWasComputed bit in the resource parameters struct causes the translations for many languages to fail to load. The app then defaults to English. This has been reported on the following devices: Motorola XT1086, XT1096, and XT1586, HTC One, and Huawei MediaPad 10 LINK. This change works around the issue by never setting localeScriptWasComputed in aapt and instead always writing the default value of false. Bug: 27872476 Change-Id: Id638d1d0e5f9dcb1c02e65d67667d76174c69b9e
279 lines
8.0 KiB
C++
279 lines
8.0 KiB
C++
/*
|
|
* Copyright (C) 2015 The Android Open Source Project
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include "Locale.h"
|
|
#include "util/Util.h"
|
|
|
|
#include <algorithm>
|
|
#include <ctype.h>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace aapt {
|
|
|
|
using android::ResTable_config;
|
|
|
|
void LocaleValue::setLanguage(const char* languageChars) {
|
|
size_t i = 0;
|
|
while ((*languageChars) != '\0') {
|
|
language[i++] = ::tolower(*languageChars);
|
|
languageChars++;
|
|
}
|
|
}
|
|
|
|
void LocaleValue::setRegion(const char* regionChars) {
|
|
size_t i = 0;
|
|
while ((*regionChars) != '\0') {
|
|
region[i++] = ::toupper(*regionChars);
|
|
regionChars++;
|
|
}
|
|
}
|
|
|
|
void LocaleValue::setScript(const char* scriptChars) {
|
|
size_t i = 0;
|
|
while ((*scriptChars) != '\0') {
|
|
if (i == 0) {
|
|
script[i++] = ::toupper(*scriptChars);
|
|
} else {
|
|
script[i++] = ::tolower(*scriptChars);
|
|
}
|
|
scriptChars++;
|
|
}
|
|
}
|
|
|
|
void LocaleValue::setVariant(const char* variantChars) {
|
|
size_t i = 0;
|
|
while ((*variantChars) != '\0') {
|
|
variant[i++] = *variantChars;
|
|
variantChars++;
|
|
}
|
|
}
|
|
|
|
static inline bool isAlpha(const std::string& str) {
|
|
return std::all_of(std::begin(str), std::end(str), ::isalpha);
|
|
}
|
|
|
|
static inline bool isNumber(const std::string& str) {
|
|
return std::all_of(std::begin(str), std::end(str), ::isdigit);
|
|
}
|
|
|
|
bool LocaleValue::initFromFilterString(const StringPiece& str) {
|
|
// A locale (as specified in the filter) is an underscore separated name such
|
|
// as "en_US", "en_Latn_US", or "en_US_POSIX".
|
|
std::vector<std::string> parts = util::splitAndLowercase(str, '_');
|
|
|
|
const int numTags = parts.size();
|
|
bool valid = false;
|
|
if (numTags >= 1) {
|
|
const std::string& lang = parts[0];
|
|
if (isAlpha(lang) && (lang.length() == 2 || lang.length() == 3)) {
|
|
setLanguage(lang.c_str());
|
|
valid = true;
|
|
}
|
|
}
|
|
|
|
if (!valid || numTags == 1) {
|
|
return valid;
|
|
}
|
|
|
|
// At this point, valid == true && numTags > 1.
|
|
const std::string& part2 = parts[1];
|
|
if ((part2.length() == 2 && isAlpha(part2)) ||
|
|
(part2.length() == 3 && isNumber(part2))) {
|
|
setRegion(part2.c_str());
|
|
} else if (part2.length() == 4 && isAlpha(part2)) {
|
|
setScript(part2.c_str());
|
|
} else if (part2.length() >= 4 && part2.length() <= 8) {
|
|
setVariant(part2.c_str());
|
|
} else {
|
|
valid = false;
|
|
}
|
|
|
|
if (!valid || numTags == 2) {
|
|
return valid;
|
|
}
|
|
|
|
// At this point, valid == true && numTags > 1.
|
|
const std::string& part3 = parts[2];
|
|
if (((part3.length() == 2 && isAlpha(part3)) ||
|
|
(part3.length() == 3 && isNumber(part3))) && script[0]) {
|
|
setRegion(part3.c_str());
|
|
} else if (part3.length() >= 4 && part3.length() <= 8) {
|
|
setVariant(part3.c_str());
|
|
} else {
|
|
valid = false;
|
|
}
|
|
|
|
if (!valid || numTags == 3) {
|
|
return valid;
|
|
}
|
|
|
|
const std::string& part4 = parts[3];
|
|
if (part4.length() >= 4 && part4.length() <= 8) {
|
|
setVariant(part4.c_str());
|
|
} else {
|
|
valid = false;
|
|
}
|
|
|
|
if (!valid || numTags > 4) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ssize_t LocaleValue::initFromParts(std::vector<std::string>::iterator iter,
|
|
std::vector<std::string>::iterator end) {
|
|
const std::vector<std::string>::iterator startIter = iter;
|
|
|
|
std::string& part = *iter;
|
|
if (part[0] == 'b' && part[1] == '+') {
|
|
// This is a "modified" BCP 47 language tag. Same semantics as BCP 47 tags,
|
|
// except that the separator is "+" and not "-".
|
|
std::vector<std::string> subtags = util::splitAndLowercase(part, '+');
|
|
subtags.erase(subtags.begin());
|
|
if (subtags.size() == 1) {
|
|
setLanguage(subtags[0].c_str());
|
|
} else if (subtags.size() == 2) {
|
|
setLanguage(subtags[0].c_str());
|
|
|
|
// The second tag can either be a region, a variant or a script.
|
|
switch (subtags[1].size()) {
|
|
case 2:
|
|
case 3:
|
|
setRegion(subtags[1].c_str());
|
|
break;
|
|
case 4:
|
|
if ('0' <= subtags[1][0] && subtags[1][0] <= '9') {
|
|
// This is a variant: fall through
|
|
} else {
|
|
setScript(subtags[1].c_str());
|
|
break;
|
|
}
|
|
case 5:
|
|
case 6:
|
|
case 7:
|
|
case 8:
|
|
setVariant(subtags[1].c_str());
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
} else if (subtags.size() == 3) {
|
|
// The language is always the first subtag.
|
|
setLanguage(subtags[0].c_str());
|
|
|
|
// The second subtag can either be a script or a region code.
|
|
// If its size is 4, it's a script code, else it's a region code.
|
|
if (subtags[1].size() == 4) {
|
|
setScript(subtags[1].c_str());
|
|
} else if (subtags[1].size() == 2 || subtags[1].size() == 3) {
|
|
setRegion(subtags[1].c_str());
|
|
} else {
|
|
return -1;
|
|
}
|
|
|
|
// The third tag can either be a region code (if the second tag was
|
|
// a script), else a variant code.
|
|
if (subtags[2].size() >= 4) {
|
|
setVariant(subtags[2].c_str());
|
|
} else {
|
|
setRegion(subtags[2].c_str());
|
|
}
|
|
} else if (subtags.size() == 4) {
|
|
setLanguage(subtags[0].c_str());
|
|
setScript(subtags[1].c_str());
|
|
setRegion(subtags[2].c_str());
|
|
setVariant(subtags[3].c_str());
|
|
} else {
|
|
return -1;
|
|
}
|
|
|
|
++iter;
|
|
|
|
} else {
|
|
if ((part.length() == 2 || part.length() == 3)
|
|
&& isAlpha(part) && part != "car") {
|
|
setLanguage(part.c_str());
|
|
++iter;
|
|
|
|
if (iter != end) {
|
|
const std::string& regionPart = *iter;
|
|
if (regionPart.c_str()[0] == 'r' && regionPart.length() == 3) {
|
|
setRegion(regionPart.c_str() + 1);
|
|
++iter;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return static_cast<ssize_t>(iter - startIter);
|
|
}
|
|
|
|
|
|
std::string LocaleValue::toDirName() const {
|
|
std::string dirName;
|
|
if (language[0]) {
|
|
dirName += language;
|
|
} else {
|
|
return dirName;
|
|
}
|
|
|
|
if (script[0]) {
|
|
dirName += "-s";
|
|
dirName += script;
|
|
}
|
|
|
|
if (region[0]) {
|
|
dirName += "-r";
|
|
dirName += region;
|
|
}
|
|
|
|
if (variant[0]) {
|
|
dirName += "-v";
|
|
dirName += variant;
|
|
}
|
|
|
|
return dirName;
|
|
}
|
|
|
|
void LocaleValue::initFromResTable(const ResTable_config& config) {
|
|
config.unpackLanguage(language);
|
|
config.unpackRegion(region);
|
|
if (config.localeScript[0] && !config.localeScriptWasComputed) {
|
|
memcpy(script, config.localeScript, sizeof(config.localeScript));
|
|
}
|
|
|
|
if (config.localeVariant[0]) {
|
|
memcpy(variant, config.localeVariant, sizeof(config.localeVariant));
|
|
}
|
|
}
|
|
|
|
void LocaleValue::writeTo(ResTable_config* out) const {
|
|
out->packLanguage(language);
|
|
out->packRegion(region);
|
|
|
|
if (script[0]) {
|
|
memcpy(out->localeScript, script, sizeof(out->localeScript));
|
|
}
|
|
|
|
if (variant[0]) {
|
|
memcpy(out->localeVariant, variant, sizeof(out->localeVariant));
|
|
}
|
|
}
|
|
|
|
} // namespace aapt
|