michael@0: /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.preferences; michael@0: michael@0: import java.util.ArrayList; michael@0: import java.util.List; michael@0: michael@0: import org.json.JSONObject; michael@0: import org.mozilla.gecko.AppConstants; michael@0: import org.mozilla.gecko.DataReportingNotification; michael@0: import org.mozilla.gecko.GeckoActivityStatus; michael@0: import org.mozilla.gecko.GeckoApp; michael@0: import org.mozilla.gecko.GeckoAppShell; michael@0: import org.mozilla.gecko.GeckoApplication; michael@0: import org.mozilla.gecko.GeckoEvent; michael@0: import org.mozilla.gecko.GeckoProfile; michael@0: import org.mozilla.gecko.GeckoSharedPrefs; michael@0: import org.mozilla.gecko.PrefsHelper; michael@0: import org.mozilla.gecko.R; michael@0: import org.mozilla.gecko.background.announcements.AnnouncementsConstants; michael@0: import org.mozilla.gecko.background.common.GlobalConstants; michael@0: import org.mozilla.gecko.background.healthreport.HealthReportConstants; michael@0: import org.mozilla.gecko.home.HomePanelPicker; michael@0: import org.mozilla.gecko.util.GeckoEventListener; michael@0: import org.mozilla.gecko.util.ThreadUtils; michael@0: michael@0: import android.app.Activity; michael@0: import android.app.AlertDialog; michael@0: import android.app.Dialog; michael@0: import android.app.Fragment; michael@0: import android.app.NotificationManager; michael@0: import android.content.Context; michael@0: import android.content.DialogInterface; michael@0: import android.content.Intent; michael@0: import android.content.SharedPreferences; michael@0: import android.os.Build; michael@0: import android.os.Bundle; michael@0: import android.preference.CheckBoxPreference; michael@0: import android.preference.EditTextPreference; michael@0: import android.preference.ListPreference; michael@0: import android.preference.Preference; michael@0: import android.preference.Preference.OnPreferenceChangeListener; michael@0: import android.preference.Preference.OnPreferenceClickListener; michael@0: import android.preference.PreferenceActivity; michael@0: import android.preference.PreferenceGroup; michael@0: import android.preference.PreferenceScreen; michael@0: import android.preference.TwoStatePreference; michael@0: import android.text.Editable; michael@0: import android.text.InputType; michael@0: import android.text.TextUtils; michael@0: import android.text.TextWatcher; michael@0: import android.util.Log; michael@0: import android.view.MenuItem; michael@0: import android.view.View; michael@0: import android.widget.AdapterView; michael@0: import android.widget.EditText; michael@0: import android.widget.LinearLayout; michael@0: import android.widget.ListAdapter; michael@0: import android.widget.ListView; michael@0: import android.widget.Toast; michael@0: michael@0: public class GeckoPreferences michael@0: extends PreferenceActivity michael@0: implements OnPreferenceChangeListener, GeckoEventListener, GeckoActivityStatus michael@0: { michael@0: private static final String LOGTAG = "GeckoPreferences"; michael@0: michael@0: private static final String NON_PREF_PREFIX = "android.not_a_preference."; michael@0: public static final String INTENT_EXTRA_RESOURCES = "resource"; michael@0: public static String PREFS_HEALTHREPORT_UPLOAD_ENABLED = NON_PREF_PREFIX + "healthreport.uploadEnabled"; michael@0: michael@0: private static boolean sIsCharEncodingEnabled = false; michael@0: private boolean mInitialized = false; michael@0: private int mPrefsRequestId = 0; michael@0: private PanelsPreferenceCategory mPanelsPreferenceCategory; michael@0: michael@0: // These match keys in resources/xml*/preferences*.xml michael@0: private static final String PREFS_SEARCH_RESTORE_DEFAULTS = NON_PREF_PREFIX + "search.restore_defaults"; michael@0: private static final String PREFS_HOME_ADD_PANEL = NON_PREF_PREFIX + "home.add_panel"; michael@0: private static final String PREFS_ANNOUNCEMENTS_ENABLED = NON_PREF_PREFIX + "privacy.announcements.enabled"; michael@0: private static final String PREFS_DATA_REPORTING_PREFERENCES = NON_PREF_PREFIX + "datareporting.preferences"; michael@0: private static final String PREFS_TELEMETRY_ENABLED = "toolkit.telemetry.enabled"; michael@0: private static final String PREFS_CRASHREPORTER_ENABLED = "datareporting.crashreporter.submitEnabled"; michael@0: private static final String PREFS_MENU_CHAR_ENCODING = "browser.menu.showCharacterEncoding"; michael@0: private static final String PREFS_MP_ENABLED = "privacy.masterpassword.enabled"; michael@0: private static final String PREFS_UPDATER_AUTODOWNLOAD = "app.update.autodownload"; michael@0: private static final String PREFS_GEO_REPORTING = "app.geo.reportdata"; michael@0: private static final String PREFS_GEO_LEARN_MORE = NON_PREF_PREFIX + "geo.learn_more"; michael@0: private static final String PREFS_HEALTHREPORT_LINK = NON_PREF_PREFIX + "healthreport.link"; michael@0: private static final String PREFS_DEVTOOLS_REMOTE_ENABLED = "devtools.debugger.remote-enabled"; michael@0: private static final String PREFS_DISPLAY_REFLOW_ON_ZOOM = "browser.zoom.reflowOnZoom"; michael@0: private static final String PREFS_SYNC = NON_PREF_PREFIX + "sync"; michael@0: michael@0: public static final String PREFS_RESTORE_SESSION = NON_PREF_PREFIX + "restoreSession3"; michael@0: michael@0: // These values are chosen to be distinct from other Activity constants. michael@0: private static final int REQUEST_CODE_PREF_SCREEN = 5; michael@0: private static final int RESULT_CODE_EXIT_SETTINGS = 6; michael@0: michael@0: @Override michael@0: protected void onCreate(Bundle savedInstanceState) { michael@0: michael@0: // For Android v11+ where we use Fragments (v11+ only due to bug 866352), michael@0: // check that PreferenceActivity.EXTRA_SHOW_FRAGMENT has been set michael@0: // (or set it) before super.onCreate() is called so Android can display michael@0: // the correct Fragment resource. michael@0: michael@0: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && michael@0: !getIntent().hasExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT)) { michael@0: // Set up the default fragment if there is no explicit fragment to show. michael@0: setupTopLevelFragmentIntent(); michael@0: } michael@0: michael@0: super.onCreate(savedInstanceState); michael@0: michael@0: // Use setResourceToOpen to specify these extras. michael@0: Bundle intentExtras = getIntent().getExtras(); michael@0: michael@0: // For versions of Android lower than Honeycomb, use xml resources instead of michael@0: // Fragments because of an Android bug in ActionBar (described in bug 866352 and michael@0: // fixed in bug 833625). michael@0: if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { michael@0: // Write prefs to our custom GeckoSharedPrefs file. michael@0: getPreferenceManager().setSharedPreferencesName(GeckoSharedPrefs.APP_PREFS_NAME); michael@0: michael@0: int res = 0; michael@0: if (intentExtras != null && intentExtras.containsKey(INTENT_EXTRA_RESOURCES)) { michael@0: // Fetch resource id from intent. michael@0: String resourceName = intentExtras.getString(INTENT_EXTRA_RESOURCES); michael@0: if (resourceName != null) { michael@0: res = getResources().getIdentifier(resourceName, "xml", getPackageName()); michael@0: if (res == 0) { michael@0: Log.e(LOGTAG, "No resource found named " + resourceName); michael@0: } michael@0: } michael@0: } michael@0: if (res == 0) { michael@0: // No resource specified, or the resource was invalid; use the default preferences screen. michael@0: Log.e(LOGTAG, "Displaying default settings."); michael@0: res = R.xml.preferences; michael@0: } michael@0: addPreferencesFromResource(res); michael@0: } michael@0: michael@0: registerEventListener("Sanitize:Finished"); michael@0: michael@0: // Add handling for long-press click. michael@0: // This is only for Android 3.0 and below (which use the long-press-context-menu paradigm). michael@0: final ListView mListView = getListView(); michael@0: mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { michael@0: @Override michael@0: public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { michael@0: // Call long-click handler if it the item implements it. michael@0: final ListAdapter listAdapter = ((ListView) parent).getAdapter(); michael@0: final Object listItem = listAdapter.getItem(position); michael@0: michael@0: // Only CustomListPreference handles long clicks. michael@0: if (listItem instanceof CustomListPreference && listItem instanceof View.OnLongClickListener) { michael@0: final View.OnLongClickListener longClickListener = (View.OnLongClickListener) listItem; michael@0: return longClickListener.onLongClick(view); michael@0: } michael@0: return false; michael@0: } michael@0: }); michael@0: michael@0: if (Build.VERSION.SDK_INT >= 14 && getActionBar() != null) michael@0: getActionBar().setHomeButtonEnabled(true); michael@0: michael@0: // If launched from notification, explicitly cancel the notification. michael@0: if (intentExtras != null && intentExtras.containsKey(DataReportingNotification.ALERT_NAME_DATAREPORTING_NOTIFICATION)) { michael@0: NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE); michael@0: notificationManager.cancel(DataReportingNotification.ALERT_NAME_DATAREPORTING_NOTIFICATION.hashCode()); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Set intent to display top-level settings fragment. michael@0: */ michael@0: private void setupTopLevelFragmentIntent() { michael@0: Intent intent = getIntent(); michael@0: // Check intent to determine settings screen to display. michael@0: Bundle intentExtras = intent.getExtras(); michael@0: Bundle fragmentArgs = new Bundle(); michael@0: // Add resource argument to fragment if it exists. michael@0: if (intentExtras != null && intentExtras.containsKey(INTENT_EXTRA_RESOURCES)) { michael@0: String resourceName = intentExtras.getString(INTENT_EXTRA_RESOURCES); michael@0: fragmentArgs.putString(INTENT_EXTRA_RESOURCES, resourceName); michael@0: } else { michael@0: // Use top-level settings screen. michael@0: if (!onIsMultiPane()) { michael@0: fragmentArgs.putString(INTENT_EXTRA_RESOURCES, "preferences"); michael@0: } else { michael@0: fragmentArgs.putString(INTENT_EXTRA_RESOURCES, "preferences_customize_tablet"); michael@0: } michael@0: } michael@0: michael@0: // Build fragment intent. michael@0: intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, GeckoPreferenceFragment.class.getName()); michael@0: intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs); michael@0: } michael@0: michael@0: @Override michael@0: public void onBuildHeaders(List
target) { michael@0: if (onIsMultiPane()) michael@0: loadHeadersFromResource(R.xml.preference_headers, target); michael@0: } michael@0: michael@0: @Override michael@0: public void onWindowFocusChanged(boolean hasFocus) { michael@0: if (!hasFocus || mInitialized) michael@0: return; michael@0: michael@0: mInitialized = true; michael@0: if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { michael@0: PreferenceScreen screen = getPreferenceScreen(); michael@0: mPrefsRequestId = setupPreferences(screen); michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: protected void onDestroy() { michael@0: super.onDestroy(); michael@0: unregisterEventListener("Sanitize:Finished"); michael@0: if (mPrefsRequestId > 0) { michael@0: PrefsHelper.removeObserver(mPrefsRequestId); michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public void onPause() { michael@0: super.onPause(); michael@0: michael@0: if (getApplication() instanceof GeckoApplication) { michael@0: ((GeckoApplication) getApplication()).onActivityPause(this); michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public void onResume() { michael@0: super.onResume(); michael@0: michael@0: if (getApplication() instanceof GeckoApplication) { michael@0: ((GeckoApplication) getApplication()).onActivityResume(this); michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public void startActivity(Intent intent) { michael@0: // For settings, we want to be able to pass results up the chain michael@0: // of preference screens so Settings can behave as a single unit. michael@0: // Specifically, when we open a link, we want to back out of all michael@0: // the settings screens. michael@0: // We need to start nested PreferenceScreens withStartActivityForResult(). michael@0: // Android doesn't let us do that (see Preference.onClick), so we're overriding here. michael@0: startActivityForResult(intent, REQUEST_CODE_PREF_SCREEN); michael@0: } michael@0: michael@0: @Override michael@0: public void startWithFragment(String fragmentName, Bundle args, michael@0: Fragment resultTo, int resultRequestCode, int titleRes, int shortTitleRes) { michael@0: // Overriding because we want to use startActivityForResult for Fragment intents. michael@0: Intent intent = onBuildStartFragmentIntent(fragmentName, args, titleRes, shortTitleRes); michael@0: if (resultTo == null) { michael@0: startActivityForResult(intent, REQUEST_CODE_PREF_SCREEN); michael@0: } else { michael@0: resultTo.startActivityForResult(intent, resultRequestCode); michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public void onActivityResult(int requestCode, int resultCode, Intent data) { michael@0: switch (requestCode) { michael@0: case REQUEST_CODE_PREF_SCREEN: michael@0: if (resultCode == RESULT_CODE_EXIT_SETTINGS) { michael@0: // Pass this result up to the parent activity. michael@0: setResult(RESULT_CODE_EXIT_SETTINGS); michael@0: finish(); michael@0: } michael@0: break; michael@0: michael@0: case HomePanelPicker.REQUEST_CODE_ADD_PANEL: michael@0: switch (resultCode) { michael@0: case Activity.RESULT_OK: michael@0: // Panel installed, refresh panels list. michael@0: mPanelsPreferenceCategory.refresh(); michael@0: break; michael@0: case Activity.RESULT_CANCELED: michael@0: // Dialog was cancelled, do nothing. michael@0: break; michael@0: default: michael@0: Log.w(LOGTAG, "Unhandled ADD_PANEL result code " + requestCode); michael@0: break; michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public void handleMessage(String event, JSONObject message) { michael@0: try { michael@0: if (event.equals("Sanitize:Finished")) { michael@0: boolean success = message.getBoolean("success"); michael@0: final int stringRes = success ? R.string.private_data_success : R.string.private_data_fail; michael@0: final Context context = this; michael@0: ThreadUtils.postToUiThread(new Runnable () { michael@0: @Override michael@0: public void run() { michael@0: Toast.makeText(context, stringRes, Toast.LENGTH_SHORT).show(); michael@0: } michael@0: }); michael@0: } michael@0: } catch (Exception e) { michael@0: Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Initialize all of the preferences (native of Gecko ones) for this screen. michael@0: * michael@0: * @param prefs The android.preference.PreferenceGroup to initialize michael@0: * @return The integer id for the PrefsHelper.PrefHandlerBase listener added michael@0: * to monitor changes to Gecko prefs. michael@0: */ michael@0: public int setupPreferences(PreferenceGroup prefs) { michael@0: ArrayList list = new ArrayList(); michael@0: setupPreferences(prefs, list); michael@0: return getGeckoPreferences(prefs, list); michael@0: } michael@0: michael@0: /** michael@0: * Recursively loop through a PreferenceGroup. Initialize native Android prefs, michael@0: * and build a list of Gecko preferences in the passed in prefs array michael@0: * michael@0: * @param preferences The android.preference.PreferenceGroup to initialize michael@0: * @param prefs An ArrayList to fill with Gecko preferences that need to be michael@0: * initialized michael@0: * @return The integer id for the PrefsHelper.PrefHandlerBase listener added michael@0: * to monitor changes to Gecko prefs. michael@0: */ michael@0: private void setupPreferences(PreferenceGroup preferences, ArrayList prefs) { michael@0: for (int i = 0; i < preferences.getPreferenceCount(); i++) { michael@0: Preference pref = preferences.getPreference(i); michael@0: String key = pref.getKey(); michael@0: if (pref instanceof PreferenceGroup) { michael@0: // If no datareporting is enabled, remove UI. michael@0: if (PREFS_DATA_REPORTING_PREFERENCES.equals(key)) { michael@0: if (!AppConstants.MOZ_DATA_REPORTING) { michael@0: preferences.removePreference(pref); michael@0: i--; michael@0: continue; michael@0: } michael@0: } else if (pref instanceof PanelsPreferenceCategory) { michael@0: mPanelsPreferenceCategory = (PanelsPreferenceCategory) pref; michael@0: } michael@0: setupPreferences((PreferenceGroup) pref, prefs); michael@0: } else { michael@0: pref.setOnPreferenceChangeListener(this); michael@0: if (!AppConstants.MOZ_UPDATER && michael@0: PREFS_UPDATER_AUTODOWNLOAD.equals(key)) { michael@0: preferences.removePreference(pref); michael@0: i--; michael@0: continue; michael@0: } else if (AppConstants.RELEASE_BUILD && michael@0: PREFS_DISPLAY_REFLOW_ON_ZOOM.equals(key)) { michael@0: // Remove UI for reflow on release builds. michael@0: preferences.removePreference(pref); michael@0: i--; michael@0: continue; michael@0: } else if (!AppConstants.MOZ_TELEMETRY_REPORTING && michael@0: PREFS_TELEMETRY_ENABLED.equals(key)) { michael@0: preferences.removePreference(pref); michael@0: i--; michael@0: continue; michael@0: } else if (!AppConstants.MOZ_SERVICES_HEALTHREPORT && michael@0: (PREFS_HEALTHREPORT_UPLOAD_ENABLED.equals(key) || michael@0: PREFS_HEALTHREPORT_LINK.equals(key))) { michael@0: preferences.removePreference(pref); michael@0: i--; michael@0: continue; michael@0: } else if (!AppConstants.MOZ_CRASHREPORTER && michael@0: PREFS_CRASHREPORTER_ENABLED.equals(key)) { michael@0: preferences.removePreference(pref); michael@0: i--; michael@0: continue; michael@0: } else if (AppConstants.RELEASE_BUILD && PREFS_GEO_REPORTING.equals(key)) { michael@0: // We don't build wifi/cell tower collection in release builds, so hide the UI. michael@0: preferences.removePreference(pref); michael@0: i--; michael@0: continue; michael@0: } else if (PREFS_DEVTOOLS_REMOTE_ENABLED.equals(key)) { michael@0: final Context thisContext = this; michael@0: pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { michael@0: @Override michael@0: public boolean onPreferenceClick(Preference preference) { michael@0: // Display toast to remind setting up tcp forwarding. michael@0: if (((CheckBoxPreference) preference).isChecked()) { michael@0: Toast.makeText(thisContext, R.string.devtools_remote_debugging_forward, Toast.LENGTH_SHORT).show(); michael@0: } michael@0: return true; michael@0: } michael@0: }); michael@0: } else if (PREFS_RESTORE_SESSION.equals(key)) { michael@0: // Set the summary string to the current entry. The summary michael@0: // for other list prefs will be set in the PrefsHelper michael@0: // callback, but since this pref doesn't live in Gecko, we michael@0: // need to handle it separately. michael@0: ListPreference listPref = (ListPreference) pref; michael@0: CharSequence selectedEntry = listPref.getEntry(); michael@0: listPref.setSummary(selectedEntry); michael@0: continue; michael@0: } else if (PREFS_SYNC.equals(key) && GeckoProfile.get(this).inGuestMode()) { michael@0: // Don't show sync prefs while in guest mode. michael@0: preferences.removePreference(pref); michael@0: i--; michael@0: continue; michael@0: } else if (PREFS_SEARCH_RESTORE_DEFAULTS.equals(key)) { michael@0: pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { michael@0: @Override michael@0: public boolean onPreferenceClick(Preference preference) { michael@0: GeckoPreferences.this.restoreDefaultSearchEngines(); michael@0: return true; michael@0: } michael@0: }); michael@0: } else if (PREFS_HOME_ADD_PANEL.equals(key)) { michael@0: pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { michael@0: @Override michael@0: public boolean onPreferenceClick(Preference preference) { michael@0: Intent dialogIntent = new Intent(GeckoPreferences.this, HomePanelPicker.class); michael@0: startActivityForResult(dialogIntent, HomePanelPicker.REQUEST_CODE_ADD_PANEL); michael@0: return true; michael@0: } michael@0: }); michael@0: } michael@0: michael@0: // Some Preference UI elements are not actually preferences, michael@0: // but they require a key to work correctly. For example, michael@0: // "Clear private data" requires a key for its state to be michael@0: // saved when the orientation changes. It uses the michael@0: // "android.not_a_preference.privacy.clear" key - which doesn't michael@0: // exist in Gecko - to satisfy this requirement. michael@0: if (key != null && !key.startsWith(NON_PREF_PREFIX)) { michael@0: prefs.add(key); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Restore default search engines in Gecko and retrigger a search engine refresh. michael@0: */ michael@0: protected void restoreDefaultSearchEngines() { michael@0: GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:RestoreDefaults", null)); michael@0: michael@0: // Send message to Gecko to get engines. SearchPreferenceCategory listens for the response. michael@0: GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:GetVisible", null)); michael@0: } michael@0: michael@0: @Override michael@0: public boolean onOptionsItemSelected(MenuItem item) { michael@0: int itemId = item.getItemId(); michael@0: switch (itemId) { michael@0: case android.R.id.home: michael@0: finish(); michael@0: return true; michael@0: } michael@0: michael@0: // Generated R.id.* apparently aren't constant expressions, so they can't be switched. michael@0: if (itemId == R.id.restore_defaults) { michael@0: restoreDefaultSearchEngines(); michael@0: return true; michael@0: } michael@0: michael@0: return super.onOptionsItemSelected(item); michael@0: } michael@0: michael@0: final private int DIALOG_CREATE_MASTER_PASSWORD = 0; michael@0: final private int DIALOG_REMOVE_MASTER_PASSWORD = 1; michael@0: michael@0: public static void setCharEncodingState(boolean enabled) { michael@0: sIsCharEncodingEnabled = enabled; michael@0: } michael@0: michael@0: public static boolean getCharEncodingState() { michael@0: return sIsCharEncodingEnabled; michael@0: } michael@0: michael@0: public static void broadcastAction(final Context context, final Intent intent) { michael@0: fillIntentWithProfileInfo(context, intent); michael@0: context.sendBroadcast(intent, GlobalConstants.PER_ANDROID_PACKAGE_PERMISSION); michael@0: } michael@0: michael@0: /** michael@0: * Broadcast an intent with pref, branch, and michael@0: * enabled extras. This is intended to represent the michael@0: * notification of a preference value to observers. michael@0: * michael@0: * The broadcast will be sent only to receivers registered with the michael@0: * (Fennec-specific) per-Android package permission. michael@0: */ michael@0: public static void broadcastPrefAction(final Context context, michael@0: final String action, michael@0: final String pref, michael@0: final boolean value) { michael@0: final Intent intent = new Intent(action) michael@0: .putExtra("pref", pref) michael@0: .putExtra("branch", GeckoSharedPrefs.APP_PREFS_NAME) michael@0: .putExtra("enabled", value); michael@0: broadcastAction(context, intent); michael@0: } michael@0: michael@0: private static void fillIntentWithProfileInfo(final Context context, final Intent intent) { michael@0: // There is a race here, but GeckoProfile returns the default profile michael@0: // when Gecko is not explicitly running for a different profile. In a michael@0: // multi-profile world, this will need to be updated (possibly to michael@0: // broadcast settings for all profiles). See Bug 882182. michael@0: GeckoProfile profile = GeckoProfile.get(context); michael@0: if (profile != null) { michael@0: intent.putExtra("profileName", profile.getName()) michael@0: .putExtra("profilePath", profile.getDir().getAbsolutePath()); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Broadcast the provided value as the value of the michael@0: * PREFS_ANNOUNCEMENTS_ENABLED pref. michael@0: */ michael@0: public static void broadcastAnnouncementsPref(final Context context, final boolean value) { michael@0: broadcastPrefAction(context, michael@0: AnnouncementsConstants.ACTION_ANNOUNCEMENTS_PREF, michael@0: PREFS_ANNOUNCEMENTS_ENABLED, michael@0: value); michael@0: } michael@0: michael@0: /** michael@0: * Broadcast the current value of the michael@0: * PREFS_ANNOUNCEMENTS_ENABLED pref. michael@0: */ michael@0: public static void broadcastAnnouncementsPref(final Context context) { michael@0: final boolean value = getBooleanPref(context, PREFS_ANNOUNCEMENTS_ENABLED, true); michael@0: broadcastAnnouncementsPref(context, value); michael@0: } michael@0: michael@0: /** michael@0: * Broadcast the provided value as the value of the michael@0: * PREFS_HEALTHREPORT_UPLOAD_ENABLED pref. michael@0: */ michael@0: public static void broadcastHealthReportUploadPref(final Context context, final boolean value) { michael@0: broadcastPrefAction(context, michael@0: HealthReportConstants.ACTION_HEALTHREPORT_UPLOAD_PREF, michael@0: PREFS_HEALTHREPORT_UPLOAD_ENABLED, michael@0: value); michael@0: } michael@0: michael@0: /** michael@0: * Broadcast the current value of the michael@0: * PREFS_HEALTHREPORT_UPLOAD_ENABLED pref. michael@0: */ michael@0: public static void broadcastHealthReportUploadPref(final Context context) { michael@0: final boolean value = getBooleanPref(context, PREFS_HEALTHREPORT_UPLOAD_ENABLED, true); michael@0: broadcastHealthReportUploadPref(context, value); michael@0: } michael@0: michael@0: public static void broadcastHealthReportPrune(final Context context) { michael@0: final Intent intent = new Intent(HealthReportConstants.ACTION_HEALTHREPORT_PRUNE); michael@0: broadcastAction(context, intent); michael@0: } michael@0: michael@0: /** michael@0: * Return the value of the named preference in the default preferences file. michael@0: * michael@0: * This corresponds to the storage that backs preferences.xml. michael@0: * @param context a Context; the michael@0: * PreferenceActivity will suffice, but this michael@0: * method is intended to be called from other contexts michael@0: * within the application, not just this Activity. michael@0: * @param name the name of the preference to retrieve. michael@0: * @param def the default value to return if the preference is not present. michael@0: * @return the value of the preference, or the default. michael@0: */ michael@0: public static boolean getBooleanPref(final Context context, final String name, boolean def) { michael@0: final SharedPreferences prefs = GeckoSharedPrefs.forApp(context); michael@0: return prefs.getBoolean(name, def); michael@0: } michael@0: michael@0: @Override michael@0: public boolean onPreferenceChange(Preference preference, Object newValue) { michael@0: String prefName = preference.getKey(); michael@0: if (PREFS_MP_ENABLED.equals(prefName)) { michael@0: showDialog((Boolean) newValue ? DIALOG_CREATE_MASTER_PASSWORD : DIALOG_REMOVE_MASTER_PASSWORD); michael@0: michael@0: // We don't want the "use master password" pref to change until the michael@0: // user has gone through the dialog. michael@0: return false; michael@0: } else if (PREFS_MENU_CHAR_ENCODING.equals(prefName)) { michael@0: setCharEncodingState(((String) newValue).equals("true")); michael@0: } else if (PREFS_ANNOUNCEMENTS_ENABLED.equals(prefName)) { michael@0: // Send a broadcast intent to the product announcements service, either to start or michael@0: // to stop the repeated background checks. michael@0: broadcastAnnouncementsPref(this, ((Boolean) newValue).booleanValue()); michael@0: } else if (PREFS_UPDATER_AUTODOWNLOAD.equals(prefName)) { michael@0: org.mozilla.gecko.updater.UpdateServiceHelper.registerForUpdates(this, (String) newValue); michael@0: } else if (PREFS_HEALTHREPORT_UPLOAD_ENABLED.equals(prefName)) { michael@0: // The healthreport pref only lives in Android, so we do not persist michael@0: // to Gecko, but we do broadcast intent to the health report michael@0: // background uploader service, which will start or stop the michael@0: // repeated background upload attempts. michael@0: broadcastHealthReportUploadPref(this, ((Boolean) newValue).booleanValue()); michael@0: } else if (PREFS_GEO_REPORTING.equals(prefName)) { michael@0: // Translate boolean value to int for geo reporting pref. michael@0: newValue = ((Boolean) newValue) ? 1 : 0; michael@0: } michael@0: michael@0: // Send Gecko-side pref changes to Gecko michael@0: if (!TextUtils.isEmpty(prefName) && !prefName.startsWith(NON_PREF_PREFIX)) { michael@0: PrefsHelper.setPref(prefName, newValue); michael@0: } michael@0: michael@0: if (preference instanceof ListPreference) { michael@0: // We need to find the entry for the new value michael@0: int newIndex = ((ListPreference) preference).findIndexOfValue((String) newValue); michael@0: CharSequence newEntry = ((ListPreference) preference).getEntries()[newIndex]; michael@0: ((ListPreference) preference).setSummary(newEntry); michael@0: } else if (preference instanceof LinkPreference) { michael@0: setResult(RESULT_CODE_EXIT_SETTINGS); michael@0: finish(); michael@0: } else if (preference instanceof FontSizePreference) { michael@0: final FontSizePreference fontSizePref = (FontSizePreference) preference; michael@0: fontSizePref.setSummary(fontSizePref.getSavedFontSizeName()); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: private EditText getTextBox(int aHintText) { michael@0: EditText input = new EditText(this); michael@0: int inputtype = InputType.TYPE_CLASS_TEXT; michael@0: inputtype |= InputType.TYPE_TEXT_VARIATION_PASSWORD | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS; michael@0: input.setInputType(inputtype); michael@0: michael@0: input.setHint(aHintText); michael@0: return input; michael@0: } michael@0: michael@0: private class PasswordTextWatcher implements TextWatcher { michael@0: EditText input1 = null; michael@0: EditText input2 = null; michael@0: AlertDialog dialog = null; michael@0: michael@0: PasswordTextWatcher(EditText aInput1, EditText aInput2, AlertDialog aDialog) { michael@0: input1 = aInput1; michael@0: input2 = aInput2; michael@0: dialog = aDialog; michael@0: } michael@0: michael@0: @Override michael@0: public void afterTextChanged(Editable s) { michael@0: if (dialog == null) michael@0: return; michael@0: michael@0: String text1 = input1.getText().toString(); michael@0: String text2 = input2.getText().toString(); michael@0: boolean disabled = TextUtils.isEmpty(text1) || TextUtils.isEmpty(text2) || !text1.equals(text2); michael@0: dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(!disabled); michael@0: } michael@0: michael@0: @Override michael@0: public void beforeTextChanged(CharSequence s, int start, int count, int after) { } michael@0: @Override michael@0: public void onTextChanged(CharSequence s, int start, int before, int count) { } michael@0: } michael@0: michael@0: private class EmptyTextWatcher implements TextWatcher { michael@0: EditText input = null; michael@0: AlertDialog dialog = null; michael@0: michael@0: EmptyTextWatcher(EditText aInput, AlertDialog aDialog) { michael@0: input = aInput; michael@0: dialog = aDialog; michael@0: } michael@0: michael@0: @Override michael@0: public void afterTextChanged(Editable s) { michael@0: if (dialog == null) michael@0: return; michael@0: michael@0: String text = input.getText().toString(); michael@0: boolean disabled = TextUtils.isEmpty(text); michael@0: dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(!disabled); michael@0: } michael@0: michael@0: @Override michael@0: public void beforeTextChanged(CharSequence s, int start, int count, int after) { } michael@0: @Override michael@0: public void onTextChanged(CharSequence s, int start, int before, int count) { } michael@0: } michael@0: michael@0: @Override michael@0: protected Dialog onCreateDialog(int id) { michael@0: AlertDialog.Builder builder = new AlertDialog.Builder(this); michael@0: LinearLayout linearLayout = new LinearLayout(this); michael@0: linearLayout.setOrientation(LinearLayout.VERTICAL); michael@0: AlertDialog dialog = null; michael@0: switch(id) { michael@0: case DIALOG_CREATE_MASTER_PASSWORD: michael@0: final EditText input1 = getTextBox(R.string.masterpassword_password); michael@0: final EditText input2 = getTextBox(R.string.masterpassword_confirm); michael@0: linearLayout.addView(input1); michael@0: linearLayout.addView(input2); michael@0: michael@0: builder.setTitle(R.string.masterpassword_create_title) michael@0: .setView((View) linearLayout) michael@0: .setPositiveButton(R.string.button_ok, new DialogInterface.OnClickListener() { michael@0: @Override michael@0: public void onClick(DialogInterface dialog, int which) { michael@0: JSONObject jsonPref = new JSONObject(); michael@0: try { michael@0: jsonPref.put("name", PREFS_MP_ENABLED); michael@0: jsonPref.put("type", "string"); michael@0: jsonPref.put("value", input1.getText().toString()); michael@0: michael@0: GeckoEvent event = GeckoEvent.createBroadcastEvent("Preferences:Set", jsonPref.toString()); michael@0: GeckoAppShell.sendEventToGecko(event); michael@0: } catch(Exception ex) { michael@0: Log.e(LOGTAG, "Error setting master password", ex); michael@0: } michael@0: return; michael@0: } michael@0: }) michael@0: .setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() { michael@0: @Override michael@0: public void onClick(DialogInterface dialog, int which) { michael@0: return; michael@0: } michael@0: }); michael@0: dialog = builder.create(); michael@0: dialog.setOnShowListener(new DialogInterface.OnShowListener() { michael@0: @Override michael@0: public void onShow(DialogInterface dialog) { michael@0: input1.setText(""); michael@0: input2.setText(""); michael@0: input1.requestFocus(); michael@0: } michael@0: }); michael@0: michael@0: PasswordTextWatcher watcher = new PasswordTextWatcher(input1, input2, dialog); michael@0: input1.addTextChangedListener((TextWatcher) watcher); michael@0: input2.addTextChangedListener((TextWatcher) watcher); michael@0: michael@0: break; michael@0: case DIALOG_REMOVE_MASTER_PASSWORD: michael@0: final EditText input = getTextBox(R.string.masterpassword_password); michael@0: linearLayout.addView(input); michael@0: michael@0: builder.setTitle(R.string.masterpassword_remove_title) michael@0: .setView((View) linearLayout) michael@0: .setPositiveButton(R.string.button_ok, new DialogInterface.OnClickListener() { michael@0: @Override michael@0: public void onClick(DialogInterface dialog, int which) { michael@0: PrefsHelper.setPref(PREFS_MP_ENABLED, input.getText().toString()); michael@0: } michael@0: }) michael@0: .setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() { michael@0: @Override michael@0: public void onClick(DialogInterface dialog, int which) { michael@0: return; michael@0: } michael@0: }); michael@0: dialog = builder.create(); michael@0: dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { michael@0: @Override michael@0: public void onDismiss(DialogInterface dialog) { michael@0: input.setText(""); michael@0: } michael@0: }); michael@0: dialog.setOnShowListener(new DialogInterface.OnShowListener() { michael@0: @Override michael@0: public void onShow(DialogInterface dialog) { michael@0: input.setText(""); michael@0: } michael@0: }); michael@0: input.addTextChangedListener(new EmptyTextWatcher(input, dialog)); michael@0: break; michael@0: default: michael@0: return null; michael@0: } michael@0: michael@0: return dialog; michael@0: } michael@0: michael@0: // Initialize preferences by requesting the preference values from Gecko michael@0: private int getGeckoPreferences(final PreferenceGroup screen, ArrayList prefs) { michael@0: return PrefsHelper.getPrefs(prefs, new PrefsHelper.PrefHandlerBase() { michael@0: private Preference getField(String prefName) { michael@0: return screen.findPreference(prefName); michael@0: } michael@0: michael@0: // Handle v14 TwoStatePreference with backwards compatibility. michael@0: class CheckBoxPrefSetter { michael@0: public void setBooleanPref(Preference preference, boolean value) { michael@0: if ((preference instanceof CheckBoxPreference) && michael@0: ((CheckBoxPreference) preference).isChecked() != value) { michael@0: ((CheckBoxPreference) preference).setChecked(value); michael@0: } michael@0: } michael@0: } michael@0: michael@0: class TwoStatePrefSetter extends CheckBoxPrefSetter { michael@0: @Override michael@0: public void setBooleanPref(Preference preference, boolean value) { michael@0: if ((preference instanceof TwoStatePreference) && michael@0: ((TwoStatePreference) preference).isChecked() != value) { michael@0: ((TwoStatePreference) preference).setChecked(value); michael@0: } michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public void prefValue(String prefName, final boolean value) { michael@0: final Preference pref = getField(prefName); michael@0: final CheckBoxPrefSetter prefSetter; michael@0: if (Build.VERSION.SDK_INT < 14) { michael@0: prefSetter = new CheckBoxPrefSetter(); michael@0: } else { michael@0: prefSetter = new TwoStatePrefSetter(); michael@0: } michael@0: ThreadUtils.postToUiThread(new Runnable() { michael@0: public void run() { michael@0: prefSetter.setBooleanPref(pref, value); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: @Override michael@0: public void prefValue(String prefName, final String value) { michael@0: final Preference pref = getField(prefName); michael@0: if (pref instanceof EditTextPreference) { michael@0: ThreadUtils.postToUiThread(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: ((EditTextPreference) pref).setText(value); michael@0: } michael@0: }); michael@0: } else if (pref instanceof ListPreference) { michael@0: ThreadUtils.postToUiThread(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: ((ListPreference) pref).setValue(value); michael@0: // Set the summary string to the current entry michael@0: CharSequence selectedEntry = ((ListPreference) pref).getEntry(); michael@0: ((ListPreference) pref).setSummary(selectedEntry); michael@0: } michael@0: }); michael@0: } else if (pref instanceof FontSizePreference) { michael@0: final FontSizePreference fontSizePref = (FontSizePreference) pref; michael@0: fontSizePref.setSavedFontSize(value); michael@0: final String fontSizeName = fontSizePref.getSavedFontSizeName(); michael@0: ThreadUtils.postToUiThread(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: fontSizePref.setSummary(fontSizeName); // Ex: "Small". michael@0: } michael@0: }); michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public void prefValue(String prefName, final int value) { michael@0: final Preference pref = getField(prefName); michael@0: final CheckBoxPrefSetter prefSetter; michael@0: if (PREFS_GEO_REPORTING.equals(prefName)) { michael@0: if (Build.VERSION.SDK_INT < 14) { michael@0: prefSetter = new CheckBoxPrefSetter(); michael@0: } else { michael@0: prefSetter = new TwoStatePrefSetter(); michael@0: } michael@0: ThreadUtils.postToUiThread(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: prefSetter.setBooleanPref(pref, value == 1); michael@0: } michael@0: }); michael@0: } else { michael@0: Log.w(LOGTAG, "Unhandled int value for pref [" + pref + "]"); michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public boolean isObserver() { michael@0: return true; michael@0: } michael@0: michael@0: @Override michael@0: public void finish() { michael@0: // enable all preferences once we have them from gecko michael@0: ThreadUtils.postToUiThread(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: screen.setEnabled(true); michael@0: } michael@0: }); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: private void registerEventListener(String event) { michael@0: GeckoAppShell.getEventDispatcher().registerEventListener(event, this); michael@0: } michael@0: michael@0: private void unregisterEventListener(String event) { michael@0: GeckoAppShell.getEventDispatcher().unregisterEventListener(event, this); michael@0: } michael@0: michael@0: @Override michael@0: public boolean isGeckoActivityOpened() { michael@0: return false; michael@0: } michael@0: michael@0: /** michael@0: * Given an Intent instance, add extras to specify which settings section to michael@0: * open. michael@0: * michael@0: * resource should be a valid Android XML resource identifier. michael@0: * michael@0: * The mechanism to open a section differs based on Android version. michael@0: */ michael@0: public static void setResourceToOpen(final Intent intent, final String resource) { michael@0: if (intent == null) { michael@0: throw new IllegalArgumentException("intent must not be null"); michael@0: } michael@0: if (resource == null) { michael@0: return; michael@0: } michael@0: michael@0: if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { michael@0: intent.putExtra("resource", resource); michael@0: } else { michael@0: intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, GeckoPreferenceFragment.class.getName()); michael@0: michael@0: Bundle fragmentArgs = new Bundle(); michael@0: fragmentArgs.putString("resource", resource); michael@0: intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs); michael@0: } michael@0: } michael@0: }