chromium/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/EngagementSignalsHandler.java

// Copyright 2023 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.customtabs.content;

import android.os.Bundle;

import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.browser.customtabs.CustomTabsSessionToken;
import androidx.browser.customtabs.EngagementSignalsCallback;

import org.chromium.chrome.browser.customtabs.CustomTabsConnection;
import org.chromium.chrome.browser.customtabs.content.TabObserverRegistrar.CustomTabTabObserver;
import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManager;
import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManager.Observer;
import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManagerImpl;

/**
 * Handles the initialization of Engagement Signals when the client sets an
 * {@link androidx.browser.customtabs.EngagementSignalsCallback}.
 */
public class EngagementSignalsHandler {
    private final CustomTabsConnection mConnection;
    private final CustomTabsSessionToken mSession;
    @Nullable private EngagementSignalsInitialScrollObserver mInitialScrollObserver;
    @Nullable private RealtimeEngagementSignalObserver mObserver;
    private TabObserverRegistrar mTabObserverRegistrar;
    private EngagementSignalsCallback mCallback;
    private PrivacyPreferencesManager.Observer mPrivacyPreferencesObserver;

    public EngagementSignalsHandler(
            CustomTabsConnection connection, CustomTabsSessionToken session) {
        mConnection = connection;
        mSession = session;
    }

    /**
     * Sets the {@link TabObserverRegistrar}. This should be called when the registrar becomes
     * available.
     * @param registrar The {@link TabObserverRegistrar}.
     */
    public void setTabObserverRegistrar(TabObserverRegistrar registrar) {
        mTabObserverRegistrar = registrar;
        if (mCallback != null) {
            // If the registrar became available after the callback, we don't need to create the
            // EngagementSignalsInitialScrollObserver. We wouldn't be able to observe the scroll
            // gestures without the registrar anyway.
            createEngagementSignalsObserver();
        } else {
            mInitialScrollObserver =
                    new EngagementSignalsInitialScrollObserver(mTabObserverRegistrar);
        }
    }

    /**
     * Sets the {@link EngagementSignalsCallback}. This should be called when the client app sets a
     * callback.
     * @param callback The {@link EngagementSignalsCallback}.
     */
    public void setEngagementSignalsCallback(EngagementSignalsCallback callback) {
        mCallback = callback;
        if (mTabObserverRegistrar != null) {
            createEngagementSignalsObserver();
        }
    }

    public void notifyTabWillCloseAndReopenWithSessionReuse() {
        if (mObserver != null) {
            mObserver.suppressNextSessionEndedCall();
        }
    }

    private void createEngagementSignalsObserver() {
        if (!PrivacyPreferencesManagerImpl.getInstance().isUsageAndCrashReportingPermitted()) {
            return;
        }
        // This can happen if the client app sets a new EngagementSignalsCallback. In that case,
        // we should recreate the observer.
        if (mObserver != null) {
            mObserver.destroy();
        }
        assert mTabObserverRegistrar != null;
        assert mCallback != null;
        boolean hadScrollDown =
                mInitialScrollObserver != null
                        && mInitialScrollObserver.hasCurrentPageHadScrollDown();
        mObserver =
                new RealtimeEngagementSignalObserver(
                        mTabObserverRegistrar, mConnection, mSession, mCallback, hadScrollDown);
        if (mInitialScrollObserver != null) {
            mInitialScrollObserver.destroy();
            mInitialScrollObserver = null;
        }

        mPrivacyPreferencesObserver =
                new Observer() {
                    @Override
                    public void onIsUsageAndCrashReportingPermittedChanged(boolean permitted) {
                        if (!permitted) {
                            if (mObserver != null) {
                                if (mCallback != null) {
                                    mCallback.onSessionEnded(false, Bundle.EMPTY);
                                }
                                mObserver.destroy();
                                mObserver = null;
                            }
                            PrivacyPreferencesManagerImpl.getInstance()
                                    .removeObserver(mPrivacyPreferencesObserver);
                            mPrivacyPreferencesObserver = null;
                        }
                    }
                };
        PrivacyPreferencesManagerImpl.getInstance().addObserver(mPrivacyPreferencesObserver);
        mTabObserverRegistrar.registerActivityTabObserver(
                new CustomTabTabObserver() {
                    @Override
                    protected void onAllTabsClosed() {
                        if (mTabObserverRegistrar != null) {
                            mTabObserverRegistrar.unregisterActivityTabObserver(this);
                            mTabObserverRegistrar = null;
                        }
                        if (mObserver != null) {
                            mObserver.destroy();
                            mObserver = null;
                        }
                        if (mPrivacyPreferencesObserver != null) {
                            PrivacyPreferencesManagerImpl.getInstance()
                                    .removeObserver(mPrivacyPreferencesObserver);
                            mPrivacyPreferencesObserver = null;
                        }
                    }
                });
    }

    @VisibleForTesting
    @Nullable
    public RealtimeEngagementSignalObserver getEngagementSignalsObserverForTesting() {
        return mObserver;
    }
}