summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java123
-rw-r--r--packages/SettingsProvider/test/Android.mk5
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java135
3 files changed, 228 insertions, 35 deletions
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index 8abdc641d675..6247b3bd4994 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -16,6 +16,11 @@
package com.android.providers.settings;
+import com.android.internal.R;
+import com.android.internal.app.LocalePicker;
+import com.android.internal.annotations.VisibleForTesting;
+
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.app.backup.IBackupManager;
@@ -24,11 +29,13 @@ import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
+import android.icu.util.ULocale;
import android.location.LocationManager;
import android.media.AudioManager;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.IPowerManager;
+import android.os.LocaleList;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -39,6 +46,9 @@ import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Slog;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
import java.util.Locale;
public class SettingsHelper {
@@ -305,59 +315,106 @@ public class SettingsHelper {
}
}
- byte[] getLocaleData() {
+ /* package */ byte[] getLocaleData() {
Configuration conf = mContext.getResources().getConfiguration();
- final Locale loc = conf.locale;
- String localeString = loc.getLanguage();
- String country = loc.getCountry();
- if (!TextUtils.isEmpty(country)) {
- localeString += "-" + country;
+ return conf.getLocales().toLanguageTags().getBytes();
+ }
+
+ private static Locale toFullLocale(@NonNull Locale locale) {
+ if (locale.getScript().isEmpty() || locale.getCountry().isEmpty()) {
+ return ULocale.addLikelySubtags(ULocale.forLocale(locale)).toLocale();
}
- return localeString.getBytes();
+ return locale;
}
/**
- * Sets the locale specified. Input data is the byte representation of a
- * BCP-47 language tag. For backwards compatibility, strings of the form
+ * Merging the locale came from backup server and current device locale.
+ *
+ * Merge works with following rules.
+ * - The backup locales are appended to the current locale with keeping order.
+ * e.g. current locale "en-US,zh-CN" and backup locale "ja-JP,ko-KR" are merged to
+ * "en-US,zh-CH,ja-JP,ko-KR".
+ *
+ * - Duplicated locales are dropped.
+ * e.g. current locale "en-US,zh-CN" and backup locale "ja-JP,zh-Hans-CN,en-US" are merged to
+ * "en-US,zh-CN,ja-JP".
+ *
+ * - Unsupported locales are dropped.
+ * e.g. current locale "en-US" and backup locale "ja-JP,zh-CN" but the supported locales
+ * are "en-US,zh-CN", the merged locale list is "en-US,zh-CN".
+ *
+ * - The final result locale list only contains the supported locales.
+ * e.g. current locale "en-US" and backup locale "zh-Hans-CN" and supported locales are
+ * "en-US,zh-CN", the merged locale list is "en-US,zh-CN".
+ *
+ * @param restore The locale list that came from backup server.
+ * @param current The device's locale setting.
+ * @param supportedLocales The list of language tags supported by this device.
+ */
+ @VisibleForTesting
+ public static LocaleList resolveLocales(LocaleList restore, LocaleList current,
+ String[] supportedLocales) {
+ final HashMap<Locale, Locale> allLocales = new HashMap<>(supportedLocales.length);
+ for (String supportedLocaleStr : supportedLocales) {
+ final Locale locale = Locale.forLanguageTag(supportedLocaleStr);
+ allLocales.put(toFullLocale(locale), locale);
+ }
+
+ final ArrayList<Locale> filtered = new ArrayList<>(current.size());
+ for (int i = 0; i < current.size(); i++) {
+ final Locale locale = current.get(i);
+ allLocales.remove(toFullLocale(locale));
+ filtered.add(locale);
+ }
+
+ for (int i = 0; i < restore.size(); i++) {
+ final Locale locale = allLocales.remove(toFullLocale(restore.get(i)));
+ if (locale != null) {
+ filtered.add(locale);
+ }
+ }
+
+ if (filtered.size() == current.size()) {
+ return current; // Nothing added to current locale list.
+ }
+
+ return new LocaleList(filtered.toArray(new Locale[filtered.size()]));
+ }
+
+ /**
+ * Sets the locale specified. Input data is the byte representation of comma separated
+ * multiple BCP-47 language tags. For backwards compatibility, strings of the form
* {@code ll_CC} are also accepted, where {@code ll} is a two letter language
* code and {@code CC} is a two letter country code.
*
- * @param data the locale string in bytes.
+ * @param data the comma separated BCP-47 language tags in bytes.
*/
- void setLocaleData(byte[] data, int size) {
- // Check if locale was set by the user:
- final ContentResolver cr = mContext.getContentResolver();
- final boolean userSetLocale = mContext.getResources().getConfiguration().userSetLocale;
- final boolean provisioned = Settings.Global.getInt(cr,
- Settings.Global.DEVICE_PROVISIONED, 0) != 0;
- if (userSetLocale || provisioned) {
- // Don't change if user set it in the SetupWizard, or if this is a post-setup
- // deferred restore operation
- Slog.i(TAG, "Not applying restored locale; "
- + (userSetLocale ? "user already specified" : "device already provisioned"));
+ /* package */ void setLocaleData(byte[] data, int size) {
+ final Configuration conf = mContext.getResources().getConfiguration();
+
+ // Replace "_" with "-" to deal with older backups.
+ final String localeCodes = new String(data, 0, size).replace('_', '-');
+ final LocaleList localeList = LocaleList.forLanguageTags(localeCodes);
+ if (localeList.isEmpty()) {
return;
}
- final String[] availableLocales = mContext.getAssets().getLocales();
- // Replace "_" with "-" to deal with older backups.
- String localeCode = new String(data, 0, size).replace('_', '-');
- Locale loc = null;
- for (int i = 0; i < availableLocales.length; i++) {
- if (availableLocales[i].equals(localeCode)) {
- loc = Locale.forLanguageTag(localeCode);
- break;
- }
+ final String[] supportedLocales = LocalePicker.getSupportedLocales(mContext);
+ final LocaleList currentLocales = conf.getLocales();
+
+ final LocaleList merged = resolveLocales(localeList, currentLocales, supportedLocales);
+ if (merged.equals(currentLocales)) {
+ return;
}
- if (loc == null) return; // Couldn't find the saved locale in this version of the software
try {
IActivityManager am = ActivityManager.getService();
Configuration config = am.getConfiguration();
- config.locale = loc;
+ config.setLocales(merged);
// indicate this isn't some passing default - the user wants this remembered
config.userSetLocale = true;
- am.updateConfiguration(config);
+ am.updatePersistentConfiguration(config);
} catch (RemoteException e) {
// Intentionally left blank
}
diff --git a/packages/SettingsProvider/test/Android.mk b/packages/SettingsProvider/test/Android.mk
index 85e611f95d78..a9707d4ae69b 100644
--- a/packages/SettingsProvider/test/Android.mk
+++ b/packages/SettingsProvider/test/Android.mk
@@ -4,10 +4,11 @@ include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
-# Note we statically link SettingsState to do some unit tests. It's not accessible otherwise
+# Note we statically link several classes to do some unit tests. It's not accessible otherwise
# because this test is not an instrumentation test. (because the target runs in the system process.)
LOCAL_SRC_FILES := $(call all-subdir-java-files) \
- ../src/com/android/providers/settings/SettingsState.java
+ ../src/com/android/providers/settings/SettingsState.java \
+ ../src/com/android/providers/settings/SettingsHelper.java
LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
new file mode 100644
index 000000000000..6fa014d4bef7
--- /dev/null
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+package com.android.providers.settings;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertSame;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.fail;
+
+import com.android.internal.app.LocalePicker;
+import com.android.providers.settings.SettingsHelper;
+
+import android.os.LocaleList;
+import android.support.test.runner.AndroidJUnit4;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for the SettingsHelperTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class SettingsHelperTest {
+ @Test
+ public void testResolveLocales() throws Exception {
+ // Empty string from backup server
+ assertEquals(LocaleList.forLanguageTags("en-US"),
+ SettingsHelper.resolveLocales(
+ LocaleList.forLanguageTags(""), // restore
+ LocaleList.forLanguageTags("en-US"), // current
+ new String[] { "en-US" })); // supported
+
+ // Same as current settings
+ assertEquals(LocaleList.forLanguageTags("en-US"),
+ SettingsHelper.resolveLocales(
+ LocaleList.forLanguageTags("en-US"), // restore
+ LocaleList.forLanguageTags("en-US"), // current
+ new String[] { "en-US" })); // supported
+
+ assertEquals(LocaleList.forLanguageTags("en-US,ja-JP"),
+ SettingsHelper.resolveLocales(
+ LocaleList.forLanguageTags("en-US,ja-JP"), // restore
+ LocaleList.forLanguageTags("en-US,ja-JP"), // current
+ new String[] { "en-US", "ja-JP" })); // supported
+
+ // Current locale must be kept at the first place.
+ assertEquals(LocaleList.forLanguageTags("ja-JP,en-US"),
+ SettingsHelper.resolveLocales(
+ LocaleList.forLanguageTags("en-US"), // restore
+ LocaleList.forLanguageTags("ja-JP"), // current
+ new String[] { "en-US", "ja-JP" })); // supported
+
+ assertEquals(LocaleList.forLanguageTags("ja-JP,ko-KR,en-US"),
+ SettingsHelper.resolveLocales(
+ LocaleList.forLanguageTags("en-US"), // restore
+ LocaleList.forLanguageTags("ja-JP,ko-KR"), // current
+ new String[] { "en-US", "ja-JP", "ko-KR" })); // supported
+
+ assertEquals(LocaleList.forLanguageTags("ja-JP,en-US,ko-KR"),
+ SettingsHelper.resolveLocales(
+ LocaleList.forLanguageTags("en-US,ko-KR"), // restore
+ LocaleList.forLanguageTags("ja-JP"), // current
+ new String[] { "en-US", "ja-JP", "ko-KR" })); // supported
+
+ // Duplicated entries must be removed.
+ assertEquals(LocaleList.forLanguageTags("ja-JP,ko-KR,en-US"),
+ SettingsHelper.resolveLocales(
+ LocaleList.forLanguageTags("en-US,ko-KR"), // restore
+ LocaleList.forLanguageTags("ja-JP,ko-KR"), // current
+ new String[] { "en-US", "ja-JP", "ko-KR" })); // supported
+
+ // Drop unsupported locales.
+ assertEquals(LocaleList.forLanguageTags("en-US"),
+ SettingsHelper.resolveLocales(
+ LocaleList.forLanguageTags("en-US,zh-CN"), // restore
+ LocaleList.forLanguageTags("en-US"), // current
+ new String[] { "en-US" })); // supported
+
+ // Comparison happens on fully-expanded locale.
+ assertEquals(LocaleList.forLanguageTags("en-US,sr-Latn-SR,sr-Cryl-SR"),
+ SettingsHelper.resolveLocales(
+ LocaleList.forLanguageTags("sr-Cryl-SR"), // restore
+ LocaleList.forLanguageTags("en-US,sr-Latn-SR"), // current
+ new String[] { "en-US", "sr-Latn-SR", "sr-Cryl-SR" })); // supported
+
+ assertEquals(LocaleList.forLanguageTags("en-US"),
+ SettingsHelper.resolveLocales(
+ LocaleList.forLanguageTags("kk-Cryl-KZ"), // restore
+ LocaleList.forLanguageTags("en-US"), // current
+ new String[] { "en-US", "kk-Latn-KZ" })); // supported
+
+ assertEquals(LocaleList.forLanguageTags("en-US,kk-Cryl-KZ"),
+ SettingsHelper.resolveLocales(
+ LocaleList.forLanguageTags("kk-Cryl-KZ"), // restore
+ LocaleList.forLanguageTags("en-US"), // current
+ new String[] { "en-US", "kk-Cryl-KZ" })); // supported
+
+ assertEquals(LocaleList.forLanguageTags("en-US,zh-CN"),
+ SettingsHelper.resolveLocales(
+ LocaleList.forLanguageTags("zh-Hans-CN"), // restore
+ LocaleList.forLanguageTags("en-US"), // current
+ new String[] { "en-US", "zh-CN" })); // supported
+
+ assertEquals(LocaleList.forLanguageTags("en-US,zh-Hans-CN"),
+ SettingsHelper.resolveLocales(
+ LocaleList.forLanguageTags("zh-CN"), // restore
+ LocaleList.forLanguageTags("en-US"), // current
+ new String[] { "en-US", "zh-Hans-CN" })); // supported
+
+ // Old langauge code should be updated.
+ assertEquals(LocaleList.forLanguageTags("en-US,he-IL,id-ID,yi"),
+ SettingsHelper.resolveLocales(
+ LocaleList.forLanguageTags("iw-IL,in-ID,ji"), // restore
+ LocaleList.forLanguageTags("en-US"), // current
+ new String[] { "he-IL", "id-ID", "yi" })); // supported
+ }
+}