Commit 26d969cf authored by David 'Digit' Turner's avatar David 'Digit' Turner Committed by Commit Bot

android: Fix Chrome startup crash due to system language choice.

Chrome currently crashes on startup on Android if the system locale is
set to something that it doesn't support.

For example, set the system locale to 'Furlan': regular Android
applications will fallback to display English strings as a fallback,
but Chrome crashes on startup immediately.

This CL fixes the issue, by detecting which locale the Android framework
uses to display strings effectively, then using this value to extract
the corresponding .pak file, instead of trying to find one that matches
the current system setting.

+ Move compressed locale file detection to background thread.
  Since this now requires I/O access when accessing the
  resources, this operation can no longer be performed on
  the UI thread.

For more details, see associated bug entry.

BUG=846633
R=agrieve@chromium.org,astevenson@chromium.org,dtrainor@chromium.org,tedchoc@chromium.org

Change-Id: If2db6136367081ad50b2b80c85b4cc0e1c2c276f
Reviewed-on: https://chromium-review.googlesource.com/1088708
Commit-Queue: David Turner <digit@chromium.org>
Reviewed-by: default avataragrieve <agrieve@chromium.org>
Reviewed-by: default avatarTed Choc <tedchoc@chromium.org>
Cr-Commit-Position: refs/heads/master@{#569738}
parent 99ef6d95
......@@ -148,6 +148,16 @@ def WriteJson(obj, path, only_if_changed=False):
def AtomicOutput(path, only_if_changed=True):
"""Helper to prevent half-written outputs.
Args:
path: Path to the final output file, which will be written atomically.
only_if_changed: If True (the default), do not touch the filesystem
if the content has not changed.
Returns:
A python context manager that yelds a NamedTemporaryFile instance
that must be used by clients to write the data to. On exit, the
manager will try to replace the final output file with the
temporary one if necessary. The temporary file is always destroyed
on exit.
Example:
with build_utils.AtomicOutput(output_path) as tmp_file:
subprocess.check_call(['prog', '--output', tmp_file.name])
......
......@@ -3,6 +3,7 @@
# found in the LICENSE file.
import("//build/config/android/rules.gni")
import("//build/config/locales.gni")
import("//testing/test.gni")
assert(is_android)
......@@ -167,10 +168,55 @@ java_strings_grd("ui_strings_grd") {
]
}
# Generate an Android resources target that contains localized strings
# describing the current locale used by the Android framework to display
# UI strings. These are used by org.chromium.ui.base.LocalizationUtils.
#
# Variables:
# ui_locales: List of Chromium locale names to generate resources for.
#
template("generate_ui_locale_resources") {
_generating_target_name = "${target_name}__generate"
_rebased_output_zip_path = rebase_path(target_gen_dir, root_gen_dir)
_output_zip = "${root_out_dir}/resource_zips/${_rebased_output_zip_path}/" +
"${target_name}.zip"
_locales = invoker.ui_locales
_depfile = "$target_gen_dir/$target_name.d"
action(_generating_target_name) {
script = "build/create_ui_locale_resources.py"
depfile = _depfile
outputs = [
_output_zip,
]
args = [
"--locale-list=$_locales",
"--depfile",
rebase_path(_depfile, root_build_dir),
"--output-zip",
rebase_path(_output_zip, root_build_dir),
]
}
android_generated_resources(target_name) {
generating_target_name = ":$_generating_target_name"
generated_resources_zip = _output_zip
}
}
generate_ui_locale_resources("ui_locale_string_resources") {
# NOTE: It is not possible to pass just |locales| here, otherwise
# the lint step will later complain that 3 strings in android_ui_strings.grd
# are not localized to the omitted locales!
ui_locales = locales - android_chrome_omitted_locales
}
android_resources("ui_java_resources") {
custom_package = "org.chromium.ui"
resource_dirs = [ "java/res" ]
deps = [
":ui_locale_string_resources",
":ui_strings_grd",
]
}
......
#!/usr/bin/env python
#
# Copyright 2018 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Generate a zip archive containing localized locale name Android resource
strings!
This script takes a list of input Chrome-specific locale names, as well as an
output zip file path.
Each output file will contain the definition of a single string resource,
named 'current_locale', whose value will be the matching Chromium locale name.
E.g. values-en-rUS/strings.xml will define 'current_locale' as 'en-US'.
"""
import argparse
import os
import sys
import zipfile
sys.path.insert(0, os.path.join(
os.path.dirname(__file__), '..', '..', '..', 'build', 'android', 'gyp'))
from util import build_utils
from util import resource_utils
# A small string template for the content of each strings.xml file.
# NOTE: The name is chosen to avoid any conflicts with other string defined
# by other resource archives.
_TEMPLATE = """\
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="current_detected_ui_locale_name">{resource_text}</string>
</resources>
"""
# The default Chrome locale value.
_DEFAULT_CHROME_LOCALE = 'en-US'
def _GenerateLocaleStringsXml(locale):
return _TEMPLATE.format(resource_text=locale)
def _AddLocaleResourceFileToZip(out_zip, android_locale, locale):
locale_data = _GenerateLocaleStringsXml(locale)
if android_locale:
zip_path = 'values-%s/strings.xml' % android_locale
else:
zip_path = 'values/strings.xml'
build_utils.AddToZipHermetic(out_zip, zip_path, data=locale_data,
compress=False)
def main():
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
build_utils.AddDepfileOption(parser)
parser.add_argument('--locale-list', required=True,
help='GN-list of Chrome-specific locale names.')
parser.add_argument('--output-zip', required=True,
help='Output zip archive path.')
args = parser.parse_args()
locale_list = build_utils.ParseGnList(args.locale_list)
if not locale_list:
raise Exception('Locale list cannot be empty!')
with build_utils.AtomicOutput(args.output_zip) as tmp_file:
with zipfile.ZipFile(tmp_file, 'w') as out_zip:
# First, write the default value, since aapt requires one.
_AddLocaleResourceFileToZip(out_zip, '', _DEFAULT_CHROME_LOCALE)
for locale in locale_list:
android_locale = \
resource_utils.CHROME_TO_ANDROID_LOCALE_MAP.get(locale, locale)
_AddLocaleResourceFileToZip(out_zip, android_locale, locale)
if args.depfile:
build_utils.WriteDepfile(args.depfile, args.output_zip)
if __name__ == '__main__':
main()
......@@ -81,5 +81,35 @@ public class LocalizationUtils {
return str.replace("$LOCALE", LocaleUtils.getDefaultLocaleString().replace('-', '_'));
}
/**
* @return the current Chromium locale used to display UI elements.
*
* This matches what the Android framework resolves localized string resources to, using the
* system locale and the application's resources. For example, if the system uses a locale
* that is not supported by Chromium resources (e.g. 'fur-rIT'), Android will likely fallback
* to 'en-rUS' strings when Resources.getString() is called, and this method will return the
* matching Chromium name (i.e. 'en-US').
*
* Using this value is only necessary to ensure that the strings accessed from the locale .pak
* files from C++ match the resources displayed by the Java-based UI views.
*/
public static String getUiLocaleStringForCompressedPak() {
String uiLocale = ContextUtils.getApplicationContext().getResources().getString(
org.chromium.ui.R.string.current_detected_ui_locale_name);
return uiLocale;
}
/**
* @return the language of the current Chromium locale used to display UI elements.
*/
public static String getUiLanguageStringForCompressedPak() {
String uiLocale = getUiLocaleStringForCompressedPak();
int pos = uiLocale.indexOf('-');
if (pos > 0) {
return uiLocale.substring(0, pos);
}
return uiLocale;
}
private static native int nativeGetFirstStrongCharacterDirection(String string);
}
......@@ -18,6 +18,7 @@ import org.chromium.base.Log;
import org.chromium.base.PathUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.TraceEvent;
import org.chromium.ui.base.LocalizationUtils;
import java.io.File;
import java.io.IOException;
......@@ -45,6 +46,8 @@ public class ResourceExtractor {
private void doInBackgroundImpl() {
final File outputDir = getOutputDir();
String[] assetsToExtract = detectFilesToExtract();
// Use a suffix for extracted files in order to guarantee that the version of the file
// on disk matches up with the version of the APK.
String extractSuffix = BuildInfo.getInstance().extractedFileSuffix;
......@@ -52,7 +55,7 @@ public class ResourceExtractor {
boolean allFilesExist = existingFileNames != null;
if (allFilesExist) {
List<String> existingFiles = Arrays.asList(existingFileNames);
for (String assetName : mAssetsToExtract) {
for (String assetName : assetsToExtract) {
allFilesExist &= existingFiles.contains(assetName + extractSuffix);
}
}
......@@ -71,7 +74,7 @@ public class ResourceExtractor {
AssetManager assetManager = ContextUtils.getApplicationAssets();
byte[] buffer = new byte[BUFFER_SIZE];
for (String assetPath : mAssetsToExtract) {
for (String assetPath : assetsToExtract) {
String assetName = assetPath.substring(assetPath.lastIndexOf('/') + 1);
File output = new File(outputDir, assetName + extractSuffix);
TraceEvent.begin("ExtractResource");
......@@ -116,7 +119,6 @@ public class ResourceExtractor {
}
private ExtractTask mExtractTask;
private final String[] mAssetsToExtract = detectFilesToExtract();
private static ResourceExtractor sInstance;
......@@ -129,12 +131,21 @@ public class ResourceExtractor {
private static String[] detectFilesToExtract() {
Locale defaultLocale = Locale.getDefault();
String language = LocaleUtils.getUpdatedLanguageForChromium(defaultLocale.getLanguage());
String androidLanguage = defaultLocale.getLanguage();
String chromiumLanguage = LocaleUtils.getUpdatedLanguageForChromium(androidLanguage);
// NOTE: The UI language will differ from the application's language
// when the system locale is not directly supported by Chrome's
// resources.
String uiLanguage = LocalizationUtils.getUiLanguageStringForCompressedPak();
Log.i(TAG, "Using UI locale %s, system locale: %s (Android name: %s)", uiLanguage,
chromiumLanguage, androidLanguage);
// Currenty (Apr 2018), this array can be as big as 6 entries, so using a capacity
// that allows a bit of growth, but is still in the right ballpark..
ArrayList<String> activeLocales = new ArrayList<String>(6);
for (String locale : BuildConfig.COMPRESSED_LOCALES) {
if (locale.startsWith(language)) {
if (locale.startsWith(uiLanguage)) {
activeLocales.add(locale);
}
}
......@@ -146,7 +157,7 @@ public class ResourceExtractor {
for (int n = 0; n < activeLocales.size(); ++n) {
localePakFiles[n] = COMPRESSED_LOCALES_DIR + '/' + activeLocales.get(n) + ".pak";
}
Log.i(TAG, "Android Locale: %s requires .pak files: %s", defaultLocale,
Log.i(TAG, "UI Language: %s requires .pak files: %s", uiLanguage,
Arrays.toString(activeLocales.toArray()));
return localePakFiles;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment