Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b61e4053 authored by Adam Lesinski's avatar Adam Lesinski
Browse files

Improve performance of LocaleList with Resources

We allow each individual Resources object to select the best
Locale for the given APK. This allows one update to the configuration
instead of multiple updates, once the locale is chosen.

The Java locale is selected from the app context's locale.

Bug:28625993
Bug:27325465
Change-Id: I99e1e53f522e560f3b80bbd1e1c605f552dbdff0
parent 0f6363e8
Loading
Loading
Loading
Loading
+45 −19
Original line number Diff line number Diff line
@@ -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();
@@ -4989,6 +4993,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();
@@ -5047,6 +5069,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);

        /**
@@ -5174,25 +5214,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
@@ -5895,6 +5918,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 ||
+4 −70
Original line number Diff line number Diff line
@@ -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());
+1 −1
Original line number Diff line number Diff line
@@ -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;
    }
+28 −3
Original line number Diff line number Diff line
@@ -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,
+13 −1
Original line number Diff line number Diff line
@@ -408,6 +408,14 @@ public final class LocaleList implements Parcelable {
                false /* assume English is not supported */);
    }

    /**
     * {@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
Loading