1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/preferences/GeckoPreferences.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,947 @@ 1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +package org.mozilla.gecko.preferences; 1.10 + 1.11 +import java.util.ArrayList; 1.12 +import java.util.List; 1.13 + 1.14 +import org.json.JSONObject; 1.15 +import org.mozilla.gecko.AppConstants; 1.16 +import org.mozilla.gecko.DataReportingNotification; 1.17 +import org.mozilla.gecko.GeckoActivityStatus; 1.18 +import org.mozilla.gecko.GeckoApp; 1.19 +import org.mozilla.gecko.GeckoAppShell; 1.20 +import org.mozilla.gecko.GeckoApplication; 1.21 +import org.mozilla.gecko.GeckoEvent; 1.22 +import org.mozilla.gecko.GeckoProfile; 1.23 +import org.mozilla.gecko.GeckoSharedPrefs; 1.24 +import org.mozilla.gecko.PrefsHelper; 1.25 +import org.mozilla.gecko.R; 1.26 +import org.mozilla.gecko.background.announcements.AnnouncementsConstants; 1.27 +import org.mozilla.gecko.background.common.GlobalConstants; 1.28 +import org.mozilla.gecko.background.healthreport.HealthReportConstants; 1.29 +import org.mozilla.gecko.home.HomePanelPicker; 1.30 +import org.mozilla.gecko.util.GeckoEventListener; 1.31 +import org.mozilla.gecko.util.ThreadUtils; 1.32 + 1.33 +import android.app.Activity; 1.34 +import android.app.AlertDialog; 1.35 +import android.app.Dialog; 1.36 +import android.app.Fragment; 1.37 +import android.app.NotificationManager; 1.38 +import android.content.Context; 1.39 +import android.content.DialogInterface; 1.40 +import android.content.Intent; 1.41 +import android.content.SharedPreferences; 1.42 +import android.os.Build; 1.43 +import android.os.Bundle; 1.44 +import android.preference.CheckBoxPreference; 1.45 +import android.preference.EditTextPreference; 1.46 +import android.preference.ListPreference; 1.47 +import android.preference.Preference; 1.48 +import android.preference.Preference.OnPreferenceChangeListener; 1.49 +import android.preference.Preference.OnPreferenceClickListener; 1.50 +import android.preference.PreferenceActivity; 1.51 +import android.preference.PreferenceGroup; 1.52 +import android.preference.PreferenceScreen; 1.53 +import android.preference.TwoStatePreference; 1.54 +import android.text.Editable; 1.55 +import android.text.InputType; 1.56 +import android.text.TextUtils; 1.57 +import android.text.TextWatcher; 1.58 +import android.util.Log; 1.59 +import android.view.MenuItem; 1.60 +import android.view.View; 1.61 +import android.widget.AdapterView; 1.62 +import android.widget.EditText; 1.63 +import android.widget.LinearLayout; 1.64 +import android.widget.ListAdapter; 1.65 +import android.widget.ListView; 1.66 +import android.widget.Toast; 1.67 + 1.68 +public class GeckoPreferences 1.69 + extends PreferenceActivity 1.70 + implements OnPreferenceChangeListener, GeckoEventListener, GeckoActivityStatus 1.71 +{ 1.72 + private static final String LOGTAG = "GeckoPreferences"; 1.73 + 1.74 + private static final String NON_PREF_PREFIX = "android.not_a_preference."; 1.75 + public static final String INTENT_EXTRA_RESOURCES = "resource"; 1.76 + public static String PREFS_HEALTHREPORT_UPLOAD_ENABLED = NON_PREF_PREFIX + "healthreport.uploadEnabled"; 1.77 + 1.78 + private static boolean sIsCharEncodingEnabled = false; 1.79 + private boolean mInitialized = false; 1.80 + private int mPrefsRequestId = 0; 1.81 + private PanelsPreferenceCategory mPanelsPreferenceCategory; 1.82 + 1.83 + // These match keys in resources/xml*/preferences*.xml 1.84 + private static final String PREFS_SEARCH_RESTORE_DEFAULTS = NON_PREF_PREFIX + "search.restore_defaults"; 1.85 + private static final String PREFS_HOME_ADD_PANEL = NON_PREF_PREFIX + "home.add_panel"; 1.86 + private static final String PREFS_ANNOUNCEMENTS_ENABLED = NON_PREF_PREFIX + "privacy.announcements.enabled"; 1.87 + private static final String PREFS_DATA_REPORTING_PREFERENCES = NON_PREF_PREFIX + "datareporting.preferences"; 1.88 + private static final String PREFS_TELEMETRY_ENABLED = "toolkit.telemetry.enabled"; 1.89 + private static final String PREFS_CRASHREPORTER_ENABLED = "datareporting.crashreporter.submitEnabled"; 1.90 + private static final String PREFS_MENU_CHAR_ENCODING = "browser.menu.showCharacterEncoding"; 1.91 + private static final String PREFS_MP_ENABLED = "privacy.masterpassword.enabled"; 1.92 + private static final String PREFS_UPDATER_AUTODOWNLOAD = "app.update.autodownload"; 1.93 + private static final String PREFS_GEO_REPORTING = "app.geo.reportdata"; 1.94 + private static final String PREFS_GEO_LEARN_MORE = NON_PREF_PREFIX + "geo.learn_more"; 1.95 + private static final String PREFS_HEALTHREPORT_LINK = NON_PREF_PREFIX + "healthreport.link"; 1.96 + private static final String PREFS_DEVTOOLS_REMOTE_ENABLED = "devtools.debugger.remote-enabled"; 1.97 + private static final String PREFS_DISPLAY_REFLOW_ON_ZOOM = "browser.zoom.reflowOnZoom"; 1.98 + private static final String PREFS_SYNC = NON_PREF_PREFIX + "sync"; 1.99 + 1.100 + public static final String PREFS_RESTORE_SESSION = NON_PREF_PREFIX + "restoreSession3"; 1.101 + 1.102 + // These values are chosen to be distinct from other Activity constants. 1.103 + private static final int REQUEST_CODE_PREF_SCREEN = 5; 1.104 + private static final int RESULT_CODE_EXIT_SETTINGS = 6; 1.105 + 1.106 + @Override 1.107 + protected void onCreate(Bundle savedInstanceState) { 1.108 + 1.109 + // For Android v11+ where we use Fragments (v11+ only due to bug 866352), 1.110 + // check that PreferenceActivity.EXTRA_SHOW_FRAGMENT has been set 1.111 + // (or set it) before super.onCreate() is called so Android can display 1.112 + // the correct Fragment resource. 1.113 + 1.114 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && 1.115 + !getIntent().hasExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT)) { 1.116 + // Set up the default fragment if there is no explicit fragment to show. 1.117 + setupTopLevelFragmentIntent(); 1.118 + } 1.119 + 1.120 + super.onCreate(savedInstanceState); 1.121 + 1.122 + // Use setResourceToOpen to specify these extras. 1.123 + Bundle intentExtras = getIntent().getExtras(); 1.124 + 1.125 + // For versions of Android lower than Honeycomb, use xml resources instead of 1.126 + // Fragments because of an Android bug in ActionBar (described in bug 866352 and 1.127 + // fixed in bug 833625). 1.128 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { 1.129 + // Write prefs to our custom GeckoSharedPrefs file. 1.130 + getPreferenceManager().setSharedPreferencesName(GeckoSharedPrefs.APP_PREFS_NAME); 1.131 + 1.132 + int res = 0; 1.133 + if (intentExtras != null && intentExtras.containsKey(INTENT_EXTRA_RESOURCES)) { 1.134 + // Fetch resource id from intent. 1.135 + String resourceName = intentExtras.getString(INTENT_EXTRA_RESOURCES); 1.136 + if (resourceName != null) { 1.137 + res = getResources().getIdentifier(resourceName, "xml", getPackageName()); 1.138 + if (res == 0) { 1.139 + Log.e(LOGTAG, "No resource found named " + resourceName); 1.140 + } 1.141 + } 1.142 + } 1.143 + if (res == 0) { 1.144 + // No resource specified, or the resource was invalid; use the default preferences screen. 1.145 + Log.e(LOGTAG, "Displaying default settings."); 1.146 + res = R.xml.preferences; 1.147 + } 1.148 + addPreferencesFromResource(res); 1.149 + } 1.150 + 1.151 + registerEventListener("Sanitize:Finished"); 1.152 + 1.153 + // Add handling for long-press click. 1.154 + // This is only for Android 3.0 and below (which use the long-press-context-menu paradigm). 1.155 + final ListView mListView = getListView(); 1.156 + mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { 1.157 + @Override 1.158 + public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { 1.159 + // Call long-click handler if it the item implements it. 1.160 + final ListAdapter listAdapter = ((ListView) parent).getAdapter(); 1.161 + final Object listItem = listAdapter.getItem(position); 1.162 + 1.163 + // Only CustomListPreference handles long clicks. 1.164 + if (listItem instanceof CustomListPreference && listItem instanceof View.OnLongClickListener) { 1.165 + final View.OnLongClickListener longClickListener = (View.OnLongClickListener) listItem; 1.166 + return longClickListener.onLongClick(view); 1.167 + } 1.168 + return false; 1.169 + } 1.170 + }); 1.171 + 1.172 + if (Build.VERSION.SDK_INT >= 14 && getActionBar() != null) 1.173 + getActionBar().setHomeButtonEnabled(true); 1.174 + 1.175 + // If launched from notification, explicitly cancel the notification. 1.176 + if (intentExtras != null && intentExtras.containsKey(DataReportingNotification.ALERT_NAME_DATAREPORTING_NOTIFICATION)) { 1.177 + NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE); 1.178 + notificationManager.cancel(DataReportingNotification.ALERT_NAME_DATAREPORTING_NOTIFICATION.hashCode()); 1.179 + } 1.180 + } 1.181 + 1.182 + /** 1.183 + * Set intent to display top-level settings fragment. 1.184 + */ 1.185 + private void setupTopLevelFragmentIntent() { 1.186 + Intent intent = getIntent(); 1.187 + // Check intent to determine settings screen to display. 1.188 + Bundle intentExtras = intent.getExtras(); 1.189 + Bundle fragmentArgs = new Bundle(); 1.190 + // Add resource argument to fragment if it exists. 1.191 + if (intentExtras != null && intentExtras.containsKey(INTENT_EXTRA_RESOURCES)) { 1.192 + String resourceName = intentExtras.getString(INTENT_EXTRA_RESOURCES); 1.193 + fragmentArgs.putString(INTENT_EXTRA_RESOURCES, resourceName); 1.194 + } else { 1.195 + // Use top-level settings screen. 1.196 + if (!onIsMultiPane()) { 1.197 + fragmentArgs.putString(INTENT_EXTRA_RESOURCES, "preferences"); 1.198 + } else { 1.199 + fragmentArgs.putString(INTENT_EXTRA_RESOURCES, "preferences_customize_tablet"); 1.200 + } 1.201 + } 1.202 + 1.203 + // Build fragment intent. 1.204 + intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, GeckoPreferenceFragment.class.getName()); 1.205 + intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs); 1.206 + } 1.207 + 1.208 + @Override 1.209 + public void onBuildHeaders(List<Header> target) { 1.210 + if (onIsMultiPane()) 1.211 + loadHeadersFromResource(R.xml.preference_headers, target); 1.212 + } 1.213 + 1.214 + @Override 1.215 + public void onWindowFocusChanged(boolean hasFocus) { 1.216 + if (!hasFocus || mInitialized) 1.217 + return; 1.218 + 1.219 + mInitialized = true; 1.220 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { 1.221 + PreferenceScreen screen = getPreferenceScreen(); 1.222 + mPrefsRequestId = setupPreferences(screen); 1.223 + } 1.224 + } 1.225 + 1.226 + @Override 1.227 + protected void onDestroy() { 1.228 + super.onDestroy(); 1.229 + unregisterEventListener("Sanitize:Finished"); 1.230 + if (mPrefsRequestId > 0) { 1.231 + PrefsHelper.removeObserver(mPrefsRequestId); 1.232 + } 1.233 + } 1.234 + 1.235 + @Override 1.236 + public void onPause() { 1.237 + super.onPause(); 1.238 + 1.239 + if (getApplication() instanceof GeckoApplication) { 1.240 + ((GeckoApplication) getApplication()).onActivityPause(this); 1.241 + } 1.242 + } 1.243 + 1.244 + @Override 1.245 + public void onResume() { 1.246 + super.onResume(); 1.247 + 1.248 + if (getApplication() instanceof GeckoApplication) { 1.249 + ((GeckoApplication) getApplication()).onActivityResume(this); 1.250 + } 1.251 + } 1.252 + 1.253 + @Override 1.254 + public void startActivity(Intent intent) { 1.255 + // For settings, we want to be able to pass results up the chain 1.256 + // of preference screens so Settings can behave as a single unit. 1.257 + // Specifically, when we open a link, we want to back out of all 1.258 + // the settings screens. 1.259 + // We need to start nested PreferenceScreens withStartActivityForResult(). 1.260 + // Android doesn't let us do that (see Preference.onClick), so we're overriding here. 1.261 + startActivityForResult(intent, REQUEST_CODE_PREF_SCREEN); 1.262 + } 1.263 + 1.264 + @Override 1.265 + public void startWithFragment(String fragmentName, Bundle args, 1.266 + Fragment resultTo, int resultRequestCode, int titleRes, int shortTitleRes) { 1.267 + // Overriding because we want to use startActivityForResult for Fragment intents. 1.268 + Intent intent = onBuildStartFragmentIntent(fragmentName, args, titleRes, shortTitleRes); 1.269 + if (resultTo == null) { 1.270 + startActivityForResult(intent, REQUEST_CODE_PREF_SCREEN); 1.271 + } else { 1.272 + resultTo.startActivityForResult(intent, resultRequestCode); 1.273 + } 1.274 + } 1.275 + 1.276 + @Override 1.277 + public void onActivityResult(int requestCode, int resultCode, Intent data) { 1.278 + switch (requestCode) { 1.279 + case REQUEST_CODE_PREF_SCREEN: 1.280 + if (resultCode == RESULT_CODE_EXIT_SETTINGS) { 1.281 + // Pass this result up to the parent activity. 1.282 + setResult(RESULT_CODE_EXIT_SETTINGS); 1.283 + finish(); 1.284 + } 1.285 + break; 1.286 + 1.287 + case HomePanelPicker.REQUEST_CODE_ADD_PANEL: 1.288 + switch (resultCode) { 1.289 + case Activity.RESULT_OK: 1.290 + // Panel installed, refresh panels list. 1.291 + mPanelsPreferenceCategory.refresh(); 1.292 + break; 1.293 + case Activity.RESULT_CANCELED: 1.294 + // Dialog was cancelled, do nothing. 1.295 + break; 1.296 + default: 1.297 + Log.w(LOGTAG, "Unhandled ADD_PANEL result code " + requestCode); 1.298 + break; 1.299 + } 1.300 + break; 1.301 + } 1.302 + } 1.303 + 1.304 + @Override 1.305 + public void handleMessage(String event, JSONObject message) { 1.306 + try { 1.307 + if (event.equals("Sanitize:Finished")) { 1.308 + boolean success = message.getBoolean("success"); 1.309 + final int stringRes = success ? R.string.private_data_success : R.string.private_data_fail; 1.310 + final Context context = this; 1.311 + ThreadUtils.postToUiThread(new Runnable () { 1.312 + @Override 1.313 + public void run() { 1.314 + Toast.makeText(context, stringRes, Toast.LENGTH_SHORT).show(); 1.315 + } 1.316 + }); 1.317 + } 1.318 + } catch (Exception e) { 1.319 + Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e); 1.320 + } 1.321 + } 1.322 + 1.323 + /** 1.324 + * Initialize all of the preferences (native of Gecko ones) for this screen. 1.325 + * 1.326 + * @param prefs The android.preference.PreferenceGroup to initialize 1.327 + * @return The integer id for the PrefsHelper.PrefHandlerBase listener added 1.328 + * to monitor changes to Gecko prefs. 1.329 + */ 1.330 + public int setupPreferences(PreferenceGroup prefs) { 1.331 + ArrayList<String> list = new ArrayList<String>(); 1.332 + setupPreferences(prefs, list); 1.333 + return getGeckoPreferences(prefs, list); 1.334 + } 1.335 + 1.336 + /** 1.337 + * Recursively loop through a PreferenceGroup. Initialize native Android prefs, 1.338 + * and build a list of Gecko preferences in the passed in prefs array 1.339 + * 1.340 + * @param preferences The android.preference.PreferenceGroup to initialize 1.341 + * @param prefs An ArrayList to fill with Gecko preferences that need to be 1.342 + * initialized 1.343 + * @return The integer id for the PrefsHelper.PrefHandlerBase listener added 1.344 + * to monitor changes to Gecko prefs. 1.345 + */ 1.346 + private void setupPreferences(PreferenceGroup preferences, ArrayList<String> prefs) { 1.347 + for (int i = 0; i < preferences.getPreferenceCount(); i++) { 1.348 + Preference pref = preferences.getPreference(i); 1.349 + String key = pref.getKey(); 1.350 + if (pref instanceof PreferenceGroup) { 1.351 + // If no datareporting is enabled, remove UI. 1.352 + if (PREFS_DATA_REPORTING_PREFERENCES.equals(key)) { 1.353 + if (!AppConstants.MOZ_DATA_REPORTING) { 1.354 + preferences.removePreference(pref); 1.355 + i--; 1.356 + continue; 1.357 + } 1.358 + } else if (pref instanceof PanelsPreferenceCategory) { 1.359 + mPanelsPreferenceCategory = (PanelsPreferenceCategory) pref; 1.360 + } 1.361 + setupPreferences((PreferenceGroup) pref, prefs); 1.362 + } else { 1.363 + pref.setOnPreferenceChangeListener(this); 1.364 + if (!AppConstants.MOZ_UPDATER && 1.365 + PREFS_UPDATER_AUTODOWNLOAD.equals(key)) { 1.366 + preferences.removePreference(pref); 1.367 + i--; 1.368 + continue; 1.369 + } else if (AppConstants.RELEASE_BUILD && 1.370 + PREFS_DISPLAY_REFLOW_ON_ZOOM.equals(key)) { 1.371 + // Remove UI for reflow on release builds. 1.372 + preferences.removePreference(pref); 1.373 + i--; 1.374 + continue; 1.375 + } else if (!AppConstants.MOZ_TELEMETRY_REPORTING && 1.376 + PREFS_TELEMETRY_ENABLED.equals(key)) { 1.377 + preferences.removePreference(pref); 1.378 + i--; 1.379 + continue; 1.380 + } else if (!AppConstants.MOZ_SERVICES_HEALTHREPORT && 1.381 + (PREFS_HEALTHREPORT_UPLOAD_ENABLED.equals(key) || 1.382 + PREFS_HEALTHREPORT_LINK.equals(key))) { 1.383 + preferences.removePreference(pref); 1.384 + i--; 1.385 + continue; 1.386 + } else if (!AppConstants.MOZ_CRASHREPORTER && 1.387 + PREFS_CRASHREPORTER_ENABLED.equals(key)) { 1.388 + preferences.removePreference(pref); 1.389 + i--; 1.390 + continue; 1.391 + } else if (AppConstants.RELEASE_BUILD && PREFS_GEO_REPORTING.equals(key)) { 1.392 + // We don't build wifi/cell tower collection in release builds, so hide the UI. 1.393 + preferences.removePreference(pref); 1.394 + i--; 1.395 + continue; 1.396 + } else if (PREFS_DEVTOOLS_REMOTE_ENABLED.equals(key)) { 1.397 + final Context thisContext = this; 1.398 + pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { 1.399 + @Override 1.400 + public boolean onPreferenceClick(Preference preference) { 1.401 + // Display toast to remind setting up tcp forwarding. 1.402 + if (((CheckBoxPreference) preference).isChecked()) { 1.403 + Toast.makeText(thisContext, R.string.devtools_remote_debugging_forward, Toast.LENGTH_SHORT).show(); 1.404 + } 1.405 + return true; 1.406 + } 1.407 + }); 1.408 + } else if (PREFS_RESTORE_SESSION.equals(key)) { 1.409 + // Set the summary string to the current entry. The summary 1.410 + // for other list prefs will be set in the PrefsHelper 1.411 + // callback, but since this pref doesn't live in Gecko, we 1.412 + // need to handle it separately. 1.413 + ListPreference listPref = (ListPreference) pref; 1.414 + CharSequence selectedEntry = listPref.getEntry(); 1.415 + listPref.setSummary(selectedEntry); 1.416 + continue; 1.417 + } else if (PREFS_SYNC.equals(key) && GeckoProfile.get(this).inGuestMode()) { 1.418 + // Don't show sync prefs while in guest mode. 1.419 + preferences.removePreference(pref); 1.420 + i--; 1.421 + continue; 1.422 + } else if (PREFS_SEARCH_RESTORE_DEFAULTS.equals(key)) { 1.423 + pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { 1.424 + @Override 1.425 + public boolean onPreferenceClick(Preference preference) { 1.426 + GeckoPreferences.this.restoreDefaultSearchEngines(); 1.427 + return true; 1.428 + } 1.429 + }); 1.430 + } else if (PREFS_HOME_ADD_PANEL.equals(key)) { 1.431 + pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { 1.432 + @Override 1.433 + public boolean onPreferenceClick(Preference preference) { 1.434 + Intent dialogIntent = new Intent(GeckoPreferences.this, HomePanelPicker.class); 1.435 + startActivityForResult(dialogIntent, HomePanelPicker.REQUEST_CODE_ADD_PANEL); 1.436 + return true; 1.437 + } 1.438 + }); 1.439 + } 1.440 + 1.441 + // Some Preference UI elements are not actually preferences, 1.442 + // but they require a key to work correctly. For example, 1.443 + // "Clear private data" requires a key for its state to be 1.444 + // saved when the orientation changes. It uses the 1.445 + // "android.not_a_preference.privacy.clear" key - which doesn't 1.446 + // exist in Gecko - to satisfy this requirement. 1.447 + if (key != null && !key.startsWith(NON_PREF_PREFIX)) { 1.448 + prefs.add(key); 1.449 + } 1.450 + } 1.451 + } 1.452 + } 1.453 + 1.454 + /** 1.455 + * Restore default search engines in Gecko and retrigger a search engine refresh. 1.456 + */ 1.457 + protected void restoreDefaultSearchEngines() { 1.458 + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:RestoreDefaults", null)); 1.459 + 1.460 + // Send message to Gecko to get engines. SearchPreferenceCategory listens for the response. 1.461 + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:GetVisible", null)); 1.462 + } 1.463 + 1.464 + @Override 1.465 + public boolean onOptionsItemSelected(MenuItem item) { 1.466 + int itemId = item.getItemId(); 1.467 + switch (itemId) { 1.468 + case android.R.id.home: 1.469 + finish(); 1.470 + return true; 1.471 + } 1.472 + 1.473 + // Generated R.id.* apparently aren't constant expressions, so they can't be switched. 1.474 + if (itemId == R.id.restore_defaults) { 1.475 + restoreDefaultSearchEngines(); 1.476 + return true; 1.477 + } 1.478 + 1.479 + return super.onOptionsItemSelected(item); 1.480 + } 1.481 + 1.482 + final private int DIALOG_CREATE_MASTER_PASSWORD = 0; 1.483 + final private int DIALOG_REMOVE_MASTER_PASSWORD = 1; 1.484 + 1.485 + public static void setCharEncodingState(boolean enabled) { 1.486 + sIsCharEncodingEnabled = enabled; 1.487 + } 1.488 + 1.489 + public static boolean getCharEncodingState() { 1.490 + return sIsCharEncodingEnabled; 1.491 + } 1.492 + 1.493 + public static void broadcastAction(final Context context, final Intent intent) { 1.494 + fillIntentWithProfileInfo(context, intent); 1.495 + context.sendBroadcast(intent, GlobalConstants.PER_ANDROID_PACKAGE_PERMISSION); 1.496 + } 1.497 + 1.498 + /** 1.499 + * Broadcast an intent with <code>pref</code>, <code>branch</code>, and 1.500 + * <code>enabled</code> extras. This is intended to represent the 1.501 + * notification of a preference value to observers. 1.502 + * 1.503 + * The broadcast will be sent only to receivers registered with the 1.504 + * (Fennec-specific) per-Android package permission. 1.505 + */ 1.506 + public static void broadcastPrefAction(final Context context, 1.507 + final String action, 1.508 + final String pref, 1.509 + final boolean value) { 1.510 + final Intent intent = new Intent(action) 1.511 + .putExtra("pref", pref) 1.512 + .putExtra("branch", GeckoSharedPrefs.APP_PREFS_NAME) 1.513 + .putExtra("enabled", value); 1.514 + broadcastAction(context, intent); 1.515 + } 1.516 + 1.517 + private static void fillIntentWithProfileInfo(final Context context, final Intent intent) { 1.518 + // There is a race here, but GeckoProfile returns the default profile 1.519 + // when Gecko is not explicitly running for a different profile. In a 1.520 + // multi-profile world, this will need to be updated (possibly to 1.521 + // broadcast settings for all profiles). See Bug 882182. 1.522 + GeckoProfile profile = GeckoProfile.get(context); 1.523 + if (profile != null) { 1.524 + intent.putExtra("profileName", profile.getName()) 1.525 + .putExtra("profilePath", profile.getDir().getAbsolutePath()); 1.526 + } 1.527 + } 1.528 + 1.529 + /** 1.530 + * Broadcast the provided value as the value of the 1.531 + * <code>PREFS_ANNOUNCEMENTS_ENABLED</code> pref. 1.532 + */ 1.533 + public static void broadcastAnnouncementsPref(final Context context, final boolean value) { 1.534 + broadcastPrefAction(context, 1.535 + AnnouncementsConstants.ACTION_ANNOUNCEMENTS_PREF, 1.536 + PREFS_ANNOUNCEMENTS_ENABLED, 1.537 + value); 1.538 + } 1.539 + 1.540 + /** 1.541 + * Broadcast the current value of the 1.542 + * <code>PREFS_ANNOUNCEMENTS_ENABLED</code> pref. 1.543 + */ 1.544 + public static void broadcastAnnouncementsPref(final Context context) { 1.545 + final boolean value = getBooleanPref(context, PREFS_ANNOUNCEMENTS_ENABLED, true); 1.546 + broadcastAnnouncementsPref(context, value); 1.547 + } 1.548 + 1.549 + /** 1.550 + * Broadcast the provided value as the value of the 1.551 + * <code>PREFS_HEALTHREPORT_UPLOAD_ENABLED</code> pref. 1.552 + */ 1.553 + public static void broadcastHealthReportUploadPref(final Context context, final boolean value) { 1.554 + broadcastPrefAction(context, 1.555 + HealthReportConstants.ACTION_HEALTHREPORT_UPLOAD_PREF, 1.556 + PREFS_HEALTHREPORT_UPLOAD_ENABLED, 1.557 + value); 1.558 + } 1.559 + 1.560 + /** 1.561 + * Broadcast the current value of the 1.562 + * <code>PREFS_HEALTHREPORT_UPLOAD_ENABLED</code> pref. 1.563 + */ 1.564 + public static void broadcastHealthReportUploadPref(final Context context) { 1.565 + final boolean value = getBooleanPref(context, PREFS_HEALTHREPORT_UPLOAD_ENABLED, true); 1.566 + broadcastHealthReportUploadPref(context, value); 1.567 + } 1.568 + 1.569 + public static void broadcastHealthReportPrune(final Context context) { 1.570 + final Intent intent = new Intent(HealthReportConstants.ACTION_HEALTHREPORT_PRUNE); 1.571 + broadcastAction(context, intent); 1.572 + } 1.573 + 1.574 + /** 1.575 + * Return the value of the named preference in the default preferences file. 1.576 + * 1.577 + * This corresponds to the storage that backs preferences.xml. 1.578 + * @param context a <code>Context</code>; the 1.579 + * <code>PreferenceActivity</code> will suffice, but this 1.580 + * method is intended to be called from other contexts 1.581 + * within the application, not just this <code>Activity</code>. 1.582 + * @param name the name of the preference to retrieve. 1.583 + * @param def the default value to return if the preference is not present. 1.584 + * @return the value of the preference, or the default. 1.585 + */ 1.586 + public static boolean getBooleanPref(final Context context, final String name, boolean def) { 1.587 + final SharedPreferences prefs = GeckoSharedPrefs.forApp(context); 1.588 + return prefs.getBoolean(name, def); 1.589 + } 1.590 + 1.591 + @Override 1.592 + public boolean onPreferenceChange(Preference preference, Object newValue) { 1.593 + String prefName = preference.getKey(); 1.594 + if (PREFS_MP_ENABLED.equals(prefName)) { 1.595 + showDialog((Boolean) newValue ? DIALOG_CREATE_MASTER_PASSWORD : DIALOG_REMOVE_MASTER_PASSWORD); 1.596 + 1.597 + // We don't want the "use master password" pref to change until the 1.598 + // user has gone through the dialog. 1.599 + return false; 1.600 + } else if (PREFS_MENU_CHAR_ENCODING.equals(prefName)) { 1.601 + setCharEncodingState(((String) newValue).equals("true")); 1.602 + } else if (PREFS_ANNOUNCEMENTS_ENABLED.equals(prefName)) { 1.603 + // Send a broadcast intent to the product announcements service, either to start or 1.604 + // to stop the repeated background checks. 1.605 + broadcastAnnouncementsPref(this, ((Boolean) newValue).booleanValue()); 1.606 + } else if (PREFS_UPDATER_AUTODOWNLOAD.equals(prefName)) { 1.607 + org.mozilla.gecko.updater.UpdateServiceHelper.registerForUpdates(this, (String) newValue); 1.608 + } else if (PREFS_HEALTHREPORT_UPLOAD_ENABLED.equals(prefName)) { 1.609 + // The healthreport pref only lives in Android, so we do not persist 1.610 + // to Gecko, but we do broadcast intent to the health report 1.611 + // background uploader service, which will start or stop the 1.612 + // repeated background upload attempts. 1.613 + broadcastHealthReportUploadPref(this, ((Boolean) newValue).booleanValue()); 1.614 + } else if (PREFS_GEO_REPORTING.equals(prefName)) { 1.615 + // Translate boolean value to int for geo reporting pref. 1.616 + newValue = ((Boolean) newValue) ? 1 : 0; 1.617 + } 1.618 + 1.619 + // Send Gecko-side pref changes to Gecko 1.620 + if (!TextUtils.isEmpty(prefName) && !prefName.startsWith(NON_PREF_PREFIX)) { 1.621 + PrefsHelper.setPref(prefName, newValue); 1.622 + } 1.623 + 1.624 + if (preference instanceof ListPreference) { 1.625 + // We need to find the entry for the new value 1.626 + int newIndex = ((ListPreference) preference).findIndexOfValue((String) newValue); 1.627 + CharSequence newEntry = ((ListPreference) preference).getEntries()[newIndex]; 1.628 + ((ListPreference) preference).setSummary(newEntry); 1.629 + } else if (preference instanceof LinkPreference) { 1.630 + setResult(RESULT_CODE_EXIT_SETTINGS); 1.631 + finish(); 1.632 + } else if (preference instanceof FontSizePreference) { 1.633 + final FontSizePreference fontSizePref = (FontSizePreference) preference; 1.634 + fontSizePref.setSummary(fontSizePref.getSavedFontSizeName()); 1.635 + } 1.636 + 1.637 + return true; 1.638 + } 1.639 + 1.640 + private EditText getTextBox(int aHintText) { 1.641 + EditText input = new EditText(this); 1.642 + int inputtype = InputType.TYPE_CLASS_TEXT; 1.643 + inputtype |= InputType.TYPE_TEXT_VARIATION_PASSWORD | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS; 1.644 + input.setInputType(inputtype); 1.645 + 1.646 + input.setHint(aHintText); 1.647 + return input; 1.648 + } 1.649 + 1.650 + private class PasswordTextWatcher implements TextWatcher { 1.651 + EditText input1 = null; 1.652 + EditText input2 = null; 1.653 + AlertDialog dialog = null; 1.654 + 1.655 + PasswordTextWatcher(EditText aInput1, EditText aInput2, AlertDialog aDialog) { 1.656 + input1 = aInput1; 1.657 + input2 = aInput2; 1.658 + dialog = aDialog; 1.659 + } 1.660 + 1.661 + @Override 1.662 + public void afterTextChanged(Editable s) { 1.663 + if (dialog == null) 1.664 + return; 1.665 + 1.666 + String text1 = input1.getText().toString(); 1.667 + String text2 = input2.getText().toString(); 1.668 + boolean disabled = TextUtils.isEmpty(text1) || TextUtils.isEmpty(text2) || !text1.equals(text2); 1.669 + dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(!disabled); 1.670 + } 1.671 + 1.672 + @Override 1.673 + public void beforeTextChanged(CharSequence s, int start, int count, int after) { } 1.674 + @Override 1.675 + public void onTextChanged(CharSequence s, int start, int before, int count) { } 1.676 + } 1.677 + 1.678 + private class EmptyTextWatcher implements TextWatcher { 1.679 + EditText input = null; 1.680 + AlertDialog dialog = null; 1.681 + 1.682 + EmptyTextWatcher(EditText aInput, AlertDialog aDialog) { 1.683 + input = aInput; 1.684 + dialog = aDialog; 1.685 + } 1.686 + 1.687 + @Override 1.688 + public void afterTextChanged(Editable s) { 1.689 + if (dialog == null) 1.690 + return; 1.691 + 1.692 + String text = input.getText().toString(); 1.693 + boolean disabled = TextUtils.isEmpty(text); 1.694 + dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(!disabled); 1.695 + } 1.696 + 1.697 + @Override 1.698 + public void beforeTextChanged(CharSequence s, int start, int count, int after) { } 1.699 + @Override 1.700 + public void onTextChanged(CharSequence s, int start, int before, int count) { } 1.701 + } 1.702 + 1.703 + @Override 1.704 + protected Dialog onCreateDialog(int id) { 1.705 + AlertDialog.Builder builder = new AlertDialog.Builder(this); 1.706 + LinearLayout linearLayout = new LinearLayout(this); 1.707 + linearLayout.setOrientation(LinearLayout.VERTICAL); 1.708 + AlertDialog dialog = null; 1.709 + switch(id) { 1.710 + case DIALOG_CREATE_MASTER_PASSWORD: 1.711 + final EditText input1 = getTextBox(R.string.masterpassword_password); 1.712 + final EditText input2 = getTextBox(R.string.masterpassword_confirm); 1.713 + linearLayout.addView(input1); 1.714 + linearLayout.addView(input2); 1.715 + 1.716 + builder.setTitle(R.string.masterpassword_create_title) 1.717 + .setView((View) linearLayout) 1.718 + .setPositiveButton(R.string.button_ok, new DialogInterface.OnClickListener() { 1.719 + @Override 1.720 + public void onClick(DialogInterface dialog, int which) { 1.721 + JSONObject jsonPref = new JSONObject(); 1.722 + try { 1.723 + jsonPref.put("name", PREFS_MP_ENABLED); 1.724 + jsonPref.put("type", "string"); 1.725 + jsonPref.put("value", input1.getText().toString()); 1.726 + 1.727 + GeckoEvent event = GeckoEvent.createBroadcastEvent("Preferences:Set", jsonPref.toString()); 1.728 + GeckoAppShell.sendEventToGecko(event); 1.729 + } catch(Exception ex) { 1.730 + Log.e(LOGTAG, "Error setting master password", ex); 1.731 + } 1.732 + return; 1.733 + } 1.734 + }) 1.735 + .setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() { 1.736 + @Override 1.737 + public void onClick(DialogInterface dialog, int which) { 1.738 + return; 1.739 + } 1.740 + }); 1.741 + dialog = builder.create(); 1.742 + dialog.setOnShowListener(new DialogInterface.OnShowListener() { 1.743 + @Override 1.744 + public void onShow(DialogInterface dialog) { 1.745 + input1.setText(""); 1.746 + input2.setText(""); 1.747 + input1.requestFocus(); 1.748 + } 1.749 + }); 1.750 + 1.751 + PasswordTextWatcher watcher = new PasswordTextWatcher(input1, input2, dialog); 1.752 + input1.addTextChangedListener((TextWatcher) watcher); 1.753 + input2.addTextChangedListener((TextWatcher) watcher); 1.754 + 1.755 + break; 1.756 + case DIALOG_REMOVE_MASTER_PASSWORD: 1.757 + final EditText input = getTextBox(R.string.masterpassword_password); 1.758 + linearLayout.addView(input); 1.759 + 1.760 + builder.setTitle(R.string.masterpassword_remove_title) 1.761 + .setView((View) linearLayout) 1.762 + .setPositiveButton(R.string.button_ok, new DialogInterface.OnClickListener() { 1.763 + @Override 1.764 + public void onClick(DialogInterface dialog, int which) { 1.765 + PrefsHelper.setPref(PREFS_MP_ENABLED, input.getText().toString()); 1.766 + } 1.767 + }) 1.768 + .setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() { 1.769 + @Override 1.770 + public void onClick(DialogInterface dialog, int which) { 1.771 + return; 1.772 + } 1.773 + }); 1.774 + dialog = builder.create(); 1.775 + dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { 1.776 + @Override 1.777 + public void onDismiss(DialogInterface dialog) { 1.778 + input.setText(""); 1.779 + } 1.780 + }); 1.781 + dialog.setOnShowListener(new DialogInterface.OnShowListener() { 1.782 + @Override 1.783 + public void onShow(DialogInterface dialog) { 1.784 + input.setText(""); 1.785 + } 1.786 + }); 1.787 + input.addTextChangedListener(new EmptyTextWatcher(input, dialog)); 1.788 + break; 1.789 + default: 1.790 + return null; 1.791 + } 1.792 + 1.793 + return dialog; 1.794 + } 1.795 + 1.796 + // Initialize preferences by requesting the preference values from Gecko 1.797 + private int getGeckoPreferences(final PreferenceGroup screen, ArrayList<String> prefs) { 1.798 + return PrefsHelper.getPrefs(prefs, new PrefsHelper.PrefHandlerBase() { 1.799 + private Preference getField(String prefName) { 1.800 + return screen.findPreference(prefName); 1.801 + } 1.802 + 1.803 + // Handle v14 TwoStatePreference with backwards compatibility. 1.804 + class CheckBoxPrefSetter { 1.805 + public void setBooleanPref(Preference preference, boolean value) { 1.806 + if ((preference instanceof CheckBoxPreference) && 1.807 + ((CheckBoxPreference) preference).isChecked() != value) { 1.808 + ((CheckBoxPreference) preference).setChecked(value); 1.809 + } 1.810 + } 1.811 + } 1.812 + 1.813 + class TwoStatePrefSetter extends CheckBoxPrefSetter { 1.814 + @Override 1.815 + public void setBooleanPref(Preference preference, boolean value) { 1.816 + if ((preference instanceof TwoStatePreference) && 1.817 + ((TwoStatePreference) preference).isChecked() != value) { 1.818 + ((TwoStatePreference) preference).setChecked(value); 1.819 + } 1.820 + } 1.821 + } 1.822 + 1.823 + @Override 1.824 + public void prefValue(String prefName, final boolean value) { 1.825 + final Preference pref = getField(prefName); 1.826 + final CheckBoxPrefSetter prefSetter; 1.827 + if (Build.VERSION.SDK_INT < 14) { 1.828 + prefSetter = new CheckBoxPrefSetter(); 1.829 + } else { 1.830 + prefSetter = new TwoStatePrefSetter(); 1.831 + } 1.832 + ThreadUtils.postToUiThread(new Runnable() { 1.833 + public void run() { 1.834 + prefSetter.setBooleanPref(pref, value); 1.835 + } 1.836 + }); 1.837 + } 1.838 + 1.839 + @Override 1.840 + public void prefValue(String prefName, final String value) { 1.841 + final Preference pref = getField(prefName); 1.842 + if (pref instanceof EditTextPreference) { 1.843 + ThreadUtils.postToUiThread(new Runnable() { 1.844 + @Override 1.845 + public void run() { 1.846 + ((EditTextPreference) pref).setText(value); 1.847 + } 1.848 + }); 1.849 + } else if (pref instanceof ListPreference) { 1.850 + ThreadUtils.postToUiThread(new Runnable() { 1.851 + @Override 1.852 + public void run() { 1.853 + ((ListPreference) pref).setValue(value); 1.854 + // Set the summary string to the current entry 1.855 + CharSequence selectedEntry = ((ListPreference) pref).getEntry(); 1.856 + ((ListPreference) pref).setSummary(selectedEntry); 1.857 + } 1.858 + }); 1.859 + } else if (pref instanceof FontSizePreference) { 1.860 + final FontSizePreference fontSizePref = (FontSizePreference) pref; 1.861 + fontSizePref.setSavedFontSize(value); 1.862 + final String fontSizeName = fontSizePref.getSavedFontSizeName(); 1.863 + ThreadUtils.postToUiThread(new Runnable() { 1.864 + @Override 1.865 + public void run() { 1.866 + fontSizePref.setSummary(fontSizeName); // Ex: "Small". 1.867 + } 1.868 + }); 1.869 + } 1.870 + } 1.871 + 1.872 + @Override 1.873 + public void prefValue(String prefName, final int value) { 1.874 + final Preference pref = getField(prefName); 1.875 + final CheckBoxPrefSetter prefSetter; 1.876 + if (PREFS_GEO_REPORTING.equals(prefName)) { 1.877 + if (Build.VERSION.SDK_INT < 14) { 1.878 + prefSetter = new CheckBoxPrefSetter(); 1.879 + } else { 1.880 + prefSetter = new TwoStatePrefSetter(); 1.881 + } 1.882 + ThreadUtils.postToUiThread(new Runnable() { 1.883 + @Override 1.884 + public void run() { 1.885 + prefSetter.setBooleanPref(pref, value == 1); 1.886 + } 1.887 + }); 1.888 + } else { 1.889 + Log.w(LOGTAG, "Unhandled int value for pref [" + pref + "]"); 1.890 + } 1.891 + } 1.892 + 1.893 + @Override 1.894 + public boolean isObserver() { 1.895 + return true; 1.896 + } 1.897 + 1.898 + @Override 1.899 + public void finish() { 1.900 + // enable all preferences once we have them from gecko 1.901 + ThreadUtils.postToUiThread(new Runnable() { 1.902 + @Override 1.903 + public void run() { 1.904 + screen.setEnabled(true); 1.905 + } 1.906 + }); 1.907 + } 1.908 + }); 1.909 + } 1.910 + 1.911 + private void registerEventListener(String event) { 1.912 + GeckoAppShell.getEventDispatcher().registerEventListener(event, this); 1.913 + } 1.914 + 1.915 + private void unregisterEventListener(String event) { 1.916 + GeckoAppShell.getEventDispatcher().unregisterEventListener(event, this); 1.917 + } 1.918 + 1.919 + @Override 1.920 + public boolean isGeckoActivityOpened() { 1.921 + return false; 1.922 + } 1.923 + 1.924 + /** 1.925 + * Given an Intent instance, add extras to specify which settings section to 1.926 + * open. 1.927 + * 1.928 + * resource should be a valid Android XML resource identifier. 1.929 + * 1.930 + * The mechanism to open a section differs based on Android version. 1.931 + */ 1.932 + public static void setResourceToOpen(final Intent intent, final String resource) { 1.933 + if (intent == null) { 1.934 + throw new IllegalArgumentException("intent must not be null"); 1.935 + } 1.936 + if (resource == null) { 1.937 + return; 1.938 + } 1.939 + 1.940 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { 1.941 + intent.putExtra("resource", resource); 1.942 + } else { 1.943 + intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, GeckoPreferenceFragment.class.getName()); 1.944 + 1.945 + Bundle fragmentArgs = new Bundle(); 1.946 + fragmentArgs.putString("resource", resource); 1.947 + intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs); 1.948 + } 1.949 + } 1.950 +}