Bug 917480 - Part 2: add 'Language' section to top-level preferences, allowing for browser locale switching. r=nalexander

* * *
Bug 917480 - Part 2d: compute locale list at build time.
This commit is contained in:
Richard Newman 2014-05-13 20:50:27 -07:00
parent 6019732cbe
commit fb4045eb34
14 changed files with 534 additions and 32 deletions

View File

@ -302,7 +302,6 @@
<activity android:name="org.mozilla.gecko.preferences.GeckoPreferences"
android:theme="@style/Gecko.Preferences"
android:label="@string/settings_title"
android:configChanges="orientation|screenSize"
android:excludeFromRecents="true"/>

View File

@ -9,6 +9,7 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.net.URLEncoder;
import java.util.EnumSet;
import java.util.Locale;
import java.util.Vector;
import org.json.JSONArray;
@ -132,6 +133,10 @@ abstract public class BrowserApp extends GeckoApp
private static final String STATE_ABOUT_HOME_TOP_PADDING = "abouthome_top_padding";
private static final String BROWSER_SEARCH_TAG = "browser_search";
// Request ID for startActivityForResult.
private static final int ACTIVITY_REQUEST_PREFERENCES = 1001;
private BrowserSearch mBrowserSearch;
private View mBrowserSearchContainer;
@ -1288,7 +1293,7 @@ abstract public class BrowserApp extends GeckoApp
}
Intent settingsIntent = new Intent(this, GeckoPreferences.class);
GeckoPreferences.setResourceToOpen(settingsIntent, resource);
startActivity(settingsIntent);
startActivityForResult(settingsIntent, ACTIVITY_REQUEST_PREFERENCES);
} else if (event.equals("Updater:Launch")) {
handleUpdaterLaunch();
} else if (event.equals("Prompt:ShowTop")) {
@ -1754,6 +1759,7 @@ abstract public class BrowserApp extends GeckoApp
@Override
public void onLocaleReady(final String locale) {
Log.d(LOGTAG, "onLocaleReady: " + locale);
super.onLocaleReady(locale);
HomePanelsManager.getInstance().onLocaleReady(locale);
@ -1764,6 +1770,35 @@ abstract public class BrowserApp extends GeckoApp
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(LOGTAG, "onActivityResult: " + requestCode + ", " + resultCode + ", " + data);
switch (requestCode) {
case ACTIVITY_REQUEST_PREFERENCES:
// We just returned from preferences. If our locale changed,
// we need to redisplay at this point, and do any other browser-level
// bookkeeping that we associate with a locale change.
if (resultCode != GeckoPreferences.RESULT_CODE_LOCALE_DID_CHANGE) {
Log.d(LOGTAG, "No locale change returning from preferences; nothing to do.");
return;
}
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
final LocaleManager localeManager = BrowserLocaleManager.getInstance();
final Locale locale = localeManager.getCurrentLocale(getApplicationContext());
Log.d(LOGTAG, "Persisted locale was " + locale);
onLocaleChanged(BrowserLocaleManager.getLanguageTag(locale));
}
});
return;
default:
return;
}
}
private void showHomePager(String panelId) {
showHomePagerWithAnimator(panelId, null);
}
@ -2420,7 +2455,10 @@ abstract public class BrowserApp extends GeckoApp
if (itemId == R.id.settings) {
intent = new Intent(this, GeckoPreferences.class);
startActivity(intent);
// We want to know when the Settings activity returns, because
// we might need to redisplay based on a locale change.
startActivityForResult(intent, ACTIVITY_REQUEST_PREFERENCES);
return true;
}

View File

