chromium/chrome/android/java/src/org/chromium/chrome/browser/segmentation_platform/SignalAccumulator.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.segmentation_platform;

import android.os.Handler;

import org.chromium.chrome.browser.segmentation_platform.ContextualPageActionController.ActionProvider;
import org.chromium.chrome.browser.tab.Tab;

import java.util.List;

/**
 * Convenient wrapper to keep track of the feature backend results and trigger the next step after
 * all the feature backends have responded or a time out has happened.
 */
public class SignalAccumulator {
    private static final long ACTION_PROVIDER_TIMEOUT_MS = 100;

    // List of signals to query. Modify hasAllSignals() when adding signals to this list.
    // TODO(crbug.com/40242243): Introduce a key set and directly populate InputContext.
    private Boolean mHasPriceTracking;
    private Boolean mHasReaderMode;
    private Boolean mHasPriceInsights;

    // Whether the backends didn't respond within the time limit. Any further response from the
    // backends will be ignored.
    private boolean mHasTimedOut;

    // Whether the results from the backends have already been used for computing the action to
    // show.
    private boolean mIsInValid;

    // The callback to be invoked at the end of getting all the signals or time out. After it is
    // run, the accumulator becomes invalid.
    private Runnable mCompletionCallback;

    private final List<ActionProvider> mActionProviders;
    private final Tab mTab;
    private final Handler mHandler;

    /**
     * Constructor.
     * @param handler A handler for posting task.
     * @param tab The given tab.
     * @param actionProviders List of action providers to get signals from.
     */
    public SignalAccumulator(Handler handler, Tab tab, List<ActionProvider> actionProviders) {
        mHandler = handler;
        mTab = tab;
        mActionProviders = actionProviders;
    }

    /**
     * Called to start getting signals from the respective action providers. The callback will be
     * invoked at the end of signal collection or timeout.
     */
    public void getSignals(Runnable callback) {
        mCompletionCallback = callback;
        for (ActionProvider actionProvider : mActionProviders) {
            actionProvider.getAction(mTab, this);
        }
        mHandler.postDelayed(
                () -> {
                    mHasTimedOut = true;
                    proceedToNextStepIfReady();
                },
                ACTION_PROVIDER_TIMEOUT_MS);
    }

    /**
     * Called to notify that one of the backends is ready and the controller can proceed to next
     * step if it has all the signals.
     */
    public void notifySignalAvailable() {
        proceedToNextStepIfReady();
    }

    /**
     * @return Whether the page is price tracking eligible. Default is false.
     */
    public Boolean hasPriceTracking() {
        return mHasPriceTracking == null ? false : mHasPriceTracking;
    }

    /** Called to set whether the page can be price tracked. */
    public void setHasPriceTracking(Boolean hasPriceTracking) {
        mHasPriceTracking = hasPriceTracking;
    }

    /** @return Whether the page is reader mode eligible. Default is false. */
    public Boolean hasReaderMode() {
        return mHasReaderMode == null ? false : mHasReaderMode;
    }

    /** Called to set whether the page can be viewed in reader mode. */
    public void setHasReaderMode(Boolean hasReaderMode) {
        mHasReaderMode = hasReaderMode;
    }

    /**
     * @return Whether the page is price insights eligible. Default is false.
     */
    public Boolean hasPriceInsights() {
        return mHasPriceInsights == null ? false : mHasPriceInsights;
    }

    /** Called to set whether the page is price insights eligible. */
    public void setHasPriceInsights(Boolean hasPriceInsights) {
        mHasPriceInsights = hasPriceInsights;
    }

    /** Central method invoked whenever a backend responds or time out happens. */
    private void proceedToNextStepIfReady() {
        boolean isReady = mHasTimedOut || hasAllSignals();
        if (!isReady || mIsInValid) return;
        mIsInValid = true;
        mCompletionCallback.run();
    }

    private boolean hasAllSignals() {
        return mHasPriceTracking != null && mHasReaderMode != null && mHasPriceInsights != null;
    }
}