diff options
Diffstat (limited to 'mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountStatusFragment.java')
-rw-r--r-- | mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountStatusFragment.java | 949 |
1 files changed, 0 insertions, 949 deletions
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountStatusFragment.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountStatusFragment.java deleted file mode 100644 index a30b92e5f..000000000 --- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountStatusFragment.java +++ /dev/null @@ -1,949 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.gecko.fxa.activities; - -import android.accounts.Account; -import android.content.BroadcastReceiver; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.os.Handler; -import android.preference.CheckBoxPreference; -import android.preference.EditTextPreference; -import android.preference.Preference; -import android.preference.Preference.OnPreferenceChangeListener; -import android.preference.Preference.OnPreferenceClickListener; -import android.preference.PreferenceCategory; -import android.preference.PreferenceScreen; -import android.support.v4.content.LocalBroadcastManager; -import android.text.TextUtils; -import android.text.format.DateUtils; - -import com.squareup.picasso.Picasso; -import com.squareup.picasso.Target; - -import org.mozilla.gecko.AppConstants; -import org.mozilla.gecko.R; -import org.mozilla.gecko.background.common.log.Logger; -import org.mozilla.gecko.background.fxa.FxAccountUtils; -import org.mozilla.gecko.background.preferences.PreferenceFragment; -import org.mozilla.gecko.fxa.FxAccountConstants; -import org.mozilla.gecko.fxa.SyncStatusListener; -import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; -import org.mozilla.gecko.fxa.login.Married; -import org.mozilla.gecko.fxa.login.State; -import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper; -import org.mozilla.gecko.sync.ExtendedJSONObject; -import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate; -import org.mozilla.gecko.sync.SyncConfiguration; -import org.mozilla.gecko.sync.setup.activities.ActivityUtils; -import org.mozilla.gecko.util.HardwareUtils; -import org.mozilla.gecko.util.ThreadUtils; - -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -/** - * A fragment that displays the status of an AndroidFxAccount. - * <p> - * The owning activity is responsible for providing an AndroidFxAccount at - * appropriate times. - */ -public class FxAccountStatusFragment - extends PreferenceFragment - implements OnPreferenceClickListener, OnPreferenceChangeListener { - private static final String LOG_TAG = FxAccountStatusFragment.class.getSimpleName(); - - /** - * If a device claims to have synced before this date, we will assume it has never synced. - */ - private static final Date EARLIEST_VALID_SYNCED_DATE; - - static { - final Calendar c = GregorianCalendar.getInstance(); - c.set(2000, Calendar.JANUARY, 1, 0, 0, 0); - EARLIEST_VALID_SYNCED_DATE = c.getTime(); - } - - // When a checkbox is toggled, wait 5 seconds (for other checkbox actions) - // before trying to sync. Should we kill off the fragment before the sync - // request happens, that's okay: the runnable will run if the UI thread is - // still around to service it, and since we're not updating any UI, we'll just - // schedule the sync as usual. See also comment below about garbage - // collection. - private static final long DELAY_IN_MILLISECONDS_BEFORE_REQUESTING_SYNC = 5 * 1000; - private static final long LAST_SYNCED_TIME_UPDATE_INTERVAL_IN_MILLISECONDS = 60 * 1000; - private static final long PROFILE_FETCH_RETRY_INTERVAL_IN_MILLISECONDS = 60 * 1000; - - private static final String[] STAGES_TO_SYNC_ON_DEVICE_NAME_CHANGE = new String[] { "clients" }; - - // By default, the auth/account server preference is only shown when the - // account is configured to use a custom server. In debug mode, this is set. - private static boolean ALWAYS_SHOW_AUTH_SERVER = false; - - // By default, the Sync server preference is only shown when the account is - // configured to use a custom Sync server. In debug mode, this is set. - private static boolean ALWAYS_SHOW_SYNC_SERVER = false; - - protected PreferenceCategory accountCategory; - protected Preference profilePreference; - protected Preference manageAccountPreference; - protected Preference authServerPreference; - protected Preference removeAccountPreference; - - protected Preference needsPasswordPreference; - protected Preference needsUpgradePreference; - protected Preference needsVerificationPreference; - protected Preference needsMasterSyncAutomaticallyEnabledPreference; - protected Preference needsFinishMigratingPreference; - - protected PreferenceCategory syncCategory; - - protected CheckBoxPreference bookmarksPreference; - protected CheckBoxPreference historyPreference; - protected CheckBoxPreference tabsPreference; - protected CheckBoxPreference passwordsPreference; - protected CheckBoxPreference readingListPreference; - - protected EditTextPreference deviceNamePreference; - protected Preference syncServerPreference; - protected Preference morePreference; - protected Preference syncNowPreference; - - protected volatile AndroidFxAccount fxAccount; - // The contract is: when fxAccount is non-null, then clientsDataDelegate is - // non-null. If violated then an IllegalStateException is thrown. - protected volatile SharedPreferencesClientsDataDelegate clientsDataDelegate; - - // Used to post delayed sync requests. - protected Handler handler; - - // Member variable so that re-posting pushes back the already posted instance. - // This Runnable references the fxAccount above, but it is not specific to a - // single account. (That is, it does not capture a single account instance.) - protected Runnable requestSyncRunnable; - - // Runnable to update last synced time. - protected Runnable lastSyncedTimeUpdateRunnable; - - // Broadcast Receiver to update profile Information. - protected FxAccountProfileInformationReceiver accountProfileInformationReceiver; - - protected final InnerSyncStatusDelegate syncStatusDelegate = new InnerSyncStatusDelegate(); - private Target profileAvatarTarget; - - protected Preference ensureFindPreference(String key) { - Preference preference = findPreference(key); - if (preference == null) { - throw new IllegalStateException("Could not find preference with key: " + key); - } - return preference; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // We need to do this before we can query the hardware menu button state. - // We're guaranteed to have an activity at this point (onAttach is called - // before onCreate). It's okay to call this multiple times (with different - // contexts). - HardwareUtils.init(getActivity()); - - addPreferences(); - } - - protected void addPreferences() { - addPreferencesFromResource(R.xml.fxaccount_status_prefscreen); - - accountCategory = (PreferenceCategory) ensureFindPreference("signed_in_as_category"); - profilePreference = ensureFindPreference("profile"); - manageAccountPreference = ensureFindPreference("manage_account"); - authServerPreference = ensureFindPreference("auth_server"); - removeAccountPreference = ensureFindPreference("remove_account"); - - needsPasswordPreference = ensureFindPreference("needs_credentials"); - needsUpgradePreference = ensureFindPreference("needs_upgrade"); - needsVerificationPreference = ensureFindPreference("needs_verification"); - needsMasterSyncAutomaticallyEnabledPreference = ensureFindPreference("needs_master_sync_automatically_enabled"); - needsFinishMigratingPreference = ensureFindPreference("needs_finish_migrating"); - - syncCategory = (PreferenceCategory) ensureFindPreference("sync_category"); - - bookmarksPreference = (CheckBoxPreference) ensureFindPreference("bookmarks"); - historyPreference = (CheckBoxPreference) ensureFindPreference("history"); - tabsPreference = (CheckBoxPreference) ensureFindPreference("tabs"); - passwordsPreference = (CheckBoxPreference) ensureFindPreference("passwords"); - - if (!FxAccountUtils.LOG_PERSONAL_INFORMATION) { - removeDebugButtons(); - } else { - connectDebugButtons(); - ALWAYS_SHOW_AUTH_SERVER = true; - ALWAYS_SHOW_SYNC_SERVER = true; - } - - profilePreference.setOnPreferenceClickListener(this); - manageAccountPreference.setOnPreferenceClickListener(this); - removeAccountPreference.setOnPreferenceClickListener(this); - - needsPasswordPreference.setOnPreferenceClickListener(this); - needsVerificationPreference.setOnPreferenceClickListener(this); - needsFinishMigratingPreference.setOnPreferenceClickListener(this); - - bookmarksPreference.setOnPreferenceClickListener(this); - historyPreference.setOnPreferenceClickListener(this); - tabsPreference.setOnPreferenceClickListener(this); - passwordsPreference.setOnPreferenceClickListener(this); - - deviceNamePreference = (EditTextPreference) ensureFindPreference("device_name"); - deviceNamePreference.setOnPreferenceChangeListener(this); - - syncServerPreference = ensureFindPreference("sync_server"); - morePreference = ensureFindPreference("more"); - morePreference.setOnPreferenceClickListener(this); - - syncNowPreference = ensureFindPreference("sync_now"); - syncNowPreference.setEnabled(true); - syncNowPreference.setOnPreferenceClickListener(this); - - ensureFindPreference("linktos").setOnPreferenceClickListener(this); - ensureFindPreference("linkprivacy").setOnPreferenceClickListener(this); - } - - /** - * We intentionally don't refresh here. Our owning activity is responsible for - * providing an AndroidFxAccount to our refresh method in its onResume method. - */ - @Override - public void onResume() { - super.onResume(); - } - - @Override - public boolean onPreferenceClick(Preference preference) { - if (preference == profilePreference) { - ActivityUtils.openURLInFennec(getActivity().getApplicationContext(), "about:accounts?action=avatar"); - return true; - } - - if (preference == manageAccountPreference) { - ActivityUtils.openURLInFennec(getActivity().getApplicationContext(), "about:accounts?action=manage"); - return true; - } - - if (preference == removeAccountPreference) { - FxAccountStatusActivity.maybeDeleteAndroidAccount(getActivity(), fxAccount.getAndroidAccount(), null); - return true; - } - - if (preference == needsPasswordPreference) { - final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_UPDATE_CREDENTIALS); - intent.putExtra(FxAccountWebFlowActivity.EXTRA_ENDPOINT, FxAccountConstants.ENDPOINT_PREFERENCES); - // Per http://stackoverflow.com/a/8992365, this triggers a known bug with - // the soft keyboard not being shown for the started activity. Why, Android, why? - intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); - startActivity(intent); - - return true; - } - - if (preference == needsFinishMigratingPreference) { - final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_FINISH_MIGRATING); - intent.putExtra(FxAccountWebFlowActivity.EXTRA_ENDPOINT, FxAccountConstants.ENDPOINT_PREFERENCES); - // Per http://stackoverflow.com/a/8992365, this triggers a known bug with - // the soft keyboard not being shown for the started activity. Why, Android, why? - intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); - startActivity(intent); - - return true; - } - - if (preference == needsVerificationPreference) { - final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_CONFIRM_ACCOUNT); - // Per http://stackoverflow.com/a/8992365, this triggers a known bug with - // the soft keyboard not being shown for the started activity. Why, Android, why? - intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); - intent.putExtra(FxAccountWebFlowActivity.EXTRA_ENDPOINT, FxAccountConstants.ENDPOINT_PREFERENCES); - startActivity(intent); - - return true; - } - - if (preference == bookmarksPreference || - preference == historyPreference || - preference == passwordsPreference || - preference == tabsPreference) { - saveEngineSelections(); - return true; - } - - if (preference == morePreference) { - getActivity().openOptionsMenu(); - return true; - } - - if (preference == syncNowPreference) { - if (fxAccount != null) { - fxAccount.requestImmediateSync(null, null); - } - return true; - } - - if (TextUtils.equals("linktos", preference.getKey())) { - ActivityUtils.openURLInFennec(getActivity().getApplicationContext(), getResources().getString(R.string.fxaccount_link_tos)); - return true; - } - - if (TextUtils.equals("linkprivacy", preference.getKey())) { - ActivityUtils.openURLInFennec(getActivity().getApplicationContext(), getResources().getString(R.string.fxaccount_link_pn)); - return true; - } - - return false; - } - - protected void setCheckboxesEnabled(boolean enabled) { - bookmarksPreference.setEnabled(enabled); - historyPreference.setEnabled(enabled); - tabsPreference.setEnabled(enabled); - passwordsPreference.setEnabled(enabled); - // Since we can't sync, we can't update our remote client record. - deviceNamePreference.setEnabled(enabled); - syncNowPreference.setEnabled(enabled); - } - - /** - * Show at most one error preference, hiding all others. - * - * @param errorPreferenceToShow - * single error preference to show; if null, hide all error preferences - */ - protected void showOnlyOneErrorPreference(Preference errorPreferenceToShow) { - final Preference[] errorPreferences = new Preference[] { - this.needsPasswordPreference, - this.needsUpgradePreference, - this.needsVerificationPreference, - this.needsMasterSyncAutomaticallyEnabledPreference, - this.needsFinishMigratingPreference, - }; - for (Preference errorPreference : errorPreferences) { - final boolean currentlyShown = null != findPreference(errorPreference.getKey()); - final boolean shouldBeShown = errorPreference == errorPreferenceToShow; - if (currentlyShown == shouldBeShown) { - continue; - } - if (shouldBeShown) { - syncCategory.addPreference(errorPreference); - } else { - syncCategory.removePreference(errorPreference); - } - } - } - - protected void showNeedsPassword() { - syncCategory.setTitle(R.string.fxaccount_status_sync); - showOnlyOneErrorPreference(needsPasswordPreference); - setCheckboxesEnabled(false); - } - - protected void showNeedsUpgrade() { - syncCategory.setTitle(R.string.fxaccount_status_sync); - showOnlyOneErrorPreference(needsUpgradePreference); - setCheckboxesEnabled(false); - } - - protected void showNeedsVerification() { - syncCategory.setTitle(R.string.fxaccount_status_sync); - showOnlyOneErrorPreference(needsVerificationPreference); - setCheckboxesEnabled(false); - } - - protected void showNeedsMasterSyncAutomaticallyEnabled() { - syncCategory.setTitle(R.string.fxaccount_status_sync); - needsMasterSyncAutomaticallyEnabledPreference.setTitle(AppConstants.Versions.preLollipop ? - R.string.fxaccount_status_needs_master_sync_automatically_enabled : - R.string.fxaccount_status_needs_master_sync_automatically_enabled_v21); - showOnlyOneErrorPreference(needsMasterSyncAutomaticallyEnabledPreference); - setCheckboxesEnabled(false); - } - - protected void showNeedsFinishMigrating() { - syncCategory.setTitle(R.string.fxaccount_status_sync); - showOnlyOneErrorPreference(needsFinishMigratingPreference); - setCheckboxesEnabled(false); - } - - protected void showConnected() { - syncCategory.setTitle(R.string.fxaccount_status_sync_enabled); - showOnlyOneErrorPreference(null); - setCheckboxesEnabled(true); - } - - protected class InnerSyncStatusDelegate implements SyncStatusListener { - protected final Runnable refreshRunnable = new Runnable() { - @Override - public void run() { - refresh(); - } - }; - - @Override - public Context getContext() { - return FxAccountStatusFragment.this.getActivity(); - } - - @Override - public Account getAccount() { - return fxAccount.getAndroidAccount(); - } - - @Override - public void onSyncStarted() { - if (fxAccount == null) { - return; - } - Logger.info(LOG_TAG, "Got sync started message; refreshing."); - getActivity().runOnUiThread(refreshRunnable); - } - - @Override - public void onSyncFinished() { - if (fxAccount == null) { - return; - } - Logger.info(LOG_TAG, "Got sync finished message; refreshing."); - getActivity().runOnUiThread(refreshRunnable); - } - } - - /** - * Notify the fragment that a new AndroidFxAccount instance is current. - * <p> - * <b>Important:</b> call this method on the UI thread! - * <p> - * In future, this might be a Loader. - * - * @param fxAccount new instance. - */ - public void refresh(AndroidFxAccount fxAccount) { - if (fxAccount == null) { - throw new IllegalArgumentException("fxAccount must not be null"); - } - this.fxAccount = fxAccount; - try { - this.clientsDataDelegate = new SharedPreferencesClientsDataDelegate(fxAccount.getSyncPrefs(), getActivity().getApplicationContext()); - } catch (Exception e) { - Logger.error(LOG_TAG, "Got exception fetching Sync prefs associated to Firefox Account; aborting.", e); - // Something is terribly wrong; best to get a stack trace rather than - // continue with a null clients delegate. - throw new IllegalStateException(e); - } - - handler = new Handler(); // Attached to current (assumed to be UI) thread. - - // Runnable is not specific to one Firefox Account. This runnable will keep - // a reference to this fragment alive, but we expect posted runnables to be - // serviced very quickly, so this is not an issue. - requestSyncRunnable = new RequestSyncRunnable(); - lastSyncedTimeUpdateRunnable = new LastSyncTimeUpdateRunnable(); - - // We would very much like register these status observers in bookended - // onResume/onPause calls, but because the Fragment gets onResume during the - // Activity's super.onResume, it hasn't yet been told its Firefox Account. - // So we register the observer here (and remove it in onPause), and open - // ourselves to the possibility that we don't have properly paired - // register/unregister calls. - FxAccountSyncStatusHelper.getInstance().startObserving(syncStatusDelegate); - - // Register a local broadcast receiver to get profile cached notification. - final IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(FxAccountConstants.ACCOUNT_PROFILE_JSON_UPDATED_ACTION); - accountProfileInformationReceiver = new FxAccountProfileInformationReceiver(); - LocalBroadcastManager.getInstance(getActivity()).registerReceiver(accountProfileInformationReceiver, intentFilter); - - // profilePreference is set during onCreate, so it's definitely not null here. - final float cornerRadius = getResources().getDimension(R.dimen.fxaccount_profile_image_width) / 2; - profileAvatarTarget = new PicassoPreferenceIconTarget(getResources(), profilePreference, cornerRadius); - - refresh(); - } - - @Override - public void onPause() { - super.onPause(); - FxAccountSyncStatusHelper.getInstance().stopObserving(syncStatusDelegate); - - // Focus lost, remove scheduled update if any. - if (lastSyncedTimeUpdateRunnable != null) { - handler.removeCallbacks(lastSyncedTimeUpdateRunnable); - } - - // Focus lost, unregister broadcast receiver. - if (accountProfileInformationReceiver != null) { - LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(accountProfileInformationReceiver); - } - - if (profileAvatarTarget != null) { - Picasso.with(getActivity()).cancelRequest(profileAvatarTarget); - profileAvatarTarget = null; - } - } - - protected void hardRefresh() { - // This is the only way to guarantee that the EditText dialogs created by - // EditTextPreferences are re-created. This works around the issue described - // at http://androiddev.orkitra.com/?p=112079. - final PreferenceScreen statusScreen = (PreferenceScreen) ensureFindPreference("status_screen"); - statusScreen.removeAll(); - addPreferences(); - - refresh(); - } - - protected void refresh() { - // refresh is called from our onResume, which can happen before the owning - // Activity tells us about an account (via our public - // refresh(AndroidFxAccount) method). - if (fxAccount == null) { - throw new IllegalArgumentException("fxAccount must not be null"); - } - - updateProfileInformation(); - updateAuthServerPreference(); - updateSyncServerPreference(); - - try { - // There are error states determined by Android, not the login state - // machine, and we have a chance to present these states here. We handle - // them specially, since we can't surface these states as part of syncing, - // because they generally stop syncs from happening regularly. Right now - // there are no such states. - - // Interrogate the Firefox Account's state. - State state = fxAccount.getState(); - switch (state.getNeededAction()) { - case NeedsUpgrade: - showNeedsUpgrade(); - break; - case NeedsPassword: - showNeedsPassword(); - break; - case NeedsVerification: - showNeedsVerification(); - break; - case NeedsFinishMigrating: - showNeedsFinishMigrating(); - break; - case None: - showConnected(); - break; - } - - // We check for the master setting last, since it is not strictly - // necessary for the user to address this error state: it's really a - // warning state. We surface it for the user's convenience, and to prevent - // confused folks wondering why Sync is not working at all. - final boolean masterSyncAutomatically = ContentResolver.getMasterSyncAutomatically(); - if (!masterSyncAutomatically) { - showNeedsMasterSyncAutomaticallyEnabled(); - return; - } - } finally { - // No matter our state, we should update the checkboxes. - updateSelectedEngines(); - } - - final String clientName = clientsDataDelegate.getClientName(); - deviceNamePreference.setSummary(clientName); - deviceNamePreference.setText(clientName); - - updateSyncNowPreference(); - } - - // This is a helper function similar to TabsAccessor.getLastSyncedString() to calculate relative "Last synced" time span. - private String getLastSyncedString(final long startTime) { - if (new Date(startTime).before(EARLIEST_VALID_SYNCED_DATE)) { - return getActivity().getString(R.string.fxaccount_status_never_synced); - } - final CharSequence relativeTimeSpanString = DateUtils.getRelativeTimeSpanString(startTime); - return getActivity().getResources().getString(R.string.fxaccount_status_last_synced, relativeTimeSpanString); - } - - protected void updateSyncNowPreference() { - final boolean currentlySyncing = fxAccount.isCurrentlySyncing(); - syncNowPreference.setEnabled(!currentlySyncing); - if (currentlySyncing) { - syncNowPreference.setTitle(R.string.fxaccount_status_syncing); - } else { - syncNowPreference.setTitle(R.string.fxaccount_status_sync_now); - } - scheduleAndUpdateLastSyncedTime(); - } - - private void updateProfileInformation() { - - final ExtendedJSONObject profileJSON = fxAccount.getProfileJSON(); - if (profileJSON == null) { - // Update the profile title with email as the fallback. - // Profile icon by default use the default avatar as the fallback. - profilePreference.setTitle(fxAccount.getEmail()); - return; - } - - updateProfileInformation(profileJSON); - } - - /** - * Update profile information from json on UI thread. - * - * @param profileJSON json fetched from server. - */ - protected void updateProfileInformation(final ExtendedJSONObject profileJSON) { - // View changes must always be done on UI thread. - ThreadUtils.assertOnUiThread(); - - FxAccountUtils.pii(LOG_TAG, "Profile JSON is: " + profileJSON.toJSONString()); - - final String userName = profileJSON.getString(FxAccountConstants.KEY_PROFILE_JSON_USERNAME); - // Update the profile username and email if available. - if (!TextUtils.isEmpty(userName)) { - profilePreference.setTitle(userName); - profilePreference.setSummary(fxAccount.getEmail()); - } else { - profilePreference.setTitle(fxAccount.getEmail()); - } - - // Avatar URI empty, skip profile image fetch. - final String avatarURI = profileJSON.getString(FxAccountConstants.KEY_PROFILE_JSON_AVATAR); - if (TextUtils.isEmpty(avatarURI)) { - Logger.info(LOG_TAG, "AvatarURI is empty, skipping profile image fetch."); - return; - } - - // Using noPlaceholder would avoid a pop of the default image, but it's not available in the version of Picasso - // we ship in the tree. - Picasso - .with(getActivity()) - .load(avatarURI) - .centerInside() - .resizeDimen(R.dimen.fxaccount_profile_image_width, R.dimen.fxaccount_profile_image_height) - .placeholder(R.drawable.sync_avatar_default) - .error(R.drawable.sync_avatar_default) - .into(profileAvatarTarget); - } - - private void scheduleAndUpdateLastSyncedTime() { - final String lastSynced = getLastSyncedString(fxAccount.getLastSyncedTimestamp()); - syncNowPreference.setSummary(lastSynced); - handler.postDelayed(lastSyncedTimeUpdateRunnable, LAST_SYNCED_TIME_UPDATE_INTERVAL_IN_MILLISECONDS); - } - - protected void updateAuthServerPreference() { - final String authServer = fxAccount.getAccountServerURI(); - final boolean shouldBeShown = ALWAYS_SHOW_AUTH_SERVER || !FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT.equals(authServer); - final boolean currentlyShown = null != findPreference(authServerPreference.getKey()); - if (currentlyShown != shouldBeShown) { - if (shouldBeShown) { - accountCategory.addPreference(authServerPreference); - } else { - accountCategory.removePreference(authServerPreference); - } - } - // Always set the summary, because on first run, the preference is visible, - // and the above block will be skipped if there is a custom value. - authServerPreference.setSummary(authServer); - } - - protected void updateSyncServerPreference() { - final String syncServer = fxAccount.getTokenServerURI(); - final boolean shouldBeShown = ALWAYS_SHOW_SYNC_SERVER || !FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT.equals(syncServer); - final boolean currentlyShown = null != findPreference(syncServerPreference.getKey()); - if (currentlyShown != shouldBeShown) { - if (shouldBeShown) { - syncCategory.addPreference(syncServerPreference); - } else { - syncCategory.removePreference(syncServerPreference); - } - } - // Always set the summary, because on first run, the preference is visible, - // and the above block will be skipped if there is a custom value. - syncServerPreference.setSummary(syncServer); - } - - /** - * Query shared prefs for the current engine state, and update the UI - * accordingly. - * <p> - * In future, we might want this to be on a background thread, or implemented - * as a Loader. - */ - protected void updateSelectedEngines() { - try { - SharedPreferences syncPrefs = fxAccount.getSyncPrefs(); - Map<String, Boolean> engines = SyncConfiguration.getUserSelectedEngines(syncPrefs); - if (engines != null) { - bookmarksPreference.setChecked(engines.containsKey("bookmarks") && engines.get("bookmarks")); - historyPreference.setChecked(engines.containsKey("history") && engines.get("history")); - passwordsPreference.setChecked(engines.containsKey("passwords") && engines.get("passwords")); - tabsPreference.setChecked(engines.containsKey("tabs") && engines.get("tabs")); - return; - } - - // We don't have user specified preferences. Perhaps we have seen a meta/global? - Set<String> enabledNames = SyncConfiguration.getEnabledEngineNames(syncPrefs); - if (enabledNames != null) { - bookmarksPreference.setChecked(enabledNames.contains("bookmarks")); - historyPreference.setChecked(enabledNames.contains("history")); - passwordsPreference.setChecked(enabledNames.contains("passwords")); - tabsPreference.setChecked(enabledNames.contains("tabs")); - return; - } - - // Okay, we don't have userSelectedEngines or enabledEngines. That means - // the user hasn't specified to begin with, we haven't specified here, and - // we haven't already seen, Sync engines. We don't know our state, so - // let's check everything (the default) and disable everything. - bookmarksPreference.setChecked(true); - historyPreference.setChecked(true); - passwordsPreference.setChecked(true); - tabsPreference.setChecked(true); - setCheckboxesEnabled(false); - } catch (Exception e) { - Logger.warn(LOG_TAG, "Got exception getting engines to select; ignoring.", e); - return; - } - } - - /** - * Persist engine selections to local shared preferences, and request a sync - * to persist selections to remote storage. - */ - protected void saveEngineSelections() { - final Map<String, Boolean> engineSelections = new HashMap<String, Boolean>(); - engineSelections.put("bookmarks", bookmarksPreference.isChecked()); - engineSelections.put("history", historyPreference.isChecked()); - engineSelections.put("passwords", passwordsPreference.isChecked()); - engineSelections.put("tabs", tabsPreference.isChecked()); - - // No GlobalSession.config, so store directly to shared prefs. We do this on - // a background thread to avoid IO on the main thread and strict mode - // warnings. - new Thread(new PersistEngineSelectionsRunnable(engineSelections)).start(); - } - - protected void requestDelayedSync() { - Logger.info(LOG_TAG, "Posting a delayed request for a sync sometime soon."); - handler.removeCallbacks(requestSyncRunnable); - handler.postDelayed(requestSyncRunnable, DELAY_IN_MILLISECONDS_BEFORE_REQUESTING_SYNC); - } - - /** - * Remove all traces of debug buttons. By default, no debug buttons are shown. - */ - protected void removeDebugButtons() { - final PreferenceScreen statusScreen = (PreferenceScreen) ensureFindPreference("status_screen"); - final PreferenceCategory debugCategory = (PreferenceCategory) ensureFindPreference("debug_category"); - statusScreen.removePreference(debugCategory); - } - - /** - * A Runnable that persists engine selections to shared prefs, and then - * requests a delayed sync. - * <p> - * References the member <code>fxAccount</code> and is specific to the Android - * account associated to that account. - */ - protected class PersistEngineSelectionsRunnable implements Runnable { - private final Map<String, Boolean> engineSelections; - - protected PersistEngineSelectionsRunnable(Map<String, Boolean> engineSelections) { - this.engineSelections = engineSelections; - } - - @Override - public void run() { - try { - // Name shadowing -- do you like it, or do you love it? - AndroidFxAccount fxAccount = FxAccountStatusFragment.this.fxAccount; - if (fxAccount == null) { - return; - } - Logger.info(LOG_TAG, "Persisting engine selections: " + engineSelections.toString()); - SyncConfiguration.storeSelectedEnginesToPrefs(fxAccount.getSyncPrefs(), engineSelections); - requestDelayedSync(); - } catch (Exception e) { - Logger.warn(LOG_TAG, "Got exception persisting selected engines; ignoring.", e); - return; - } - } - } - - /** - * A Runnable that requests a sync. - * <p> - * References the member <code>fxAccount</code>, but is not specific to the - * Android account associated to that account. - */ - protected class RequestSyncRunnable implements Runnable { - @Override - public void run() { - // Name shadowing -- do you like it, or do you love it? - AndroidFxAccount fxAccount = FxAccountStatusFragment.this.fxAccount; - if (fxAccount == null) { - return; - } - Logger.info(LOG_TAG, "Requesting a sync sometime soon."); - fxAccount.requestEventualSync(null, null); - } - } - - /** - * The Runnable that schedules a future update and updates the last synced time. - */ - protected class LastSyncTimeUpdateRunnable implements Runnable { - @Override - public void run() { - scheduleAndUpdateLastSyncedTime(); - } - } - - /** - * Broadcast receiver to receive updates for the cached profile action. - */ - public class FxAccountProfileInformationReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - if (!intent.getAction().equals(FxAccountConstants.ACCOUNT_PROFILE_JSON_UPDATED_ACTION)) { - return; - } - - Logger.info(LOG_TAG, "Profile avatar cache update action broadcast received."); - // Update the UI from cached profile json on the main thread. - getActivity().runOnUiThread(new Runnable() { - @Override - public void run() { - updateProfileInformation(); - } - }); - } - } - - /** - * A separate listener to separate debug logic from main code paths. - */ - protected class DebugPreferenceClickListener implements OnPreferenceClickListener { - @Override - public boolean onPreferenceClick(Preference preference) { - final String key = preference.getKey(); - if ("debug_refresh".equals(key)) { - Logger.info(LOG_TAG, "Refreshing."); - refresh(); - } else if ("debug_dump".equals(key)) { - fxAccount.dump(); - } else if ("debug_force_sync".equals(key)) { - Logger.info(LOG_TAG, "Force syncing."); - fxAccount.requestImmediateSync(null, null); - // No sense refreshing, since the sync will complete in the future. - } else if ("debug_forget_certificate".equals(key)) { - State state = fxAccount.getState(); - try { - Married married = (Married) state; - Logger.info(LOG_TAG, "Moving to Cohabiting state: Forgetting certificate."); - fxAccount.setState(married.makeCohabitingState()); - refresh(); - } catch (ClassCastException e) { - Logger.info(LOG_TAG, "Not in Married state; can't forget certificate."); - // Ignore. - } - } else if ("debug_invalidate_certificate".equals(key)) { - State state = fxAccount.getState(); - try { - Married married = (Married) state; - Logger.info(LOG_TAG, "Invalidating certificate."); - fxAccount.setState(married.makeCohabitingState().withCertificate("INVALID CERTIFICATE")); - refresh(); - } catch (ClassCastException e) { - Logger.info(LOG_TAG, "Not in Married state; can't invalidate certificate."); - // Ignore. - } - } else if ("debug_require_password".equals(key)) { - Logger.info(LOG_TAG, "Moving to Separated state: Forgetting password."); - State state = fxAccount.getState(); - fxAccount.setState(state.makeSeparatedState()); - refresh(); - } else if ("debug_require_upgrade".equals(key)) { - Logger.info(LOG_TAG, "Moving to Doghouse state: Requiring upgrade."); - State state = fxAccount.getState(); - fxAccount.setState(state.makeDoghouseState()); - refresh(); - } else if ("debug_migrated_from_sync11".equals(key)) { - Logger.info(LOG_TAG, "Moving to MigratedFromSync11 state: Requiring password."); - State state = fxAccount.getState(); - fxAccount.setState(state.makeMigratedFromSync11State(null)); - refresh(); - } else if ("debug_make_account_stage".equals(key)) { - Logger.info(LOG_TAG, "Moving Account endpoints, in place, to stage. Deleting Sync and RL prefs and requiring password."); - fxAccount.unsafeTransitionToStageEndpoints(); - refresh(); - } else if ("debug_make_account_default".equals(key)) { - Logger.info(LOG_TAG, "Moving Account endpoints, in place, to default (production). Deleting Sync and RL prefs and requiring password."); - fxAccount.unsafeTransitionToDefaultEndpoints(); - refresh(); - } else { - return false; - } - return true; - } - } - - /** - * Iterate through debug buttons, adding a special debug preference click - * listener to each of them. - */ - protected void connectDebugButtons() { - // Separate listener to really separate debug logic from main code paths. - final OnPreferenceClickListener listener = new DebugPreferenceClickListener(); - - // We don't want to use Android resource strings for debug UI, so we just - // use the keys throughout. - final PreferenceCategory debugCategory = (PreferenceCategory) ensureFindPreference("debug_category"); - debugCategory.setTitle(debugCategory.getKey()); - - for (int i = 0; i < debugCategory.getPreferenceCount(); i++) { - final Preference button = debugCategory.getPreference(i); - button.setTitle(button.getKey()); // Not very friendly, but this is for debugging only! - button.setOnPreferenceClickListener(listener); - } - } - - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - if (preference == deviceNamePreference) { - String newClientName = (String) newValue; - if (TextUtils.isEmpty(newClientName)) { - newClientName = clientsDataDelegate.getDefaultClientName(); - } - final long now = System.currentTimeMillis(); - clientsDataDelegate.setClientName(newClientName, now); - // Force sync the client record, we want the user to see the device name change immediately - // on the FxA Device Manager if possible ( = we are online) to avoid confusion - // ("I changed my Android's device name but I don't see it on my computer"). - fxAccount.requestImmediateSync(STAGES_TO_SYNC_ON_DEVICE_NAME_CHANGE, null); - hardRefresh(); // Updates the value displayed to the user, among other things. - return true; - } - - // For everything else, accept the change. - return true; - } -} |