@ -14,10 +14,19 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.util.Log;
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.util.GeckoJarReader;
/**
* This class manages persistence, application, and otherwise handling of
* user-specified locales.
@ -39,6 +48,8 @@ public class BrowserLocaleManager implements LocaleManager {
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";
// This is volatile because we don't impose restrictions
// over which thread calls our methods.
private volatile Locale currentLocale = null;
@ -92,7 +103,7 @@ public class BrowserLocaleManager implements LocaleManager {
return language + "-" + country;
}
private static Locale parseLocaleCode(final String localeCode) {
public static Locale parseLocaleCode(final String localeCode) {
int index;
if ((index = localeCode.indexOf('-')) != -1 ||
(index = localeCode.indexOf('_')) != -1) {
@ -244,7 +255,8 @@ public class BrowserLocaleManager implements LocaleManager {
settings.edit().putString(PREF_LOCALE, localeCode).commit();
}
private Locale getCurrentLocale(Context context) {
@Override
public Locale getCurrentLocale(Context context) {
if (currentLocale != null) {
return currentLocale;
}
@ -285,4 +297,67 @@ public class BrowserLocaleManager implements LocaleManager {
return locale.toString();
}
/**
* 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 apkPath = context.getPackageResourcePath();
final String jarURL = "jar:jar:" + new File(apkPath).toURI() + "!/" +
AppConstants.OMNIJAR_NAME + "!/" +
resPath;
final String contents = GeckoJarReader.getText(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.
*/
public static String getFallbackLocaleTag() {
return FALLBACK_LOCALE_TAG;
}
}

View File

