chromium/chrome/android/java/src/org/chromium/chrome/browser/TabUsageTracker.java

// Copyright 2022 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;

import org.chromium.base.CallbackController;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.lifecycle.DestroyObserver;
import org.chromium.chrome.browser.lifecycle.PauseResumeWithNativeObserver;
import org.chromium.chrome.browser.lifecycle.StartStopWithNativeObserver;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabModelObserver;
import org.chromium.chrome.browser.tabmodel.TabModelUtils;

import java.util.HashSet;
import java.util.Set;

/**
 * Captures the percentage of tabs used, for metrics. This is done by finding the ratio of the
 * number of tabs used, to the total number of tabs available between ChromeTabbedActivity onResume
 * and onStop.
 */
public class TabUsageTracker
        implements StartStopWithNativeObserver, DestroyObserver, PauseResumeWithNativeObserver {
    private static final String PERCENTAGE_OF_TABS_USED_HISTOGRAM =
            "Android.ActivityStop.PercentageOfTabsUsed";
    private static final String NUMBER_OF_TABS_USED_HISTOGRAM =
            "Android.ActivityStop.NumberOfTabsUsed";

    private final Set<Integer> mTabsUsed = new HashSet<>();

    private int mInitialTabCount;
    private int mNewlyAddedTabCount;
    private final ActivityLifecycleDispatcher mLifecycleDispatcher;
    private final TabModelSelector mModelSelector;
    private TabModelSelectorTabModelObserver mTabModelSelectorTabModelObserver;
    private boolean mApplicationResumed;
    private CallbackController mCallbackController = new CallbackController();

    /**
     * This method is used to initialize the TabUsageTracker.
     *
     * @param lifecycleDispatcher LifecycleDispatcher used to subscribe class to lifecycle events.
     * @param modelSelector TabModelSelector used to subscribe to TabModelSelectorTabModelObserver
     *     to capture when tabs are selected or new tabs are added.
     */
    public static void initialize(
            ActivityLifecycleDispatcher lifecycleDispatcher, TabModelSelector modelSelector) {
        new TabUsageTracker(lifecycleDispatcher, modelSelector);
    }

    public TabUsageTracker(
            ActivityLifecycleDispatcher lifecycleDispatcher, TabModelSelector modelSelector) {
        mInitialTabCount = 0;
        mNewlyAddedTabCount = 0;
        mModelSelector = modelSelector;
        mApplicationResumed = false;

        mLifecycleDispatcher = lifecycleDispatcher;
        mLifecycleDispatcher.register(this);
    }

    @Override
    public void onDestroy() {
        mCallbackController.destroy();
        mLifecycleDispatcher.unregister(this);
    }

    @Override
    public void onStartWithNative() {}

    /**
     * Records 2 histograms.
     * 1. Percentage of tabs used.
     * 2. Number of tabs used.
     */
    @Override
    public void onStopWithNative() {
        // If onResume was never called, return early to omit invalid samples.
        if (!mApplicationResumed) return;

        int totalTabCount = mInitialTabCount + mNewlyAddedTabCount;
        float totalTabsUsedPercentage = (float) mTabsUsed.size() / (float) totalTabCount * 100;

        RecordHistogram.recordPercentageHistogram(
                PERCENTAGE_OF_TABS_USED_HISTOGRAM, Math.round(totalTabsUsedPercentage));
        RecordHistogram.recordCount100Histogram(NUMBER_OF_TABS_USED_HISTOGRAM, mTabsUsed.size());

        mTabsUsed.clear();
        mNewlyAddedTabCount = 0;
        mInitialTabCount = 0;
        mTabModelSelectorTabModelObserver.destroy();
        mApplicationResumed = false;
    }

    /**
     * Initializes the tab count and the selected tab when CTA is resumed and starts observing for
     * tab selections or any new tab creations.
     */
    @Override
    public void onResumeWithNative() {
        TabModelUtils.runOnTabStateInitialized(
                mModelSelector,
                mCallbackController.makeCancelable(
                        (tabModelSelector) -> {
                            mInitialTabCount = tabModelSelector.getTotalTabCount();
                        }));

        Tab currentlySelectedTab = mModelSelector.getCurrentTab();
        if (currentlySelectedTab != null) mTabsUsed.add(currentlySelectedTab.getId());

        mTabModelSelectorTabModelObserver =
                new TabModelSelectorTabModelObserver(mModelSelector) {
                    @Override
                    public void didAddTab(
                            Tab tab, int type, int creationState, boolean markedForSelection) {
                        mNewlyAddedTabCount++;
                    }

                    @Override
                    public void didSelectTab(Tab tab, int type, int lastId) {
                        mTabsUsed.add(tab.getId());
                    }
                };
        mApplicationResumed = true;
    }

    @Override
    public void onPauseWithNative() {}

    public TabModelSelectorTabModelObserver getTabModelSelectorTabModelObserverForTests() {
        return mTabModelSelectorTabModelObserver;
    }
}