mobile/android/base/preferences/GeckoPreferences.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

     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/. */
     6 package org.mozilla.gecko.preferences;
     8 import java.util.ArrayList;
     9 import java.util.List;
    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;
    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;
    65 public class GeckoPreferences
    66     extends PreferenceActivity
    67     implements OnPreferenceChangeListener, GeckoEventListener, GeckoActivityStatus
    68 {
    69     private static final String LOGTAG = "GeckoPreferences";
    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";
    75     private static boolean sIsCharEncodingEnabled = false;
    76     private boolean mInitialized = false;
    77     private int mPrefsRequestId = 0;
    78     private PanelsPreferenceCategory mPanelsPreferenceCategory;
    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";
    97     public static final String PREFS_RESTORE_SESSION = NON_PREF_PREFIX + "restoreSession3";
    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;
   103     @Override
   104     protected void onCreate(Bundle savedInstanceState) {
   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.
   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         }
   117         super.onCreate(savedInstanceState);
   119         // Use setResourceToOpen to specify these extras.
   120         Bundle intentExtras = getIntent().getExtras();
   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);
   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         }
   148         registerEventListener("Sanitize:Finished");
   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);
   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         });
   169         if (Build.VERSION.SDK_INT >= 14 && getActionBar() != null)
   170             getActionBar().setHomeButtonEnabled(true);
   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     }
   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         }
   200         // Build fragment intent.
   201         intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, GeckoPreferenceFragment.class.getName());
   202         intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs);
   203     }
   205     @Override
   206     public void onBuildHeaders(List<Header> target) {
   207         if (onIsMultiPane())
   208             loadHeadersFromResource(R.xml.preference_headers, target);
   209     }
   211     @Override
   212     public void onWindowFocusChanged(boolean hasFocus) {
   213         if (!hasFocus || mInitialized)
   214             return;
   216         mInitialized = true;
   217         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
   218             PreferenceScreen screen = getPreferenceScreen();
   219             mPrefsRequestId = setupPreferences(screen);
   220         }
   221     }
   223     @Override
   224     protected void onDestroy() {
   225         super.onDestroy();
   226         unregisterEventListener("Sanitize:Finished");
   227         if (mPrefsRequestId > 0) {
   228             PrefsHelper.removeObserver(mPrefsRequestId);
   229         }
   230     }
   232     @Override
   233     public void onPause() {
   234         super.onPause();
   236         if (getApplication() instanceof GeckoApplication) {
   237             ((GeckoApplication) getApplication()).onActivityPause(this);
   238         }
   239     }
   241     @Override
   242     public void onResume() {
   243         super.onResume();
   245         if (getApplication() instanceof GeckoApplication) {
   246             ((GeckoApplication) getApplication()).onActivityResume(this);
   247         }
   248     }
   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     }
   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     }
   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;
   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     }
   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     }
   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     }
   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                 }
   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     }
   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));
   457         // Send message to Gecko to get engines. SearchPreferenceCategory listens for the response.
   458         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:GetVisible", null));
   459     }
   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         }
   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        }
   476         return super.onOptionsItemSelected(item);
   477     }
   479     final private int DIALOG_CREATE_MASTER_PASSWORD = 0;
   480     final private int DIALOG_REMOVE_MASTER_PASSWORD = 1;
   482     public static void setCharEncodingState(boolean enabled) {
   483         sIsCharEncodingEnabled = enabled;
   484     }
   486     public static boolean getCharEncodingState() {
   487         return sIsCharEncodingEnabled;
   488     }
   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     }
   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     }
   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     }
   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     }
   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     }
   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     }
   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     }
   566     public static void broadcastHealthReportPrune(final Context context) {
   567         final Intent intent = new Intent(HealthReportConstants.ACTION_HEALTHREPORT_PRUNE);
   568         broadcastAction(context, intent);
   569     }
   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     }
   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);
   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         }
   616         // Send Gecko-side pref changes to Gecko
   617         if (!TextUtils.isEmpty(prefName) && !prefName.startsWith(NON_PREF_PREFIX)) {
   618             PrefsHelper.setPref(prefName, newValue);
   619         }
   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         }
   634         return true;
   635     }
   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);
   643         input.setHint(aHintText);
   644         return input;
   645     }
   647     private class PasswordTextWatcher implements TextWatcher {
   648         EditText input1 = null;
   649         EditText input2 = null;
   650         AlertDialog dialog = null;
   652         PasswordTextWatcher(EditText aInput1, EditText aInput2, AlertDialog aDialog) {
   653             input1 = aInput1;
   654             input2 = aInput2;
   655             dialog = aDialog;
   656         }
   658         @Override
   659         public void afterTextChanged(Editable s) {
   660             if (dialog == null)
   661                 return;
   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         }
   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     }
   675     private class EmptyTextWatcher implements TextWatcher {
   676         EditText input = null;
   677         AlertDialog dialog = null;
   679         EmptyTextWatcher(EditText aInput, AlertDialog aDialog) {
   680             input = aInput;
   681             dialog = aDialog;
   682         }
   684         @Override
   685         public void afterTextChanged(Editable s) {
   686             if (dialog == null)
   687                 return;
   689             String text = input.getText().toString();
   690             boolean disabled = TextUtils.isEmpty(text);
   691             dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(!disabled);
   692         }
   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     }
   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);
   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());
   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                         });
   748                         PasswordTextWatcher watcher = new PasswordTextWatcher(input1, input2, dialog);
   749                         input1.addTextChangedListener((TextWatcher) watcher);
   750                         input2.addTextChangedListener((TextWatcher) watcher);
   752                 break;
   753             case DIALOG_REMOVE_MASTER_PASSWORD:
   754                 final EditText input = getTextBox(R.string.masterpassword_password);
   755                 linearLayout.addView(input);
   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         }
   790         return dialog;
   791     }
   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             }
   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             }
   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             }
   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             }
   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             }
   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             }
   890             @Override
   891             public boolean isObserver() {
   892                 return true;
   893             }
   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     }
   908     private void registerEventListener(String event) {
   909         GeckoAppShell.getEventDispatcher().registerEventListener(event, this);
   910     }
   912     private void unregisterEventListener(String event) {
   913         GeckoAppShell.getEventDispatcher().unregisterEventListener(event, this);
   914     }
   916     @Override
   917     public boolean isGeckoActivityOpened() {
   918         return false;
   919     }
   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         }
   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());
   942             Bundle fragmentArgs = new Bundle();
   943             fragmentArgs.putString("resource", resource);
   944             intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs);
   945         }
   946     }
   947 }

mercurial