Bug 1232447 : Update Sync now preference setting item height r?nalexander
* SyncPreference summary, title and icon update handled from Fragment instead of the view.
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferenceFragment.java
+++ b/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferenceFragment.java
@@ -1,54 +1,82 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* 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.preferences;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
import java.util.Locale;
+import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.BrowserLocaleManager;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.LocaleManager;
import org.mozilla.gecko.PrefsHelper;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.TelemetryContract.Method;
import org.mozilla.gecko.fxa.AccountLoaderNative;
+import org.mozilla.gecko.fxa.FxAccountConstants;
+import org.mozilla.gecko.fxa.activities.PicassoPreferenceIconTarget;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
+import org.mozilla.gecko.sync.ExtendedJSONObject;
+import org.mozilla.gecko.util.ThreadUtils;
import android.accounts.Account;
import android.app.ActionBar;
import android.app.Activity;
import android.app.LoaderManager;
import android.content.Context;
import android.content.Loader;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
+import android.os.Handler;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
+import com.squareup.picasso.Picasso;
+
/* A simple implementation of PreferenceFragment for large screen devices
* This will strip category headers (so that they aren't shown to the user twice)
* as well as initializing Gecko prefs when a fragment is shown.
*/
public class GeckoPreferenceFragment extends PreferenceFragment {
- public static final int ACCOUNT_LOADER_ID = 1;
+ /**
+ * 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;
+ private static final long LAST_SYNCED_TIME_UPDATE_INTERVAL_IN_MILLISECONDS = 60 * 1000;
+ private static final int ACCOUNT_LOADER_ID = 1;
+
private AccountLoaderCallbacks accountLoaderCallbacks;
private SyncPreference syncPreference;
+ private PicassoPreferenceIconTarget profileAvatarTarget;
+ private Handler handler;
+ private Runnable lastSyncedTimeUpdateRunnable;
+
+ static {
+ final Calendar c = GregorianCalendar.getInstance();
+ c.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
+ EARLIEST_VALID_SYNCED_DATE = c.getTime();
+ }
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Log.d(LOGTAG, "onConfigurationChanged: " + newConfig.locale);
final Activity context = getActivity();
@@ -84,16 +112,21 @@ public class GeckoPreferenceFragment ext
}
addPreferencesFromResource(res);
PreferenceScreen screen = getPreferenceScreen();
setPreferenceScreen(screen);
mPrefsRequestId = ((GeckoPreferences)getActivity()).setupPreferences(screen);
syncPreference = (SyncPreference) findPreference(GeckoPreferences.PREFS_SYNC);
+ if (syncPreference != null) {
+ handler = new Handler();
+ final float cornerRadius = getActivity().getResources().getDimension(R.dimen.fxaccount_profile_image_width) / 2;
+ profileAvatarTarget = new PicassoPreferenceIconTarget(getActivity().getResources(), syncPreference, cornerRadius);
+ }
}
/**
* Return the title to use for this preference fragment.
*
* We only return titles for the preference screens that are
* launched directly, and thus might need to be redisplayed.
*
@@ -185,16 +218,27 @@ public class GeckoPreferenceFragment ext
// super.onResume that you wouldn't do in onCreate.
applyLocale(Locale.getDefault());
super.onResume();
// Force reload as the account may have been deleted while the app was in background.
getLoaderManager().restartLoader(ACCOUNT_LOADER_ID, null, accountLoaderCallbacks);
}
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ // Focus lost, remove scheduled update if any.
+ if (lastSyncedTimeUpdateRunnable != null) {
+ handler.removeCallbacks(lastSyncedTimeUpdateRunnable);
+ lastSyncedTimeUpdateRunnable = null;
+ }
+ }
+
private void applyLocale(final Locale currentLocale) {
final Context context = getActivity().getApplicationContext();
BrowserLocaleManager.getInstance().updateConfiguration(context, currentLocale);
if (!currentLocale.equals(lastLocale)) {
// Locales differ. Let's redisplay.
Log.d(LOGTAG, "Locale changed: " + currentLocale);
@@ -251,38 +295,115 @@ public class GeckoPreferenceFragment ext
if (mPrefsRequestId > 0) {
PrefsHelper.removeObserver(mPrefsRequestId);
}
final int res = getResource();
if (res == R.xml.preferences) {
Telemetry.stopUISession(TelemetryContract.Session.SETTINGS);
}
+
+ if (syncPreference != null) {
+ Picasso.with(getActivity()).cancelRequest(profileAvatarTarget);
+ }
+ }
+
+
+ private void update(final AndroidFxAccount fxAccount) {
+ if (syncPreference == null) {
+ return;
+ }
+
+ if (fxAccount == null) {
+ ThreadUtils.postToUiThread(new Runnable() {
+ @Override
+ public void run() {
+ syncPreference.setTitle(R.string.pref_sync);
+ syncPreference.setSummary(R.string.pref_sync_summary);
+ if (AppConstants.Versions.feature11Plus) {
+ // Cancel any pending task.
+ Picasso.with(getActivity()).cancelRequest(profileAvatarTarget);
+ // Clear previously set icon.
+ syncPreference.setIcon(R.drawable.sync_avatar_default);
+ }
+
+ }
+ });
+ return;
+ }
+
+ // Update title and last synced time from account information.
+ final ExtendedJSONObject profileJSON = fxAccount.getProfileJSON();
+ final String userName = profileJSON != null ? profileJSON.getString(FxAccountConstants.KEY_PROFILE_JSON_USERNAME) : null;
+ ThreadUtils.postToUiThread(new Runnable() {
+ @Override
+ public void run() {
+ syncPreference.setTitle(TextUtils.isEmpty(userName) ? fxAccount.getEmail() : userName);
+ syncPreference.setSummary(getLastSyncedString(fxAccount.getLastSyncedTimestamp()));
+ }
+ });
+
+ // Schedule a runnable to update last sync time.
+ lastSyncedTimeUpdateRunnable = new LastSyncTimeUpdateRunnable(fxAccount);
+ handler.postDelayed(lastSyncedTimeUpdateRunnable, LAST_SYNCED_TIME_UPDATE_INTERVAL_IN_MILLISECONDS);
+
+ // Updating icons from Java is not supported prior to API 11.
+ if (!AppConstants.Versions.feature11Plus) {
+ return;
+ }
+
+ // Avatar URI empty, return early.
+ final String avatarURI = profileJSON != null ? profileJSON.getString(FxAccountConstants.KEY_PROFILE_JSON_AVATAR) : null;
+ if (TextUtils.isEmpty(avatarURI)) {
+ return;
+ }
+
+ 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);
+ }
+
+ // 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);
+ }
+
+ /**
+ * The Runnable that schedules a future update and updates the last synced time.
+ */
+ protected class LastSyncTimeUpdateRunnable implements Runnable {
+ private final AndroidFxAccount fxAccount;
+ LastSyncTimeUpdateRunnable(AndroidFxAccount fxAccount) {
+ this.fxAccount = fxAccount;
+ }
+
+ @Override
+ public void run() {
+ syncPreference.setSummary(getLastSyncedString(fxAccount.getLastSyncedTimestamp()));
+ handler.postDelayed(lastSyncedTimeUpdateRunnable, LAST_SYNCED_TIME_UPDATE_INTERVAL_IN_MILLISECONDS);
+ }
}
private class AccountLoaderCallbacks implements LoaderManager.LoaderCallbacks<Account> {
@Override
public Loader<Account> onCreateLoader(int id, Bundle args) {
return new AccountLoaderNative(getActivity());
}
@Override
public void onLoadFinished(Loader<Account> loader, Account account) {
- if (syncPreference == null) {
- return;
- }
-
- if (account == null) {
- syncPreference.update(null);
- return;
- }
-
- syncPreference.update(new AndroidFxAccount(getActivity(), account));
+ update(account == null ? null : new AndroidFxAccount(getActivity(), account));
}
@Override
public void onLoaderReset(Loader<Account> loader) {
- if (syncPreference != null) {
- syncPreference.update(null);
- }
+ update(null);
}
}
}
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/SyncPreference.java
+++ b/mobile/android/base/java/org/mozilla/gecko/preferences/SyncPreference.java
@@ -1,53 +1,36 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* 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.preferences;
-import android.accounts.Account;
import android.content.Context;
import android.content.Intent;
import android.preference.Preference;
-import android.text.TextUtils;
import android.util.AttributeSet;
-import android.view.View;
-import android.widget.TextView;
-import com.squareup.picasso.Picasso;
-import com.squareup.picasso.Target;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.R;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.TelemetryContract.Method;
-import org.mozilla.gecko.fxa.FirefoxAccounts;
import org.mozilla.gecko.fxa.FxAccountConstants;
import org.mozilla.gecko.fxa.activities.FxAccountWebFlowActivity;
-import org.mozilla.gecko.fxa.activities.PicassoPreferenceIconTarget;
-import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.setup.SyncAccounts;
import org.mozilla.gecko.sync.setup.activities.SetupSyncActivity;
-import org.mozilla.gecko.util.ThreadUtils;
class SyncPreference extends Preference {
private static final boolean DEFAULT_TO_FXA = true;
private final Context mContext;
- private final Target profileAvatarTarget;
public SyncPreference(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
- final float cornerRadius = mContext.getResources().getDimension(R.dimen.fxaccount_profile_image_width) / 2;
- profileAvatarTarget = new PicassoPreferenceIconTarget(mContext.getResources(), this, cornerRadius);
}
private void openSync11Settings() {
// Show Sync setup if no accounts exist; otherwise, show account settings.
if (SyncAccounts.syncAccountsExist(mContext)) {
// We don't check for failure here. If you already have Sync set up,
// then there's nothing we can do.
SyncAccounts.openSyncSettings(mContext);
@@ -60,69 +43,16 @@ class SyncPreference extends Preference
private void launchFxASetup() {
final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_GET_STARTED);
intent.putExtra(FxAccountWebFlowActivity.EXTRA_ENDPOINT, FxAccountConstants.ENDPOINT_PREFERENCES);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
mContext.startActivity(intent);
}
- public void update(final AndroidFxAccount fxAccount) {
- if (fxAccount == null) {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- setTitle(R.string.pref_sync);
- setSummary(R.string.pref_sync_summary);
- if (AppConstants.Versions.feature11Plus) {
- // Cancel any pending task.
- Picasso.with(mContext).cancelRequest(profileAvatarTarget);
- // Clear previously set icon.
- setIcon(R.drawable.sync_avatar_default);
- }
-
- }
- });
- return;
- }
-
- // Update title from account email.
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- setTitle(fxAccount.getEmail());
- setSummary("");
- }
- });
-
- // Updating icons from Java is not supported prior to API 11.
- if (!AppConstants.Versions.feature11Plus) {
- return;
- }
-
- final ExtendedJSONObject profileJSON = fxAccount.getProfileJSON();
- if (profileJSON == null) {
- return;
- }
-
- // Avatar URI empty, return early.
- final String avatarURI = profileJSON.getString(FxAccountConstants.KEY_PROFILE_JSON_AVATAR);
- if (TextUtils.isEmpty(avatarURI)) {
- return;
- }
-
- Picasso.with(mContext)
- .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);
- }
-
@Override
protected void onClick() {
// If we're not defaulting to FxA, just do what we've always done.
if (!DEFAULT_TO_FXA) {
openSync11Settings();
return;
}