Bug 936756 - Switch locales via pref, not via system locale setting. r=mfinkle,sriram (relanded as one patch)

This commit is contained in:
Richard Newman 2013-12-03 15:05:43 -08:00
parent ddc7ad74e3
commit c34e10b816
19 changed files with 552 additions and 227 deletions

View File

@ -124,7 +124,7 @@ abstract public class BrowserApp extends GeckoApp
private static final int GECKO_TOOLS_MENU = -1;
private static final int ADDON_MENU_OFFSET = 1000;
private class MenuItemInfo {
private static class MenuItemInfo {
public int id;
public String label;
public String icon;
@ -133,6 +133,7 @@ abstract public class BrowserApp extends GeckoApp
public boolean enabled = true;
public boolean visible = true;
public int parent;
public boolean added = false; // So we can re-add after a locale change.
}
// The types of guest mdoe dialogs we show
@ -1639,6 +1640,20 @@ abstract public class BrowserApp extends GeckoApp
showHomePagerWithAnimator(page, null);
}
@Override
public void onLocaleReady(final String locale) {
super.onLocaleReady(locale);
if (mHomePager != null) {
// Blow it away and rebuild it with the right strings.
mHomePager.redisplay(getSupportFragmentManager());
}
if (mMenu != null) {
mMenu.clear();
onCreateOptionsMenu(mMenu);
}
}
private void showHomePagerWithAnimator(HomePager.Page page, PropertyAnimator animator) {
if (isHomePagerVisible()) {
return;
@ -1811,7 +1826,7 @@ abstract public class BrowserApp extends GeckoApp
}
}
private Menu findParentMenu(Menu menu, MenuItem item) {
private static Menu findParentMenu(Menu menu, MenuItem item) {
final int itemId = item.getItemId();
final int count = (menu != null) ? menu.size() : 0;
@ -1831,54 +1846,58 @@ abstract public class BrowserApp extends GeckoApp
return null;
}
private void addAddonMenuItem(final MenuItemInfo info) {
if (mMenu == null) {
if (mAddonMenuItemsCache == null)
mAddonMenuItemsCache = new Vector<MenuItemInfo>();
mAddonMenuItemsCache.add(info);
return;
}
Menu menu;
/**
* Add the provided item to the provided menu, which should be
* the root (mMenu).
*/
private void addAddonMenuItemToMenu(final Menu menu, final MenuItemInfo info) {
info.added = true;
final Menu destination;
if (info.parent == 0) {
menu = mMenu;
destination = menu;
} else if (info.parent == GECKO_TOOLS_MENU) {
MenuItem tools = mMenu.findItem(R.id.tools);
menu = tools != null ? tools.getSubMenu() : mMenu;
MenuItem tools = menu.findItem(R.id.tools);
destination = tools != null ? tools.getSubMenu() : menu;
} else {
MenuItem parent = mMenu.findItem(info.parent);
if (parent == null)
MenuItem parent = menu.findItem(info.parent);
if (parent == null) {
return;
}
Menu parentMenu = findParentMenu(mMenu, parent);
Menu parentMenu = findParentMenu(menu, parent);
if (!parent.hasSubMenu()) {
parentMenu.removeItem(parent.getItemId());
menu = parentMenu.addSubMenu(Menu.NONE, parent.getItemId(), Menu.NONE, parent.getTitle());
if (parent.getIcon() != null)
((SubMenu) menu).getItem().setIcon(parent.getIcon());
destination = parentMenu.addSubMenu(Menu.NONE, parent.getItemId(), Menu.NONE, parent.getTitle());
if (parent.getIcon() != null) {
((SubMenu) destination).getItem().setIcon(parent.getIcon());
}
} else {
menu = parent.getSubMenu();
destination = parent.getSubMenu();
}
}
MenuItem item = menu.add(Menu.NONE, info.id, Menu.NONE, info.label);
MenuItem item = destination.add(Menu.NONE, info.id, Menu.NONE, info.label);
item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
Log.i(LOGTAG, "menu item clicked");
Log.i(LOGTAG, "Menu item clicked");
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Menu:Clicked", Integer.toString(info.id - ADDON_MENU_OFFSET)));
return true;
}
});
if (info.icon != null) {
if (info.icon == null) {
item.setIcon(R.drawable.ic_menu_addons_filler);
} else {
final int id = info.id;
BitmapUtils.getDrawable(this, info.icon, new BitmapUtils.BitmapLoader() {
@Override
public void onBitmapFound(Drawable d) {
MenuItem item = mMenu.findItem(id);
// TODO: why do we re-find the item?
MenuItem item = destination.findItem(id);
if (item == null) {
return;
}
@ -1889,8 +1908,6 @@ abstract public class BrowserApp extends GeckoApp
item.setIcon(d);
}
});
} else {
item.setIcon(R.drawable.ic_menu_addons_filler);
}
item.setCheckable(info.checkable);
@ -1899,6 +1916,24 @@ abstract public class BrowserApp extends GeckoApp
item.setVisible(info.visible);
}
private void addAddonMenuItem(final MenuItemInfo info) {
if (mAddonMenuItemsCache == null) {
mAddonMenuItemsCache = new Vector<MenuItemInfo>();
}
// Mark it as added if the menu was ready.
info.added = (mMenu != null);
// Always cache so we can rebuild after a locale switch.
mAddonMenuItemsCache.add(info);
if (mMenu == null) {
return;
}
addAddonMenuItemToMenu(mMenu, info);
}
private void removeAddonMenuItem(int id) {
// Remove add-on menu item from cache, if available.
if (mAddonMenuItemsCache != null && !mAddonMenuItemsCache.isEmpty()) {
@ -1928,13 +1963,15 @@ abstract public class BrowserApp extends GeckoApp
item.checked = options.optBoolean("checked", item.checked);
item.enabled = options.optBoolean("enabled", item.enabled);
item.visible = options.optBoolean("visible", item.visible);
item.added = (mMenu != null);
break;
}
}
}
if (mMenu == null)
if (mMenu == null) {
return;
}
MenuItem menuItem = mMenu.findItem(id);
if (menuItem != null) {
@ -1948,22 +1985,23 @@ abstract public class BrowserApp extends GeckoApp
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Sets mMenu = menu.
super.onCreateOptionsMenu(menu);
// Inform the menu about the action-items bar.
if (menu instanceof GeckoMenu && HardwareUtils.isTablet())
if (menu instanceof GeckoMenu &&
HardwareUtils.isTablet()) {
((GeckoMenu) menu).setActionItemBarPresenter(mBrowserToolbar);
}
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.browser_app_menu, mMenu);
// Add add-on menu items if any.
// Add add-on menu items, if any exist.
if (mAddonMenuItemsCache != null && !mAddonMenuItemsCache.isEmpty()) {
for (MenuItemInfo item : mAddonMenuItemsCache) {
addAddonMenuItem(item);
addAddonMenuItemToMenu(mMenu, item);
}
mAddonMenuItemsCache.clear();
}
// Action providers are available only ICS+.

