LocaleManagerCompat.java

/*
 * Copyright 2022 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 androidx.core.app;

import android.app.LocaleManager;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Build;
import android.os.LocaleList;

import androidx.annotation.AnyThread;
import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import androidx.core.os.BuildCompat;
import androidx.core.os.LocaleListCompat;

import java.util.Locale;

/**
 * Helper for accessing features in {@link android.app.LocaleManager} in a backwards compatible
 * fashion.
 *
 * <p><b>Note:</b> Backwards compatibility for
 * {@link LocaleManager#setApplicationLocales(LocaleList)} and
 * {@link LocaleManager#getApplicationLocales()} is available via AppCompatDelegate.
 */
public final class LocaleManagerCompat {

    private LocaleManagerCompat() {}

    /**
     * Returns the current system locales, ignoring app-specific overrides.
     *
     * <p><b>Note:</b> Apps should generally access the user's locale preferences as indicated in
     * their in-process {@link android.os.LocaleList}s. However, in case an app-specific locale
     * is set, this method helps cater to rare use-cases which might require specifically knowing
     * the system locale.
     */
    @OptIn(markerClass = androidx.core.os.BuildCompat.PrereleaseSdkCheck.class)
    @NonNull
    @AnyThread
    public static LocaleListCompat getSystemLocales(@NonNull Context context) {
        LocaleListCompat systemLocales = LocaleListCompat.getEmptyLocaleList();
        // TODO: modify the check to Build.Version.SDK_INT >= 33.
        if (BuildCompat.isAtLeastT()) {
            // If the API version is 33 or above we want to redirect the call to the framework API.
            Object localeManager = getLocaleManagerForApplication(context);
            if (localeManager != null) {
                systemLocales = LocaleListCompat.wrap(Api33Impl.localeManagerGetSystemLocales(
                        localeManager));
            }
        } else {
            // Changing app locales using AppCompatDelegate for API < 33 does not modify the
            // context's configuration and hence this configuration can be used to fetch system
            // locales.
            systemLocales = getConfigurationLocales(context.getApplicationContext()
                    .getResources().getConfiguration());
        }
        return systemLocales;
    }

    /**
     * Returns the localeManager for the current application.
     */
    @RequiresApi(33)
    private static Object getLocaleManagerForApplication(Context context) {
        return context.getSystemService(Context.LOCALE_SERVICE);
    }

    @VisibleForTesting
    static LocaleListCompat getConfigurationLocales(Configuration conf) {
        if (Build.VERSION.SDK_INT >= 24) {
            return Api24Impl.getLocales(conf);
        } else if (Build.VERSION.SDK_INT >= 21) {
            return LocaleListCompat.forLanguageTags(Api21Impl.toLanguageTag(conf.locale));
        } else {
            // Create LocaleListCompat using the configuration locale directly since
            // Locale.toLanguageTag() was added for API level 21 and above.
            return LocaleListCompat.create(conf.locale);
        }
    }

    @RequiresApi(21)
    static class Api21Impl {
        private Api21Impl() {}

        @DoNotInline
        static String toLanguageTag(Locale locale) {
            return locale.toLanguageTag();
        }
    }

    @RequiresApi(24)
    static class Api24Impl {
        private Api24Impl() {}

        @DoNotInline
        static LocaleListCompat getLocales(Configuration configuration) {
            return LocaleListCompat.forLanguageTags(configuration.getLocales().toLanguageTags());
        }
    }

    @RequiresApi(33)
    static class Api33Impl {
        private Api33Impl() {}

        @DoNotInline
        static LocaleList localeManagerGetSystemLocales(Object localeManager) {
            LocaleManager mLocaleManager = (LocaleManager) localeManager;
            return mLocaleManager.getSystemLocales();
        }
    }
}