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

import org.chromium.base.Callback;
import org.chromium.base.lifetime.Destroyable;
import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabObserver;
import org.chromium.components.browser_ui.widget.gesture.BackPressHandler;
import org.chromium.content_public.browser.RenderFrameHost;
import org.chromium.content_public.browser.WebContents;

/**
 * Class responsible for ensuring a web-exposed CloseWatcher is able to intercept a back gesture and
 * perform an app-specific behavior.
 */
public class CloseListenerManager implements BackPressHandler, Destroyable {
    private final ObservableSupplierImpl<Boolean> mBackPressChangedSupplier =
            new ObservableSupplierImpl<>();
    private final ObservableSupplier<Tab> mActivityTabSupplier;
    private final Callback<Tab> mOnTabChanged = this::onTabChanged;
    private Tab mTab;
    private TabObserver mTabObserver;

    public CloseListenerManager(ObservableSupplier<Tab> activityTabSupplier) {
        mActivityTabSupplier = activityTabSupplier;
        mActivityTabSupplier.addObserver(mOnTabChanged);
        updateObserver();
    }

    @Override
    public @BackPressResult int handleBackPress() {
        RenderFrameHost focusedFrame = getFocusedFrameIfCloseWatcherActive();
        if (focusedFrame == null) return BackPressResult.FAILURE;
        focusedFrame.signalCloseWatcherIfActive();
        return BackPressResult.SUCCESS;
    }

    @Override
    public ObservableSupplier<Boolean> getHandleBackPressChangedSupplier() {
        return mBackPressChangedSupplier;
    }

    @Override
    public void destroy() {
        mActivityTabSupplier.removeObserver(mOnTabChanged);
        if (mTabObserver != null) {
            mTab.removeObserver(mTabObserver);
            mTabObserver = null;
            mTab = null;
        }
    }

    private void onTabChanged(Tab tab) {
        onBackPressStateChanged();
        updateObserver();
    }

    private void updateObserver() {
        if (mTabObserver != null) {
            mTab.removeObserver(mTabObserver);
            mTabObserver = null;
        }

        mTab = mActivityTabSupplier.get();
        if (mTab == null) return;

        mTabObserver =
                new EmptyTabObserver() {
                    @Override
                    public void onNavigationStateChanged() {
                        onBackPressStateChanged();
                    }

                    @Override
                    public void onDidChangeCloseSignalInterceptStatus() {
                        onBackPressStateChanged();
                    }
                };
        mTab.addObserver(mTabObserver);
    }

    private void onBackPressStateChanged() {
        mBackPressChangedSupplier.set(shouldInterceptBackPress());
    }

    private boolean shouldInterceptBackPress() {
        return getFocusedFrameIfCloseWatcherActive() != null;
    }

    private RenderFrameHost getFocusedFrameIfCloseWatcherActive() {
        Tab tab = mActivityTabSupplier.get();
        if (tab == null) return null;
        WebContents contents = tab.getWebContents();
        if (contents == null) return null;
        RenderFrameHost focusedFrame = contents.getFocusedFrame();
        if (focusedFrame == null || !focusedFrame.isCloseWatcherActive()) return null;
        return focusedFrame;
    }
}