Commit 2b8f008e authored by Trevor Perrier's avatar Trevor Perrier Committed by Commit Bot

[Android] Add utilities for Clank language decouple

This second of four CLs to enable Clank to be in a seperate
locale from the system.

This CL adds the Android preference key APPLICATION_OVERRIDE_LANGUAGE
that will be used to store the override language for Clank.

It also adds utility functions and tests for managing the language
preference and modifying Configuration locales.

Bug: 1068667
Change-Id: Ia8aa6d55c6373696b8af12be43c8741d29bf38d1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2355489
Commit-Queue: Trevor  Perrier <perrier@chromium.org>
Reviewed-by: default avatarBrandon Wylie <wylieb@chromium.org>
Reviewed-by: default avatarAndrew Grieve <agrieve@chromium.org>
Cr-Commit-Position: refs/heads/master@{#802894}
parent d13bcfa6
...@@ -5,13 +5,17 @@ ...@@ -5,13 +5,17 @@
package org.chromium.base; package org.chromium.base;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Build; import android.os.Build;
import android.os.LocaleList; import android.os.LocaleList;
import android.text.TextUtils; import android.text.TextUtils;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import org.chromium.base.annotations.CalledByNative; import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.VerifiesOnN;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Locale; import java.util.Locale;
...@@ -220,4 +224,88 @@ public class LocaleUtils { ...@@ -220,4 +224,88 @@ public class LocaleUtils {
: Locale.getDefault().getCountry(); : Locale.getDefault().getCountry();
} }
/**
* Return the language tag of the first language in Configuration.
* @param config Configuration to get language for.
* @return The BCP 47 tag representation of the configuration's first locale.
* Configuration.locale is deprecated on N+. However, read only is equivalent to
* Configuration.getLocales()[0]. Change when minSdkVersion >= 24.
*/
@SuppressWarnings("deprecation")
public static String getConfigurationLanguage(Configuration config) {
Locale locale = config.locale;
return (locale != null) ? locale.toLanguageTag() : "";
}
/**
* Return the language tag of the first language in the configuration
* @param context Context to get language for.
* @return The BCP 47 tag representation of the context's first locale.
*/
public static String getContextLanguage(Context context) {
return getConfigurationLanguage(context.getResources().getConfiguration());
}
/**
* Prepend languageTag to the default locales on config.
* @param base The Context to use for the base configuration.
* @param config The Configuration to update.
* @param languageTag The language to prepend to default locales.
*/
public static void updateConfig(Context base, Configuration config, String languageTag) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
ApisN.setConfigLocales(base, config, languageTag);
} else {
config.setLocale(Locale.forLanguageTag(languageTag));
}
}
/**
* Updates the default Locale/LocaleList to those of config.
* @param config
*/
public static void setDefaultLocalesFromConfiguration(Configuration config) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
ApisN.setLocaleList(config);
} else {
Locale.setDefault(config.locale);
}
}
/**
* Helper class for N only code that is not validated on pre-N devices.
*/
@RequiresApi(Build.VERSION_CODES.N)
@VerifiesOnN
@VisibleForTesting
static class ApisN {
static void setConfigLocales(Context base, Configuration config, String language) {
LocaleList updatedLocales = prependToLocaleList(
language, base.getResources().getConfiguration().getLocales());
config.setLocales(updatedLocales);
}
static void setLocaleList(Configuration config) {
LocaleList.setDefault(config.getLocales());
}
/**
* Create a new LocaleList with languageTag added to the front.
* If languageTag is already in the list the existing tag is moved to the front.
* @param languageTag String of language tag to prepend
* @param localeList LocaleList to prepend to.
* @return LocaleList
*/
static LocaleList prependToLocaleList(String languageTag, LocaleList localeList) {
String languageList = localeList.toLanguageTags();
// Remove the first instance of languageTag with associated comma if present.
// Pattern example: "(^|,)en-US$|en-US,"
String pattern = String.format("(^|,)%1$s$|%1$s,", languageTag);
languageList = languageList.replaceFirst(pattern, "");
return LocaleList.forLanguageTags(
String.format("%1$s,%2$s", languageTag, languageList));
}
}
} }
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
package org.chromium.base; package org.chromium.base;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.res.Configuration;
import android.os.Build; import android.os.Build;
import android.os.LocaleList; import android.os.LocaleList;
...@@ -272,4 +273,107 @@ public class LocaleUtilsTest { ...@@ -272,4 +273,107 @@ public class LocaleUtilsTest {
Assert.assertEquals("", LocaleUtils.toLanguage("")); Assert.assertEquals("", LocaleUtils.toLanguage(""));
Assert.assertEquals("fil", LocaleUtils.toLanguage("fil")); Assert.assertEquals("fil", LocaleUtils.toLanguage("fil"));
} }
// Test for getConfigurationLocale < N
@Test
@SmallTest
public void testGetConfigurationLocale() {
Configuration config = new Configuration();
Assert.assertEquals("", LocaleUtils.getConfigurationLanguage(config));
config.setLocale(Locale.forLanguageTag("hi-IN"));
Assert.assertEquals("hi-IN", LocaleUtils.getConfigurationLanguage(config));
config.setLocale(new Locale("ar"));
Assert.assertEquals("ar", LocaleUtils.getConfigurationLanguage(config));
}
// Test for getConfigurationLocale N+ (with LocaleList)
@Test
@SmallTest
@MinAndroidSdkLevel(Build.VERSION_CODES.N)
public void testGetConfigurationN() {
Configuration config = new Configuration();
Locale locale1 = new Locale("hi", "IN");
Locale locale2 = new Locale("tl", "PH");
LocaleList localeList = new LocaleList(locale1, locale2);
config.setLocales(localeList);
Assert.assertEquals("hi-IN", LocaleUtils.getConfigurationLanguage(config));
locale1 = new Locale("ceb");
locale2 = new Locale("tl", "PH");
localeList = new LocaleList(locale1, locale2);
config.setLocales(localeList);
Assert.assertEquals("ceb", LocaleUtils.getConfigurationLanguage(config));
}
// Test for setDefaultLocalesFromConfiguration
@Test
@SmallTest
public void testSetDefaultLocalesFromConfiguration() {
Configuration config = new Configuration();
config.setLocale(new Locale("tl", "PH"));
LocaleUtils.setDefaultLocalesFromConfiguration(config);
Assert.assertEquals("tl-PH", Locale.getDefault().toLanguageTag());
config.setLocale(new Locale("es", "AR"));
LocaleUtils.setDefaultLocalesFromConfiguration(config);
Assert.assertEquals("es-AR", Locale.getDefault().toLanguageTag());
}
// Test for setDefaultLocalesFromConfiguration N+ (with LocaleList)
@Test
@SmallTest
@MinAndroidSdkLevel(Build.VERSION_CODES.N)
public void testSetDefaultLocalesFromConfigurationN() {
Configuration config = new Configuration();
String tags = "tl-PH,es-AR,en";
config.setLocales(LocaleList.forLanguageTags(tags));
LocaleUtils.setDefaultLocalesFromConfiguration(config);
Assert.assertEquals("tl-PH", Locale.getDefault().toLanguageTag());
Assert.assertEquals(tags, LocaleList.getDefault().toLanguageTags());
tags = "en,en-US,en-GB";
config.setLocales(LocaleList.forLanguageTags(tags));
LocaleUtils.setDefaultLocalesFromConfiguration(config);
Assert.assertEquals("en", Locale.getDefault().toLanguageTag());
Assert.assertEquals(tags, LocaleList.getDefault().toLanguageTags());
}
// Test for prependToLocaleList
@Test
@SmallTest
@MinAndroidSdkLevel(Build.VERSION_CODES.N)
public void testPrependToLocaleList() {
// Prepend to empty list
LocaleList resultList = LocaleUtils.ApisN.prependToLocaleList("ceb-PH", new LocaleList());
Assert.assertEquals("ceb-PH", resultList.toLanguageTags());
// Prepend and not in list
LocaleList baseList = LocaleList.forLanguageTags("en,es-ES,fr");
resultList = LocaleUtils.ApisN.prependToLocaleList("zu", baseList);
Assert.assertEquals("zu,en,es-ES,fr", resultList.toLanguageTags());
// Prepend and in middle of list
resultList = LocaleUtils.ApisN.prependToLocaleList("es-ES", baseList);
Assert.assertEquals("es-ES,en,fr", resultList.toLanguageTags());
// Prepend and at end of list
resultList = LocaleUtils.ApisN.prependToLocaleList("fr", baseList);
Assert.assertEquals("fr,en,es-ES", resultList.toLanguageTags());
// Prepend and at front of list
resultList = LocaleUtils.ApisN.prependToLocaleList("en", baseList);
Assert.assertEquals("en,es-ES,fr", resultList.toLanguageTags());
// Prepend to list of one
baseList = LocaleList.forLanguageTags("fr");
resultList = LocaleUtils.ApisN.prependToLocaleList("en", baseList);
Assert.assertEquals("en,fr", resultList.toLanguageTags());
// Prepend to list of one (self)
resultList = LocaleUtils.ApisN.prependToLocaleList("fr", baseList);
Assert.assertEquals("fr", resultList.toLanguageTags());
}
} }
...@@ -870,6 +870,7 @@ chrome_java_sources = [ ...@@ -870,6 +870,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/invalidation/ChromeBrowserSyncAdapterService.java", "java/src/org/chromium/chrome/browser/invalidation/ChromeBrowserSyncAdapterService.java",
"java/src/org/chromium/chrome/browser/invalidation/ResumableDelayedTaskRunner.java", "java/src/org/chromium/chrome/browser/invalidation/ResumableDelayedTaskRunner.java",
"java/src/org/chromium/chrome/browser/invalidation/SessionsInvalidationManager.java", "java/src/org/chromium/chrome/browser/invalidation/SessionsInvalidationManager.java",
"java/src/org/chromium/chrome/browser/language/AppLocaleUtils.java",
"java/src/org/chromium/chrome/browser/language/LanguageAskPrompt.java", "java/src/org/chromium/chrome/browser/language/LanguageAskPrompt.java",
"java/src/org/chromium/chrome/browser/language/settings/AddLanguageFragment.java", "java/src/org/chromium/chrome/browser/language/settings/AddLanguageFragment.java",
"java/src/org/chromium/chrome/browser/language/settings/LanguageItem.java", "java/src/org/chromium/chrome/browser/language/settings/LanguageItem.java",
......
...@@ -249,6 +249,7 @@ chrome_test_java_sources = [ ...@@ -249,6 +249,7 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/interstitials/LookalikeInterstitialTest.java", "javatests/src/org/chromium/chrome/browser/interstitials/LookalikeInterstitialTest.java",
"javatests/src/org/chromium/chrome/browser/jsdialog/JavascriptAppModalDialogTest.java", "javatests/src/org/chromium/chrome/browser/jsdialog/JavascriptAppModalDialogTest.java",
"javatests/src/org/chromium/chrome/browser/jsdialog/JavascriptTabModalDialogTest.java", "javatests/src/org/chromium/chrome/browser/jsdialog/JavascriptTabModalDialogTest.java",
"javatests/src/org/chromium/chrome/browser/language/AppLocaleUtilsTest.java",
"javatests/src/org/chromium/chrome/browser/language/settings/LanguageSettingsTest.java", "javatests/src/org/chromium/chrome/browser/language/settings/LanguageSettingsTest.java",
"javatests/src/org/chromium/chrome/browser/locale/DefaultSearchEngineDialogHelperTest.java", "javatests/src/org/chromium/chrome/browser/locale/DefaultSearchEngineDialogHelperTest.java",
"javatests/src/org/chromium/chrome/browser/locale/DefaultSearchEngineDialogHelperUtils.java", "javatests/src/org/chromium/chrome/browser/locale/DefaultSearchEngineDialogHelperUtils.java",
......
// Copyright 2020 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.
package org.chromium.chrome.browser.language;
import android.text.TextUtils;
import com.google.android.play.core.splitinstall.SplitInstallManager;
import com.google.android.play.core.splitinstall.SplitInstallManagerFactory;
import com.google.android.play.core.splitinstall.SplitInstallRequest;
import org.chromium.base.BundleUtils;
import org.chromium.base.ContextUtils;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
import java.util.Locale;
/**
* Provides utility functions to assist with overriding the application language.
* This class manages the AppLanguagePref.
*/
public class AppLocaleUtils {
private AppLocaleUtils(){};
/**
* Return true if languageName is the same as the current application override
* language stored preference.
* @return boolean
*/
public static boolean isAppLanguagePref(String languageName) {
return TextUtils.equals(getAppLanguagePref(), languageName);
}
/**
* Get the value of application language shared preference or null if there is none.
* @return String
*/
public static String getAppLanguagePref() {
return SharedPreferencesManager.getInstance().readString(
ChromePreferenceKeys.APPLICATION_OVERRIDE_LANGUAGE, null);
}
/**
* Set the value of application language shared preference. If set to null
* the system language will be used.
*/
public static void setAppLanguagePref(String languageName) {
SharedPreferencesManager.getInstance().writeString(
ChromePreferenceKeys.APPLICATION_OVERRIDE_LANGUAGE, languageName);
if (BundleUtils.isBundle()) {
ensureLaguageSplitInstalled(languageName);
}
}
/**
* For bundle builds ensure that the language split for languageName is download.
*/
private static void ensureLaguageSplitInstalled(String languageName) {
SplitInstallManager splitInstallManager =
SplitInstallManagerFactory.create(ContextUtils.getApplicationContext());
// TODO(perrier): check if languageName is already installed. https://crbug.com/1103806
if (languageName != null) {
SplitInstallRequest installRequest =
SplitInstallRequest.newBuilder()
.addLanguage(Locale.forLanguageTag(languageName))
.build();
splitInstallManager.startInstall(installRequest);
}
}
}
// Copyright 2020 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.
package org.chromium.chrome.browser.language;
import androidx.test.filters.SmallTest;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
/**
* Tests for the AppLocalUtils class.
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class AppLocaleUtilsTest {
// Test getAppLanguagePref.
@Test
@SmallTest
public void testGetAppLanguagePref() {
String lang = AppLocaleUtils.getAppLanguagePref();
Assert.assertEquals(null, lang);
AppLocaleUtils.setAppLanguagePref("en-US");
lang = AppLocaleUtils.getAppLanguagePref();
Assert.assertEquals("en-US", lang);
}
// Test setAppLanguagePref.
@Test
@SmallTest
public void testSetAppLanguagePref() {
assertLanguagePrefEquals(null);
AppLocaleUtils.setAppLanguagePref("en-US");
assertLanguagePrefEquals("en-US");
AppLocaleUtils.setAppLanguagePref("fr");
assertLanguagePrefEquals("fr");
}
// Test isAppLanguagePref.
@Test
@SmallTest
public void testIsAppLanguagePref() {
Assert.assertFalse(AppLocaleUtils.isAppLanguagePref("en"));
AppLocaleUtils.setAppLanguagePref("en-US");
Assert.assertTrue(AppLocaleUtils.isAppLanguagePref("en-US"));
Assert.assertFalse(AppLocaleUtils.isAppLanguagePref("en"));
}
// Helper function to manually get and check AppLanguagePref.
private void assertLanguagePrefEquals(String language) {
Assert.assertEquals(language,
SharedPreferencesManager.getInstance().readString(
ChromePreferenceKeys.APPLICATION_OVERRIDE_LANGUAGE, null));
}
}
...@@ -42,13 +42,17 @@ import java.util.List; ...@@ -42,13 +42,17 @@ import java.util.List;
* sanity of this file. * sanity of this file.
*/ */
public final class ChromePreferenceKeys { public final class ChromePreferenceKeys {
/* /**
* Whether the simplified tab switcher is enabled when accessibility mode is enabled. Keep in * Whether the simplified tab switcher is enabled when accessibility mode is enabled. Keep in
* sync with accessibility_preferences.xml. * sync with accessibility_preferences.xml.
* Default value is true. * Default value is true.
*/ */
public static final String ACCESSIBILITY_TAB_SWITCHER = "accessibility_tab_switcher"; public static final String ACCESSIBILITY_TAB_SWITCHER = "accessibility_tab_switcher";
/** The language code to override application language with. */
public static final String APPLICATION_OVERRIDE_LANGUAGE =
"Chrome.Language.ApplicationOverrideLanguage";
public static final String APP_LOCALE = "locale"; public static final String APP_LOCALE = "locale";
/** Whether Autofill Assistant is enabled */ /** Whether Autofill Assistant is enabled */
...@@ -791,6 +795,7 @@ public final class ChromePreferenceKeys { ...@@ -791,6 +795,7 @@ public final class ChromePreferenceKeys {
return Arrays.asList( return Arrays.asList(
AUTOFILL_ASSISTANT_FIRST_TIME_LITE_SCRIPT_USER, AUTOFILL_ASSISTANT_FIRST_TIME_LITE_SCRIPT_USER,
AUTOFILL_ASSISTANT_NUMBER_OF_LITE_SCRIPTS_CANCELED, AUTOFILL_ASSISTANT_NUMBER_OF_LITE_SCRIPTS_CANCELED,
APPLICATION_OVERRIDE_LANGUAGE,
CLIPBOARD_SHARED_URI, CLIPBOARD_SHARED_URI,
CONDITIONAL_TAB_STRIP_CONTINUOUS_DISMISS_COUNTER, CONDITIONAL_TAB_STRIP_CONTINUOUS_DISMISS_COUNTER,
CONDITIONAL_TAB_STRIP_FEATURE_STATUS, CONDITIONAL_TAB_STRIP_FEATURE_STATUS,
......
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