mobile/android/base/BrowserLocaleManager.java

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
     2  * This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 package org.mozilla.gecko;
     8 import android.content.BroadcastReceiver;
     9 import android.content.Context;
    10 import android.content.Intent;
    11 import android.content.IntentFilter;
    12 import android.content.SharedPreferences;
    13 import android.content.res.Configuration;
    14 import android.content.res.Resources;
    15 import android.util.Log;
    17 import java.util.Locale;
    18 import java.util.concurrent.atomic.AtomicBoolean;
    19 import java.util.concurrent.atomic.AtomicReference;
    21 /**
    22  * This class manages persistence, application, and otherwise handling of
    23  * user-specified locales.
    24  *
    25  * Of note:
    26  *
    27  * * It's a singleton, because its scope extends to that of the application,
    28  *   and definitionally all changes to the locale of the app must go through
    29  *   this.
    30  * * It's lazy.
    31  * * It has ties into the Gecko event system, because it has to tell Gecko when
    32  *   to switch locale.
    33  * * It relies on using the SharedPreferences file owned by the browser (in
    34  *   Fennec's case, "GeckoApp") for performance.
    35  */
    36 public class BrowserLocaleManager implements LocaleManager {
    37     private static final String LOG_TAG = "GeckoLocales";
    39     private static final String EVENT_LOCALE_CHANGED = "Locale:Changed";
    40     private static final String PREF_LOCALE = "locale";
    42     // This is volatile because we don't impose restrictions
    43     // over which thread calls our methods.
    44     private volatile Locale currentLocale = null;
    46     private AtomicBoolean inited = new AtomicBoolean(false);
    47     private boolean systemLocaleDidChange = false;
    48     private BroadcastReceiver receiver;
    50     private static AtomicReference<LocaleManager> instance = new AtomicReference<LocaleManager>();
    52     public static LocaleManager getInstance() {
    53         LocaleManager localeManager = instance.get();
    54         if (localeManager != null) {
    55             return localeManager;
    56         }
    58         localeManager = new BrowserLocaleManager();
    59         if (instance.compareAndSet(null, localeManager)) {
    60             return localeManager;
    61         } else {
    62             return instance.get();
    63         }
    64     }
    66     /**
    67      * Gecko uses locale codes like "es-ES", whereas a Java {@link Locale}
    68      * stringifies as "es_ES".
    69      *
    70      * This method approximates the Java 7 method <code>Locale#toLanguageTag()</code>.
    71      *
    72      * @return a locale string suitable for passing to Gecko.
    73      */
    74     public static String getLanguageTag(final Locale locale) {
    75         // If this were Java 7:
    76         // return locale.toLanguageTag();
    78         String language = locale.getLanguage();  // Can, but should never be, an empty string.
    79         // Modernize certain language codes.
    80         if (language.equals("iw")) {
    81             language = "he";
    82         } else if (language.equals("in")) {
    83             language = "id";
    84         } else if (language.equals("ji")) {
    85             language = "yi";
    86         }
    88         String country = locale.getCountry();    // Can be an empty string.
    89         if (country.equals("")) {
    90             return language;
    91         }
    92         return language + "-" + country;
    93     }
    95     private static Locale parseLocaleCode(final String localeCode) {
    96         int index;
    97         if ((index = localeCode.indexOf('-')) != -1 ||
    98             (index = localeCode.indexOf('_')) != -1) {
    99             final String langCode = localeCode.substring(0, index);
   100             final String countryCode = localeCode.substring(index + 1);
   101             return new Locale(langCode, countryCode);
   102         } else {
   103             return new Locale(localeCode);
   104         }
   105     }
   107     /**
   108      * Ensure that you call this early in your application startup,
   109      * and with a context that's sufficiently long-lived (typically
   110      * the application context).
   111      *
   112      * Calling multiple times is harmless.
   113      */
   114     @Override
   115     public void initialize(final Context context) {
   116         if (!inited.compareAndSet(false, true)) {
   117             return;
   118         }
   120         receiver = new BroadcastReceiver() {
   121             @Override
   122             public void onReceive(Context context, Intent intent) {
   123                 systemLocaleDidChange = true;
   124             }
   125         };
   126         context.registerReceiver(receiver, new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
   127     }
   129     @Override
   130     public boolean systemLocaleDidChange() {
   131         return systemLocaleDidChange;
   132     }
   134     /**
   135      * Every time the system gives us a new configuration, it
   136      * carries the external locale. Fix it.
   137      */
   138     @Override
   139     public void correctLocale(Context context, Resources res, Configuration config) {
   140         final Locale current = getCurrentLocale(context);
   141         if (current == null) {
   142             return;
   143         }
   145         // I know it's tempting to short-circuit here if the config seems to be
   146         // up-to-date, but the rest is necessary.
   148         config.locale = current;
   150         // The following two lines are heavily commented in case someone
   151         // decides to chase down performance improvements and decides to
   152         // question what's going on here.
   153         // Both lines should be cheap, *but*...
   155         // This is unnecessary for basic string choice, but it almost
   156         // certainly comes into play when rendering numbers, deciding on RTL,
   157         // etc. Take it out if you can prove that's not the case.
   158         Locale.setDefault(current);
   160         // This seems to be a no-op, but every piece of documentation under the
   161         // sun suggests that it's necessary, and it certainly makes sense.
   162         res.updateConfiguration(config, res.getDisplayMetrics());
   163     }
   165     @Override
   166     public String getAndApplyPersistedLocale(Context context) {
   167         initialize(context);
   169         final long t1 = android.os.SystemClock.uptimeMillis();
   170         final String localeCode = getPersistedLocale(context);
   171         if (localeCode == null) {
   172             return null;
   173         }
   175         // Note that we don't tell Gecko about this. We notify Gecko when the
   176         // locale is set, not when we update Java.
   177         final String resultant = updateLocale(context, localeCode);
   179         if (resultant == null) {
   180             // Update the configuration anyway.
   181             updateConfiguration(context, currentLocale);
   182         }
   184         final long t2 = android.os.SystemClock.uptimeMillis();
   185         Log.i(LOG_TAG, "Locale read and update took: " + (t2 - t1) + "ms.");
   186         return resultant;
   187     }
   189     /**
   190      * Returns the set locale if it changed.
   191      *
   192      * Always persists and notifies Gecko.
   193      */
   194     @Override
   195     public String setSelectedLocale(Context context, String localeCode) {
   196         final String resultant = updateLocale(context, localeCode);
   198         // We always persist and notify Gecko, even if nothing seemed to
   199         // change. This might happen if you're picking a locale that's the same
   200         // as the current OS locale. The OS locale might change next time we
   201         // launch, and we need the Gecko pref and persisted locale to have been
   202         // set by the time that happens.
   203         persistLocale(context, localeCode);
   205         // Tell Gecko.
   206         GeckoEvent ev = GeckoEvent.createBroadcastEvent(EVENT_LOCALE_CHANGED, BrowserLocaleManager.getLanguageTag(getCurrentLocale(context)));
   207         GeckoAppShell.sendEventToGecko(ev);
   209         return resultant;
   210     }
   212     /**
   213      * This is public to allow for an activity to force the
   214      * current locale to be applied if necessary (e.g., when
   215      * a new activity launches).
   216      */
   217     @Override
   218     public void updateConfiguration(Context context, Locale locale) {
   219         Resources res = context.getResources();
   220         Configuration config = res.getConfiguration();
   221         config.locale = locale;
   222         res.updateConfiguration(config, res.getDisplayMetrics());
   223     }
   225     private SharedPreferences getSharedPreferences(Context context) {
   226         return GeckoSharedPrefs.forApp(context);
   227     }
   229     private String getPersistedLocale(Context context) {
   230         final SharedPreferences settings = getSharedPreferences(context);
   231         final String locale = settings.getString(PREF_LOCALE, "");
   233         if ("".equals(locale)) {
   234             return null;
   235         }
   236         return locale;
   237     }
   239     private void persistLocale(Context context, String localeCode) {
   240         final SharedPreferences settings = getSharedPreferences(context);
   241         settings.edit().putString(PREF_LOCALE, localeCode).commit();
   242     }
   244     private Locale getCurrentLocale(Context context) {
   245         if (currentLocale != null) {
   246             return currentLocale;
   247         }
   249         final String current = getPersistedLocale(context);
   250         if (current == null) {
   251             return null;
   252         }
   253         return currentLocale = parseLocaleCode(current);
   254     }
   256     /**
   257      * Updates the Java locale and the Android configuration.
   258      *
   259      * Returns the persisted locale if it differed.
   260      *
   261      * Does not notify Gecko.
   262      */
   263     private String updateLocale(Context context, String localeCode) {
   264         // Fast path.
   265         final Locale defaultLocale = Locale.getDefault();
   266         if (defaultLocale.toString().equals(localeCode)) {
   267             return null;
   268         }
   270         final Locale locale = parseLocaleCode(localeCode);
   272         // Fast path.
   273         if (defaultLocale.equals(locale)) {
   274             return null;
   275         }
   277         Locale.setDefault(locale);
   278         currentLocale = locale;
   280         // Update resources.
   281         updateConfiguration(context, locale);
   283         return locale.toString();
   284     }
   285 }

mercurial