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

Commit a1d1e800 authored by Mark Punzalan's avatar Mark Punzalan
Browse files

Ensure there is at least one locale in the Configuration object

When there is no intersection between the system locales and the
app-supported locales defined in the LocaleConfig, the locales in the
Configuration object are set to an empty LocaleList, which can trigger a
crash (NullPointerException) in the app and possibly in system_server or
systemui in some cases.

This fixes that issue by falling back in that case to the previous
behavior (before the improved resource multi-locale support) of
selecting the best matching locale available in the app (if any), or the
first system locale.

Additionally:
- Renamed `calcConfigChanges` to `applyConfigChanges` because it applies the new configuration in addition to calculating the changes.
- Added more comments to explain the locale selection logic.

Bug: 400163083
Test: Manually on Pixel 6 Pro with repro steps in b/400163083#comment18
Flag: EXEMPT bugfix
Change-Id: I3f525b44f89589d521b3d3408a976b11b1cfe064
parent 11538b19
Loading
Loading
Loading
Loading
+108 −104
Original line number Diff line number Diff line
@@ -2629,12 +2629,16 @@ public class Resources {
    }

    /**
     * Called by ConfigurationBoundResourceCacheTest.
     * Called by ConfigurationBoundResourceCacheTest. Applies the new configuration, returning a
     * bitmask of the changes between the old and new configurations.
     *
     * @param config the new configuration
     * @return bitmask of config changes
     * @hide
     */
    @VisibleForTesting
    public int calcConfigChanges(Configuration config) {
        return mResourcesImpl.calcConfigChanges(config);
    public int applyConfigChanges(Configuration config) {
        return mResourcesImpl.applyConfigChanges(config);
    }

    /**
+68 −29
Original line number Diff line number Diff line
@@ -500,34 +500,55 @@ public class ResourcesImpl {
                // the framework.
                mDisplayAdjustments.getCompatibilityInfo().applyToDisplayMetrics(mMetrics);

                final @Config int configChanges = calcConfigChanges(config);
                final @Config int configChanges = applyConfigChanges(config);

                // If even after the update there are no Locales set, grab the default locales.
                LocaleList locales = mConfiguration.getLocales();
                if (locales.isEmpty()) {
                    locales = LocaleList.getDefault();
                    mConfiguration.setLocales(locales);
                LocaleList configLocales = mConfiguration.getLocales();
                if (configLocales.isEmpty()) {
                    configLocales = LocaleList.getDefault();
                    mConfiguration.setLocales(configLocales);
                }

                // Note: `locales` should be a list in the following order:
                // 1. App-specific locales (typically at most one, set by the user in settings)
                // 2. One or more system locales

                String[] selectedLocales = null;
                String defaultLocale = null;
                if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) {
                    if (locales.size() > 1) {
                    if (configLocales.size() > 1) {
                        // There is more than one locale in the configuration. We need to update
                        // the locales in the configuration based on what is supported by the app
                        // so that we will use the same locales on other non-locale config changes.

                        Locale[] intersection = null;
                        if (Flags.defaultLocale() && (mLocaleConfig.getDefaultLocale() != null)) {
                            Locale[] intersection =
                                    locales.getIntersection(mLocaleConfig.getSupportedLocales());
                            // Note: getIntersection() returns a list of locales in the same order
                            // currently in `configLocales`, which is desired (i.e., app-specific
                            // locales first before system locales).
                            defaultLocale = adjustLanguageTag(
                                    mLocaleConfig.getDefaultLocale().toLanguageTag());
                            intersection =
                                    configLocales.getIntersection(
                                            mLocaleConfig.getSupportedLocales());
                            if (intersection.length > 0) {
                                mConfiguration.setLocales(new LocaleList(intersection));
                                selectedLocales = new String[intersection.length];
                                for (int i = 0; i < intersection.length; i++) {
                                    selectedLocales[i] =
                                            adjustLanguageTag(intersection[i].toLanguageTag());
                                }
                            defaultLocale = adjustLanguageTag(
                                    mLocaleConfig.getDefaultLocale().toLanguageTag());
                                Slog.v(TAG, "Updating configuration, with default locale "
                                        + defaultLocale + " and selected locales "
                                        + Arrays.toString(selectedLocales));
                        } else {
                            }
                        }

                        if (intersection == null || intersection.length == 0) {
                            // This is the fallback behavior when multi-locale is not enabled,
                            // or when there was no intersection between the app's supported
                            // locales and the locales in the configuration.

                            String[] availableLocales;
                            // The LocaleList has changed. We must query the AssetManager's
                            // available Locales and figure out the best matching Locale in the new
@@ -542,31 +563,46 @@ public class ResourcesImpl {
                            }

                            if (availableLocales != null) {
                                final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
                                final Locale bestLocale =
                                        configLocales.getFirstMatchWithEnglishSupported(
                                                availableLocales);
                                if (bestLocale != null) {
                                    selectedLocales = new String[]{
                                            adjustLanguageTag(bestLocale.toLanguageTag())};
                                    if (!bestLocale.equals(locales.get(0))) {
                                    if (!bestLocale.equals(configLocales.get(0))) {
                                        // There was an locale available in the app that matched.
                                        // Update the configuration so that it goes first.
                                        mConfiguration.setLocales(
                                                new LocaleList(bestLocale, locales));
                                                new LocaleList(bestLocale, configLocales));
                                        Slog.v(TAG, "Updating configuration with selected locales "
                                                + Arrays.toString(selectedLocales));
                                    }
                                }
                            }
                        }
                    }
                }
                if (selectedLocales == null) {

                if (selectedLocales == null || selectedLocales.length == 0) {
                    // Either:
                    // 1. The locales were not changed in the configuration, in which case we
                    //    use whatever was last set in the configuration.
                    // 2. Locales changed, but there was only one locale in the configuration, in
                    //    which case we simply use that locale.
                    // 3. Locales changed, and there were multiple locales in the configuration,
                    //    but the app does not support any of them, so we use whatever locales are
                    //    currently in the configuration.
                    if (Flags.defaultLocale() && (mLocaleConfig.getDefaultLocale() != null)) {
                        selectedLocales = new String[locales.size()];
                        for (int i = 0; i < locales.size(); i++) {
                            selectedLocales[i] = adjustLanguageTag(locales.get(i).toLanguageTag());
                        selectedLocales = new String[configLocales.size()];
                        for (int i = 0; i < configLocales.size(); i++) {
                            selectedLocales[i] =
                                    adjustLanguageTag(configLocales.get(i).toLanguageTag());
                        }
                        defaultLocale = adjustLanguageTag(
                                mLocaleConfig.getDefaultLocale().toLanguageTag());
                    } else {
                        selectedLocales = new String[]{
                                adjustLanguageTag(locales.get(0).toLanguageTag())};
                                adjustLanguageTag(configLocales.get(0).toLanguageTag())};
                    }
                }

@@ -646,7 +682,7 @@ public class ResourcesImpl {
     * @param config the new configuration
     * @return bitmask of config changes
     */
    public @Config int calcConfigChanges(@Nullable Configuration config) {
    public @Config int applyConfigChanges(@Nullable Configuration config) {
        if (config == null) {
            // If there is no configuration, assume all flags have changed.
            return 0xFFFFFFFF;
@@ -892,7 +928,10 @@ public class ResourcesImpl {
                }
            } else {
                if (verifyPreloadConfig(
                        changingConfigs, ActivityInfo.CONFIG_LAYOUT_DIRECTION, value.resourceId, "drawable")) {
                        changingConfigs,
                        ActivityInfo.CONFIG_LAYOUT_DIRECTION,
                        value.resourceId,
                        "drawable")) {
                    if ((changingConfigs & ActivityInfo.CONFIG_LAYOUT_DIRECTION) == 0) {
                        // If this resource does not vary based on layout direction,
                        // we can put it in all of the preload maps.
+6 −6
Original line number Diff line number Diff line
@@ -111,7 +111,7 @@ public class ConfigurationBoundResourceCacheTest {
        newCnf.orientation = cfg.orientation == Configuration.ORIENTATION_LANDSCAPE ?
                Configuration.ORIENTATION_PORTRAIT
                : Configuration.ORIENTATION_LANDSCAPE;
        int changes = calcConfigChanges(res, newCnf);
        int changes = applyConfigChanges(res, newCnf);
        assertEquals(staticDim, mCache.getInstance(key, res, mContext.getTheme()));
        mCache.onConfigurationChange(changes);
        assertEquals(staticDim, mCache.getInstance(key, res, mContext.getTheme()));
@@ -134,7 +134,7 @@ public class ConfigurationBoundResourceCacheTest {
        newCnf.orientation = cfg.orientation == Configuration.ORIENTATION_LANDSCAPE ?
                Configuration.ORIENTATION_PORTRAIT
                : Configuration.ORIENTATION_LANDSCAPE;
        int changes = calcConfigChanges(res, newCnf);
        int changes = applyConfigChanges(res, newCnf);
        assertEquals(changingDim,
                mCache.getInstance(key, res, mContext.getTheme()));
        mCache.onConfigurationChange(changes);
@@ -162,7 +162,7 @@ public class ConfigurationBoundResourceCacheTest {
        newCnf.orientation = cfg.orientation == Configuration.ORIENTATION_LANDSCAPE ?
                Configuration.ORIENTATION_PORTRAIT
                : Configuration.ORIENTATION_LANDSCAPE;
        int changes = calcConfigChanges(res, newCnf);
        int changes = applyConfigChanges(res, newCnf);
        assertEquals(staticDim, mCache.getInstance(R.dimen.resource_cache_test_generic, res,
                mContext.getTheme()));
        assertEquals(changingDim,
@@ -205,7 +205,7 @@ public class ConfigurationBoundResourceCacheTest {
        newCnf.orientation = cfg.orientation == Configuration.ORIENTATION_LANDSCAPE ?
                Configuration.ORIENTATION_PORTRAIT
                : Configuration.ORIENTATION_LANDSCAPE;
        int changes = calcConfigChanges(res, newCnf);
        int changes = applyConfigChanges(res, newCnf);
        for (int i = 0; i < 2; i++) {
            final Resources.Theme theme = i == 0 ? mContext.getTheme() : null;
            assertEquals(staticDim,
@@ -224,8 +224,8 @@ public class ConfigurationBoundResourceCacheTest {
        }
    }

    private static int calcConfigChanges(Resources resources, Configuration configuration) {
        return resources.calcConfigChanges(configuration);
    private static int applyConfigChanges(Resources resources, Configuration configuration) {
        return resources.applyConfigChanges(configuration);
    }

    static class DummyFloatConstantState extends ConstantState<Float> {