View File

@ -1,6 +1,15 @@
package org.mozilla.gecko;
import android.content.Context;
/* -*- 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/. */
interface ContextGetter {
Context getContext();
package org.mozilla.gecko;
import android.content.Context;
import android.content.SharedPreferences;
public interface ContextGetter {
Context getContext();
SharedPreferences getSharedPreferences();
}

View File

@ -12,6 +12,15 @@ public class GeckoActivity extends FragmentActivity implements GeckoActivityStat
// has this activity recently started another Gecko activity?
private boolean mGeckoActivityOpened = false;
/**
* Display any resources that show strings or encompass locale-specific
* representations.
*
* onLocaleReady must always be called on the UI thread.
*/
public void onLocaleReady(final String locale) {
}
@Override
public void onPause() {
super.onPause();

View File

@ -126,12 +126,18 @@ import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
abstract public class GeckoApp
extends GeckoActivity
implements GeckoEventListener, SensorEventListener, LocationListener,
Tabs.OnTabsChangedListener, GeckoEventResponder,
GeckoMenu.Callback, GeckoMenu.MenuPresenter,
ContextGetter, GeckoAppShell.GeckoInterface
public abstract class GeckoApp
extends GeckoActivity
implements
ContextGetter,
GeckoAppShell.GeckoInterface,
GeckoEventListener,
GeckoEventResponder,
GeckoMenu.Callback,
GeckoMenu.MenuPresenter,
LocationListener,
SensorEventListener,
Tabs.OnTabsChangedListener
{
private static final String LOGTAG = "GeckoApp";
@ -233,10 +239,20 @@ abstract public class GeckoApp
void focusChrome() { }
@Override
public Context getContext() {
return sAppContext;
}
@Override
public SharedPreferences getSharedPreferences() {
return GeckoApp.getAppSharedPreferences();
}
public static SharedPreferences getAppSharedPreferences() {
return GeckoApp.sAppContext.getSharedPreferences(GeckoApp.PREFS_NAME, 0);
}
public Activity getActivity() {
return this;
}
@ -258,10 +274,6 @@ abstract public class GeckoApp
return this;
}
public static SharedPreferences getAppSharedPreferences() {
return GeckoApp.sAppContext.getSharedPreferences(PREFS_NAME, 0);
}
public View getCameraView() {
return mCameraView;
}
@ -703,6 +715,8 @@ abstract public class GeckoApp
GeckoAppShell.openUriExternal(message.optString("url"),
message.optString("mime"), message.optString("packageName"),
message.optString("className"), message.optString("action"), message.optString("title"));
} else if (event.equals("Locale:Set")) {
setLocale(message.getString("locale"));
}
} catch (Exception e) {
Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
@ -1179,7 +1193,7 @@ abstract public class GeckoApp
}
BrowserDB.initialize(getProfile().getName());
((GeckoApplication)getApplication()).initialize();
((GeckoApplication) getApplication()).initialize();
sAppContext = this;
GeckoAppShell.setContextGetter(this);
@ -1193,12 +1207,13 @@ abstract public class GeckoApp
Log.e(LOGTAG, "Exception starting favicon cache. Corrupt resources?", e);
}
// When we detect a locale change, we need to restart Gecko, which
// actually means restarting the entire application. This logic should
// actually be handled elsewhere since GeckoApp may not be alive to
// handle this event if "Don't keep activities" is enabled (filed as
// bug 889082).
if (((GeckoApplication)getApplication()).needsRestart()) {
// Did the OS locale change while we were backgrounded? If so,
// we need to die so that Gecko will re-init add-ons that touch
// the UI.
// This is using a sledgehammer to crack a nut, but it'll do for
// now.
if (LocaleManager.systemLocaleDidChange()) {
Log.i(LOGTAG, "System locale changed. Restarting.");
doRestart();
System.exit(0);
return;
@ -1279,6 +1294,11 @@ abstract public class GeckoApp
public void run() {
final SharedPreferences prefs = GeckoApp.getAppSharedPreferences();
// Wait until now to set this, because we'd rather throw an exception than
// have a caller of LocaleManager regress startup.
LocaleManager.setContextGetter(GeckoApp.this);
LocaleManager.initialize();
SessionInformation previousSession = SessionInformation.fromSharedPrefs(prefs);
if (previousSession.wasKilled()) {
Telemetry.HistogramAdd("FENNEC_WAS_KILLED", 1);
@ -1299,17 +1319,29 @@ abstract public class GeckoApp
final String profilePath = getProfile().getDir().getAbsolutePath();
final EventDispatcher dispatcher = GeckoAppShell.getEventDispatcher();
Log.i(LOGTAG, "Creating BrowserHealthRecorder.");
final String osLocale = Locale.getDefault().toString();
Log.d(LOGTAG, "Locale is " + osLocale);
// Replace the duplicate `osLocale` argument when we support switchable
// application locales.
final String osLocale = Locale.getDefault().toString();
String appLocale = LocaleManager.getAndApplyPersistedLocale();
Log.d(LOGTAG, "OS locale is " + osLocale + ", app locale is " + appLocale);
if (appLocale == null) {
appLocale = osLocale;
}
mHealthRecorder = new BrowserHealthRecorder(GeckoApp.this,
profilePath,
dispatcher,
osLocale,
osLocale, // Placeholder.
appLocale,
previousSession);
final String uiLocale = appLocale;
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
GeckoApp.this.onLocaleReady(uiLocale);
}
});
}
});
@ -1317,6 +1349,30 @@ abstract public class GeckoApp
NotificationHelper.init(getApplicationContext());
}
/**
* At this point, the resource system and the rest of the browser are
* aware of the locale.
*
* Now we can display strings!
*/
@Override
public void onLocaleReady(final String locale) {
if (!ThreadUtils.isOnUiThread()) {
throw new RuntimeException("onLocaleReady must always be called from the UI thread.");
}
// The URL bar hint needs to be populated.
TextView urlBar = (TextView) findViewById(R.id.url_bar_title);
if (urlBar == null) {
return;
}
final String hint = getResources().getString(R.string.url_bar_default_text);
urlBar.setHint(hint);
// Allow onConfigurationChanged to take care of the rest.
onConfigurationChanged(getResources().getConfiguration());
}
protected void initializeChrome() {
mDoorHangerPopup = new DoorHangerPopup(this, null);
mPluginContainer = (AbsoluteLayout) findViewById(R.id.plugin_container);
@ -1526,6 +1582,7 @@ abstract public class GeckoApp
registerEventListener("Contact:Add");
registerEventListener("Intent:Open");
registerEventListener("Intent:GetHandlers");
registerEventListener("Locale:Set");
if (SmsManager.getInstance() != null) {
SmsManager.getInstance().start();
@ -1579,23 +1636,6 @@ abstract public class GeckoApp
// intervals.
GeckoPreferences.broadcastAnnouncementsPref(context);
GeckoPreferences.broadcastHealthReportUploadPref(context);
/*
XXXX see Bug 635342.
We want to disable this code if possible. It is about 145ms in runtime.
If this code ever becomes live again, you'll need to chain the
new locale into BrowserHealthRecorder correctly. See
GeckoAppShell.setSelectedLocale.
We pass the OS locale into the BHR constructor: we need to grab
that *before* we modify the current locale!
SharedPreferences settings = getPreferences(Activity.MODE_PRIVATE);
String localeCode = settings.getString(getPackageName() + ".locale", "");
if (localeCode != null && localeCode.length() > 0)
GeckoAppShell.setSelectedLocale(localeCode);
*/
if (!GeckoThread.checkLaunchState(GeckoThread.LaunchState.Launched)) {
return;
}
@ -2140,6 +2180,8 @@ abstract public class GeckoApp
@Override
public void onConfigurationChanged(Configuration newConfig) {
Log.d(LOGTAG, "onConfigurationChanged: " + newConfig.locale);
LocaleManager.correctLocale(getResources(), newConfig);
super.onConfigurationChanged(newConfig);
if (mOrientation != newConfig.orientation) {
@ -2725,4 +2767,52 @@ abstract public class GeckoApp
}
return versionCode;
}
// FHR reason code for a session end prior to a restart for a
// locale change.
private static final String SESSION_END_LOCALE_CHANGED = "L";
/**
* Use LocaleManager to change our persisted and current locales,
* and poke BrowserHealthRecorder to tell it of our changed state.
*/
private void setLocale(final String locale) {
if (locale == null) {
return;
}
final String resultant = LocaleManager.setSelectedLocale(locale);
if (resultant == null) {
return;
}
final BrowserHealthRecorder rec = mHealthRecorder;
if (rec == null) {
return;
}
final boolean startNewSession = true;
final boolean shouldRestart = false;
rec.onAppLocaleChanged(resultant);
rec.onEnvironmentChanged(startNewSession, SESSION_END_LOCALE_CHANGED);
if (!shouldRestart) {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
GeckoApp.this.onLocaleReady(resultant);
}
});
return;
}
// Do this in the background so that the health recorder has its
// time to finish.
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
GeckoApp.this.doRestart();
GeckoApp.this.finish();
}
});
}
}