@ -2712,18 +2712,12 @@ public abstract class GeckoApp
private static final String SESSION_END_LOCALE_CHANGED = "L";
/**
* Use BrowserLocaleManager to change our persisted and current locales,
* and poke HealthRecorder to tell it of our changed state.
* This exists so that a locale can be applied in two places: when saved
* in a nested activity, and then again when we get back up to GeckoApp.
*
* GeckoApp needs to do a bunch more stuff than, say, GeckoPreferences.
*/
private void setLocale(final String locale) {
if (locale == null) {
return;
}
final String resultant = BrowserLocaleManager.getInstance().setSelectedLocale(this, locale);
if (resultant == null) {
return;
}
protected void onLocaleChanged(final String locale) {
final boolean startNewSession = true;
final boolean shouldRestart = false;
@ -2732,7 +2726,7 @@ public abstract class GeckoApp
// with the wrong locale.
final HealthRecorder rec = mHealthRecorder;
if (rec != null) {
rec.onAppLocaleChanged(resultant);
rec.onAppLocaleChanged(locale);
rec.onEnvironmentChanged(startNewSession, SESSION_END_LOCALE_CHANGED);
}
@ -2740,7 +2734,7 @@ public abstract class GeckoApp
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
GeckoApp.this.onLocaleReady(resultant);
GeckoApp.this.onLocaleReady(locale);
}
});
return;
@ -2757,6 +2751,24 @@ public abstract class GeckoApp
});
}
/**
* Use BrowserLocaleManager to change our persisted and current locales,
* and poke HealthRecorder to tell it of our changed state.
*/
protected void setLocale(final String locale) {
Log.d(LOGTAG, "setLocale: " + locale);
if (locale == null) {
return;
}
final String resultant = BrowserLocaleManager.getInstance().setSelectedLocale(this, locale);
if (resultant == null) {
return;
}
onLocaleChanged(resultant);
}
private void setSystemUiVisible(final boolean visible) {
if (Build.VERSION.SDK_INT < 14) {
return;

View File

@ -12,6 +12,7 @@ import android.content.res.Resources;
public interface LocaleManager {
void initialize(Context context);
Locale getCurrentLocale(Context context);
String getAndApplyPersistedLocale(Context context);
void correctLocale(Context context, Resources resources, Configuration newConfig);
void updateConfiguration(Context context, Locale locale);

View File

@ -67,6 +67,18 @@
<!ENTITY settings_title "Settings">
<!ENTITY pref_category_advanced "Advanced">
<!ENTITY pref_category_customize "Customize">
<!-- Localization note (pref_category_language) : This is the preferences
section in which the user picks the locale in which to display Firefox
UI. The locale includes both language and region concepts. -->
<!ENTITY pref_category_language "Language">
<!ENTITY pref_browser_locale "Browser language">
<!-- Localization note (locale_system_default) : This string indicates that
Firefox will use the locale currently selected in Android's settings
to display browser chrome. -->
<!ENTITY locale_system_default "System default">
<!ENTITY pref_category_search3 "Search">
<!ENTITY pref_category_search_summary "Customize your search providers">
<!ENTITY pref_category_display "Display">

View File

@ -323,6 +323,7 @@ gbjar.sources += [
'preferences/GeckoPreferenceFragment.java',
'preferences/GeckoPreferences.java',
'preferences/LinkPreference.java',
'preferences/LocaleListPreference.java',
'preferences/ModifiableHintPreference.java',
'preferences/MultiChoicePreference.java',
'preferences/PanelsPreference.java',

View File

@ -6,12 +6,18 @@
package org.mozilla.gecko.preferences;
import java.lang.reflect.Field;
import java.util.Locale;
import org.mozilla.gecko.BrowserLocaleManager;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.PrefsHelper;
import org.mozilla.gecko.R;
import android.app.ActionBar;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
@ -29,6 +35,7 @@ public class GeckoPreferenceFragment extends PreferenceFragment {
private static final String LOGTAG = "GeckoPreferenceFragment";
private int mPrefsRequestId = 0;
private Locale lastLocale = Locale.getDefault();
@Override
public void onCreate(Bundle savedInstanceState) {
@ -51,6 +58,64 @@ public class GeckoPreferenceFragment extends PreferenceFragment {
mPrefsRequestId = ((GeckoPreferences)getActivity()).setupPreferences(screen);
}
/**
* Return the title to use for this preference fragment. This allows
* for us to redisplay this fragment in a different locale.
*
* We only return titles for the preference screens that are in the
* flow for selecting a locale, and thus might need to be redisplayed.
*/
protected String getTitle() {
final int res = getResource();
if (res == R.xml.preferences_locale) {
return getString(R.string.pref_category_language);
}
if (res == R.xml.preferences) {
return getString(R.string.settings_title);
}
return null;
}
private void updateTitle() {
final String newTitle = getTitle();
if (newTitle != null) {
final Activity activity = getActivity();
Log.v(LOGTAG, "Setting activity title to " + newTitle);
activity.setTitle(newTitle);
if (Build.VERSION.SDK_INT >= 14) {
final ActionBar actionBar = activity.getActionBar();
actionBar.setTitle(newTitle);
}
}
}
@Override
public void onResume() {
final Locale currentLocale = Locale.getDefault();
final Context context = getActivity().getApplicationContext();
BrowserLocaleManager.getInstance().updateConfiguration(context, currentLocale);
if (!currentLocale.equals(lastLocale)) {
// Locales differ. Let's redisplay.
Log.d(LOGTAG, "Locale changed: " + currentLocale);
this.lastLocale = currentLocale;
// Rebuild the list to reflect the current locale.
getPreferenceScreen().removeAll();
addPreferencesFromResource(getResource());
}
// Fix the parent title regardless.
updateTitle();
super.onResume();
}
/*
* Get the resource from Fragment arguments and return it.
*
@ -59,19 +124,21 @@ public class GeckoPreferenceFragment extends PreferenceFragment {
private int getResource() {
int resid = 0;
String resourceName = getArguments().getString("resource");
final String resourceName = getArguments().getString("resource");
final Activity activity = getActivity();
if (resourceName != null) {
// Fetch resource id by resource name.
resid = getActivity().getResources().getIdentifier(resourceName,
"xml",
getActivity().getPackageName());
final Resources resources = activity.getResources();
final String packageName = activity.getPackageName();
resid = resources.getIdentifier(resourceName, "xml", packageName);
}
if (resid == 0) {
// The resource was invalid. Use the default resource.
Log.e(LOGTAG, "Failed to find resource: " + resourceName + ". Displaying default settings.");
boolean isMultiPane = ((PreferenceActivity) getActivity()).onIsMultiPane();
boolean isMultiPane = ((PreferenceActivity) activity).onIsMultiPane();
resid = isMultiPane ? R.xml.preferences_customize_tablet : R.xml.preferences;
}

View File

@ -7,18 +7,21 @@ package org.mozilla.gecko.preferences;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.json.JSONObject;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.BrowserApp;
import org.mozilla.gecko.BrowserLocaleManager;
import org.mozilla.gecko.DataReportingNotification;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.GeckoActivityStatus;
import org.mozilla.gecko.GeckoApp;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.LocaleManager;
import org.mozilla.gecko.PrefsHelper;
import org.mozilla.gecko.R;
import org.mozilla.gecko.background.announcements.AnnouncementsConstants;
@ -28,6 +31,7 @@ import org.mozilla.gecko.home.HomePanelPicker;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.ThreadUtils;
import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
@ -104,8 +108,79 @@ public class GeckoPreferences
private static final int REQUEST_CODE_PREF_SCREEN = 5;
private static final int RESULT_CODE_EXIT_SETTINGS = 6;
// Result code used when a locale preference changes.
// Callers can recognize this code to refresh themselves to
// accommodate a locale change.
public static final int RESULT_CODE_LOCALE_DID_CHANGE = 7;
/**
* Track the last locale so we know whether to redisplay.
*/
private Locale lastLocale = Locale.getDefault();
private void updateTitle(int title) {
// Due to locale switching, we need to dynamically impose the title on
// the default preferences view.
final String newTitle = getString(title);
if (newTitle != null) {
Log.v(LOGTAG, "Setting activity title to " + newTitle);
setTitle(newTitle);
if (Build.VERSION.SDK_INT >= 14) {
final ActionBar actionBar = getActionBar();
actionBar.setTitle(newTitle);
}
}
}
private void updateTitleForPrefsResource(int res) {
// At present we only need to do this for the top-level prefs view
// and the locale switcher itself.
// The others don't allow you to change locales, and have their
// titles set in their fragment descriptors.
if (res == R.xml.preferences) {
updateTitle(R.string.settings_title);
return;
}
if (res == R.xml.preferences_locale) {
updateTitle(R.string.pref_category_language);
return;
}
}
private void onLocaleChanged(Locale newLocale) {
Log.d(LOGTAG, "onLocaleChanged: " + newLocale);
BrowserLocaleManager.getInstance().updateConfiguration(getApplicationContext(), newLocale);
this.lastLocale = newLocale;
// Cause the current fragment to redisplay, the hard way.
// This avoids nonsense with trying to reach inside fragments and force them
// to redisplay themselves.
// We also don't need to update the title.
final Intent intent = (Intent) getIntent().clone();
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivityForResult(intent, REQUEST_CODE_PREF_SCREEN);
setResult(RESULT_CODE_LOCALE_DID_CHANGE);
finish();
}
private void checkLocale() {
final Locale currentLocale = Locale.getDefault();
if (currentLocale.equals(lastLocale)) {
return;
}
onLocaleChanged(currentLocale);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
// Apply the current user-selected locale, if necessary.
checkLocale();
// For Android v11+ where we use Fragments (v11+ only due to bug 866352),
// check that PreferenceActivity.EXTRA_SHOW_FRAGMENT has been set
@ -146,6 +221,9 @@ public class GeckoPreferences
Log.e(LOGTAG, "Displaying default settings.");
res = R.xml.preferences;
}
// We don't include a title in the XML, so set it here, in a locale-aware fashion.
updateTitleForPrefsResource(res);
addPreferencesFromResource(res);
}
@ -171,8 +249,14 @@ public class GeckoPreferences
}
});
if (Build.VERSION.SDK_INT >= 14)
getActionBar().setHomeButtonEnabled(true);
if (Build.VERSION.SDK_INT >= 14) {
final ActionBar actionBar = getActionBar();
actionBar.setHomeButtonEnabled(true);
}
// N.B., if we ever need to redisplay the locale selection UI without
// just finishing and recreating the activity, right here we'll need to
// capture EXTRA_SHOW_FRAGMENT_TITLE from the intent and store the title ID.
// If launched from notification, explicitly cancel the notification.
if (intentExtras != null && intentExtras.containsKey(DataReportingNotification.ALERT_NAME_DATAREPORTING_NOTIFICATION)) {
@ -182,7 +266,8 @@ public class GeckoPreferences
}
/**
* Set intent to display top-level settings fragment.
* Set intent to display top-level settings fragment,
* and show the correct title.
*/
private void setupTopLevelFragmentIntent() {
Intent intent = getIntent();
@ -205,6 +290,9 @@ public class GeckoPreferences
// Build fragment intent.
intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, GeckoPreferenceFragment.class.getName());
intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs);
// Show the title for the top level.
updateTitle(R.string.settings_title);
}
@Override
@ -267,6 +355,8 @@ public class GeckoPreferences
@Override
public void startWithFragment(String fragmentName, Bundle args,
Fragment resultTo, int resultRequestCode, int titleRes, int shortTitleRes) {
Log.v(LOGTAG, "Starting with fragment: " + fragmentName + ", title " + titleRes);
// Overriding because we want to use startActivityForResult for Fragment intents.
Intent intent = onBuildStartFragmentIntent(fragmentName, args, titleRes, shortTitleRes);
if (resultTo == null) {
@ -278,6 +368,10 @@ public class GeckoPreferences
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// We might have just returned from a settings activity that allows us
// to switch locales, so reflect any change that occurred.
checkLocale();
switch (requestCode) {
case REQUEST_CODE_PREF_SCREEN:
if (resultCode == RESULT_CODE_EXIT_SETTINGS) {
@ -290,8 +384,8 @@ public class GeckoPreferences
case HomePanelPicker.REQUEST_CODE_ADD_PANEL:
switch (resultCode) {
case Activity.RESULT_OK:
// Panel installed, refresh panels list.
mPanelsPreferenceCategory.refresh();
// Panel installed, refresh panels list.
mPanelsPreferenceCategory.refresh();
break;
case Activity.RESULT_CANCELED:
// Dialog was cancelled, do nothing.
@ -403,7 +497,8 @@ public class GeckoPreferences
return true;
}
});
} else if (PREFS_RESTORE_SESSION.equals(key)) {
} else if (PREFS_RESTORE_SESSION.equals(key) ||
PREFS_BROWSER_LOCALE.equals(key)) {
// Set the summary string to the current entry. The summary
// for other list prefs will be set in the PrefsHelper
// callback, but since this pref doesn't live in Gecko, we
@ -602,16 +697,74 @@ public class GeckoPreferences
return prefs.getBoolean(name, def);
}
/**
* Immediately handle the user's selection of a browser locale.
*
* Earlier locale-handling code did this with centralized logic in
* GeckoApp, delegating to LocaleManager for persistence and refreshing
* the activity as necessary.
*
* We no longer handle this by sending a message to GeckoApp, for
* several reasons:
*
* * GeckoApp might not be running. Activities don't always stick around.
* A Java bridge message might not be handled.
* * We need to adapt the preferences UI to the locale ourselves.
* * The user might not hit Back (or Up) -- they might hit Home and never
* come back.
*
* We handle the case of the user returning to the browser via the
* onActivityResult mechanism: see {@link BrowserApp#onActivityResult(int, int, Intent)}.
*/
private boolean onLocaleSelected(final String newValue) {
if (newValue.equals("")) {
// TODO: reset our locale to match system.
return false;
}
final Context context = getApplicationContext();
// LocaleManager operations need to occur on the background thread.
// ... but activity operations need to occur on the UI thread. So we
// have nested runnables.
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
final LocaleManager localeManager = BrowserLocaleManager.getInstance();
if (null == localeManager.setSelectedLocale(context, newValue)) {
localeManager.updateConfiguration(context, Locale.getDefault());
}
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
onLocaleChanged(Locale.getDefault());
}
});
}
});
return true;
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
String prefName = preference.getKey();
final String prefName = preference.getKey();
if (PREFS_MP_ENABLED.equals(prefName)) {
showDialog((Boolean) newValue ? DIALOG_CREATE_MASTER_PASSWORD : DIALOG_REMOVE_MASTER_PASSWORD);
// We don't want the "use master password" pref to change until the
// user has gone through the dialog.
return false;
} else if (PREFS_MENU_CHAR_ENCODING.equals(prefName)) {
}
if (PREFS_BROWSER_LOCALE.equals(prefName)) {
// Even though this is a list preference, we don't want to handle it
// below, so we return here.
return onLocaleSelected((String) newValue);
}
if (PREFS_MENU_CHAR_ENCODING.equals(prefName)) {
setCharEncodingState(((String) newValue).equals("true"));
} else if (PREFS_ANNOUNCEMENTS_ENABLED.equals(prefName)) {
// Send a broadcast intent to the product announcements service, either to start or

View File

@ -0,0 +1,106 @@
/* 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.preferences;
import java.text.Collator;
import java.util.Arrays;
import java.util.Collection;
import java.util.Locale;
import org.mozilla.gecko.BrowserLocaleManager;
import org.mozilla.gecko.R;
import android.content.Context;
import android.preference.ListPreference;
import android.util.AttributeSet;
public class LocaleListPreference extends ListPreference {
public LocaleListPreference(Context context) {
this(context, null);
}
public LocaleListPreference(Context context, AttributeSet attributes) {
super(context, attributes);
buildList();
}
private static final class LocaleDescriptor implements Comparable<LocaleDescriptor> {
// We use Locale.US here to ensure a stable ordering of entries.
private static final Collator COLLATOR = Collator.getInstance(Locale.US);
public final String tag;
private final String nativeName;
public LocaleDescriptor(String tag) {
this(BrowserLocaleManager.parseLocaleCode(tag), tag);
}
public LocaleDescriptor(Locale locale, String tag) {
this.nativeName = locale.getDisplayName(locale);
this.tag = tag;
}
public String getTag() {
return this.tag;
}
public String getDisplayName() {
return this.nativeName;
}
@Override
public String toString() {
return this.nativeName;
}
@Override
public int compareTo(LocaleDescriptor another) {
// We sort by name, so we use Collator.
return COLLATOR.compare(this.nativeName, another.nativeName);
}
}
private LocaleDescriptor[] getShippingLocales() {
Collection<String> shippingLocales = BrowserLocaleManager.getPackagedLocaleTags(getContext());
// Future: single-locale builds should be specified, too.
if (shippingLocales == null) {
final String fallbackTag = BrowserLocaleManager.getFallbackLocaleTag();
return new LocaleDescriptor[] { new LocaleDescriptor(fallbackTag) };
}
final int count = shippingLocales.size();
final LocaleDescriptor[] descriptors = new LocaleDescriptor[count];
int i = 0;
for (String tag : shippingLocales) {
descriptors[i++] = new LocaleDescriptor(tag);
}
Arrays.sort(descriptors, 0, count);
return descriptors;
}
private void buildList() {
final LocaleDescriptor[] descriptors = getShippingLocales();
final int count = descriptors.length;
// We leave room for "System default".
final String[] entries = new String[count + 1];
final String[] values = new String[count + 1];
entries[0] = getContext().getString(R.string.locale_system_default);
values[0] = "";
for (int i = 0; i < count; ++i) {
entries[i + 1] = descriptors[i].getDisplayName();
values[i + 1] = descriptors[i].getTag();
}
setEntries(entries);
setEntryValues(values);
}
}

View File

@ -34,6 +34,11 @@
android:value="preferences_privacy" />
</PreferenceScreen>
<PreferenceScreen android:title="@string/pref_category_language"
android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" >
<extra android:name="resource"
android:value="preferences_locale" />
</PreferenceScreen>
<PreferenceScreen android:title="@string/pref_category_vendor"
android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" >

View File

@ -45,6 +45,16 @@
</intent>
</PreferenceScreen>
<PreferenceScreen android:title="@string/pref_category_language" >
<intent android:action="android.intent.action.VIEW"
android:targetPackage="@string/android_package_name"
android:targetClass="org.mozilla.gecko.preferences.GeckoPreferences" >
<extra
android:name="resource"
android:value="preferences_locale" />
</intent>
</PreferenceScreen>
<PreferenceScreen android:title="@string/pref_category_vendor">
<intent android:action="android.intent.action.VIEW"
android:targetPackage="@string/android_package_name"

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:gecko="http://schemas.android.com/apk/res-auto"
android:title="@string/pref_category_language"
android:enabled="false">
<PreferenceCategory android:title="@string/pref_browser_locale">
<!-- No title set here. We set the title to the current locale in
GeckoPreferences. -->
<org.mozilla.gecko.preferences.LocaleListPreference
android:key="locale"
android:persistent="true"
android:defaultValue=""
/>
</PreferenceCategory>
</PreferenceScreen>

View File

@ -108,6 +108,10 @@
<string name="pref_search_restore_defaults_summary">&pref_search_restore_defaults_summary;</string>
<string name="pref_search_hint">&pref_search_hint;</string>
<string name="pref_category_language">&pref_category_language;</string>
<string name="pref_browser_locale">&pref_browser_locale;</string>
<string name="locale_system_default">&locale_system_default;</string>
<string name="pref_category_devtools">&pref_category_devtools;</string>
<string name="pref_developer_remotedebugging">&pref_developer_remotedebugging;</string>