mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-06-06 16:38:55 +00:00
849 lines
33 KiB
Java
849 lines
33 KiB
Java
/* 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.goanna.fxa.activities;
|
|
|
|
import java.util.Calendar;
|
|
import java.util.Date;
|
|
import java.util.GregorianCalendar;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
|
|
import org.mozilla.goanna.AppConstants;
|
|
import org.mozilla.goanna.R;
|
|
import org.mozilla.goanna.background.common.log.Logger;
|
|
import org.mozilla.goanna.background.fxa.FxAccountUtils;
|
|
import org.mozilla.goanna.background.preferences.PreferenceFragment;
|
|
import org.mozilla.goanna.fxa.FirefoxAccounts;
|
|
import org.mozilla.goanna.fxa.FxAccountConstants;
|
|
import org.mozilla.goanna.fxa.authenticator.AndroidFxAccount;
|
|
import org.mozilla.goanna.fxa.login.Married;
|
|
import org.mozilla.goanna.fxa.login.State;
|
|
import org.mozilla.goanna.fxa.sync.FxAccountSyncStatusHelper;
|
|
import org.mozilla.goanna.fxa.tasks.FxAccountCodeResender;
|
|
import org.mozilla.goanna.sync.ExtendedJSONObject;
|
|
import org.mozilla.goanna.sync.SharedPreferencesClientsDataDelegate;
|
|
import org.mozilla.goanna.sync.SyncConfiguration;
|
|
import org.mozilla.goanna.util.HardwareUtils;
|
|
|
|
import android.accounts.Account;
|
|
import android.content.ContentResolver;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
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.text.TextUtils;
|
|
import android.text.format.DateUtils;
|
|
import android.widget.Toast;
|
|
|
|
|
|
/**
|
|
* 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;
|
|
|
|
// 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;
|
|
|
|
// If the user clicks the email field this many times, the debug / personal
|
|
// information logging setting will toggle. The setting is not permanent: it
|
|
// lasts until this process is killed. We don't want to dump PII to the log
|
|
// for a long time!
|
|
private final int NUMBER_OF_CLICKS_TO_TOGGLE_DEBUG =
|
|
// !defined(MOZILLA_OFFICIAL) || defined(NIGHTLY_BUILD) || defined(MOZ_DEBUG)
|
|
(!AppConstants.MOZILLA_OFFICIAL || AppConstants.NIGHTLY_BUILD || AppConstants.DEBUG_BUILD) ? 5 : -1 /* infinite */;
|
|
private int debugClickCount = 0;
|
|
|
|
protected PreferenceCategory accountCategory;
|
|
protected Preference emailPreference;
|
|
protected Preference authServerPreference;
|
|
|
|
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;
|
|
|
|
protected final InnerSyncStatusDelegate syncStatusDelegate = new InnerSyncStatusDelegate();
|
|
|
|
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");
|
|
emailPreference = ensureFindPreference("email");
|
|
authServerPreference = ensureFindPreference("auth_server");
|
|
|
|
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;
|
|
}
|
|
|
|
emailPreference.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);
|
|
|
|
if (HardwareUtils.hasMenuButton()) {
|
|
syncCategory.removePreference(morePreference);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 == emailPreference) {
|
|
debugClickCount += 1;
|
|
if (NUMBER_OF_CLICKS_TO_TOGGLE_DEBUG > 0 && debugClickCount >= NUMBER_OF_CLICKS_TO_TOGGLE_DEBUG) {
|
|
debugClickCount = 0;
|
|
FxAccountUtils.LOG_PERSONAL_INFORMATION = !FxAccountUtils.LOG_PERSONAL_INFORMATION;
|
|
Toast.makeText(getActivity(), "Toggled logging Firefox Account personal information!", Toast.LENGTH_LONG).show();
|
|
hardRefresh(); // Display or hide debug options.
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (preference == needsPasswordPreference) {
|
|
Intent intent = new Intent(getActivity(), FxAccountUpdateCredentialsActivity.class);
|
|
final Bundle extras = getExtrasForAccount();
|
|
if (extras != null) {
|
|
intent.putExtras(extras);
|
|
}
|
|
// 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(getActivity(), FxAccountFinishMigratingActivity.class);
|
|
final Bundle extras = getExtrasForAccount();
|
|
if (extras != null) {
|
|
intent.putExtras(extras);
|
|
}
|
|
// 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) {
|
|
FxAccountCodeResender.resendCode(getActivity().getApplicationContext(), fxAccount);
|
|
|
|
Intent intent = new Intent(getActivity(), FxAccountConfirmAccountActivity.class);
|
|
// 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 == bookmarksPreference ||
|
|
preference == historyPreference ||
|
|
preference == passwordsPreference ||
|
|
preference == tabsPreference) {
|
|
saveEngineSelections();
|
|
return true;
|
|
}
|
|
|
|
if (preference == morePreference) {
|
|
getActivity().openOptionsMenu();
|
|
return true;
|
|
}
|
|
|
|
if (preference == syncNowPreference) {
|
|
if (fxAccount != null) {
|
|
FirefoxAccounts.requestSync(fxAccount.getAndroidAccount(), FirefoxAccounts.FORCE, null, null);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
protected Bundle getExtrasForAccount() {
|
|
final Bundle extras = new Bundle();
|
|
final ExtendedJSONObject o = new ExtendedJSONObject();
|
|
o.put(FxAccountAbstractSetupActivity.JSON_KEY_AUTH, fxAccount.getAccountServerURI());
|
|
final ExtendedJSONObject services = new ExtendedJSONObject();
|
|
services.put(FxAccountAbstractSetupActivity.JSON_KEY_SYNC, fxAccount.getTokenServerURI());
|
|
o.put(FxAccountAbstractSetupActivity.JSON_KEY_SERVICES, services);
|
|
extras.putString(FxAccountAbstractSetupActivity.EXTRA_EXTRAS, o.toJSONString());
|
|
return extras;
|
|
}
|
|
|
|
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 FirefoxAccounts.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);
|
|
|
|
refresh();
|
|
}
|
|
|
|
@Override
|
|
public void onPause() {
|
|
super.onPause();
|
|
FxAccountSyncStatusHelper.getInstance().stopObserving(syncStatusDelegate);
|
|
|
|
// Focus lost, remove scheduled update if any.
|
|
if (lastSyncedTimeUpdateRunnable != null) {
|
|
handler.removeCallbacks(lastSyncedTimeUpdateRunnable);
|
|
}
|
|
}
|
|
|
|
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");
|
|
}
|
|
|
|
emailPreference.setTitle(fxAccount.getEmail());
|
|
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 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.requestSync();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The Runnable that schedules a future update and updates the last synced time.
|
|
*/
|
|
protected class LastSyncTimeUpdateRunnable implements Runnable {
|
|
@Override
|
|
public void run() {
|
|
scheduleAndUpdateLastSyncedTime();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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.requestSync(FirefoxAccounts.FORCE);
|
|
// 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);
|
|
requestDelayedSync(); // Try to update our remote client record.
|
|
hardRefresh(); // Updates the value displayed to the user, among other things.
|
|
return true;
|
|
}
|
|
|
|
// For everything else, accept the change.
|
|
return true;
|
|
}
|
|
}
|