chromium/chrome/android/java/src/org/chromium/chrome/browser/display_cutout/DisplayCutoutTabHelper.java

// Copyright 2020 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.display_cutout;

import android.app.Activity;

import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.browser.trusted.TrustedWebActivityDisplayMode;

import org.chromium.base.UserData;
import org.chromium.base.UserDataHost;
import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.chrome.browser.customtabs.BaseCustomTabActivity;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabObserver;
import org.chromium.chrome.browser.tab.TabSelectionType;
import org.chromium.chrome.browser.ui.edge_to_edge.EdgeToEdgeUtils;
import org.chromium.components.browser_ui.display_cutout.DisplayCutoutController;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.WebContentsObserver;
import org.chromium.ui.InsetObserver;
import org.chromium.ui.base.WindowAndroid;

/**
 * Wraps a {@link DisplayCutoutController} for a Chrome {@link Tab}.
 *
 * <p>This will only be created once the tab sets a non-default viewport fit.
 */
public class DisplayCutoutTabHelper implements UserData {
    private static final Class<DisplayCutoutTabHelper> USER_DATA_KEY = DisplayCutoutTabHelper.class;

    /** The tab that this object belongs to. */
    private Tab mTab;

    @VisibleForTesting DisplayCutoutController mCutoutController;

    /** Listens to various Tab events. */
    private final TabObserver mTabObserver =
            new EmptyTabObserver() {
                @Override
                public void onShown(Tab tab, @TabSelectionType int type) {
                    assert tab == mTab;

                    // Force a layout update if we are now being shown.
                    mCutoutController.maybeUpdateLayout();
                }

                @Override
                public void onInteractabilityChanged(Tab tab, boolean interactable) {
                    // Force a layout update if the tab is now in the foreground.
                    mCutoutController.maybeUpdateLayout();
                }

                @Override
                public void onActivityAttachmentChanged(Tab tab, @Nullable WindowAndroid window) {
                    assert tab == mTab;

                    mCutoutController.onActivityAttachmentChanged(window);
                }
            };

    public static DisplayCutoutTabHelper from(Tab tab) {
        UserDataHost host = tab.getUserDataHost();
        DisplayCutoutTabHelper tabHelper = host.getUserData(USER_DATA_KEY);
        return tabHelper == null
                ? host.setUserData(USER_DATA_KEY, new DisplayCutoutTabHelper(tab))
                : tabHelper;
    }

    @VisibleForTesting
    static class ChromeDisplayCutoutDelegate implements DisplayCutoutController.Delegate {
        private Tab mTab;

        ChromeDisplayCutoutDelegate(Tab tab) {
            mTab = tab;
        }

        @Override
        public Activity getAttachedActivity() {
            return mTab.getWindowAndroid().getActivity().get();
        }

        @Override
        public WebContents getWebContents() {
            return mTab.getWebContents();
        }

        @Override
        public InsetObserver getInsetObserver() {
            return mTab.getWindowAndroid().getInsetObserver();
        }

        @Override
        public ObservableSupplier<Integer> getBrowserDisplayCutoutModeSupplier() {
            WindowAndroid window = mTab.getWindowAndroid();
            return window == null ? null : ActivityDisplayCutoutModeSupplier.from(window);
        }

        @Override
        public boolean isInteractable() {
            return mTab.isUserInteractable();
        }

        @Override
        public boolean isInBrowserFullscreen() {
            Activity activity = getAttachedActivity();
            if (!(activity instanceof BaseCustomTabActivity)) {
                return false;
            }
            BaseCustomTabActivity baseCustomTabActivity = (BaseCustomTabActivity) activity;
            return (baseCustomTabActivity.getIntentDataProvider().getTwaDisplayMode()
                    instanceof TrustedWebActivityDisplayMode.ImmersiveMode);
        }

        @Override
        public boolean isDrawEdgeToEdgeEnabled() {
            return EdgeToEdgeUtils.isEnabled();
        }
    }

    /**
     * Constructs a new DisplayCutoutTabHelper for a specific tab.
     * @param tab The tab that this object belongs to.
     */
    @VisibleForTesting
    DisplayCutoutTabHelper(Tab tab) {
        mTab = tab;
        tab.addObserver(mTabObserver);
        mCutoutController =
                DisplayCutoutController.createForTab(mTab, new ChromeDisplayCutoutDelegate(mTab));
    }

    /**
     * Set the viewport fit value for the tab.
     * @param value The new viewport fit value.
     */
    public void setViewportFit(@WebContentsObserver.ViewportFitType int value) {
        mCutoutController.setViewportFit(value);
    }

    @Override
    public void destroy() {
        mTab.removeObserver(mTabObserver);
        mCutoutController.destroy();
    }

    static void initForTesting(Tab tab, DisplayCutoutController controller) {
        DisplayCutoutTabHelper tabHelper = new DisplayCutoutTabHelper(tab);
        tabHelper.mCutoutController = controller;
        tab.getUserDataHost().setUserData(USER_DATA_KEY, tabHelper);
    }

    @VisibleForTesting
    DisplayCutoutController getDisplayCutoutController() {
        return mCutoutController;
    }
}