1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/fxa/activities/FxAccountStatusFragment.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,573 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +package org.mozilla.gecko.fxa.activities; 1.9 + 1.10 +import java.util.HashMap; 1.11 +import java.util.Map; 1.12 +import java.util.Set; 1.13 + 1.14 +import org.mozilla.gecko.R; 1.15 +import org.mozilla.gecko.background.common.log.Logger; 1.16 +import org.mozilla.gecko.background.preferences.PreferenceFragment; 1.17 +import org.mozilla.gecko.fxa.FirefoxAccounts; 1.18 +import org.mozilla.gecko.fxa.FxAccountConstants; 1.19 +import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; 1.20 +import org.mozilla.gecko.fxa.login.Married; 1.21 +import org.mozilla.gecko.fxa.login.State; 1.22 +import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper; 1.23 +import org.mozilla.gecko.sync.SyncConfiguration; 1.24 + 1.25 +import android.accounts.Account; 1.26 +import android.content.ContentResolver; 1.27 +import android.content.Context; 1.28 +import android.content.Intent; 1.29 +import android.content.SharedPreferences; 1.30 +import android.os.Bundle; 1.31 +import android.os.Handler; 1.32 +import android.preference.CheckBoxPreference; 1.33 +import android.preference.Preference; 1.34 +import android.preference.Preference.OnPreferenceClickListener; 1.35 +import android.preference.PreferenceCategory; 1.36 +import android.preference.PreferenceScreen; 1.37 + 1.38 +/** 1.39 + * A fragment that displays the status of an AndroidFxAccount. 1.40 + * <p> 1.41 + * The owning activity is responsible for providing an AndroidFxAccount at 1.42 + * appropriate times. 1.43 + */ 1.44 +public class FxAccountStatusFragment extends PreferenceFragment implements OnPreferenceClickListener { 1.45 + private static final String LOG_TAG = FxAccountStatusFragment.class.getSimpleName(); 1.46 + 1.47 + // When a checkbox is toggled, wait 5 seconds (for other checkbox actions) 1.48 + // before trying to sync. Should we kill off the fragment before the sync 1.49 + // request happens, that's okay: the runnable will run if the UI thread is 1.50 + // still around to service it, and since we're not updating any UI, we'll just 1.51 + // schedule the sync as usual. See also comment below about garbage 1.52 + // collection. 1.53 + private static final long DELAY_IN_MILLISECONDS_BEFORE_REQUESTING_SYNC = 5 * 1000; 1.54 + 1.55 + protected Preference emailPreference; 1.56 + 1.57 + protected Preference needsPasswordPreference; 1.58 + protected Preference needsUpgradePreference; 1.59 + protected Preference needsVerificationPreference; 1.60 + protected Preference needsMasterSyncAutomaticallyEnabledPreference; 1.61 + protected Preference needsAccountEnabledPreference; 1.62 + 1.63 + protected PreferenceCategory syncCategory; 1.64 + 1.65 + protected CheckBoxPreference bookmarksPreference; 1.66 + protected CheckBoxPreference historyPreference; 1.67 + protected CheckBoxPreference tabsPreference; 1.68 + protected CheckBoxPreference passwordsPreference; 1.69 + 1.70 + protected volatile AndroidFxAccount fxAccount; 1.71 + 1.72 + // Used to post delayed sync requests. 1.73 + protected Handler handler; 1.74 + 1.75 + // Member variable so that re-posting pushes back the already posted instance. 1.76 + // This Runnable references the fxAccount above, but it is not specific to a 1.77 + // single account. (That is, it does not capture a single account instance.) 1.78 + protected Runnable requestSyncRunnable; 1.79 + 1.80 + protected final InnerSyncStatusDelegate syncStatusDelegate = new InnerSyncStatusDelegate(); 1.81 + 1.82 + protected Preference ensureFindPreference(String key) { 1.83 + Preference preference = findPreference(key); 1.84 + if (preference == null) { 1.85 + throw new IllegalStateException("Could not find preference with key: " + key); 1.86 + } 1.87 + return preference; 1.88 + } 1.89 + 1.90 + @Override 1.91 + public void onCreate(Bundle savedInstanceState) { 1.92 + super.onCreate(savedInstanceState); 1.93 + addPreferencesFromResource(R.xml.fxaccount_status_prefscreen); 1.94 + 1.95 + emailPreference = ensureFindPreference("email"); 1.96 + 1.97 + needsPasswordPreference = ensureFindPreference("needs_credentials"); 1.98 + needsUpgradePreference = ensureFindPreference("needs_upgrade"); 1.99 + needsVerificationPreference = ensureFindPreference("needs_verification"); 1.100 + needsMasterSyncAutomaticallyEnabledPreference = ensureFindPreference("needs_master_sync_automatically_enabled"); 1.101 + needsAccountEnabledPreference = ensureFindPreference("needs_account_enabled"); 1.102 + 1.103 + syncCategory = (PreferenceCategory) ensureFindPreference("sync_category"); 1.104 + 1.105 + bookmarksPreference = (CheckBoxPreference) ensureFindPreference("bookmarks"); 1.106 + historyPreference = (CheckBoxPreference) ensureFindPreference("history"); 1.107 + tabsPreference = (CheckBoxPreference) ensureFindPreference("tabs"); 1.108 + passwordsPreference = (CheckBoxPreference) ensureFindPreference("passwords"); 1.109 + 1.110 + if (!FxAccountConstants.LOG_PERSONAL_INFORMATION) { 1.111 + removeDebugButtons(); 1.112 + } else { 1.113 + connectDebugButtons(); 1.114 + } 1.115 + 1.116 + needsPasswordPreference.setOnPreferenceClickListener(this); 1.117 + needsVerificationPreference.setOnPreferenceClickListener(this); 1.118 + needsAccountEnabledPreference.setOnPreferenceClickListener(this); 1.119 + 1.120 + bookmarksPreference.setOnPreferenceClickListener(this); 1.121 + historyPreference.setOnPreferenceClickListener(this); 1.122 + tabsPreference.setOnPreferenceClickListener(this); 1.123 + passwordsPreference.setOnPreferenceClickListener(this); 1.124 + } 1.125 + 1.126 + /** 1.127 + * We intentionally don't refresh here. Our owning activity is responsible for 1.128 + * providing an AndroidFxAccount to our refresh method in its onResume method. 1.129 + */ 1.130 + @Override 1.131 + public void onResume() { 1.132 + super.onResume(); 1.133 + } 1.134 + 1.135 + @Override 1.136 + public boolean onPreferenceClick(Preference preference) { 1.137 + if (preference == needsPasswordPreference) { 1.138 + Intent intent = new Intent(getActivity(), FxAccountUpdateCredentialsActivity.class); 1.139 + // Per http://stackoverflow.com/a/8992365, this triggers a known bug with 1.140 + // the soft keyboard not being shown for the started activity. Why, Android, why? 1.141 + intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); 1.142 + startActivity(intent); 1.143 + 1.144 + return true; 1.145 + } 1.146 + 1.147 + if (preference == needsVerificationPreference) { 1.148 + FxAccountConfirmAccountActivity.resendCode(getActivity().getApplicationContext(), fxAccount); 1.149 + 1.150 + Intent intent = new Intent(getActivity(), FxAccountConfirmAccountActivity.class); 1.151 + // Per http://stackoverflow.com/a/8992365, this triggers a known bug with 1.152 + // the soft keyboard not being shown for the started activity. Why, Android, why? 1.153 + intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); 1.154 + startActivity(intent); 1.155 + 1.156 + return true; 1.157 + } 1.158 + 1.159 + if (preference == needsAccountEnabledPreference) { 1.160 + fxAccount.enableSyncing(); 1.161 + refresh(); 1.162 + 1.163 + return true; 1.164 + } 1.165 + 1.166 + if (preference == bookmarksPreference || 1.167 + preference == historyPreference || 1.168 + preference == passwordsPreference || 1.169 + preference == tabsPreference) { 1.170 + saveEngineSelections(); 1.171 + return true; 1.172 + } 1.173 + 1.174 + return false; 1.175 + } 1.176 + 1.177 + protected void setCheckboxesEnabled(boolean enabled) { 1.178 + bookmarksPreference.setEnabled(enabled); 1.179 + historyPreference.setEnabled(enabled); 1.180 + tabsPreference.setEnabled(enabled); 1.181 + passwordsPreference.setEnabled(enabled); 1.182 + } 1.183 + 1.184 + /** 1.185 + * Show at most one error preference, hiding all others. 1.186 + * 1.187 + * @param errorPreferenceToShow 1.188 + * single error preference to show; if null, hide all error preferences 1.189 + */ 1.190 + protected void showOnlyOneErrorPreference(Preference errorPreferenceToShow) { 1.191 + final Preference[] errorPreferences = new Preference[] { 1.192 + this.needsPasswordPreference, 1.193 + this.needsUpgradePreference, 1.194 + this.needsVerificationPreference, 1.195 + this.needsMasterSyncAutomaticallyEnabledPreference, 1.196 + this.needsAccountEnabledPreference, 1.197 + }; 1.198 + for (Preference errorPreference : errorPreferences) { 1.199 + final boolean currentlyShown = null != findPreference(errorPreference.getKey()); 1.200 + final boolean shouldBeShown = errorPreference == errorPreferenceToShow; 1.201 + if (currentlyShown == shouldBeShown) { 1.202 + continue; 1.203 + } 1.204 + if (shouldBeShown) { 1.205 + syncCategory.addPreference(errorPreference); 1.206 + } else { 1.207 + syncCategory.removePreference(errorPreference); 1.208 + } 1.209 + } 1.210 + } 1.211 + 1.212 + protected void showNeedsPassword() { 1.213 + syncCategory.setTitle(R.string.fxaccount_status_sync); 1.214 + showOnlyOneErrorPreference(needsPasswordPreference); 1.215 + setCheckboxesEnabled(false); 1.216 + } 1.217 + 1.218 + protected void showNeedsUpgrade() { 1.219 + syncCategory.setTitle(R.string.fxaccount_status_sync); 1.220 + showOnlyOneErrorPreference(needsUpgradePreference); 1.221 + setCheckboxesEnabled(false); 1.222 + } 1.223 + 1.224 + protected void showNeedsVerification() { 1.225 + syncCategory.setTitle(R.string.fxaccount_status_sync); 1.226 + showOnlyOneErrorPreference(needsVerificationPreference); 1.227 + setCheckboxesEnabled(false); 1.228 + } 1.229 + 1.230 + protected void showNeedsMasterSyncAutomaticallyEnabled() { 1.231 + syncCategory.setTitle(R.string.fxaccount_status_sync); 1.232 + showOnlyOneErrorPreference(needsMasterSyncAutomaticallyEnabledPreference); 1.233 + setCheckboxesEnabled(false); 1.234 + } 1.235 + 1.236 + protected void showNeedsAccountEnabled() { 1.237 + syncCategory.setTitle(R.string.fxaccount_status_sync); 1.238 + showOnlyOneErrorPreference(needsAccountEnabledPreference); 1.239 + setCheckboxesEnabled(false); 1.240 + } 1.241 + 1.242 + protected void showConnected() { 1.243 + syncCategory.setTitle(R.string.fxaccount_status_sync_enabled); 1.244 + showOnlyOneErrorPreference(null); 1.245 + setCheckboxesEnabled(true); 1.246 + } 1.247 + 1.248 + protected class InnerSyncStatusDelegate implements FirefoxAccounts.SyncStatusListener { 1.249 + protected final Runnable refreshRunnable = new Runnable() { 1.250 + @Override 1.251 + public void run() { 1.252 + refresh(); 1.253 + } 1.254 + }; 1.255 + 1.256 + @Override 1.257 + public Context getContext() { 1.258 + return FxAccountStatusFragment.this.getActivity(); 1.259 + } 1.260 + 1.261 + @Override 1.262 + public Account getAccount() { 1.263 + return fxAccount.getAndroidAccount(); 1.264 + } 1.265 + 1.266 + @Override 1.267 + public void onSyncStarted() { 1.268 + if (fxAccount == null) { 1.269 + return; 1.270 + } 1.271 + Logger.info(LOG_TAG, "Got sync started message; refreshing."); 1.272 + getActivity().runOnUiThread(refreshRunnable); 1.273 + } 1.274 + 1.275 + @Override 1.276 + public void onSyncFinished() { 1.277 + if (fxAccount == null) { 1.278 + return; 1.279 + } 1.280 + Logger.info(LOG_TAG, "Got sync finished message; refreshing."); 1.281 + getActivity().runOnUiThread(refreshRunnable); 1.282 + } 1.283 + } 1.284 + 1.285 + /** 1.286 + * Notify the fragment that a new AndroidFxAccount instance is current. 1.287 + * <p> 1.288 + * <b>Important:</b> call this method on the UI thread! 1.289 + * <p> 1.290 + * In future, this might be a Loader. 1.291 + * 1.292 + * @param fxAccount new instance. 1.293 + */ 1.294 + public void refresh(AndroidFxAccount fxAccount) { 1.295 + if (fxAccount == null) { 1.296 + throw new IllegalArgumentException("fxAccount must not be null"); 1.297 + } 1.298 + this.fxAccount = fxAccount; 1.299 + 1.300 + handler = new Handler(); // Attached to current (assumed to be UI) thread. 1.301 + 1.302 + // Runnable is not specific to one Firefox Account. This runnable will keep 1.303 + // a reference to this fragment alive, but we expect posted runnables to be 1.304 + // serviced very quickly, so this is not an issue. 1.305 + requestSyncRunnable = new RequestSyncRunnable(); 1.306 + 1.307 + // We would very much like register these status observers in bookended 1.308 + // onResume/onPause calls, but because the Fragment gets onResume during the 1.309 + // Activity's super.onResume, it hasn't yet been told its Firefox Account. 1.310 + // So we register the observer here (and remove it in onPause), and open 1.311 + // ourselves to the possibility that we don't have properly paired 1.312 + // register/unregister calls. 1.313 + FxAccountSyncStatusHelper.getInstance().startObserving(syncStatusDelegate); 1.314 + 1.315 + refresh(); 1.316 + } 1.317 + 1.318 + @Override 1.319 + public void onPause() { 1.320 + super.onPause(); 1.321 + FxAccountSyncStatusHelper.getInstance().stopObserving(syncStatusDelegate); 1.322 + } 1.323 + 1.324 + protected void refresh() { 1.325 + // refresh is called from our onResume, which can happen before the owning 1.326 + // Activity tells us about an account (via our public 1.327 + // refresh(AndroidFxAccount) method). 1.328 + if (fxAccount == null) { 1.329 + throw new IllegalArgumentException("fxAccount must not be null"); 1.330 + } 1.331 + 1.332 + emailPreference.setTitle(fxAccount.getEmail()); 1.333 + 1.334 + try { 1.335 + // There are error states determined by Android, not the login state 1.336 + // machine, and we have a chance to present these states here. We handle 1.337 + // them specially, since we can't surface these states as part of syncing, 1.338 + // because they generally stop syncs from happening regularly. 1.339 + 1.340 + // The action to enable syncing the Firefox Account doesn't require 1.341 + // leaving this activity, so let's present it first. 1.342 + final boolean isSyncing = fxAccount.isSyncing(); 1.343 + if (!isSyncing) { 1.344 + showNeedsAccountEnabled(); 1.345 + return; 1.346 + } 1.347 + 1.348 + // Interrogate the Firefox Account's state. 1.349 + State state = fxAccount.getState(); 1.350 + switch (state.getNeededAction()) { 1.351 + case NeedsUpgrade: 1.352 + showNeedsUpgrade(); 1.353 + break; 1.354 + case NeedsPassword: 1.355 + showNeedsPassword(); 1.356 + break; 1.357 + case NeedsVerification: 1.358 + showNeedsVerification(); 1.359 + break; 1.360 + default: 1.361 + showConnected(); 1.362 + } 1.363 + 1.364 + // We check for the master setting last, since it is not strictly 1.365 + // necessary for the user to address this error state: it's really a 1.366 + // warning state. We surface it for the user's convenience, and to prevent 1.367 + // confused folks wondering why Sync is not working at all. 1.368 + final boolean masterSyncAutomatically = ContentResolver.getMasterSyncAutomatically(); 1.369 + if (!masterSyncAutomatically) { 1.370 + showNeedsMasterSyncAutomaticallyEnabled(); 1.371 + return; 1.372 + } 1.373 + } finally { 1.374 + // No matter our state, we should update the checkboxes. 1.375 + updateSelectedEngines(); 1.376 + } 1.377 + } 1.378 + 1.379 + /** 1.380 + * Query shared prefs for the current engine state, and update the UI 1.381 + * accordingly. 1.382 + * <p> 1.383 + * In future, we might want this to be on a background thread, or implemented 1.384 + * as a Loader. 1.385 + */ 1.386 + protected void updateSelectedEngines() { 1.387 + try { 1.388 + SharedPreferences syncPrefs = fxAccount.getSyncPrefs(); 1.389 + Map<String, Boolean> engines = SyncConfiguration.getUserSelectedEngines(syncPrefs); 1.390 + if (engines != null) { 1.391 + bookmarksPreference.setChecked(engines.containsKey("bookmarks") && engines.get("bookmarks")); 1.392 + historyPreference.setChecked(engines.containsKey("history") && engines.get("history")); 1.393 + passwordsPreference.setChecked(engines.containsKey("passwords") && engines.get("passwords")); 1.394 + tabsPreference.setChecked(engines.containsKey("tabs") && engines.get("tabs")); 1.395 + return; 1.396 + } 1.397 + 1.398 + // We don't have user specified preferences. Perhaps we have seen a meta/global? 1.399 + Set<String> enabledNames = SyncConfiguration.getEnabledEngineNames(syncPrefs); 1.400 + if (enabledNames != null) { 1.401 + bookmarksPreference.setChecked(enabledNames.contains("bookmarks")); 1.402 + historyPreference.setChecked(enabledNames.contains("history")); 1.403 + passwordsPreference.setChecked(enabledNames.contains("passwords")); 1.404 + tabsPreference.setChecked(enabledNames.contains("tabs")); 1.405 + return; 1.406 + } 1.407 + 1.408 + // Okay, we don't have userSelectedEngines or enabledEngines. That means 1.409 + // the user hasn't specified to begin with, we haven't specified here, and 1.410 + // we haven't already seen, Sync engines. We don't know our state, so 1.411 + // let's check everything (the default) and disable everything. 1.412 + bookmarksPreference.setChecked(true); 1.413 + historyPreference.setChecked(true); 1.414 + passwordsPreference.setChecked(true); 1.415 + tabsPreference.setChecked(true); 1.416 + setCheckboxesEnabled(false); 1.417 + } catch (Exception e) { 1.418 + Logger.warn(LOG_TAG, "Got exception getting engines to select; ignoring.", e); 1.419 + return; 1.420 + } 1.421 + } 1.422 + 1.423 + /** 1.424 + * Persist engine selections to local shared preferences, and request a sync 1.425 + * to persist selections to remote storage. 1.426 + */ 1.427 + protected void saveEngineSelections() { 1.428 + final Map<String, Boolean> engineSelections = new HashMap<String, Boolean>(); 1.429 + engineSelections.put("bookmarks", bookmarksPreference.isChecked()); 1.430 + engineSelections.put("history", historyPreference.isChecked()); 1.431 + engineSelections.put("passwords", passwordsPreference.isChecked()); 1.432 + engineSelections.put("tabs", tabsPreference.isChecked()); 1.433 + 1.434 + // No GlobalSession.config, so store directly to shared prefs. We do this on 1.435 + // a background thread to avoid IO on the main thread and strict mode 1.436 + // warnings. 1.437 + new Thread(new PersistEngineSelectionsRunnable(engineSelections)).start(); 1.438 + } 1.439 + 1.440 + protected void requestDelayedSync() { 1.441 + Logger.info(LOG_TAG, "Posting a delayed request for a sync sometime soon."); 1.442 + handler.removeCallbacks(requestSyncRunnable); 1.443 + handler.postDelayed(requestSyncRunnable, DELAY_IN_MILLISECONDS_BEFORE_REQUESTING_SYNC); 1.444 + } 1.445 + 1.446 + /** 1.447 + * Remove all traces of debug buttons. By default, no debug buttons are shown. 1.448 + */ 1.449 + protected void removeDebugButtons() { 1.450 + final PreferenceScreen statusScreen = (PreferenceScreen) ensureFindPreference("status_screen"); 1.451 + final PreferenceCategory debugCategory = (PreferenceCategory) ensureFindPreference("debug_category"); 1.452 + statusScreen.removePreference(debugCategory); 1.453 + } 1.454 + 1.455 + /** 1.456 + * A Runnable that persists engine selections to shared prefs, and then 1.457 + * requests a delayed sync. 1.458 + * <p> 1.459 + * References the member <code>fxAccount</code> and is specific to the Android 1.460 + * account associated to that account. 1.461 + */ 1.462 + protected class PersistEngineSelectionsRunnable implements Runnable { 1.463 + private final Map<String, Boolean> engineSelections; 1.464 + 1.465 + protected PersistEngineSelectionsRunnable(Map<String, Boolean> engineSelections) { 1.466 + this.engineSelections = engineSelections; 1.467 + } 1.468 + 1.469 + @Override 1.470 + public void run() { 1.471 + try { 1.472 + // Name shadowing -- do you like it, or do you love it? 1.473 + AndroidFxAccount fxAccount = FxAccountStatusFragment.this.fxAccount; 1.474 + if (fxAccount == null) { 1.475 + return; 1.476 + } 1.477 + Logger.info(LOG_TAG, "Persisting engine selections: " + engineSelections.toString()); 1.478 + SyncConfiguration.storeSelectedEnginesToPrefs(fxAccount.getSyncPrefs(), engineSelections); 1.479 + requestDelayedSync(); 1.480 + } catch (Exception e) { 1.481 + Logger.warn(LOG_TAG, "Got exception persisting selected engines; ignoring.", e); 1.482 + return; 1.483 + } 1.484 + } 1.485 + } 1.486 + 1.487 + /** 1.488 + * A Runnable that requests a sync. 1.489 + * <p> 1.490 + * References the member <code>fxAccount</code>, but is not specific to the 1.491 + * Android account associated to that account. 1.492 + */ 1.493 + protected class RequestSyncRunnable implements Runnable { 1.494 + @Override 1.495 + public void run() { 1.496 + // Name shadowing -- do you like it, or do you love it? 1.497 + AndroidFxAccount fxAccount = FxAccountStatusFragment.this.fxAccount; 1.498 + if (fxAccount == null) { 1.499 + return; 1.500 + } 1.501 + Logger.info(LOG_TAG, "Requesting a sync sometime soon."); 1.502 + fxAccount.requestSync(); 1.503 + } 1.504 + } 1.505 + 1.506 + /** 1.507 + * A separate listener to separate debug logic from main code paths. 1.508 + */ 1.509 + protected class DebugPreferenceClickListener implements OnPreferenceClickListener { 1.510 + @Override 1.511 + public boolean onPreferenceClick(Preference preference) { 1.512 + final String key = preference.getKey(); 1.513 + if ("debug_refresh".equals(key)) { 1.514 + Logger.info(LOG_TAG, "Refreshing."); 1.515 + refresh(); 1.516 + } else if ("debug_dump".equals(key)) { 1.517 + fxAccount.dump(); 1.518 + } else if ("debug_force_sync".equals(key)) { 1.519 + Logger.info(LOG_TAG, "Force syncing."); 1.520 + fxAccount.requestSync(FirefoxAccounts.FORCE); 1.521 + // No sense refreshing, since the sync will complete in the future. 1.522 + } else if ("debug_forget_certificate".equals(key)) { 1.523 + State state = fxAccount.getState(); 1.524 + try { 1.525 + Married married = (Married) state; 1.526 + Logger.info(LOG_TAG, "Moving to Cohabiting state: Forgetting certificate."); 1.527 + fxAccount.setState(married.makeCohabitingState()); 1.528 + refresh(); 1.529 + } catch (ClassCastException e) { 1.530 + Logger.info(LOG_TAG, "Not in Married state; can't forget certificate."); 1.531 + // Ignore. 1.532 + } 1.533 + } else if ("debug_require_password".equals(key)) { 1.534 + Logger.info(LOG_TAG, "Moving to Separated state: Forgetting password."); 1.535 + State state = fxAccount.getState(); 1.536 + fxAccount.setState(state.makeSeparatedState()); 1.537 + refresh(); 1.538 + } else if ("debug_require_upgrade".equals(key)) { 1.539 + Logger.info(LOG_TAG, "Moving to Doghouse state: Requiring upgrade."); 1.540 + State state = fxAccount.getState(); 1.541 + fxAccount.setState(state.makeDoghouseState()); 1.542 + refresh(); 1.543 + } else { 1.544 + return false; 1.545 + } 1.546 + return true; 1.547 + } 1.548 + } 1.549 + 1.550 + /** 1.551 + * Iterate through debug buttons, adding a special deubg preference click 1.552 + * listener to each of them. 1.553 + */ 1.554 + protected void connectDebugButtons() { 1.555 + // Separate listener to really separate debug logic from main code paths. 1.556 + final OnPreferenceClickListener listener = new DebugPreferenceClickListener(); 1.557 + 1.558 + // We don't want to use Android resource strings for debug UI, so we just 1.559 + // use the keys throughout. 1.560 + final Preference debugCategory = ensureFindPreference("debug_category"); 1.561 + debugCategory.setTitle(debugCategory.getKey()); 1.562 + 1.563 + String[] debugKeys = new String[] { 1.564 + "debug_refresh", 1.565 + "debug_dump", 1.566 + "debug_force_sync", 1.567 + "debug_forget_certificate", 1.568 + "debug_require_password", 1.569 + "debug_require_upgrade" }; 1.570 + for (String debugKey : debugKeys) { 1.571 + final Preference button = ensureFindPreference(debugKey); 1.572 + button.setTitle(debugKey); // Not very friendly, but this is for debugging only! 1.573 + button.setOnPreferenceClickListener(listener); 1.574 + } 1.575 + } 1.576 +}