mobile/android/base/preferences/GeckoPreferences.java

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:68eda933217b
1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 package org.mozilla.gecko.preferences;
7
8 import java.util.ArrayList;
9 import java.util.List;
10
11 import org.json.JSONObject;
12 import org.mozilla.gecko.AppConstants;
13 import org.mozilla.gecko.DataReportingNotification;
14 import org.mozilla.gecko.GeckoActivityStatus;
15 import org.mozilla.gecko.GeckoApp;
16 import org.mozilla.gecko.GeckoAppShell;
17 import org.mozilla.gecko.GeckoApplication;
18 import org.mozilla.gecko.GeckoEvent;
19 import org.mozilla.gecko.GeckoProfile;
20 import org.mozilla.gecko.GeckoSharedPrefs;
21 import org.mozilla.gecko.PrefsHelper;
22 import org.mozilla.gecko.R;
23 import org.mozilla.gecko.background.announcements.AnnouncementsConstants;
24 import org.mozilla.gecko.background.common.GlobalConstants;
25 import org.mozilla.gecko.background.healthreport.HealthReportConstants;
26 import org.mozilla.gecko.home.HomePanelPicker;
27 import org.mozilla.gecko.util.GeckoEventListener;
28 import org.mozilla.gecko.util.ThreadUtils;
29
30 import android.app.Activity;
31 import android.app.AlertDialog;
32 import android.app.Dialog;
33 import android.app.Fragment;
34 import android.app.NotificationManager;
35 import android.content.Context;
36 import android.content.DialogInterface;
37 import android.content.Intent;
38 import android.content.SharedPreferences;
39 import android.os.Build;
40 import android.os.Bundle;
41 import android.preference.CheckBoxPreference;
42 import android.preference.EditTextPreference;
43 import android.preference.ListPreference;
44 import android.preference.Preference;
45 import android.preference.Preference.OnPreferenceChangeListener;
46 import android.preference.Preference.OnPreferenceClickListener;
47 import android.preference.PreferenceActivity;
48 import android.preference.PreferenceGroup;
49 import android.preference.PreferenceScreen;
50 import android.preference.TwoStatePreference;
51 import android.text.Editable;
52 import android.text.InputType;
53 import android.text.TextUtils;
54 import android.text.TextWatcher;
55 import android.util.Log;
56 import android.view.MenuItem;
57 import android.view.View;
58 import android.widget.AdapterView;
59 import android.widget.EditText;
60 import android.widget.LinearLayout;
61 import android.widget.ListAdapter;
62 import android.widget.ListView;
63 import android.widget.Toast;
64
65 public class GeckoPreferences
66 extends PreferenceActivity
67 implements OnPreferenceChangeListener, GeckoEventListener, GeckoActivityStatus
68 {
69 private static final String LOGTAG = "GeckoPreferences";
70
71 private static final String NON_PREF_PREFIX = "android.not_a_preference.";
72 public static final String INTENT_EXTRA_RESOURCES = "resource";
73 public static String PREFS_HEALTHREPORT_UPLOAD_ENABLED = NON_PREF_PREFIX + "healthreport.uploadEnabled";
74
75 private static boolean sIsCharEncodingEnabled = false;
76 private boolean mInitialized = false;
77 private int mPrefsRequestId = 0;
78 private PanelsPreferenceCategory mPanelsPreferenceCategory;
79
80 // These match keys in resources/xml*/preferences*.xml
81 private static final String PREFS_SEARCH_RESTORE_DEFAULTS = NON_PREF_PREFIX + "search.restore_defaults";
82 private static final String PREFS_HOME_ADD_PANEL = NON_PREF_PREFIX + "home.add_panel";
83 private static final String PREFS_ANNOUNCEMENTS_ENABLED = NON_PREF_PREFIX + "privacy.announcements.enabled";
84 private static final String PREFS_DATA_REPORTING_PREFERENCES = NON_PREF_PREFIX + "datareporting.preferences";
85 private static final String PREFS_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";
86 private static final String PREFS_CRASHREPORTER_ENABLED = "datareporting.crashreporter.submitEnabled";
87 private static final String PREFS_MENU_CHAR_ENCODING = "browser.menu.showCharacterEncoding";
88 private static final String PREFS_MP_ENABLED = "privacy.masterpassword.enabled";
89 private static final String PREFS_UPDATER_AUTODOWNLOAD = "app.update.autodownload";
90 private static final String PREFS_GEO_REPORTING = "app.geo.reportdata";
91 private static final String PREFS_GEO_LEARN_MORE = NON_PREF_PREFIX + "geo.learn_more";
92 private static final String PREFS_HEALTHREPORT_LINK = NON_PREF_PREFIX + "healthreport.link";
93 private static final String PREFS_DEVTOOLS_REMOTE_ENABLED = "devtools.debugger.remote-enabled";
94 private static final String PREFS_DISPLAY_REFLOW_ON_ZOOM = "browser.zoom.reflowOnZoom";
95 private static final String PREFS_SYNC = NON_PREF_PREFIX + "sync";
96
97 public static final String PREFS_RESTORE_SESSION = NON_PREF_PREFIX + "restoreSession3";
98
99 // These values are chosen to be distinct from other Activity constants.
100 private static final int REQUEST_CODE_PREF_SCREEN = 5;
101 private static final int RESULT_CODE_EXIT_SETTINGS = 6;
102
103 @Override
104 protected void onCreate(Bundle savedInstanceState) {
105
106 // For Android v11+ where we use Fragments (v11+ only due to bug 866352),
107 // check that PreferenceActivity.EXTRA_SHOW_FRAGMENT has been set
108 // (or set it) before super.onCreate() is called so Android can display
109 // the correct Fragment resource.
110
111 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB &&
112 !getIntent().hasExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT)) {
113 // Set up the default fragment if there is no explicit fragment to show.
114 setupTopLevelFragmentIntent();
115 }
116
117 super.onCreate(savedInstanceState);
118
119 // Use setResourceToOpen to specify these extras.
120 Bundle intentExtras = getIntent().getExtras();
121
122 // For versions of Android lower than Honeycomb, use xml resources instead of
123 // Fragments because of an Android bug in ActionBar (described in bug 866352 and
124 // fixed in bug 833625).
125 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
126 // Write prefs to our custom GeckoSharedPrefs file.
127 getPreferenceManager().setSharedPreferencesName(GeckoSharedPrefs.APP_PREFS_NAME);
128
129 int res = 0;
130 if (intentExtras != null && intentExtras.containsKey(INTENT_EXTRA_RESOURCES)) {
131 // Fetch resource id from intent.
132 String resourceName = intentExtras.getString(INTENT_EXTRA_RESOURCES);
133 if (resourceName != null) {
134 res = getResources().getIdentifier(resourceName, "xml", getPackageName());
135 if (res == 0) {
136 Log.e(LOGTAG, "No resource found named " + resourceName);
137 }
138 }
139 }
140 if (res == 0) {
141 // No resource specified, or the resource was invalid; use the default preferences screen.
142 Log.e(LOGTAG, "Displaying default settings.");
143 res = R.xml.preferences;
144 }
145 addPreferencesFromResource(res);
146 }
147
148 registerEventListener("Sanitize:Finished");
149
150 // Add handling for long-press click.
151 // This is only for Android 3.0 and below (which use the long-press-context-menu paradigm).
152 final ListView mListView = getListView();
153 mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
154 @Override
155 public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
156 // Call long-click handler if it the item implements it.
157 final ListAdapter listAdapter = ((ListView) parent).getAdapter();
158 final Object listItem = listAdapter.getItem(position);
159
160 // Only CustomListPreference handles long clicks.
161 if (listItem instanceof CustomListPreference && listItem instanceof View.OnLongClickListener) {
162 final View.OnLongClickListener longClickListener = (View.OnLongClickListener) listItem;
163 return longClickListener.onLongClick(view);
164 }
165 return false;
166 }
167 });
168
169 if (Build.VERSION.SDK_INT >= 14 && getActionBar() != null)
170 getActionBar().setHomeButtonEnabled(true);
171
172 // If launched from notification, explicitly cancel the notification.
173 if (intentExtras != null && intentExtras.containsKey(DataReportingNotification.ALERT_NAME_DATAREPORTING_NOTIFICATION)) {
174 NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
175 notificationManager.cancel(DataReportingNotification.ALERT_NAME_DATAREPORTING_NOTIFICATION.hashCode());
176 }
177 }
178
179 /**
180 * Set intent to display top-level settings fragment.
181 */
182 private void setupTopLevelFragmentIntent() {
183 Intent intent = getIntent();
184 // Check intent to determine settings screen to display.
185 Bundle intentExtras = intent.getExtras();
186 Bundle fragmentArgs = new Bundle();
187 // Add resource argument to fragment if it exists.
188 if (intentExtras != null && intentExtras.containsKey(INTENT_EXTRA_RESOURCES)) {
189 String resourceName = intentExtras.getString(INTENT_EXTRA_RESOURCES);
190 fragmentArgs.putString(INTENT_EXTRA_RESOURCES, resourceName);
191 } else {
192 // Use top-level settings screen.
193 if (!onIsMultiPane()) {
194 fragmentArgs.putString(INTENT_EXTRA_RESOURCES, "preferences");
195 } else {
196 fragmentArgs.putString(INTENT_EXTRA_RESOURCES, "preferences_customize_tablet");
197 }
198 }
199
200 // Build fragment intent.
201 intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, GeckoPreferenceFragment.class.getName());
202 intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs);
203 }
204
205 @Override
206 public void onBuildHeaders(List<Header> target) {
207 if (onIsMultiPane())
208 loadHeadersFromResource(R.xml.preference_headers, target);
209 }
210
211 @Override
212 public void onWindowFocusChanged(boolean hasFocus) {
213 if (!hasFocus || mInitialized)
214 return;
215
216 mInitialized = true;
217 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
218 PreferenceScreen screen = getPreferenceScreen();
219 mPrefsRequestId = setupPreferences(screen);
220 }
221 }
222
223 @Override
224 protected void onDestroy() {
225 super.onDestroy();
226 unregisterEventListener("Sanitize:Finished");
227 if (mPrefsRequestId > 0) {
228 PrefsHelper.removeObserver(mPrefsRequestId);
229 }
230 }
231
232 @Override
233 public void onPause() {
234 super.onPause();
235
236 if (getApplication() instanceof GeckoApplication) {
237 ((GeckoApplication) getApplication()).onActivityPause(this);
238 }
239 }
240
241 @Override
242 public void onResume() {
243 super.onResume();
244
245 if (getApplication() instanceof GeckoApplication) {
246 ((GeckoApplication) getApplication()).onActivityResume(this);
247 }
248 }
249
250 @Override
251 public void startActivity(Intent intent) {
252 // For settings, we want to be able to pass results up the chain
253 // of preference screens so Settings can behave as a single unit.
254 // Specifically, when we open a link, we want to back out of all
255 // the settings screens.
256 // We need to start nested PreferenceScreens withStartActivityForResult().
257 // Android doesn't let us do that (see Preference.onClick), so we're overriding here.
258 startActivityForResult(intent, REQUEST_CODE_PREF_SCREEN);
259 }
260
261 @Override
262 public void startWithFragment(String fragmentName, Bundle args,
263 Fragment resultTo, int resultRequestCode, int titleRes, int shortTitleRes) {
264 // Overriding because we want to use startActivityForResult for Fragment intents.
265 Intent intent = onBuildStartFragmentIntent(fragmentName, args, titleRes, shortTitleRes);
266 if (resultTo == null) {
267 startActivityForResult(intent, REQUEST_CODE_PREF_SCREEN);
268 } else {
269 resultTo.startActivityForResult(intent, resultRequestCode);
270 }
271 }
272
273 @Override
274 public void onActivityResult(int requestCode, int resultCode, Intent data) {
275 switch (requestCode) {
276 case REQUEST_CODE_PREF_SCREEN:
277 if (resultCode == RESULT_CODE_EXIT_SETTINGS) {
278 // Pass this result up to the parent activity.
279 setResult(RESULT_CODE_EXIT_SETTINGS);
280 finish();
281 }
282 break;
283
284 case HomePanelPicker.REQUEST_CODE_ADD_PANEL:
285 switch (resultCode) {
286 case Activity.RESULT_OK:
287 // Panel installed, refresh panels list.
288 mPanelsPreferenceCategory.refresh();
289 break;
290 case Activity.RESULT_CANCELED:
291 // Dialog was cancelled, do nothing.
292 break;
293 default:
294 Log.w(LOGTAG, "Unhandled ADD_PANEL result code " + requestCode);
295 break;
296 }
297 break;
298 }
299 }
300
301 @Override
302 public void handleMessage(String event, JSONObject message) {
303 try {
304 if (event.equals("Sanitize:Finished")) {
305 boolean success = message.getBoolean("success");
306 final int stringRes = success ? R.string.private_data_success : R.string.private_data_fail;
307 final Context context = this;
308 ThreadUtils.postToUiThread(new Runnable () {
309 @Override
310 public void run() {
311 Toast.makeText(context, stringRes, Toast.LENGTH_SHORT).show();
312 }
313 });
314 }
315 } catch (Exception e) {
316 Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
317 }
318 }
319
320 /**
321 * Initialize all of the preferences (native of Gecko ones) for this screen.
322 *
323 * @param prefs The android.preference.PreferenceGroup to initialize
324 * @return The integer id for the PrefsHelper.PrefHandlerBase listener added
325 * to monitor changes to Gecko prefs.
326 */
327 public int setupPreferences(PreferenceGroup prefs) {
328 ArrayList<String> list = new ArrayList<String>();
329 setupPreferences(prefs, list);
330 return getGeckoPreferences(prefs, list);
331 }
332
333 /**
334 * Recursively loop through a PreferenceGroup. Initialize native Android prefs,
335 * and build a list of Gecko preferences in the passed in prefs array
336 *
337 * @param preferences The android.preference.PreferenceGroup to initialize
338 * @param prefs An ArrayList to fill with Gecko preferences that need to be
339 * initialized
340 * @return The integer id for the PrefsHelper.PrefHandlerBase listener added
341 * to monitor changes to Gecko prefs.
342 */
343 private void setupPreferences(PreferenceGroup preferences, ArrayList<String> prefs) {
344 for (int i = 0; i < preferences.getPreferenceCount(); i++) {
345 Preference pref = preferences.getPreference(i);
346 String key = pref.getKey();
347 if (pref instanceof PreferenceGroup) {
348 // If no datareporting is enabled, remove UI.
349 if (PREFS_DATA_REPORTING_PREFERENCES.equals(key)) {
350 if (!AppConstants.MOZ_DATA_REPORTING) {
351 preferences.removePreference(pref);
352 i--;
353 continue;
354 }
355 } else if (pref instanceof PanelsPreferenceCategory) {
356 mPanelsPreferenceCategory = (PanelsPreferenceCategory) pref;
357 }
358 setupPreferences((PreferenceGroup) pref, prefs);
359 } else {
360 pref.setOnPreferenceChangeListener(this);
361 if (!AppConstants.MOZ_UPDATER &&
362 PREFS_UPDATER_AUTODOWNLOAD.equals(key)) {
363 preferences.removePreference(pref);
364 i--;
365 continue;
366 } else if (AppConstants.RELEASE_BUILD &&
367 PREFS_DISPLAY_REFLOW_ON_ZOOM.equals(key)) {
368 // Remove UI for reflow on release builds.
369 preferences.removePreference(pref);
370 i--;
371 continue;
372 } else if (!AppConstants.MOZ_TELEMETRY_REPORTING &&
373 PREFS_TELEMETRY_ENABLED.equals(key)) {
374 preferences.removePreference(pref);
375 i--;
376 continue;
377 } else if (!AppConstants.MOZ_SERVICES_HEALTHREPORT &&
378 (PREFS_HEALTHREPORT_UPLOAD_ENABLED.equals(key) ||
379 PREFS_HEALTHREPORT_LINK.equals(key))) {
380 preferences.removePreference(pref);
381 i--;
382 continue;
383 } else if (!AppConstants.MOZ_CRASHREPORTER &&
384 PREFS_CRASHREPORTER_ENABLED.equals(key)) {
385 preferences.removePreference(pref);
386 i--;
387 continue;
388 } else if (AppConstants.RELEASE_BUILD && PREFS_GEO_REPORTING.equals(key)) {
389 // We don't build wifi/cell tower collection in release builds, so hide the UI.
390 preferences.removePreference(pref);
391 i--;
392 continue;
393 } else if (PREFS_DEVTOOLS_REMOTE_ENABLED.equals(key)) {
394 final Context thisContext = this;
395 pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
396 @Override
397 public boolean onPreferenceClick(Preference preference) {
398 // Display toast to remind setting up tcp forwarding.
399 if (((CheckBoxPreference) preference).isChecked()) {
400 Toast.makeText(thisContext, R.string.devtools_remote_debugging_forward, Toast.LENGTH_SHORT).show();
401 }
402 return true;
403 }
404 });
405 } else if (PREFS_RESTORE_SESSION.equals(key)) {
406 // Set the summary string to the current entry. The summary
407 // for other list prefs will be set in the PrefsHelper
408 // callback, but since this pref doesn't live in Gecko, we
409 // need to handle it separately.
410 ListPreference listPref = (ListPreference) pref;
411 CharSequence selectedEntry = listPref.getEntry();
412 listPref.setSummary(selectedEntry);
413 continue;
414 } else if (PREFS_SYNC.equals(key) && GeckoProfile.get(this).inGuestMode()) {
415 // Don't show sync prefs while in guest mode.
416 preferences.removePreference(pref);
417 i--;
418 continue;
419 } else if (PREFS_SEARCH_RESTORE_DEFAULTS.equals(key)) {
420 pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
421 @Override
422 public boolean onPreferenceClick(Preference preference) {
423 GeckoPreferences.this.restoreDefaultSearchEngines();
424 return true;
425 }
426 });
427 } else if (PREFS_HOME_ADD_PANEL.equals(key)) {
428 pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
429 @Override
430 public boolean onPreferenceClick(Preference preference) {
431 Intent dialogIntent = new Intent(GeckoPreferences.this, HomePanelPicker.class);
432 startActivityForResult(dialogIntent, HomePanelPicker.REQUEST_CODE_ADD_PANEL);
433 return true;
434 }
435 });
436 }
437
438 // Some Preference UI elements are not actually preferences,
439 // but they require a key to work correctly. For example,
440 // "Clear private data" requires a key for its state to be
441 // saved when the orientation changes. It uses the
442 // "android.not_a_preference.privacy.clear" key - which doesn't
443 // exist in Gecko - to satisfy this requirement.
444 if (key != null && !key.startsWith(NON_PREF_PREFIX)) {
445 prefs.add(key);
446 }
447 }
448 }
449 }
450
451 /**
452 * Restore default search engines in Gecko and retrigger a search engine refresh.
453 */
454 protected void restoreDefaultSearchEngines() {
455 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:RestoreDefaults", null));
456
457 // Send message to Gecko to get engines. SearchPreferenceCategory listens for the response.
458 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:GetVisible", null));
459 }
460
461 @Override
462 public boolean onOptionsItemSelected(MenuItem item) {
463 int itemId = item.getItemId();
464 switch (itemId) {
465 case android.R.id.home:
466 finish();
467 return true;
468 }
469
470 // Generated R.id.* apparently aren't constant expressions, so they can't be switched.
471 if (itemId == R.id.restore_defaults) {
472 restoreDefaultSearchEngines();
473 return true;
474 }
475
476 return super.onOptionsItemSelected(item);
477 }
478
479 final private int DIALOG_CREATE_MASTER_PASSWORD = 0;
480 final private int DIALOG_REMOVE_MASTER_PASSWORD = 1;
481
482 public static void setCharEncodingState(boolean enabled) {
483 sIsCharEncodingEnabled = enabled;
484 }
485
486 public static boolean getCharEncodingState() {
487 return sIsCharEncodingEnabled;
488 }
489
490 public static void broadcastAction(final Context context, final Intent intent) {
491 fillIntentWithProfileInfo(context, intent);
492 context.sendBroadcast(intent, GlobalConstants.PER_ANDROID_PACKAGE_PERMISSION);
493 }
494
495 /**
496 * Broadcast an intent with <code>pref</code>, <code>branch</code>, and
497 * <code>enabled</code> extras. This is intended to represent the
498 * notification of a preference value to observers.
499 *
500 * The broadcast will be sent only to receivers registered with the
501 * (Fennec-specific) per-Android package permission.
502 */
503 public static void broadcastPrefAction(final Context context,
504 final String action,
505 final String pref,
506 final boolean value) {
507 final Intent intent = new Intent(action)
508 .putExtra("pref", pref)
509 .putExtra("branch", GeckoSharedPrefs.APP_PREFS_NAME)
510 .putExtra("enabled", value);
511 broadcastAction(context, intent);
512 }
513
514 private static void fillIntentWithProfileInfo(final Context context, final Intent intent) {
515 // There is a race here, but GeckoProfile returns the default profile
516 // when Gecko is not explicitly running for a different profile. In a
517 // multi-profile world, this will need to be updated (possibly to
518 // broadcast settings for all profiles). See Bug 882182.
519 GeckoProfile profile = GeckoProfile.get(context);
520 if (profile != null) {
521 intent.putExtra("profileName", profile.getName())
522 .putExtra("profilePath", profile.getDir().getAbsolutePath());
523 }
524 }
525
526 /**
527 * Broadcast the provided value as the value of the
528 * <code>PREFS_ANNOUNCEMENTS_ENABLED</code> pref.
529 */
530 public static void broadcastAnnouncementsPref(final Context context, final boolean value) {
531 broadcastPrefAction(context,
532 AnnouncementsConstants.ACTION_ANNOUNCEMENTS_PREF,
533 PREFS_ANNOUNCEMENTS_ENABLED,
534 value);
535 }
536
537 /**
538 * Broadcast the current value of the
539 * <code>PREFS_ANNOUNCEMENTS_ENABLED</code> pref.
540 */
541 public static void broadcastAnnouncementsPref(final Context context) {
542 final boolean value = getBooleanPref(context, PREFS_ANNOUNCEMENTS_ENABLED, true);
543 broadcastAnnouncementsPref(context, value);
544 }
545
546 /**
547 * Broadcast the provided value as the value of the
548 * <code>PREFS_HEALTHREPORT_UPLOAD_ENABLED</code> pref.
549 */
550 public static void broadcastHealthReportUploadPref(final Context context, final boolean value) {
551 broadcastPrefAction(context,
552 HealthReportConstants.ACTION_HEALTHREPORT_UPLOAD_PREF,
553 PREFS_HEALTHREPORT_UPLOAD_ENABLED,
554 value);
555 }
556
557 /**
558 * Broadcast the current value of the
559 * <code>PREFS_HEALTHREPORT_UPLOAD_ENABLED</code> pref.
560 */
561 public static void broadcastHealthReportUploadPref(final Context context) {
562 final boolean value = getBooleanPref(context, PREFS_HEALTHREPORT_UPLOAD_ENABLED, true);
563 broadcastHealthReportUploadPref(context, value);
564 }
565
566 public static void broadcastHealthReportPrune(final Context context) {
567 final Intent intent = new Intent(HealthReportConstants.ACTION_HEALTHREPORT_PRUNE);
568 broadcastAction(context, intent);
569 }
570
571 /**
572 * Return the value of the named preference in the default preferences file.
573 *
574 * This corresponds to the storage that backs preferences.xml.
575 * @param context a <code>Context</code>; the
576 * <code>PreferenceActivity</code> will suffice, but this
577 * method is intended to be called from other contexts
578 * within the application, not just this <code>Activity</code>.
579 * @param name the name of the preference to retrieve.
580 * @param def the default value to return if the preference is not present.
581 * @return the value of the preference, or the default.
582 */
583 public static boolean getBooleanPref(final Context context, final String name, boolean def) {
584 final SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
585 return prefs.getBoolean(name, def);
586 }
587
588 @Override
589 public boolean onPreferenceChange(Preference preference, Object newValue) {
590 String prefName = preference.getKey();
591 if (PREFS_MP_ENABLED.equals(prefName)) {
592 showDialog((Boolean) newValue ? DIALOG_CREATE_MASTER_PASSWORD : DIALOG_REMOVE_MASTER_PASSWORD);
593
594 // We don't want the "use master password" pref to change until the
595 // user has gone through the dialog.
596 return false;
597 } else if (PREFS_MENU_CHAR_ENCODING.equals(prefName)) {
598 setCharEncodingState(((String) newValue).equals("true"));
599 } else if (PREFS_ANNOUNCEMENTS_ENABLED.equals(prefName)) {
600 // Send a broadcast intent to the product announcements service, either to start or
601 // to stop the repeated background checks.
602 broadcastAnnouncementsPref(this, ((Boolean) newValue).booleanValue());
603 } else if (PREFS_UPDATER_AUTODOWNLOAD.equals(prefName)) {
604 org.mozilla.gecko.updater.UpdateServiceHelper.registerForUpdates(this, (String) newValue);
605 } else if (PREFS_HEALTHREPORT_UPLOAD_ENABLED.equals(prefName)) {
606 // The healthreport pref only lives in Android, so we do not persist
607 // to Gecko, but we do broadcast intent to the health report
608 // background uploader service, which will start or stop the
609 // repeated background upload attempts.
610 broadcastHealthReportUploadPref(this, ((Boolean) newValue).booleanValue());
611 } else if (PREFS_GEO_REPORTING.equals(prefName)) {
612 // Translate boolean value to int for geo reporting pref.
613 newValue = ((Boolean) newValue) ? 1 : 0;
614 }
615
616 // Send Gecko-side pref changes to Gecko
617 if (!TextUtils.isEmpty(prefName) && !prefName.startsWith(NON_PREF_PREFIX)) {
618 PrefsHelper.setPref(prefName, newValue);
619 }
620
621 if (preference instanceof ListPreference) {
622 // We need to find the entry for the new value
623 int newIndex = ((ListPreference) preference).findIndexOfValue((String) newValue);
624 CharSequence newEntry = ((ListPreference) preference).getEntries()[newIndex];
625 ((ListPreference) preference).setSummary(newEntry);
626 } else if (preference instanceof LinkPreference) {
627 setResult(RESULT_CODE_EXIT_SETTINGS);
628 finish();
629 } else if (preference instanceof FontSizePreference) {
630 final FontSizePreference fontSizePref = (FontSizePreference) preference;
631 fontSizePref.setSummary(fontSizePref.getSavedFontSizeName());
632 }
633
634 return true;
635 }
636
637 private EditText getTextBox(int aHintText) {
638 EditText input = new EditText(this);
639 int inputtype = InputType.TYPE_CLASS_TEXT;
640 inputtype |= InputType.TYPE_TEXT_VARIATION_PASSWORD | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
641 input.setInputType(inputtype);
642
643 input.setHint(aHintText);
644 return input;
645 }
646
647 private class PasswordTextWatcher implements TextWatcher {
648 EditText input1 = null;
649 EditText input2 = null;
650 AlertDialog dialog = null;
651
652 PasswordTextWatcher(EditText aInput1, EditText aInput2, AlertDialog aDialog) {
653 input1 = aInput1;
654 input2 = aInput2;
655 dialog = aDialog;
656 }
657
658 @Override
659 public void afterTextChanged(Editable s) {
660 if (dialog == null)
661 return;
662
663 String text1 = input1.getText().toString();
664 String text2 = input2.getText().toString();
665 boolean disabled = TextUtils.isEmpty(text1) || TextUtils.isEmpty(text2) || !text1.equals(text2);
666 dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(!disabled);
667 }
668
669 @Override
670 public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
671 @Override
672 public void onTextChanged(CharSequence s, int start, int before, int count) { }
673 }
674
675 private class EmptyTextWatcher implements TextWatcher {
676 EditText input = null;
677 AlertDialog dialog = null;
678
679 EmptyTextWatcher(EditText aInput, AlertDialog aDialog) {
680 input = aInput;
681 dialog = aDialog;
682 }
683
684 @Override
685 public void afterTextChanged(Editable s) {
686 if (dialog == null)
687 return;
688
689 String text = input.getText().toString();
690 boolean disabled = TextUtils.isEmpty(text);
691 dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(!disabled);
692 }
693
694 @Override
695 public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
696 @Override
697 public void onTextChanged(CharSequence s, int start, int before, int count) { }
698 }
699
700 @Override
701 protected Dialog onCreateDialog(int id) {
702 AlertDialog.Builder builder = new AlertDialog.Builder(this);
703 LinearLayout linearLayout = new LinearLayout(this);
704 linearLayout.setOrientation(LinearLayout.VERTICAL);
705 AlertDialog dialog = null;
706 switch(id) {
707 case DIALOG_CREATE_MASTER_PASSWORD:
708 final EditText input1 = getTextBox(R.string.masterpassword_password);
709 final EditText input2 = getTextBox(R.string.masterpassword_confirm);
710 linearLayout.addView(input1);
711 linearLayout.addView(input2);
712
713 builder.setTitle(R.string.masterpassword_create_title)
714 .setView((View) linearLayout)
715 .setPositiveButton(R.string.button_ok, new DialogInterface.OnClickListener() {
716 @Override
717 public void onClick(DialogInterface dialog, int which) {
718 JSONObject jsonPref = new JSONObject();
719 try {
720 jsonPref.put("name", PREFS_MP_ENABLED);
721 jsonPref.put("type", "string");
722 jsonPref.put("value", input1.getText().toString());
723
724 GeckoEvent event = GeckoEvent.createBroadcastEvent("Preferences:Set", jsonPref.toString());
725 GeckoAppShell.sendEventToGecko(event);
726 } catch(Exception ex) {
727 Log.e(LOGTAG, "Error setting master password", ex);
728 }
729 return;
730 }
731 })
732 .setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() {
733 @Override
734 public void onClick(DialogInterface dialog, int which) {
735 return;
736 }
737 });
738 dialog = builder.create();
739 dialog.setOnShowListener(new DialogInterface.OnShowListener() {
740 @Override
741 public void onShow(DialogInterface dialog) {
742 input1.setText("");
743 input2.setText("");
744 input1.requestFocus();
745 }
746 });
747
748 PasswordTextWatcher watcher = new PasswordTextWatcher(input1, input2, dialog);
749 input1.addTextChangedListener((TextWatcher) watcher);
750 input2.addTextChangedListener((TextWatcher) watcher);
751
752 break;
753 case DIALOG_REMOVE_MASTER_PASSWORD:
754 final EditText input = getTextBox(R.string.masterpassword_password);
755 linearLayout.addView(input);
756
757 builder.setTitle(R.string.masterpassword_remove_title)
758 .setView((View) linearLayout)
759 .setPositiveButton(R.string.button_ok, new DialogInterface.OnClickListener() {
760 @Override
761 public void onClick(DialogInterface dialog, int which) {
762 PrefsHelper.setPref(PREFS_MP_ENABLED, input.getText().toString());
763 }
764 })
765 .setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() {
766 @Override
767 public void onClick(DialogInterface dialog, int which) {
768 return;
769 }
770 });
771 dialog = builder.create();
772 dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
773 @Override
774 public void onDismiss(DialogInterface dialog) {
775 input.setText("");
776 }
777 });
778 dialog.setOnShowListener(new DialogInterface.OnShowListener() {
779 @Override
780 public void onShow(DialogInterface dialog) {
781 input.setText("");
782 }
783 });
784 input.addTextChangedListener(new EmptyTextWatcher(input, dialog));
785 break;
786 default:
787 return null;
788 }
789
790 return dialog;
791 }
792
793 // Initialize preferences by requesting the preference values from Gecko
794 private int getGeckoPreferences(final PreferenceGroup screen, ArrayList<String> prefs) {
795 return PrefsHelper.getPrefs(prefs, new PrefsHelper.PrefHandlerBase() {
796 private Preference getField(String prefName) {
797 return screen.findPreference(prefName);
798 }
799
800 // Handle v14 TwoStatePreference with backwards compatibility.
801 class CheckBoxPrefSetter {
802 public void setBooleanPref(Preference preference, boolean value) {
803 if ((preference instanceof CheckBoxPreference) &&
804 ((CheckBoxPreference) preference).isChecked() != value) {
805 ((CheckBoxPreference) preference).setChecked(value);
806 }
807 }
808 }
809
810 class TwoStatePrefSetter extends CheckBoxPrefSetter {
811 @Override
812 public void setBooleanPref(Preference preference, boolean value) {
813 if ((preference instanceof TwoStatePreference) &&
814 ((TwoStatePreference) preference).isChecked() != value) {
815 ((TwoStatePreference) preference).setChecked(value);
816 }
817 }
818 }
819
820 @Override
821 public void prefValue(String prefName, final boolean value) {
822 final Preference pref = getField(prefName);
823 final CheckBoxPrefSetter prefSetter;
824 if (Build.VERSION.SDK_INT < 14) {
825 prefSetter = new CheckBoxPrefSetter();
826 } else {
827 prefSetter = new TwoStatePrefSetter();
828 }
829 ThreadUtils.postToUiThread(new Runnable() {
830 public void run() {
831 prefSetter.setBooleanPref(pref, value);
832 }
833 });
834 }
835
836 @Override
837 public void prefValue(String prefName, final String value) {
838 final Preference pref = getField(prefName);
839 if (pref instanceof EditTextPreference) {
840 ThreadUtils.postToUiThread(new Runnable() {
841 @Override
842 public void run() {
843 ((EditTextPreference) pref).setText(value);
844 }
845 });
846 } else if (pref instanceof ListPreference) {
847 ThreadUtils.postToUiThread(new Runnable() {
848 @Override
849 public void run() {
850 ((ListPreference) pref).setValue(value);
851 // Set the summary string to the current entry
852 CharSequence selectedEntry = ((ListPreference) pref).getEntry();
853 ((ListPreference) pref).setSummary(selectedEntry);
854 }
855 });
856 } else if (pref instanceof FontSizePreference) {
857 final FontSizePreference fontSizePref = (FontSizePreference) pref;
858 fontSizePref.setSavedFontSize(value);
859 final String fontSizeName = fontSizePref.getSavedFontSizeName();
860 ThreadUtils.postToUiThread(new Runnable() {
861 @Override
862 public void run() {
863 fontSizePref.setSummary(fontSizeName); // Ex: "Small".
864 }
865 });
866 }
867 }
868
869 @Override
870 public void prefValue(String prefName, final int value) {
871 final Preference pref = getField(prefName);
872 final CheckBoxPrefSetter prefSetter;
873 if (PREFS_GEO_REPORTING.equals(prefName)) {
874 if (Build.VERSION.SDK_INT < 14) {
875 prefSetter = new CheckBoxPrefSetter();
876 } else {
877 prefSetter = new TwoStatePrefSetter();
878 }
879 ThreadUtils.postToUiThread(new Runnable() {
880 @Override
881 public void run() {
882 prefSetter.setBooleanPref(pref, value == 1);
883 }
884 });
885 } else {
886 Log.w(LOGTAG, "Unhandled int value for pref [" + pref + "]");
887 }
888 }
889
890 @Override
891 public boolean isObserver() {
892 return true;
893 }
894
895 @Override
896 public void finish() {
897 // enable all preferences once we have them from gecko
898 ThreadUtils.postToUiThread(new Runnable() {
899 @Override
900 public void run() {
901 screen.setEnabled(true);
902 }
903 });
904 }
905 });
906 }
907
908 private void registerEventListener(String event) {
909 GeckoAppShell.getEventDispatcher().registerEventListener(event, this);
910 }
911
912 private void unregisterEventListener(String event) {
913 GeckoAppShell.getEventDispatcher().unregisterEventListener(event, this);
914 }
915
916 @Override
917 public boolean isGeckoActivityOpened() {
918 return false;
919 }
920
921 /**
922 * Given an Intent instance, add extras to specify which settings section to
923 * open.
924 *
925 * resource should be a valid Android XML resource identifier.
926 *
927 * The mechanism to open a section differs based on Android version.
928 */
929 public static void setResourceToOpen(final Intent intent, final String resource) {
930 if (intent == null) {
931 throw new IllegalArgumentException("intent must not be null");
932 }
933 if (resource == null) {
934 return;
935 }
936
937 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
938 intent.putExtra("resource", resource);
939 } else {
940 intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, GeckoPreferenceFragment.class.getName());
941
942 Bundle fragmentArgs = new Bundle();
943 fragmentArgs.putString("resource", resource);
944 intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs);
945 }
946 }
947 }

mercurial