summaryrefslogtreecommitdiffstats
path: root/mobile/android/base/java/org/mozilla/gecko/BrowserLocaleManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/base/java/org/mozilla/gecko/BrowserLocaleManager.java')
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/BrowserLocaleManager.java439
1 files changed, 0 insertions, 439 deletions
diff --git a/mobile/android/base/java/org/mozilla/gecko/BrowserLocaleManager.java b/mobile/android/base/java/org/mozilla/gecko/BrowserLocaleManager.java
deleted file mode 100644
index c5c041c7a..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserLocaleManager.java
+++ /dev/null
@@ -1,439 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Locale;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.annotation.ReflectionTarget;
-import org.mozilla.gecko.util.GeckoJarReader;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.util.Log;
-
-/**
- * This class manages persistence, application, and otherwise handling of
- * user-specified locales.
- *
- * Of note:
- *
- * * It's a singleton, because its scope extends to that of the application,
- * and definitionally all changes to the locale of the app must go through
- * this.
- * * It's lazy.
- * * It has ties into the Gecko event system, because it has to tell Gecko when
- * to switch locale.
- * * It relies on using the SharedPreferences file owned by the browser (in
- * Fennec's case, "GeckoApp") for performance.
- */
-public class BrowserLocaleManager implements LocaleManager {
- private static final String LOG_TAG = "GeckoLocales";
-
- private static final String EVENT_LOCALE_CHANGED = "Locale:Changed";
- private static final String PREF_LOCALE = "locale";
-
- private static final String FALLBACK_LOCALE_TAG = "en-US";
-
- // These are volatile because we don't impose restrictions
- // over which thread calls our methods.
- private volatile Locale currentLocale;
- private volatile Locale systemLocale = Locale.getDefault();
-
- private final AtomicBoolean inited = new AtomicBoolean(false);
- private boolean systemLocaleDidChange;
- private BroadcastReceiver receiver;
-
- private static final AtomicReference<LocaleManager> instance = new AtomicReference<LocaleManager>();
-
- @ReflectionTarget
- public static LocaleManager getInstance() {
- LocaleManager localeManager = instance.get();
- if (localeManager != null) {
- return localeManager;
- }
-
- localeManager = new BrowserLocaleManager();
- if (instance.compareAndSet(null, localeManager)) {
- return localeManager;
- } else {
- return instance.get();
- }
- }
-
- @Override
- public boolean isEnabled() {
- return AppConstants.MOZ_LOCALE_SWITCHER;
- }
-
- /**
- * Ensure that you call this early in your application startup,
- * and with a context that's sufficiently long-lived (typically
- * the application context).
- *
- * Calling multiple times is harmless.
- */
- @Override
- public void initialize(final Context context) {
- if (!inited.compareAndSet(false, true)) {
- return;
- }
-
- receiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final Locale current = systemLocale;
-
- // We don't trust Locale.getDefault() here, because we make a
- // habit of mutating it! Use the one Android supplies, because
- // that gets regularly reset.
- // The default value of systemLocale is fine, because we haven't
- // yet swizzled Locale during static initialization.
- systemLocale = context.getResources().getConfiguration().locale;
- systemLocaleDidChange = true;
-
- Log.d(LOG_TAG, "System locale changed from " + current + " to " + systemLocale);
- }
- };
- context.registerReceiver(receiver, new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
- }
-
- @Override
- public boolean systemLocaleDidChange() {
- return systemLocaleDidChange;
- }
-
- /**
- * Every time the system gives us a new configuration, it
- * carries the external locale. Fix it.
- */
- @Override
- public void correctLocale(Context context, Resources res, Configuration config) {
- final Locale current = getCurrentLocale(context);
- if (current == null) {
- Log.d(LOG_TAG, "No selected locale. No correction needed.");
- return;
- }
-
- // I know it's tempting to short-circuit here if the config seems to be
- // up-to-date, but the rest is necessary.
-
- config.locale = current;
-
- // The following two lines are heavily commented in case someone
- // decides to chase down performance improvements and decides to
- // question what's going on here.
- // Both lines should be cheap, *but*...
-
- // This is unnecessary for basic string choice, but it almost
- // certainly comes into play when rendering numbers, deciding on RTL,
- // etc. Take it out if you can prove that's not the case.
- Locale.setDefault(current);
-
- // This seems to be a no-op, but every piece of documentation under the
- // sun suggests that it's necessary, and it certainly makes sense.
- res.updateConfiguration(config, null);
- }
-
- /**
- * We can be in one of two states.
- *
- * If the user has not explicitly chosen a Firefox-specific locale, we say
- * we are "mirroring" the system locale.
- *
- * When we are not mirroring, system locale changes do not impact Firefox
- * and are essentially ignored; the user's locale selection is the only
- * thing we care about, and we actively correct incoming configuration
- * changes to reflect the user's chosen locale.
- *
- * By contrast, when we are mirroring, system locale changes cause Firefox
- * to reflect the new system locale, as if the user picked the new locale.
- *
- * If we're currently mirroring the system locale, this method returns the
- * supplied configuration's locale, unless the current activity locale is
- * correct. If we're not currently mirroring, this method updates the
- * configuration object to match the user's currently selected locale, and
- * returns that, unless the current activity locale is correct.
- *
- * If the current activity locale is correct, returns null.
- *
- * The caller is expected to redisplay themselves accordingly.
- *
- * This method is intended to be called from inside
- * <code>onConfigurationChanged(Configuration)</code> as part of a strategy
- * to detect and either apply or undo system locale changes.
- */
- @Override
- public Locale onSystemConfigurationChanged(final Context context, final Resources resources, final Configuration configuration, final Locale currentActivityLocale) {
- if (!isMirroringSystemLocale(context)) {
- correctLocale(context, resources, configuration);
- }
-
- final Locale changed = configuration.locale;
- if (changed.equals(currentActivityLocale)) {
- return null;
- }
-
- return changed;
- }
-
- /**
- * Gecko needs to know the OS locale to compute a useful Accept-Language
- * header. If it changed since last time, send a message to Gecko and
- * persist the new value. If unchanged, returns immediately.
- *
- * @param prefs the SharedPreferences instance to use. Cannot be null.
- * @param osLocale the new locale instance. Safe if null.
- */
- public static void storeAndNotifyOSLocale(final SharedPreferences prefs,
- final Locale osLocale) {
- if (osLocale == null) {
- return;
- }
-
- final String lastOSLocale = prefs.getString("osLocale", null);
- final String osLocaleString = osLocale.toString();
-
- if (osLocaleString.equals(lastOSLocale)) {
- return;
- }
-
- // Store the Java-native form.
- prefs.edit().putString("osLocale", osLocaleString).apply();
-
- // The value we send to Gecko should be a language tag, not
- // a Java locale string.
- final String osLanguageTag = Locales.getLanguageTag(osLocale);
- GeckoAppShell.notifyObservers("Locale:OS", osLanguageTag);
- }
-
- @Override
- public String getAndApplyPersistedLocale(Context context) {
- initialize(context);
-
- final long t1 = android.os.SystemClock.uptimeMillis();
- final String localeCode = getPersistedLocale(context);
- if (localeCode == null) {
- return null;
- }
-
- // Note that we don't tell Gecko about this. We notify Gecko when the
- // locale is set, not when we update Java.
- final String resultant = updateLocale(context, localeCode);
-
- if (resultant == null) {
- // Update the configuration anyway.
- updateConfiguration(context, currentLocale);
- }
-
- final long t2 = android.os.SystemClock.uptimeMillis();
- Log.i(LOG_TAG, "Locale read and update took: " + (t2 - t1) + "ms.");
- return resultant;
- }
-
- /**
- * Returns the set locale if it changed.
- *
- * Always persists and notifies Gecko.
- */
- @Override
- public String setSelectedLocale(Context context, String localeCode) {
- final String resultant = updateLocale(context, localeCode);
-
- // We always persist and notify Gecko, even if nothing seemed to
- // change. This might happen if you're picking a locale that's the same
- // as the current OS locale. The OS locale might change next time we
- // launch, and we need the Gecko pref and persisted locale to have been
- // set by the time that happens.
- persistLocale(context, localeCode);
-
- // Tell Gecko.
- GeckoAppShell.notifyObservers(EVENT_LOCALE_CHANGED, Locales.getLanguageTag(getCurrentLocale(context)));
-
- return resultant;
- }
-
- @Override
- public void resetToSystemLocale(Context context) {
- // Wipe the pref.
- final SharedPreferences settings = getSharedPreferences(context);
- settings.edit().remove(PREF_LOCALE).apply();
-
- // Apply the system locale.
- updateLocale(context, systemLocale);
-
- // Tell Gecko.
- GeckoAppShell.notifyObservers(EVENT_LOCALE_CHANGED, "");
- }
-
- /**
- * This is public to allow for an activity to force the
- * current locale to be applied if necessary (e.g., when
- * a new activity launches).
- */
- @Override
- public void updateConfiguration(Context context, Locale locale) {
- Resources res = context.getResources();
- Configuration config = res.getConfiguration();
-
- // We should use setLocale, but it's unexpectedly missing
- // on real devices.
- config.locale = locale;
- res.updateConfiguration(config, null);
- }
-
- private SharedPreferences getSharedPreferences(Context context) {
- return GeckoSharedPrefs.forApp(context);
- }
-
- /**
- * @return the persisted locale in Java format: "en_US".
- */
- private String getPersistedLocale(Context context) {
- final SharedPreferences settings = getSharedPreferences(context);
- final String locale = settings.getString(PREF_LOCALE, "");
-
- if ("".equals(locale)) {
- return null;
- }
- return locale;
- }
-
- private void persistLocale(Context context, String localeCode) {
- final SharedPreferences settings = getSharedPreferences(context);
- settings.edit().putString(PREF_LOCALE, localeCode).apply();
- }
-
- @Override
- public Locale getCurrentLocale(Context context) {
- if (currentLocale != null) {
- return currentLocale;
- }
-
- final String current = getPersistedLocale(context);
- if (current == null) {
- return null;
- }
- return currentLocale = Locales.parseLocaleCode(current);
- }
-
- /**
- * Updates the Java locale and the Android configuration.
- *
- * Returns the persisted locale if it differed.
- *
- * Does not notify Gecko.
- *
- * @param localeCode a locale string in Java format: "en_US".
- * @return if it differed, a locale string in Java format: "en_US".
- */
- private String updateLocale(Context context, String localeCode) {
- // Fast path.
- final Locale defaultLocale = Locale.getDefault();
- if (defaultLocale.toString().equals(localeCode)) {
- return null;
- }
-
- final Locale locale = Locales.parseLocaleCode(localeCode);
-
- return updateLocale(context, locale);
- }
-
- /**
- * @return the Java locale string: e.g., "en_US".
- */
- private String updateLocale(Context context, final Locale locale) {
- // Fast path.
- if (Locale.getDefault().equals(locale)) {
- return null;
- }
-
- Locale.setDefault(locale);
- currentLocale = locale;
-
- // Update resources.
- updateConfiguration(context, locale);
-
- return locale.toString();
- }
-
- private boolean isMirroringSystemLocale(final Context context) {
- return getPersistedLocale(context) == null;
- }
-
- /**
- * Examines <code>multilocale.json</code>, returning the included list of
- * locale codes.
- *
- * If <code>multilocale.json</code> is not present, returns
- * <code>null</code>. In that case, consider {@link #getFallbackLocaleTag()}.
- *
- * multilocale.json currently looks like this:
- *
- * <code>
- * {"locales": ["en-US", "be", "ca", "cs", "da", "de", "en-GB",
- * "en-ZA", "es-AR", "es-ES", "es-MX", "et", "fi",
- * "fr", "ga-IE", "hu", "id", "it", "ja", "ko",
- * "lt", "lv", "nb-NO", "nl", "pl", "pt-BR",
- * "pt-PT", "ro", "ru", "sk", "sl", "sv-SE", "th",
- * "tr", "uk", "zh-CN", "zh-TW", "en-US"]}
- * </code>
- */
- public static Collection<String> getPackagedLocaleTags(final Context context) {
- final String resPath = "res/multilocale.json";
- final String jarURL = GeckoJarReader.getJarURL(context, resPath);
-
- final String contents = GeckoJarReader.getText(context, jarURL);
- if (contents == null) {
- // GeckoJarReader logs and swallows exceptions.
- return null;
- }
-
- try {
- final JSONObject multilocale = new JSONObject(contents);
- final JSONArray locales = multilocale.getJSONArray("locales");
- if (locales == null) {
- Log.e(LOG_TAG, "No 'locales' array in multilocales.json!");
- return null;
- }
-
- final Set<String> out = new HashSet<String>(locales.length());
- for (int i = 0; i < locales.length(); ++i) {
- // If any item in the array is invalid, this will throw,
- // and the entire clause will fail, being caught below
- // and returning null.
- out.add(locales.getString(i));
- }
-
- return out;
- } catch (JSONException e) {
- Log.e(LOG_TAG, "Unable to parse multilocale.json.", e);
- return null;
- }
- }
-
- /**
- * @return the single default locale baked into this application.
- * Applicable when there is no multilocale.json present.
- */
- @SuppressWarnings("static-method")
- public String getFallbackLocaleTag() {
- return FALLBACK_LOCALE_TAG;
- }
-}