| Index: chrome/android/java/src/org/chromium/chrome/browser/invalidation/InvalidationController.java
|
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/invalidation/InvalidationController.java b/chrome/android/java/src/org/chromium/chrome/browser/invalidation/InvalidationController.java
|
| index 7c9f8ee37ad2bc56bc911cffdf5f667d8b3d1f49..b2bf677110ee60d165c946801f40dd6eb816af56 100644
|
| --- a/chrome/android/java/src/org/chromium/chrome/browser/invalidation/InvalidationController.java
|
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/invalidation/InvalidationController.java
|
| @@ -6,6 +6,8 @@ package org.chromium.chrome.browser.invalidation;
|
|
|
| import android.content.Context;
|
| import android.content.Intent;
|
| +import android.os.Handler;
|
| +import android.os.SystemClock;
|
|
|
| import org.chromium.base.ApplicationState;
|
| import org.chromium.base.ApplicationStatus;
|
| @@ -25,6 +27,98 @@ import java.util.HashSet;
|
| * client library used by Sync.
|
| */
|
| public class InvalidationController implements ApplicationStatus.ApplicationStateListener {
|
| + /**
|
| + * Timer which can be paused. When the timer is paused, the execution of its scheduled task is
|
| + * delayed till the timer is resumed.
|
| + */
|
| + private static class Timer {
|
| + private Handler mHandler;
|
| +
|
| + /**
|
| + * Runnable which is added to the handler's message queue.
|
| + */
|
| + private Runnable mHandlerRunnable;
|
| +
|
| + /**
|
| + * User provided task.
|
| + */
|
| + private Runnable mRunnable;
|
| +
|
| + /**
|
| + * Time at which the task is scheduled.
|
| + */
|
| + private long mScheduledTime;
|
| +
|
| + public Timer() {
|
| + mHandler = new Handler();
|
| + }
|
| +
|
| + /**
|
| + * Sets the task to run. The task will run after the delay or once {@link #resume()} is
|
| + * called, whichever occurs last. The previously scheduled task, if any, is cancelled.
|
| + * @param r Task to run.
|
| + * @param delayMs Delay in milliseconds after which to run the task.
|
| + */
|
| + public void setRunnable(Runnable r, long delayMs) {
|
| + cancel();
|
| + mRunnable = r;
|
| + mScheduledTime = SystemClock.elapsedRealtime() + delayMs;
|
| + }
|
| +
|
| + /**
|
| + * Blocks the task from being run.
|
| + */
|
| + public void pause() {
|
| + if (mHandlerRunnable == null) return;
|
| +
|
| + mHandler.removeCallbacks(mHandlerRunnable);
|
| + mHandlerRunnable = null;
|
| + }
|
| +
|
| + /**
|
| + * Unblocks the task from being run. If the task was scheduled for a time in the past, runs
|
| + * the task. Does nothing if no task is scheduled.
|
| + */
|
| + public void resume() {
|
| + if (mRunnable == null || mHandlerRunnable != null) return;
|
| +
|
| + long delayMs = Math.max(mScheduledTime - SystemClock.elapsedRealtime(), 0);
|
| + mHandlerRunnable = new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + Runnable r = mRunnable;
|
| + mRunnable = null;
|
| + mHandlerRunnable = null;
|
| + r.run();
|
| + }
|
| + };
|
| + mHandler.postDelayed(mHandlerRunnable, delayMs);
|
| + }
|
| +
|
| + /**
|
| + * Cancels the scheduled task, if any.
|
| + */
|
| + public void cancel() {
|
| + pause();
|
| + mRunnable = null;
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * The amount of time after the RecentTabsPage is opened to register for session sync
|
| + * invalidations. The delay is designed so that only users who linger on the RecentTabsPage
|
| + * register for session sync invalidations. How long users spend on the RecentTabsPage is
|
| + * measured by the NewTabPage.RecentTabsPage.TimeVisibleAndroid UMA metric.
|
| + */
|
| + private static final int REGISTER_FOR_SESSION_SYNC_INVALIDATIONS_DELAY_MS = 20000;
|
| +
|
| + /**
|
| + * The amount of time after the RecentTabsPage is closed to unregister for session sync
|
| + * invalidations. The delay is long to avoid registering and unregistering a lot if the user
|
| + * visits the RecentTabsPage a lot.
|
| + */
|
| + private static final int UNREGISTER_FOR_SESSION_SYNC_INVALIDATIONS_DELAY_MS = 3600000; // 1hr
|
| +
|
| private static final Object LOCK = new Object();
|
|
|
| private static InvalidationController sInstance;
|
| @@ -32,18 +126,43 @@ public class InvalidationController implements ApplicationStatus.ApplicationStat
|
| private final Context mContext;
|
|
|
| /**
|
| - * Whether session sync invalidations should be disabled.
|
| + * Whether session sync invalidations can be disabled.
|
| + */
|
| + private final boolean mCanDisableSessionInvalidations;
|
| +
|
| + /**
|
| + * Whether the controller was started.
|
| + */
|
| + private boolean mStarted;
|
| +
|
| + /**
|
| + * Used to schedule tasks to enable and disable session sync invalidations.
|
| + */
|
| + private Timer mEnableSessionInvalidationsTimer;
|
| +
|
| + /**
|
| + * Whether session sync invalidations are enabled.
|
| + */
|
| + private boolean mSessionInvalidationsEnabled;
|
| +
|
| + /**
|
| + * The number of open RecentTabsPages
|
| */
|
| - private final boolean mDisableSessionInvalidations;
|
| + private int mNumRecentTabPages;
|
|
|
| /**
|
| * Updates the sync invalidation types that the client is registered for based on the preferred
|
| * sync types. Starts the client if needed.
|
| */
|
| public void ensureStartedAndUpdateRegisteredTypes() {
|
| + mStarted = true;
|
| + // Do not apply changes to {@link #mSessionInvalidationsEnabled} yet because the timer task
|
| + // may be scheduled far into the future.
|
| + mEnableSessionInvalidationsTimer.resume();
|
| +
|
| HashSet<ModelType> typesToRegister = new HashSet<ModelType>();
|
| typesToRegister.addAll(ProfileSyncService.get(mContext).getPreferredDataTypes());
|
| - if (mDisableSessionInvalidations) {
|
| + if (!mSessionInvalidationsEnabled) {
|
| typesToRegister.remove(ModelType.SESSION);
|
| typesToRegister.remove(ModelType.FAVICON_TRACKING);
|
| typesToRegister.remove(ModelType.FAVICON_IMAGE);
|
| @@ -60,6 +179,8 @@ public class InvalidationController implements ApplicationStatus.ApplicationStat
|
| * Starts the invalidation client without updating the registered invalidation types.
|
| */
|
| private void start() {
|
| + mStarted = true;
|
| + mEnableSessionInvalidationsTimer.resume();
|
| Intent intent = new Intent(mContext, InvalidationClientService.class);
|
| mContext.startService(intent);
|
| }
|
| @@ -68,12 +189,39 @@ public class InvalidationController implements ApplicationStatus.ApplicationStat
|
| * Stops the invalidation client.
|
| */
|
| public void stop() {
|
| + mStarted = false;
|
| + mEnableSessionInvalidationsTimer.pause();
|
| Intent intent = new Intent(mContext, InvalidationClientService.class);
|
| intent.putExtra(InvalidationIntentProtocol.EXTRA_STOP, true);
|
| mContext.startService(intent);
|
| }
|
|
|
| /**
|
| + * Called when a RecentTabsPage is opened.
|
| + */
|
| + public void onRecentTabsPageOpened() {
|
| + if (!mCanDisableSessionInvalidations) return;
|
| +
|
| + ++mNumRecentTabPages;
|
| + if (mNumRecentTabPages == 1) {
|
| + setSessionInvalidationsEnabled(true, REGISTER_FOR_SESSION_SYNC_INVALIDATIONS_DELAY_MS);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Called when a RecentTabsPage is closed.
|
| + */
|
| + public void onRecentTabsPageClosed() {
|
| + if (!mCanDisableSessionInvalidations) return;
|
| +
|
| + --mNumRecentTabPages;
|
| + if (mNumRecentTabPages == 0) {
|
| + setSessionInvalidationsEnabled(
|
| + false, UNREGISTER_FOR_SESSION_SYNC_INVALIDATIONS_DELAY_MS);
|
| + }
|
| + }
|
| +
|
| + /**
|
| * Returns the instance that will use {@code context} to issue intents.
|
| *
|
| * Calling this method will create the instance if it does not yet exist.
|
| @@ -81,29 +229,56 @@ public class InvalidationController implements ApplicationStatus.ApplicationStat
|
| public static InvalidationController get(Context context) {
|
| synchronized (LOCK) {
|
| if (sInstance == null) {
|
| - boolean disableSessionInvalidations =
|
| + boolean canDisableSessionInvalidations =
|
| FieldTrialList.findFullName("AndroidSessionNotifications")
|
| .equals("Disabled");
|
| - sInstance = new InvalidationController(context, disableSessionInvalidations);
|
| + sInstance = new InvalidationController(context, canDisableSessionInvalidations);
|
| }
|
| return sInstance;
|
| }
|
| }
|
|
|
| /**
|
| + * Schedules a task to enable/disable session sync invalidations. Cancels any previously
|
| + * scheduled tasks to enable/disable session sync invalidations.
|
| + * @param enabled whether to enable or disable session sync invalidations.
|
| + * @param delayMs Delay in milliseconds after which to apply change.
|
| + */
|
| + private void setSessionInvalidationsEnabled(final boolean enabled, long delayMs) {
|
| + mEnableSessionInvalidationsTimer.cancel();
|
| + if (mSessionInvalidationsEnabled == enabled) return;
|
| +
|
| + mEnableSessionInvalidationsTimer.setRunnable(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + mSessionInvalidationsEnabled = enabled;
|
| + ensureStartedAndUpdateRegisteredTypes();
|
| + }
|
| + }, delayMs);
|
| + if (mStarted) {
|
| + mEnableSessionInvalidationsTimer.resume();
|
| + }
|
| + }
|
| +
|
| + /**
|
| * Creates an instance using {@code context} to send intents.
|
| */
|
| @VisibleForTesting
|
| - InvalidationController(Context context, boolean disableSessionInvalidations) {
|
| + InvalidationController(Context context, boolean canDisableSessionInvalidations) {
|
| Context appContext = context.getApplicationContext();
|
| if (appContext == null) throw new NullPointerException("Unable to get application context");
|
| mContext = appContext;
|
| - mDisableSessionInvalidations = disableSessionInvalidations;
|
| + mCanDisableSessionInvalidations = canDisableSessionInvalidations;
|
| + mSessionInvalidationsEnabled = !mCanDisableSessionInvalidations;
|
| + mEnableSessionInvalidationsTimer = new Timer();
|
| +
|
| ApplicationStatus.registerApplicationStateListener(this);
|
| }
|
|
|
| @Override
|
| public void onApplicationStateChange(int newState) {
|
| + // The isSyncEnabled() check is used to check whether the InvalidationController would be
|
| + // started if it did not stop itself when the application is paused.
|
| if (AndroidSyncSettings.isSyncEnabled(mContext)) {
|
| if (newState == ApplicationState.HAS_RUNNING_ACTIVITIES) {
|
| start();
|
|
|