|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 package org.mozilla.gecko.fxa.activities; |
|
6 |
|
7 import java.util.HashMap; |
|
8 import java.util.Map; |
|
9 import java.util.Set; |
|
10 |
|
11 import org.mozilla.gecko.R; |
|
12 import org.mozilla.gecko.background.common.log.Logger; |
|
13 import org.mozilla.gecko.background.preferences.PreferenceFragment; |
|
14 import org.mozilla.gecko.fxa.FirefoxAccounts; |
|
15 import org.mozilla.gecko.fxa.FxAccountConstants; |
|
16 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; |
|
17 import org.mozilla.gecko.fxa.login.Married; |
|
18 import org.mozilla.gecko.fxa.login.State; |
|
19 import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper; |
|
20 import org.mozilla.gecko.sync.SyncConfiguration; |
|
21 |
|
22 import android.accounts.Account; |
|
23 import android.content.ContentResolver; |
|
24 import android.content.Context; |
|
25 import android.content.Intent; |
|
26 import android.content.SharedPreferences; |
|
27 import android.os.Bundle; |
|
28 import android.os.Handler; |
|
29 import android.preference.CheckBoxPreference; |
|
30 import android.preference.Preference; |
|
31 import android.preference.Preference.OnPreferenceClickListener; |
|
32 import android.preference.PreferenceCategory; |
|
33 import android.preference.PreferenceScreen; |
|
34 |
|
35 /** |
|
36 * A fragment that displays the status of an AndroidFxAccount. |
|
37 * <p> |
|
38 * The owning activity is responsible for providing an AndroidFxAccount at |
|
39 * appropriate times. |
|
40 */ |
|
41 public class FxAccountStatusFragment extends PreferenceFragment implements OnPreferenceClickListener { |
|
42 private static final String LOG_TAG = FxAccountStatusFragment.class.getSimpleName(); |
|
43 |
|
44 // When a checkbox is toggled, wait 5 seconds (for other checkbox actions) |
|
45 // before trying to sync. Should we kill off the fragment before the sync |
|
46 // request happens, that's okay: the runnable will run if the UI thread is |
|
47 // still around to service it, and since we're not updating any UI, we'll just |
|
48 // schedule the sync as usual. See also comment below about garbage |
|
49 // collection. |
|
50 private static final long DELAY_IN_MILLISECONDS_BEFORE_REQUESTING_SYNC = 5 * 1000; |
|
51 |
|
52 protected Preference emailPreference; |
|
53 |
|
54 protected Preference needsPasswordPreference; |
|
55 protected Preference needsUpgradePreference; |
|
56 protected Preference needsVerificationPreference; |
|
57 protected Preference needsMasterSyncAutomaticallyEnabledPreference; |
|
58 protected Preference needsAccountEnabledPreference; |
|
59 |
|
60 protected PreferenceCategory syncCategory; |
|
61 |
|
62 protected CheckBoxPreference bookmarksPreference; |
|
63 protected CheckBoxPreference historyPreference; |
|
64 protected CheckBoxPreference tabsPreference; |
|
65 protected CheckBoxPreference passwordsPreference; |
|
66 |
|
67 protected volatile AndroidFxAccount fxAccount; |
|
68 |
|
69 // Used to post delayed sync requests. |
|
70 protected Handler handler; |
|
71 |
|
72 // Member variable so that re-posting pushes back the already posted instance. |
|
73 // This Runnable references the fxAccount above, but it is not specific to a |
|
74 // single account. (That is, it does not capture a single account instance.) |
|
75 protected Runnable requestSyncRunnable; |
|
76 |
|
77 protected final InnerSyncStatusDelegate syncStatusDelegate = new InnerSyncStatusDelegate(); |
|
78 |
|
79 protected Preference ensureFindPreference(String key) { |
|
80 Preference preference = findPreference(key); |
|
81 if (preference == null) { |
|
82 throw new IllegalStateException("Could not find preference with key: " + key); |
|
83 } |
|
84 return preference; |
|
85 } |
|
86 |
|
87 @Override |
|
88 public void onCreate(Bundle savedInstanceState) { |
|
89 super.onCreate(savedInstanceState); |
|
90 addPreferencesFromResource(R.xml.fxaccount_status_prefscreen); |
|
91 |
|
92 emailPreference = ensureFindPreference("email"); |
|
93 |
|
94 needsPasswordPreference = ensureFindPreference("needs_credentials"); |
|
95 needsUpgradePreference = ensureFindPreference("needs_upgrade"); |
|
96 needsVerificationPreference = ensureFindPreference("needs_verification"); |
|
97 needsMasterSyncAutomaticallyEnabledPreference = ensureFindPreference("needs_master_sync_automatically_enabled"); |
|
98 needsAccountEnabledPreference = ensureFindPreference("needs_account_enabled"); |
|
99 |
|
100 syncCategory = (PreferenceCategory) ensureFindPreference("sync_category"); |
|
101 |
|
102 bookmarksPreference = (CheckBoxPreference) ensureFindPreference("bookmarks"); |
|
103 historyPreference = (CheckBoxPreference) ensureFindPreference("history"); |
|
104 tabsPreference = (CheckBoxPreference) ensureFindPreference("tabs"); |
|
105 passwordsPreference = (CheckBoxPreference) ensureFindPreference("passwords"); |
|
106 |
|
107 if (!FxAccountConstants.LOG_PERSONAL_INFORMATION) { |
|
108 removeDebugButtons(); |
|
109 } else { |
|
110 connectDebugButtons(); |
|
111 } |
|
112 |
|
113 needsPasswordPreference.setOnPreferenceClickListener(this); |
|
114 needsVerificationPreference.setOnPreferenceClickListener(this); |
|
115 needsAccountEnabledPreference.setOnPreferenceClickListener(this); |
|
116 |
|
117 bookmarksPreference.setOnPreferenceClickListener(this); |
|
118 historyPreference.setOnPreferenceClickListener(this); |
|
119 tabsPreference.setOnPreferenceClickListener(this); |
|
120 passwordsPreference.setOnPreferenceClickListener(this); |
|
121 } |
|
122 |
|
123 /** |
|
124 * We intentionally don't refresh here. Our owning activity is responsible for |
|
125 * providing an AndroidFxAccount to our refresh method in its onResume method. |
|
126 */ |
|
127 @Override |
|
128 public void onResume() { |
|
129 super.onResume(); |
|
130 } |
|
131 |
|
132 @Override |
|
133 public boolean onPreferenceClick(Preference preference) { |
|
134 if (preference == needsPasswordPreference) { |
|
135 Intent intent = new Intent(getActivity(), FxAccountUpdateCredentialsActivity.class); |
|
136 // Per http://stackoverflow.com/a/8992365, this triggers a known bug with |
|
137 // the soft keyboard not being shown for the started activity. Why, Android, why? |
|
138 intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); |
|
139 startActivity(intent); |
|
140 |
|
141 return true; |
|
142 } |
|
143 |
|
144 if (preference == needsVerificationPreference) { |
|
145 FxAccountConfirmAccountActivity.resendCode(getActivity().getApplicationContext(), fxAccount); |
|
146 |
|
147 Intent intent = new Intent(getActivity(), FxAccountConfirmAccountActivity.class); |
|
148 // Per http://stackoverflow.com/a/8992365, this triggers a known bug with |
|
149 // the soft keyboard not being shown for the started activity. Why, Android, why? |
|
150 intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); |
|
151 startActivity(intent); |
|
152 |
|
153 return true; |
|
154 } |
|
155 |
|
156 if (preference == needsAccountEnabledPreference) { |
|
157 fxAccount.enableSyncing(); |
|
158 refresh(); |
|
159 |
|
160 return true; |
|
161 } |
|
162 |
|
163 if (preference == bookmarksPreference || |
|
164 preference == historyPreference || |
|
165 preference == passwordsPreference || |
|
166 preference == tabsPreference) { |
|
167 saveEngineSelections(); |
|
168 return true; |
|
169 } |
|
170 |
|
171 return false; |
|
172 } |
|
173 |
|
174 protected void setCheckboxesEnabled(boolean enabled) { |
|
175 bookmarksPreference.setEnabled(enabled); |
|
176 historyPreference.setEnabled(enabled); |
|
177 tabsPreference.setEnabled(enabled); |
|
178 passwordsPreference.setEnabled(enabled); |
|
179 } |
|
180 |
|
181 /** |
|
182 * Show at most one error preference, hiding all others. |
|
183 * |
|
184 * @param errorPreferenceToShow |
|
185 * single error preference to show; if null, hide all error preferences |
|
186 */ |
|
187 protected void showOnlyOneErrorPreference(Preference errorPreferenceToShow) { |
|
188 final Preference[] errorPreferences = new Preference[] { |
|
189 this.needsPasswordPreference, |
|
190 this.needsUpgradePreference, |
|
191 this.needsVerificationPreference, |
|
192 this.needsMasterSyncAutomaticallyEnabledPreference, |
|
193 this.needsAccountEnabledPreference, |
|
194 }; |
|
195 for (Preference errorPreference : errorPreferences) { |
|
196 final boolean currentlyShown = null != findPreference(errorPreference.getKey()); |
|
197 final boolean shouldBeShown = errorPreference == errorPreferenceToShow; |
|
198 if (currentlyShown == shouldBeShown) { |
|
199 continue; |
|
200 } |
|
201 if (shouldBeShown) { |
|
202 syncCategory.addPreference(errorPreference); |
|
203 } else { |
|
204 syncCategory.removePreference(errorPreference); |
|
205 } |
|
206 } |
|
207 } |
|
208 |
|
209 protected void showNeedsPassword() { |
|
210 syncCategory.setTitle(R.string.fxaccount_status_sync); |
|
211 showOnlyOneErrorPreference(needsPasswordPreference); |
|
212 setCheckboxesEnabled(false); |
|
213 } |
|
214 |
|
215 protected void showNeedsUpgrade() { |
|
216 syncCategory.setTitle(R.string.fxaccount_status_sync); |
|
217 showOnlyOneErrorPreference(needsUpgradePreference); |
|
218 setCheckboxesEnabled(false); |
|
219 } |
|
220 |
|
221 protected void showNeedsVerification() { |
|
222 syncCategory.setTitle(R.string.fxaccount_status_sync); |
|
223 showOnlyOneErrorPreference(needsVerificationPreference); |
|
224 setCheckboxesEnabled(false); |
|
225 } |
|
226 |
|
227 protected void showNeedsMasterSyncAutomaticallyEnabled() { |
|
228 syncCategory.setTitle(R.string.fxaccount_status_sync); |
|
229 showOnlyOneErrorPreference(needsMasterSyncAutomaticallyEnabledPreference); |
|
230 setCheckboxesEnabled(false); |
|
231 } |
|
232 |
|
233 protected void showNeedsAccountEnabled() { |
|
234 syncCategory.setTitle(R.string.fxaccount_status_sync); |
|
235 showOnlyOneErrorPreference(needsAccountEnabledPreference); |
|
236 setCheckboxesEnabled(false); |
|
237 } |
|
238 |
|
239 protected void showConnected() { |
|
240 syncCategory.setTitle(R.string.fxaccount_status_sync_enabled); |
|
241 showOnlyOneErrorPreference(null); |
|
242 setCheckboxesEnabled(true); |
|
243 } |
|
244 |
|
245 protected class InnerSyncStatusDelegate implements FirefoxAccounts.SyncStatusListener { |
|
246 protected final Runnable refreshRunnable = new Runnable() { |
|
247 @Override |
|
248 public void run() { |
|
249 refresh(); |
|
250 } |
|
251 }; |
|
252 |
|
253 @Override |
|
254 public Context getContext() { |
|
255 return FxAccountStatusFragment.this.getActivity(); |
|
256 } |
|
257 |
|
258 @Override |
|
259 public Account getAccount() { |
|
260 return fxAccount.getAndroidAccount(); |
|
261 } |
|
262 |
|
263 @Override |
|
264 public void onSyncStarted() { |
|
265 if (fxAccount == null) { |
|
266 return; |
|
267 } |
|
268 Logger.info(LOG_TAG, "Got sync started message; refreshing."); |
|
269 getActivity().runOnUiThread(refreshRunnable); |
|
270 } |
|
271 |
|
272 @Override |
|
273 public void onSyncFinished() { |
|
274 if (fxAccount == null) { |
|
275 return; |
|
276 } |
|
277 Logger.info(LOG_TAG, "Got sync finished message; refreshing."); |
|
278 getActivity().runOnUiThread(refreshRunnable); |
|
279 } |
|
280 } |
|
281 |
|
282 /** |
|
283 * Notify the fragment that a new AndroidFxAccount instance is current. |
|
284 * <p> |
|
285 * <b>Important:</b> call this method on the UI thread! |
|
286 * <p> |
|
287 * In future, this might be a Loader. |
|
288 * |
|
289 * @param fxAccount new instance. |
|
290 */ |
|
291 public void refresh(AndroidFxAccount fxAccount) { |
|
292 if (fxAccount == null) { |
|
293 throw new IllegalArgumentException("fxAccount must not be null"); |
|
294 } |
|
295 this.fxAccount = fxAccount; |
|
296 |
|
297 handler = new Handler(); // Attached to current (assumed to be UI) thread. |
|
298 |
|
299 // Runnable is not specific to one Firefox Account. This runnable will keep |
|
300 // a reference to this fragment alive, but we expect posted runnables to be |
|
301 // serviced very quickly, so this is not an issue. |
|
302 requestSyncRunnable = new RequestSyncRunnable(); |
|
303 |
|
304 // We would very much like register these status observers in bookended |
|
305 // onResume/onPause calls, but because the Fragment gets onResume during the |
|
306 // Activity's super.onResume, it hasn't yet been told its Firefox Account. |
|
307 // So we register the observer here (and remove it in onPause), and open |
|
308 // ourselves to the possibility that we don't have properly paired |
|
309 // register/unregister calls. |
|
310 FxAccountSyncStatusHelper.getInstance().startObserving(syncStatusDelegate); |
|
311 |
|
312 refresh(); |
|
313 } |
|
314 |
|
315 @Override |
|
316 public void onPause() { |
|
317 super.onPause(); |
|
318 FxAccountSyncStatusHelper.getInstance().stopObserving(syncStatusDelegate); |
|
319 } |
|
320 |
|
321 protected void refresh() { |
|
322 // refresh is called from our onResume, which can happen before the owning |
|
323 // Activity tells us about an account (via our public |
|
324 // refresh(AndroidFxAccount) method). |
|
325 if (fxAccount == null) { |
|
326 throw new IllegalArgumentException("fxAccount must not be null"); |
|
327 } |
|
328 |
|
329 emailPreference.setTitle(fxAccount.getEmail()); |
|
330 |
|
331 try { |
|
332 // There are error states determined by Android, not the login state |
|
333 // machine, and we have a chance to present these states here. We handle |
|
334 // them specially, since we can't surface these states as part of syncing, |
|
335 // because they generally stop syncs from happening regularly. |
|
336 |
|
337 // The action to enable syncing the Firefox Account doesn't require |
|
338 // leaving this activity, so let's present it first. |
|
339 final boolean isSyncing = fxAccount.isSyncing(); |
|
340 if (!isSyncing) { |
|
341 showNeedsAccountEnabled(); |
|
342 return; |
|
343 } |
|
344 |
|
345 // Interrogate the Firefox Account's state. |
|
346 State state = fxAccount.getState(); |
|
347 switch (state.getNeededAction()) { |
|
348 case NeedsUpgrade: |
|
349 showNeedsUpgrade(); |
|
350 break; |
|
351 case NeedsPassword: |
|
352 showNeedsPassword(); |
|
353 break; |
|
354 case NeedsVerification: |
|
355 showNeedsVerification(); |
|
356 break; |
|
357 default: |
|
358 showConnected(); |
|
359 } |
|
360 |
|
361 // We check for the master setting last, since it is not strictly |
|
362 // necessary for the user to address this error state: it's really a |
|
363 // warning state. We surface it for the user's convenience, and to prevent |
|
364 // confused folks wondering why Sync is not working at all. |
|
365 final boolean masterSyncAutomatically = ContentResolver.getMasterSyncAutomatically(); |
|
366 if (!masterSyncAutomatically) { |
|
367 showNeedsMasterSyncAutomaticallyEnabled(); |
|
368 return; |
|
369 } |
|
370 } finally { |
|
371 // No matter our state, we should update the checkboxes. |
|
372 updateSelectedEngines(); |
|
373 } |
|
374 } |
|
375 |
|
376 /** |
|
377 * Query shared prefs for the current engine state, and update the UI |
|
378 * accordingly. |
|
379 * <p> |
|
380 * In future, we might want this to be on a background thread, or implemented |
|
381 * as a Loader. |
|
382 */ |
|
383 protected void updateSelectedEngines() { |
|
384 try { |
|
385 SharedPreferences syncPrefs = fxAccount.getSyncPrefs(); |
|
386 Map<String, Boolean> engines = SyncConfiguration.getUserSelectedEngines(syncPrefs); |
|
387 if (engines != null) { |
|
388 bookmarksPreference.setChecked(engines.containsKey("bookmarks") && engines.get("bookmarks")); |
|
389 historyPreference.setChecked(engines.containsKey("history") && engines.get("history")); |
|
390 passwordsPreference.setChecked(engines.containsKey("passwords") && engines.get("passwords")); |
|
391 tabsPreference.setChecked(engines.containsKey("tabs") && engines.get("tabs")); |
|
392 return; |
|
393 } |
|
394 |
|
395 // We don't have user specified preferences. Perhaps we have seen a meta/global? |
|
396 Set<String> enabledNames = SyncConfiguration.getEnabledEngineNames(syncPrefs); |
|
397 if (enabledNames != null) { |
|
398 bookmarksPreference.setChecked(enabledNames.contains("bookmarks")); |
|
399 historyPreference.setChecked(enabledNames.contains("history")); |
|
400 passwordsPreference.setChecked(enabledNames.contains("passwords")); |
|
401 tabsPreference.setChecked(enabledNames.contains("tabs")); |
|
402 return; |
|
403 } |
|
404 |
|
405 // Okay, we don't have userSelectedEngines or enabledEngines. That means |
|
406 // the user hasn't specified to begin with, we haven't specified here, and |
|
407 // we haven't already seen, Sync engines. We don't know our state, so |
|
408 // let's check everything (the default) and disable everything. |
|
409 bookmarksPreference.setChecked(true); |
|
410 historyPreference.setChecked(true); |
|
411 passwordsPreference.setChecked(true); |
|
412 tabsPreference.setChecked(true); |
|
413 setCheckboxesEnabled(false); |
|
414 } catch (Exception e) { |
|
415 Logger.warn(LOG_TAG, "Got exception getting engines to select; ignoring.", e); |
|
416 return; |
|
417 } |
|
418 } |
|
419 |
|
420 /** |
|
421 * Persist engine selections to local shared preferences, and request a sync |
|
422 * to persist selections to remote storage. |
|
423 */ |
|
424 protected void saveEngineSelections() { |
|
425 final Map<String, Boolean> engineSelections = new HashMap<String, Boolean>(); |
|
426 engineSelections.put("bookmarks", bookmarksPreference.isChecked()); |
|
427 engineSelections.put("history", historyPreference.isChecked()); |
|
428 engineSelections.put("passwords", passwordsPreference.isChecked()); |
|
429 engineSelections.put("tabs", tabsPreference.isChecked()); |
|
430 |
|
431 // No GlobalSession.config, so store directly to shared prefs. We do this on |
|
432 // a background thread to avoid IO on the main thread and strict mode |
|
433 // warnings. |
|
434 new Thread(new PersistEngineSelectionsRunnable(engineSelections)).start(); |
|
435 } |
|
436 |
|
437 protected void requestDelayedSync() { |
|
438 Logger.info(LOG_TAG, "Posting a delayed request for a sync sometime soon."); |
|
439 handler.removeCallbacks(requestSyncRunnable); |
|
440 handler.postDelayed(requestSyncRunnable, DELAY_IN_MILLISECONDS_BEFORE_REQUESTING_SYNC); |
|
441 } |
|
442 |
|
443 /** |
|
444 * Remove all traces of debug buttons. By default, no debug buttons are shown. |
|
445 */ |
|
446 protected void removeDebugButtons() { |
|
447 final PreferenceScreen statusScreen = (PreferenceScreen) ensureFindPreference("status_screen"); |
|
448 final PreferenceCategory debugCategory = (PreferenceCategory) ensureFindPreference("debug_category"); |
|
449 statusScreen.removePreference(debugCategory); |
|
450 } |
|
451 |
|
452 /** |
|
453 * A Runnable that persists engine selections to shared prefs, and then |
|
454 * requests a delayed sync. |
|
455 * <p> |
|
456 * References the member <code>fxAccount</code> and is specific to the Android |
|
457 * account associated to that account. |
|
458 */ |
|
459 protected class PersistEngineSelectionsRunnable implements Runnable { |
|
460 private final Map<String, Boolean> engineSelections; |
|
461 |
|
462 protected PersistEngineSelectionsRunnable(Map<String, Boolean> engineSelections) { |
|
463 this.engineSelections = engineSelections; |
|
464 } |
|
465 |
|
466 @Override |
|
467 public void run() { |
|
468 try { |
|
469 // Name shadowing -- do you like it, or do you love it? |
|
470 AndroidFxAccount fxAccount = FxAccountStatusFragment.this.fxAccount; |
|
471 if (fxAccount == null) { |
|
472 return; |
|
473 } |
|
474 Logger.info(LOG_TAG, "Persisting engine selections: " + engineSelections.toString()); |
|
475 SyncConfiguration.storeSelectedEnginesToPrefs(fxAccount.getSyncPrefs(), engineSelections); |
|
476 requestDelayedSync(); |
|
477 } catch (Exception e) { |
|
478 Logger.warn(LOG_TAG, "Got exception persisting selected engines; ignoring.", e); |
|
479 return; |
|
480 } |
|
481 } |
|
482 } |
|
483 |
|
484 /** |
|
485 * A Runnable that requests a sync. |
|
486 * <p> |
|
487 * References the member <code>fxAccount</code>, but is not specific to the |
|
488 * Android account associated to that account. |
|
489 */ |
|
490 protected class RequestSyncRunnable implements Runnable { |
|
491 @Override |
|
492 public void run() { |
|
493 // Name shadowing -- do you like it, or do you love it? |
|
494 AndroidFxAccount fxAccount = FxAccountStatusFragment.this.fxAccount; |
|
495 if (fxAccount == null) { |
|
496 return; |
|
497 } |
|
498 Logger.info(LOG_TAG, "Requesting a sync sometime soon."); |
|
499 fxAccount.requestSync(); |
|
500 } |
|
501 } |
|
502 |
|
503 /** |
|
504 * A separate listener to separate debug logic from main code paths. |
|
505 */ |
|
506 protected class DebugPreferenceClickListener implements OnPreferenceClickListener { |
|
507 @Override |
|
508 public boolean onPreferenceClick(Preference preference) { |
|
509 final String key = preference.getKey(); |
|
510 if ("debug_refresh".equals(key)) { |
|
511 Logger.info(LOG_TAG, "Refreshing."); |
|
512 refresh(); |
|
513 } else if ("debug_dump".equals(key)) { |
|
514 fxAccount.dump(); |
|
515 } else if ("debug_force_sync".equals(key)) { |
|
516 Logger.info(LOG_TAG, "Force syncing."); |
|
517 fxAccount.requestSync(FirefoxAccounts.FORCE); |
|
518 // No sense refreshing, since the sync will complete in the future. |
|
519 } else if ("debug_forget_certificate".equals(key)) { |
|
520 State state = fxAccount.getState(); |
|
521 try { |
|
522 Married married = (Married) state; |
|
523 Logger.info(LOG_TAG, "Moving to Cohabiting state: Forgetting certificate."); |
|
524 fxAccount.setState(married.makeCohabitingState()); |
|
525 refresh(); |
|
526 } catch (ClassCastException e) { |
|
527 Logger.info(LOG_TAG, "Not in Married state; can't forget certificate."); |
|
528 // Ignore. |
|
529 } |
|
530 } else if ("debug_require_password".equals(key)) { |
|
531 Logger.info(LOG_TAG, "Moving to Separated state: Forgetting password."); |
|
532 State state = fxAccount.getState(); |
|
533 fxAccount.setState(state.makeSeparatedState()); |
|
534 refresh(); |
|
535 } else if ("debug_require_upgrade".equals(key)) { |
|
536 Logger.info(LOG_TAG, "Moving to Doghouse state: Requiring upgrade."); |
|
537 State state = fxAccount.getState(); |
|
538 fxAccount.setState(state.makeDoghouseState()); |
|
539 refresh(); |
|
540 } else { |
|
541 return false; |
|
542 } |
|
543 return true; |
|
544 } |
|
545 } |
|
546 |
|
547 /** |
|
548 * Iterate through debug buttons, adding a special deubg preference click |
|
549 * listener to each of them. |
|
550 */ |
|
551 protected void connectDebugButtons() { |
|
552 // Separate listener to really separate debug logic from main code paths. |
|
553 final OnPreferenceClickListener listener = new DebugPreferenceClickListener(); |
|
554 |
|
555 // We don't want to use Android resource strings for debug UI, so we just |
|
556 // use the keys throughout. |
|
557 final Preference debugCategory = ensureFindPreference("debug_category"); |
|
558 debugCategory.setTitle(debugCategory.getKey()); |
|
559 |
|
560 String[] debugKeys = new String[] { |
|
561 "debug_refresh", |
|
562 "debug_dump", |
|
563 "debug_force_sync", |
|
564 "debug_forget_certificate", |
|
565 "debug_require_password", |
|
566 "debug_require_upgrade" }; |
|
567 for (String debugKey : debugKeys) { |
|
568 final Preference button = ensureFindPreference(debugKey); |
|
569 button.setTitle(debugKey); // Not very friendly, but this is for debugging only! |
|
570 button.setOnPreferenceClickListener(listener); |
|
571 } |
|
572 } |
|
573 } |