diff options
author | 2016-05-24 21:41:20 +0000 | |
---|---|---|
committer | 2016-05-24 21:41:20 +0000 | |
commit | 9ed84d86120dd6487eacba503f45f6d147f05fbf (patch) | |
tree | 63efb7557e624417ce3c233f2ef35b3e19f60213 | |
parent | 4d1d0bcc4a9342f2b41827d852145a2b66a795b6 (diff) | |
parent | 5e106dea4545547f3f7c50dad30f792809659b96 (diff) |
Merge "Improve performance of LocaleList with Resources" into nyc-dev am: bb26248bbb
am: 5e106dea45
* commit '5e106dea4545547f3f7c50dad30f792809659b96':
Improve performance of LocaleList with Resources
Change-Id: I733cdad0b30069ac7830fa91ef0188dd0f0cfac9
11 files changed, 262 insertions, 103 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 7fc64e73eaf2..635bd75c44bd 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -62,6 +62,7 @@ import android.os.DropBoxManager; import android.os.Environment; import android.os.Handler; import android.os.IBinder; +import android.os.LocaleList; import android.os.Looper; import android.os.Message; import android.os.MessageQueue; @@ -130,6 +131,7 @@ import java.text.DateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.TimeZone; @@ -4684,6 +4686,8 @@ public final class ActivityThread { + config); mResourcesManager.applyConfigurationToResourcesLocked(config, compat); + updateLocaleListFromAppContext(mInitialApplication.getApplicationContext(), + mResourcesManager.getConfiguration().getLocales()); if (mConfiguration == null) { mConfiguration = new Configuration(); @@ -4987,6 +4991,24 @@ public final class ActivityThread { return insInfo.nativeLibraryDir; } + /** + * The LocaleList set for the app's resources may have been shuffled so that the preferred + * Locale is at position 0. We must find the index of this preferred Locale in the + * original LocaleList. + */ + private void updateLocaleListFromAppContext(Context context, LocaleList newLocaleList) { + final Locale bestLocale = context.getResources().getConfiguration().getLocales().get(0); + final int newLocaleListSize = newLocaleList.size(); + for (int i = 0; i < newLocaleListSize; i++) { + if (bestLocale.equals(newLocaleList.get(i))) { + LocaleList.setDefault(newLocaleList, i); + return; + } + } + throw new AssertionError("chosen locale " + bestLocale + " must be present in LocaleList: " + + newLocaleList.toLanguageTags()); + } + private void handleBindApplication(AppBindData data) { // Register the UI Thread as a sensitive thread to the runtime. VMRuntime.registerSensitiveThread(); @@ -5045,6 +5067,24 @@ public final class ActivityThread { */ TimeZone.setDefault(null); + /* + * Set the LocaleList. This may change once we create the App Context. + */ + LocaleList.setDefault(data.config.getLocales()); + + synchronized (mResourcesManager) { + /* + * Update the system configuration since its preloaded and might not + * reflect configuration changes. The configuration object passed + * in AppBindData can be safely assumed to be up to date + */ + mResourcesManager.applyConfigurationToResourcesLocked(data.config, data.compatInfo); + mCurDefaultDisplayDpi = data.config.densityDpi; + + // This calls mResourcesManager so keep it within the synchronized block. + applyCompatConfiguration(mCurDefaultDisplayDpi); + } + data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo); /** @@ -5172,25 +5212,8 @@ public final class ActivityThread { } final ContextImpl appContext = ContextImpl.createAppContext(this, data.info); - synchronized (mResourcesManager) { - /* - * Initialize the default locales in this process for the reasons we set the time zone. - * - * We do this through ResourcesManager, since we need to do locale negotiation. - */ - mResourcesManager.setDefaultLocalesLocked(data.config.getLocales()); - - /* - * Update the system configuration since its preloaded and might not - * reflect configuration changes. The configuration object passed - * in AppBindData can be safely assumed to be up to date - */ - mResourcesManager.applyConfigurationToResourcesLocked(data.config, data.compatInfo); - mCurDefaultDisplayDpi = data.config.densityDpi; - - // This calls mResourcesManager so keep it within the synchronized block. - applyCompatConfiguration(mCurDefaultDisplayDpi); - } + updateLocaleListFromAppContext(appContext, + mResourcesManager.getConfiguration().getLocales()); if (!Process.isIsolated() && !"android".equals(appContext.getPackageName())) { // This cache location probably points at credential-encrypted @@ -5893,6 +5916,9 @@ public final class ActivityThread { // immediately, because upon returning the view // hierarchy will be informed about it. if (mResourcesManager.applyConfigurationToResourcesLocked(newConfig, null)) { + updateLocaleListFromAppContext(mInitialApplication.getApplicationContext(), + mResourcesManager.getConfiguration().getLocales()); + // This actually changed the resources! Tell // everyone about it. if (mPendingConfiguration == null || diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index b4e9db86d18c..25a8b66a42aa 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -67,10 +67,6 @@ public class ResourcesManager { } }; - private String[] mSystemLocales = null; - private final HashSet<String> mNonSystemLocales = new HashSet<>(); - private boolean mHasNonSystemLocales = false; - /** * The global compatibility settings. */ @@ -479,12 +475,7 @@ public class ResourcesManager { */ private Resources getOrCreateResources(@Nullable IBinder activityToken, @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) { - final boolean findSystemLocales; - final boolean hasNonSystemLocales; synchronized (this) { - findSystemLocales = (mSystemLocales == null || mSystemLocales.length == 0); - hasNonSystemLocales = mHasNonSystemLocales; - if (DEBUG) { Throwable here = new Throwable(); here.fillInStackTrace(); @@ -538,24 +529,7 @@ public class ResourcesManager { // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now. ResourcesImpl resourcesImpl = createResourcesImpl(key); - final String[] systemLocales = findSystemLocales - ? AssetManager.getSystem().getLocales() : null; - final String[] nonSystemLocales = resourcesImpl.getAssets().getNonSystemLocales(); - - // Avoid checking for non-pseudo-locales if we already know there were some from a previous - // Resources. The default value (for when hasNonSystemLocales is true) doesn't matter, - // since mHasNonSystemLocales will also be true, and thus isPseudoLocalesOnly would not be - // able to affect mHasNonSystemLocales. - final boolean isPseudoLocalesOnly = hasNonSystemLocales || - LocaleList.isPseudoLocalesOnly(nonSystemLocales); - synchronized (this) { - if (mSystemLocales == null || mSystemLocales.length == 0) { - mSystemLocales = systemLocales; - } - mNonSystemLocales.addAll(Arrays.asList(nonSystemLocales)); - mHasNonSystemLocales = mHasNonSystemLocales || !isPseudoLocalesOnly; - ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key); if (existingResourcesImpl != null) { if (DEBUG) { @@ -745,23 +719,6 @@ public class ResourcesManager { } } - /* package */ void setDefaultLocalesLocked(@NonNull LocaleList locales) { - if (mSystemLocales == null) { - throw new RuntimeException("ResourcesManager is not ready to negotiate locales."); - } - final int bestLocale; - if (mHasNonSystemLocales) { - bestLocale = locales.getFirstMatchIndexWithEnglishSupported(mNonSystemLocales); - } else { - // We fallback to system locales if there was no locale specifically supported by the - // assets. This is to properly support apps that only rely on the shared system assets - // and don't need assets of their own. - bestLocale = locales.getFirstMatchIndexWithEnglishSupported(mSystemLocales); - } - // set it for Java, this also affects newly created Resources - LocaleList.setDefault(locales, bestLocale); - } - public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config, @Nullable CompatibilityInfo compat) { try { @@ -786,30 +743,7 @@ public class ResourcesManager { | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; } - Configuration localeAdjustedConfig = config; - final LocaleList configLocales = config.getLocales(); - if (!configLocales.isEmpty()) { - setDefaultLocalesLocked(configLocales); - final LocaleList adjustedLocales = LocaleList.getAdjustedDefault(); - if (adjustedLocales - != configLocales) { // has the same result as .equals() in this case - // The first locale in the list was not chosen. So we create a modified - // configuration with the adjusted locales (which moves the chosen locale to the - // front). - localeAdjustedConfig = new Configuration(); - localeAdjustedConfig.setTo(config); - localeAdjustedConfig.setLocales(adjustedLocales); - // Also adjust the locale list in mResConfiguration, so that the Resources - // created later would have the same locale list. - if (!mResConfiguration.getLocales().equals(adjustedLocales)) { - mResConfiguration.setLocales(adjustedLocales); - changes |= ActivityInfo.CONFIG_LOCALE; - } - } - } - - Resources.updateSystemConfiguration(localeAdjustedConfig, defaultDisplayMetrics, - compat); + Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat); ApplicationPackageManager.configurationChanged(); //Slog.i(TAG, "Configuration changed in " + currentPackageName()); @@ -821,7 +755,7 @@ public class ResourcesManager { ResourcesImpl r = mResourceImpls.valueAt(i).get(); if (r != null) { if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources " - + r + " config to: " + localeAdjustedConfig); + + r + " config to: " + config); int displayId = key.mDisplayId; boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); DisplayMetrics dm = defaultDisplayMetrics; @@ -830,7 +764,7 @@ public class ResourcesManager { if (tmpConfig == null) { tmpConfig = new Configuration(); } - tmpConfig.setTo(localeAdjustedConfig); + tmpConfig.setTo(config); if (!isDefaultDisplay) { dm = getDisplayMetrics(displayId); applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig); @@ -840,7 +774,7 @@ public class ResourcesManager { } r.updateConfiguration(tmpConfig, dm, compat); } else { - r.updateConfiguration(localeAdjustedConfig, dm, compat); + r.updateConfiguration(config, dm, compat); } //Slog.i(TAG, "Updated app resources " + v.getKey() // + " " + r + ": " + r.getConfiguration()); diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index c1aac8584acb..f6445e6cd090 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -1446,7 +1446,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration * * @return The locale list. */ - public LocaleList getLocales() { + public @NonNull LocaleList getLocales() { fixUpLocaleList(); return mLocaleList; } diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index dada61280c1e..32a27951677a 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -330,18 +330,43 @@ public class ResourcesImpl { // doing the conversion here. This impl should be okay because // we make sure to return a compatible display in the places // where there are public APIs to retrieve the display... but - // it would be cleaner and more maintainble to just be + // it would be cleaner and more maintainable to just be // consistently dealing with a compatible display everywhere in // the framework. mCompatibilityInfo.applyToDisplayMetrics(mMetrics); final @Config int configChanges = calcConfigChanges(config); + // If even after the update there are no Locales set, grab the default locales. LocaleList locales = mConfiguration.getLocales(); if (locales.isEmpty()) { - locales = LocaleList.getAdjustedDefault(); + locales = LocaleList.getDefault(); mConfiguration.setLocales(locales); } + + if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) { + if (locales.size() > 1) { + // The LocaleList has changed. We must query the AssetManager's available + // Locales and figure out the best matching Locale in the new LocaleList. + String[] availableLocales = mAssets.getNonSystemLocales(); + if (LocaleList.isPseudoLocalesOnly(availableLocales)) { + // No app defined locales, so grab the system locales. + availableLocales = mAssets.getLocales(); + if (LocaleList.isPseudoLocalesOnly(availableLocales)) { + availableLocales = null; + } + } + + if (availableLocales != null) { + final Locale bestLocale = locales.getFirstMatchWithEnglishSupported( + availableLocales); + if (bestLocale != null && bestLocale != locales.get(0)) { + mConfiguration.setLocales(new LocaleList(bestLocale, locales)); + } + } + } + } + if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) { mMetrics.densityDpi = mConfiguration.densityDpi; mMetrics.density = @@ -370,7 +395,7 @@ public class ResourcesImpl { } mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc, - adjustLanguageTag(locales.get(0).toLanguageTag()), + adjustLanguageTag(mConfiguration.getLocales().get(0).toLanguageTag()), mConfiguration.orientation, mConfiguration.touchscreen, mConfiguration.densityDpi, mConfiguration.keyboard, diff --git a/core/java/android/os/LocaleList.java b/core/java/android/os/LocaleList.java index 8136796574e0..60b618a2f689 100644 --- a/core/java/android/os/LocaleList.java +++ b/core/java/android/os/LocaleList.java @@ -409,6 +409,14 @@ public final class LocaleList implements Parcelable { } /** + * {@hide} + */ + public int getFirstMatchIndex(String[] supportedLocales) { + return computeFirstMatchIndex(Arrays.asList(supportedLocales), + false /* assume English is not supported */); + } + + /** * Same as getFirstMatch(), but with English assumed to be supported, even if it's not. * {@hide} */ @@ -437,7 +445,11 @@ public final class LocaleList implements Parcelable { * Assumes that there is no repetition in the input. * {@hide} */ - public static boolean isPseudoLocalesOnly(String[] supportedLocales) { + public static boolean isPseudoLocalesOnly(@Nullable String[] supportedLocales) { + if (supportedLocales == null) { + return true; + } + if (supportedLocales.length > NUM_PSEUDO_LOCALES + 1) { // This is for optimization. Since there's no repetition in the input, if we have more // than the number of pseudo-locales plus one for the empty string, it's guaranteed diff --git a/core/tests/coretests/apks/locales/Android.mk b/core/tests/coretests/apks/locales/Android.mk new file mode 100644 index 000000000000..9cb13dd4cd0e --- /dev/null +++ b/core/tests/coretests/apks/locales/Android.mk @@ -0,0 +1,8 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_PACKAGE_NAME := locales + +include $(FrameworkCoreTests_BUILD_PACKAGE) diff --git a/core/tests/coretests/apks/locales/AndroidManifest.xml b/core/tests/coretests/apks/locales/AndroidManifest.xml new file mode 100644 index 000000000000..8a1f575510e7 --- /dev/null +++ b/core/tests/coretests/apks/locales/AndroidManifest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.frameworks.coretests.locales"> + + <application android:hasCode="false"> + </application> +</manifest> diff --git a/core/tests/coretests/apks/locales/res/values-pl/values.xml b/core/tests/coretests/apks/locales/res/values-pl/values.xml new file mode 100644 index 000000000000..22237fa81001 --- /dev/null +++ b/core/tests/coretests/apks/locales/res/values-pl/values.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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. +--> +<resources> + <string name="text">Text (pl)</string> +</resources> diff --git a/core/tests/coretests/apks/locales/res/values/values.xml b/core/tests/coretests/apks/locales/res/values/values.xml new file mode 100644 index 000000000000..cb990d20215e --- /dev/null +++ b/core/tests/coretests/apks/locales/res/values/values.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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. +--> +<resources> + <string name="text">Text</string> +</resources> diff --git a/core/tests/coretests/src/android/content/res/ResourcesLocaleTest.java b/core/tests/coretests/src/android/content/res/ResourcesLocaleTest.java new file mode 100644 index 000000000000..e85666eb1639 --- /dev/null +++ b/core/tests/coretests/src/android/content/res/ResourcesLocaleTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2016 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 android.content.res; + +import android.os.FileUtils; +import android.os.LocaleList; +import android.support.test.filters.SmallTest; +import android.test.AndroidTestCase; +import android.util.DisplayMetrics; +import android.view.Display; + +import com.android.frameworks.coretests.R; + +import java.io.File; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Locale; + +public class ResourcesLocaleTest extends AndroidTestCase { + + private String extractApkAndGetPath(int id) throws Exception { + final Resources resources = getContext().getResources(); + try (InputStream is = resources.openRawResource(id)) { + File path = new File(getContext().getFilesDir(), resources.getResourceEntryName(id)); + FileUtils.copyToFileOrThrow(is, path); + return path.getAbsolutePath(); + } + } + + private Resources createResourcesWithApk(int rawApkId) throws Exception { + final AssetManager assets = new AssetManager(); + assertTrue(assets.addAssetPath(extractApkAndGetPath(rawApkId)) != 0); + + final DisplayMetrics dm = new DisplayMetrics(); + dm.setToDefaults(); + return new Resources(assets, dm, new Configuration()); + } + + private static void ensureNoLanguage(Resources resources, String language) { + final String[] supportedLocales = resources.getAssets().getNonSystemLocales(); + for (String languageTag : supportedLocales) { + if ("en-XA".equals(languageTag)) { + continue; + } + assertFalse( + "supported locales: " + Arrays.toString(supportedLocales), + language.equals(Locale.forLanguageTag(languageTag).getLanguage())); + } + } + + @SmallTest + public void testEnglishIsAlwaysConsideredSupported() throws Exception { + final Resources resources = createResourcesWithApk(R.raw.locales); + ensureNoLanguage(resources, "en"); + + final LocaleList preferredLocales = LocaleList.forLanguageTags("en-US,pl-PL"); + final Configuration config = new Configuration(); + config.setLocales(preferredLocales); + + resources.updateConfiguration(config, null); + + // The APK we loaded has default and Polish languages. If English is first in the list, + // always take it the default (assumed to be English). + assertEquals(Locale.forLanguageTag("en-US"), + resources.getConfiguration().getLocales().get(0)); + } + + @SmallTest + public void testSelectFirstSupportedLanguage() throws Exception { + final Resources resources = createResourcesWithApk(R.raw.locales); + ensureNoLanguage(resources, "fr"); + + final LocaleList preferredLocales = LocaleList.forLanguageTags("fr-FR,pl-PL"); + final Configuration config = new Configuration(); + config.setLocales(preferredLocales); + + resources.updateConfiguration(config, null); + + // The APK we loaded has default and Polish languages. We expect the Polish language to + // therefore be chosen. + assertEquals(Locale.forLanguageTag("pl-PL"), + resources.getConfiguration().getLocales().get(0)); + } +} diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 808b254aa738..f4bb524ff156 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -18325,21 +18325,21 @@ public final class ActivityManagerService extends ActivityManagerNative EventLog.writeEvent(EventLogTags.CONFIGURATION_CHANGED, changes); if (!initLocale && !values.getLocales().isEmpty() && values.userSetLocale) { - final Locale locale; - if (values.getLocales().size() == 1) { - // This is an optimization to avoid the JNI call when the result of - // getFirstMatch() does not depend on the supported locales. - locale = values.getLocales().get(0); - } else { + final LocaleList locales = values.getLocales(); + int bestLocaleIndex = 0; + if (locales.size() > 1) { if (mSupportedSystemLocales == null) { mSupportedSystemLocales = Resources.getSystem().getAssets().getLocales(); } - locale = values.getLocales().getFirstMatch(mSupportedSystemLocales); + bestLocaleIndex = Math.max(0, + locales.getFirstMatchIndex(mSupportedSystemLocales)); } - SystemProperties.set("persist.sys.locale", locale.toLanguageTag()); + SystemProperties.set("persist.sys.locale", + locales.get(bestLocaleIndex).toLanguageTag()); + LocaleList.setDefault(locales, bestLocaleIndex); mHandler.sendMessage(mHandler.obtainMessage(SEND_LOCALE_TO_MOUNT_DAEMON_MSG, - locale)); + locales.get(bestLocaleIndex))); } mConfigurationSeq++; |