chromium/chrome/android/java/src/org/chromium/chrome/browser/invalidation/SessionsInvalidationManager.java

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.invalidation;

import android.text.format.DateUtils;

import androidx.annotation.VisibleForTesting;

import org.chromium.base.ApplicationState;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.ThreadUtils;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.recent_tabs.ForeignSessionHelper;

/**
 * Class responsible for managing registration for invalidations for noisy sync
 * data types such as SESSIONS on Android. It keeps track of how many recent
 * pages tab open and register/unregister for SESSIONS invalidation accordingly.
 * It should be used only from the UI thread.
 */
public class SessionsInvalidationManager implements ApplicationStatus.ApplicationStateListener {
    /**
     * 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.
     */
    static final int REGISTER_FOR_SESSION_SYNC_INVALIDATIONS_DELAY_MS =
            (int) DateUtils.SECOND_IN_MILLIS * 20;

    /** Used to schedule tasks to enable and disable session sync invalidations. */
    private final ResumableDelayedTaskRunner mEnableSessionInvalidationsRunner;

    /** Used to call native code that enables and disables session invalidations. */
    private final Profile mProfile;

    private static SessionsInvalidationManager sInstance;

    /** Whether session sync invalidations are enabled. */
    private boolean mIsSessionInvalidationsEnabled;

    /** The number of open RecentTabsPages */
    private int mNumRecentTabPages;

    /**
     * Returns the a singleton SessionsInvalidationManager.
     *
     * Calling this method will create the instance if it does not yet exist.
     */
    public static SessionsInvalidationManager get(Profile profile) {
        ThreadUtils.assertOnUiThread();
        if (sInstance == null) {
            sInstance = new SessionsInvalidationManager(profile, new ResumableDelayedTaskRunner());
        }
        return sInstance;
    }

    @VisibleForTesting
    SessionsInvalidationManager(Profile profile, ResumableDelayedTaskRunner runner) {
        mProfile = profile;
        mIsSessionInvalidationsEnabled = false;
        mEnableSessionInvalidationsRunner = runner;
        ApplicationStatus.registerApplicationStateListener(this);
    }

    /** Called when a RecentTabsPage is opened. */
    public void onRecentTabsPageOpened() {
        ++mNumRecentTabPages;
        if (mNumRecentTabPages == 1) {
            setSessionInvalidationsEnabled(true, REGISTER_FOR_SESSION_SYNC_INVALIDATIONS_DELAY_MS);
        }
    }

    /** Called when a RecentTabsPage is closed. */
    public void onRecentTabsPageClosed() {
        --mNumRecentTabPages;
        if (mNumRecentTabPages == 0) {
            setSessionInvalidationsEnabled(false, 0);
        }
    }

    /**
     * Schedules a task to enable/disable session sync invalidations. Cancels any previously
     * scheduled tasks to enable/disable session sync invalidations.
     * @param isEnabled whether to enable or disable session sync invalidations.
     * @param delayMs Delay in milliseconds after which to apply change.
     */
    private void setSessionInvalidationsEnabled(boolean isEnabled, long delayMs) {
        mEnableSessionInvalidationsRunner.cancel();

        if (mIsSessionInvalidationsEnabled == isEnabled) {
            return;
        }

        mEnableSessionInvalidationsRunner.setRunnable(
                () -> {
                    mIsSessionInvalidationsEnabled = isEnabled;
                    ForeignSessionHelper foreignSessionHelper = new ForeignSessionHelper(mProfile);
                    foreignSessionHelper.setInvalidationsForSessionsEnabled(isEnabled);
                    foreignSessionHelper.destroy();
                },
                delayMs);
        mEnableSessionInvalidationsRunner.resume();
    }

    @Override
    public void onApplicationStateChange(int newState) {
        // Registering for receiving invalidation requires calling native code. Therefore, we should
        // pause the runner when Chrome goes to the background to postpone calling up native code
        // until next time Chrome is running again.
        if (newState == ApplicationState.HAS_RUNNING_ACTIVITIES) {
            mEnableSessionInvalidationsRunner.resume();
        } else if (newState == ApplicationState.HAS_PAUSED_ACTIVITIES) {
            mEnableSessionInvalidationsRunner.pause();
        }
    }

    @VisibleForTesting
    public boolean isSessionInvalidationsEnabled() {
        return mIsSessionInvalidationsEnabled;
    }
}