View File

@ -229,8 +229,7 @@ public class GeckoAppShell
}
if (e instanceof OutOfMemoryError) {
SharedPreferences prefs =
getContext().getSharedPreferences(GeckoApp.PREFS_NAME, 0);
SharedPreferences prefs = getSharedPreferences();
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(GeckoApp.PREFS_OOM_EXCEPTION, true);
editor.commit();
@ -1587,45 +1586,6 @@ public class GeckoAppShell
}
}
@WrapElementForJNI
public static void setSelectedLocale(String localeCode) {
/* Bug 713464: This method is still called from Gecko side.
Earlier we had an option to run Firefox in a language other than system's language.
However, this is not supported as of now.
Gecko resets the locale to en-US by calling this function with an empty string.
This affects GeckoPreferences activity in multi-locale builds.
N.B., if this code ever becomes live again, you need to hook it up to locale
recording in BrowserHealthRecorder: we track the current app and OS locales
as part of the recorded environment.
See similar note in GeckoApp.java for the startup path.
//We're not using this, not need to save it (see bug 635342)
SharedPreferences settings =
getContext().getPreferences(Activity.MODE_PRIVATE);
settings.edit().putString(getContext().getPackageName() + ".locale",
localeCode).commit();
Locale locale;
int index;
if ((index = localeCode.indexOf('-')) != -1 ||
(index = localeCode.indexOf('_')) != -1) {
String langCode = localeCode.substring(0, index);
String countryCode = localeCode.substring(index + 1);
locale = new Locale(langCode, countryCode);
} else {
locale = new Locale(localeCode);
}
Locale.setDefault(locale);
Resources res = getContext().getBaseContext().getResources();
Configuration config = res.getConfiguration();
config.locale = locale;
res.updateConfiguration(config, res.getDisplayMetrics());
*/
}
@WrapElementForJNI(stubName = "GetSystemColoursWrapper")
public static int[] getSystemColors() {
// attrsAppearance[] must correspond to AndroidSystemColors structure in android/AndroidBridge.h
@ -2146,6 +2106,13 @@ public class GeckoAppShell
sContextGetter = cg;
}
public static SharedPreferences getSharedPreferences() {
if (sContextGetter == null) {
throw new IllegalStateException("No ContextGetter; cannot fetch prefs.");
}
return sContextGetter.getSharedPreferences();
}
public interface AppStateListener {
public void onPause();
public void onResume();

View File

@ -12,20 +12,39 @@ import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.util.ThreadUtils;
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.util.Log;
public class GeckoApplication extends Application {
private boolean mInited;
private boolean mInBackground;
private boolean mPausedGecko;
private boolean mNeedsRestart;
private LightweightTheme mLightweightTheme;
/**
* We need to do locale work here, because we need to intercept
* each hit to onConfigurationChanged.
*/
@Override
public void onConfigurationChanged(Configuration config) {
Log.d("GeckoApplication", "onConfigurationChanged: " + config.locale +
", background: " + mInBackground);
// Do nothing if we're in the background. It'll simply cause a loop
// (Bug 936756 Comment 11), and it's not necessary.
if (mInBackground) {
super.onConfigurationChanged(config);
return;
}
// Otherwise, correct the locale. This catches some cases that GeckoApp
// doesn't get a chance to.
LocaleManager.correctLocale(getResources(), config);
super.onConfigurationChanged(config);
}
protected void initialize() {
if (mInited)
return;
@ -43,14 +62,6 @@ public class GeckoApplication extends Application {
GeckoNetworkManager.getInstance().init(getApplicationContext());
MemoryMonitor.getInstance().init(getApplicationContext());
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
mNeedsRestart = true;
}
};
registerReceiver(receiver, new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
mInited = true;
}
@ -90,10 +101,6 @@ public class GeckoApplication extends Application {
mInBackground = false;
}
protected boolean needsRestart() {
return mNeedsRestart;
}
@Override
public void onCreate() {
HardwareUtils.init(getApplicationContext());

View File

@ -19,6 +19,7 @@ import org.json.JSONObject;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.os.Handler;
import android.util.AttributeSet;
@ -30,6 +31,7 @@ import java.util.List;
public class GeckoView extends LayerView
implements GeckoEventListener, ContextGetter {
private static final String DEFAULT_SHARED_PREFERENCES_FILE = "GeckoView";
private static final String LOGTAG = "GeckoView";
private ChromeDelegate mChromeDelegate;
@ -305,6 +307,14 @@ public class GeckoView extends LayerView
return GeckoAppShell.getGeckoInterface();
}
protected String getSharedPreferencesFile() {
return DEFAULT_SHARED_PREFERENCES_FILE;
}
public SharedPreferences getSharedPreferences() {
return getContext().getSharedPreferences(getSharedPreferencesFile(), 0);
}
/**
* Wrapper for a browser in the GeckoView container. Associated with a browser
* element in the Gecko system.

View File

@ -0,0 +1,215 @@
/* -*- 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 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;
import java.util.Locale;
/**
* 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 LocaleManager {
private static final String LOG_TAG = "GeckoLocales";
// These are both volatile because we don't impose restrictions
// over which thread calls our methods.
private static volatile ContextGetter getter = null;
private static volatile Locale currentLocale = null;
private static volatile boolean inited = false;
private static boolean systemLocaleDidChange = false;
private static BroadcastReceiver receiver;
public static void setContextGetter(ContextGetter getter) {
LocaleManager.getter = getter;
}
public static void initialize() {
if (inited) {
return;
}
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
systemLocaleDidChange = true;
}
};
getContext().registerReceiver(receiver, new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
inited = true;
}
public static boolean systemLocaleDidChange() {
return systemLocaleDidChange;
}
private static Context getContext() {
if (getter == null) {
throw new IllegalStateException("No ContextGetter; cannot fetch context.");
}
return getter.getContext();
}
private static SharedPreferences getSharedPreferences() {
if (getter == null) {
throw new IllegalStateException("No ContextGetter; cannot fetch prefs.", new RuntimeException("No prefs."));
}
return getter.getSharedPreferences();
}
/**
* Every time the system gives us a new configuration, it
* carries the external locale. Fix it.
*/
public static void correctLocale(Resources res, Configuration config) {
Locale current = getCurrentLocale();
if (current == null) {
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, res.getDisplayMetrics());
}
private static Locale parseLocaleCode(final String localeCode) {
int index;
if ((index = localeCode.indexOf('-')) != -1 ||
(index = localeCode.indexOf('_')) != -1) {
final String langCode = localeCode.substring(0, index);
final String countryCode = localeCode.substring(index + 1);
return new Locale(langCode, countryCode);
} else {
return new Locale(localeCode);
}
}
public static Locale getCurrentLocale() {
if (currentLocale != null) {
return currentLocale;
}
final String current = getPersistedLocale();
if (current == null) {
return null;
}
return currentLocale = parseLocaleCode(current);
}
/**
* Returns the persisted locale if it differed from the current.
*/
public static String updateLocale(String localeCode) {
// Fast path.
final Locale defaultLocale = Locale.getDefault();
if (defaultLocale.toString().equals(localeCode)) {
return null;
}
final Locale locale = parseLocaleCode(localeCode);
// Fast path.
if (defaultLocale.equals(locale)) {
return null;
}
Locale.setDefault(locale);
currentLocale = locale;
// Update resources.
Resources res = getContext().getResources();
Configuration config = res.getConfiguration();
config.locale = locale;
res.updateConfiguration(config, res.getDisplayMetrics());
// Tell Gecko.
GeckoEvent ev = GeckoEvent.createBroadcastEvent("Locale:Changed", locale.toString());
GeckoAppShell.sendEventToGecko(ev);
return locale.toString();
}
private static String getPrefName() {
return getContext().getPackageName() + ".locale";
}
public static String getPersistedLocale() {
final SharedPreferences settings = getSharedPreferences();
// N.B., it is expected that any per-profile settings will be
// implemented via SharedPreferences multiplexing in ContextGetter, not
// via profile-annotated preference names.
final String locale = settings.getString(getPrefName(), "");
if ("".equals(locale)) {
return null;
}
return locale;
}
private static void persistLocale(String localeCode) {
final SharedPreferences settings = getSharedPreferences();
settings.edit().putString(getPrefName(), localeCode).commit();
}
public static String getAndApplyPersistedLocale() {
final long t1 = android.os.SystemClock.uptimeMillis();
final String localeCode = getPersistedLocale();
if (localeCode == null) {
return null;
}
updateLocale(localeCode);
final long t2 = android.os.SystemClock.uptimeMillis();
Log.i(LOG_TAG, "Locale read and update took: " + (t2 - t1) + "ms.");
return localeCode;
}
/**
* Returns the set locale if it changed. Always persists.
*/
public static String setSelectedLocale(String localeCode) {
final String resultant = updateLocale(localeCode);
persistLocale(localeCode);
return resultant;
}
}

View File

@ -319,6 +319,7 @@ public class BrowserHealthRecorder implements GeckoEventListener {
}
public void onAppLocaleChanged(String to) {
Log.d(LOG_TAG, "Setting health recorder app locale to " + to);
this.profileCache.beginInitialization();
this.profileCache.setAppLocale(to);
}
@ -349,10 +350,19 @@ public class BrowserHealthRecorder implements GeckoEventListener {
* Invoke this method after calls that mutate the environment.
*
* If this change resulted in a transition between two environments, {@link
* #onEnvironmentTransition(int, int)} will be invoked on the background
* #onEnvironmentTransition(int, int, boolean, String)} will be invoked on the background
* thread.
*/
public synchronized void onEnvironmentChanged() {
onEnvironmentChanged(true, "E");
}
/**
* If `startNewSession` is false, it means no new session should begin
* (e.g., because we're about to restart, and we don't want to create
* an orphan).
*/
public synchronized void onEnvironmentChanged(final boolean startNewSession, final String sessionEndReason) {
final int previousEnv = this.env;
this.env = -1;
try {
@ -374,7 +384,7 @@ public class BrowserHealthRecorder implements GeckoEventListener {
@Override
public void run() {
try {
onEnvironmentTransition(previousEnv, updatedEnv);
onEnvironmentTransition(previousEnv, updatedEnv, startNewSession, sessionEndReason);
} catch (Exception e) {
Log.w(LOG_TAG, "Could not record environment transition.", e);
}
@ -643,7 +653,7 @@ public class BrowserHealthRecorder implements GeckoEventListener {
* Invoked in the background whenever the environment transitions between
* two valid values.
*/
protected void onEnvironmentTransition(int prev, int env) {
protected void onEnvironmentTransition(int prev, int env, boolean startNewSession, String sessionEndReason) {
if (this.state != State.INITIALIZED) {
Log.d(LOG_TAG, "Not initialized: not recording env transition (" + prev + " => " + env + ").");
return;
@ -652,7 +662,12 @@ public class BrowserHealthRecorder implements GeckoEventListener {
final SharedPreferences prefs = GeckoApp.getAppSharedPreferences();
final SharedPreferences.Editor editor = prefs.edit();
recordSessionEnd("E", editor, prev);
recordSessionEnd(sessionEndReason, editor, prev);
if (!startNewSession) {
editor.commit();
return;
}
final SessionInformation newSession = SessionInformation.forRuntimeTransition();
setCurrentSession(newSession);

View File

@ -129,6 +129,11 @@ public class HomePager extends ViewPager {
super.addView(child, index, params);
}
public void redisplay(FragmentManager fm) {
final TabsAdapter adapter = (TabsAdapter) getAdapter();
show(fm, adapter.getCurrentPage(), null);
}
/**
* Loads and initializes the pager.
*
@ -281,6 +286,12 @@ public class HomePager extends ViewPager {
return -1;
}
public Page getCurrentPage() {
int currentItem = getCurrentItem();
TabInfo info = mTabs.get(currentItem);
return info.page;
}
@Override
public int getCount() {
return mTabs.size();

View File

@ -95,7 +95,7 @@ public class TabMenuStrip extends LinearLayout
}
}
// Page scroll animates the drawable and it's bounds from the previous to next child view.
// Page scroll animates the drawable and its bounds from the previous to next child view.
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (mStrip == null) {

View File

@ -10,6 +10,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ActionProvider;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@ -114,7 +115,7 @@ public class GeckoMenu extends ListView
mItems = new ArrayList<GeckoMenuItem>();
mActionItems = new HashMap<GeckoMenuItem, View>();
mActionItemBarPresenter = (DefaultActionItemBar) LayoutInflater.from(context).inflate(R.layout.menu_action_bar, null);
mActionItemBarPresenter = (DefaultActionItemBar) LayoutInflater.from(context).inflate(R.layout.menu_action_bar, null);
}
@Override
@ -219,14 +220,26 @@ public class GeckoMenu extends ListView
public void clear() {
for (GeckoMenuItem menuItem : mItems) {
if (menuItem.hasSubMenu()) {
menuItem.getSubMenu().clear();
SubMenu sub = menuItem.getSubMenu();
if (sub == null) {
continue;
}
try {
sub.clear();
} catch (Exception ex) {
Log.e(LOGTAG, "Couldn't clear submenu.", ex);
}
}
}
mAdapter.clear();
mItems.clear();
/*
* Reinflating the menu will re-add any action items to the toolbar, so
* remove the old ones. This also ensures that any text associated with
* these is switched to the correct locale.
*/
if (mActionItemBarPresenter != null) {
for (View item : mActionItems.values()) {
mActionItemBarPresenter.removeActionItem(item);

View File

@ -142,7 +142,11 @@ public class GeckoMenuItem implements MenuItem {
@Override
public SubMenu getSubMenu() {
return mSubMenu;
// For consistency with hasSubMenu.
if (mActionProvider == null) {
return mSubMenu;
}
return null;
}
@Override

View File

@ -232,6 +232,7 @@ gbjar.sources += [
'JavaAddonManager.java',
'LightweightTheme.java',
'LightweightThemeDrawable.java',
'LocaleManager.java',
'MemoryMonitor.java',
'menu/GeckoMenu.java',
'menu/GeckoMenuInflater.java',

View File

@ -104,7 +104,6 @@
android:textColor="@color/url_bar_title"
android:textColorHint="@color/url_bar_title_hint"
android:gravity="center_vertical|left"
android:hint="@string/url_bar_default_text"
android:layout_gravity="center_vertical"
gecko:autoUpdateTheme="false"/>

View File

@ -274,6 +274,7 @@ var BrowserApp = {
Services.androidBridge.browserApp = this;
Services.obs.addObserver(this, "Locale:Changed", false);
Services.obs.addObserver(this, "Tab:Load", false);
Services.obs.addObserver(this, "Tab:Selected", false);
Services.obs.addObserver(this, "Tab:Closed", false);
@ -410,6 +411,14 @@ var BrowserApp = {
return "";
},
/**
* Pass this a locale string, such as "fr" or "es_ES".
*/
setLocale: function (locale) {
console.log("browser.js: requesting locale set: " + locale);
sendMessageToJava({ type: "Locale:Set", locale: locale });
},
initContextMenu: function ba_initContextMenu() {
// TODO: These should eventually move into more appropriate classes
NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.openInNewTab"),
@ -1496,6 +1505,13 @@ var BrowserApp = {
this.notifyPrefObservers(aData);
break;
case "Locale:Changed":
// TODO: do we need to be more nuanced here -- e.g., checking for the
// OS locale -- or should it always be false on Fennec?
Services.prefs.setBoolPref("intl.locale.matchOS", false);
Services.prefs.setCharPref("general.useragent.locale", aData);
break;
default:
dump('BrowserApp.observe: unexpected topic "' + aTopic + '"\n');
break;

View File

@ -77,7 +77,6 @@ jmethodID GeckoAppShell::jScheduleRestart = 0;
jmethodID GeckoAppShell::jSendMessageWrapper = 0;
jmethodID GeckoAppShell::jSetFullScreen = 0;
jmethodID GeckoAppShell::jSetKeepScreenOn = 0;
jmethodID GeckoAppShell::jSetSelectedLocale = 0;
jmethodID GeckoAppShell::jSetURITitle = 0;
jmethodID GeckoAppShell::jShowAlertNotificationWrapper = 0;
jmethodID GeckoAppShell::jShowFilePickerAsyncWrapper = 0;
@ -158,7 +157,6 @@ void GeckoAppShell::InitStubs(JNIEnv *jEnv) {
jSendMessageWrapper = getStaticMethod("sendMessage", "(Ljava/lang/String;Ljava/lang/String;I)V");
jSetFullScreen = getStaticMethod("setFullScreen", "(Z)V");
jSetKeepScreenOn = getStaticMethod("setKeepScreenOn", "(Z)V");
jSetSelectedLocale = getStaticMethod("setSelectedLocale", "(Ljava/lang/String;)V");
jSetURITitle = getStaticMethod("setUriTitle", "(Ljava/lang/String;Ljava/lang/String;)V");
jShowAlertNotificationWrapper = getStaticMethod("showAlertNotification", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
jShowFilePickerAsyncWrapper = getStaticMethod("showFilePickerAsync", "(Ljava/lang/String;J)V");
@ -2055,35 +2053,6 @@ void GeckoAppShell::SetKeepScreenOn(bool a0) {
env->PopLocalFrame(NULL);
}
void GeckoAppShell::SetSelectedLocale(const nsAString& a0) {
JNIEnv *env = AndroidBridge::GetJNIEnv();
if (!env) {
ALOG_BRIDGE("Aborted: No env - %s", __PRETTY_FUNCTION__);
return;
}
if (env->PushLocalFrame(1) != 0) {
ALOG_BRIDGE("Exceptional exit of: %s", __PRETTY_FUNCTION__);
env->ExceptionDescribe();
env->ExceptionClear();
return;
}
jstring j0 = AndroidBridge::NewJavaString(env, a0);
env->CallStaticVoidMethod(mGeckoAppShellClass, jSetSelectedLocale, j0);
if (env->ExceptionCheck()) {
ALOG_BRIDGE("Exceptional exit of: %s", __PRETTY_FUNCTION__);
env->ExceptionDescribe();
env->ExceptionClear();
env->PopLocalFrame(NULL);
return;
}
env->PopLocalFrame(NULL);
}
void GeckoAppShell::SetURITitle(const nsAString& a0, const nsAString& a1) {
JNIEnv *env = AndroidBridge::GetJNIEnv();
if (!env) {

View File

@ -84,7 +84,6 @@ public:
static void SendMessageWrapper(const nsAString& a0, const nsAString& a1, int32_t a2);
static void SetFullScreen(bool a0);
static void SetKeepScreenOn(bool a0);
static void SetSelectedLocale(const nsAString& a0);
static void SetURITitle(const nsAString& a0, const nsAString& a1);
static void ShowAlertNotificationWrapper(const nsAString& a0, const nsAString& a1, const nsAString& a2, const nsAString& a3, const nsAString& a4);
static void ShowFilePickerAsyncWrapper(const nsAString& a0, int64_t a1);

View File

@ -163,12 +163,8 @@ nsAppShell::NotifyNativeEvent()
mQueueCond.Notify();
}
#define PREFNAME_MATCH_OS "intl.locale.matchOS"
#define PREFNAME_UA_LOCALE "general.useragent.locale"
#define PREFNAME_COALESCE_TOUCHES "dom.event.touch.coalescing.enabled"
static const char* kObservedPrefs[] = {
PREFNAME_MATCH_OS,
PREFNAME_UA_LOCALE,
PREFNAME_COALESCE_TOUCHES,
nullptr
};
@ -182,8 +178,6 @@ nsAppShell::Init()
#endif
nsresult rv = nsBaseAppShell::Init();
AndroidBridge* bridge = AndroidBridge::Bridge();
nsCOMPtr<nsIObserverService> obsServ =
mozilla::services::GetObserverService();
if (obsServ) {
@ -193,27 +187,7 @@ nsAppShell::Init()
if (sPowerManagerService)
sPowerManagerService->AddWakeLockListener(sWakeLockListener);
if (!bridge)
return rv;
Preferences::AddStrongObservers(this, kObservedPrefs);
bool match;
rv = Preferences::GetBool(PREFNAME_MATCH_OS, &match);
NS_ENSURE_SUCCESS(rv, rv);
if (match) {
GeckoAppShell::SetSelectedLocale(EmptyString());
return NS_OK;
}
nsAutoString locale;
rv = Preferences::GetLocalizedString(PREFNAME_UA_LOCALE, &locale);
if (NS_FAILED(rv)) {
rv = Preferences::GetString(PREFNAME_UA_LOCALE, &locale);
}
GeckoAppShell::SetSelectedLocale(locale);
mAllowCoalescingTouches = Preferences::GetBool(PREFNAME_COALESCE_TOUCHES, true);
return rv;
}
@ -228,30 +202,9 @@ nsAppShell::Observe(nsISupports* aSubject,
// or we'll see crashes, as the app shell outlives XPConnect.
mObserversHash.Clear();
return nsBaseAppShell::Observe(aSubject, aTopic, aData);
} else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) && aData && (
nsDependentString(aData).Equals(
NS_LITERAL_STRING(PREFNAME_UA_LOCALE)) ||
nsDependentString(aData).Equals(
NS_LITERAL_STRING(PREFNAME_COALESCE_TOUCHES)) ||
nsDependentString(aData).Equals(
NS_LITERAL_STRING(PREFNAME_MATCH_OS)))) {
bool match;
nsresult rv = Preferences::GetBool(PREFNAME_MATCH_OS, &match);
NS_ENSURE_SUCCESS(rv, rv);
if (match) {
GeckoAppShell::SetSelectedLocale(EmptyString());
return NS_OK;
}
nsAutoString locale;
if (NS_FAILED(Preferences::GetLocalizedString(PREFNAME_UA_LOCALE,
&locale))) {
locale = Preferences::GetString(PREFNAME_UA_LOCALE);
}
GeckoAppShell::SetSelectedLocale(locale);
} else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) &&
aData &&
nsDependentString(aData).Equals(NS_LITERAL_STRING(PREFNAME_COALESCE_TOUCHES))) {
mAllowCoalescingTouches = Preferences::GetBool(PREFNAME_COALESCE_TOUCHES, true);
return NS_OK;
}