[go: nahoru, domu]

blob: 3c2d3f141e8d06788ea8f7256db1d331df9041be [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// 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.modaldialog;
import android.content.res.Resources;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.MarginLayoutParams;
import android.view.ViewStub;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.supplier.Supplier;
import org.chromium.cc.input.BrowserControlsState;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.app.ChromeActivity;
import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
import org.chromium.chrome.browser.browser_controls.BrowserControlsUtils;
import org.chromium.chrome.browser.browser_controls.BrowserControlsVisibilityManager;
import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel;
import org.chromium.chrome.browser.contextualsearch.ContextualSearchManager;
import org.chromium.chrome.browser.fullscreen.FullscreenManager;
import org.chromium.chrome.browser.omnibox.OmniboxFocusReason;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabAttributeKeys;
import org.chromium.chrome.browser.tab.TabAttributes;
import org.chromium.chrome.browser.tab.TabBrowserControlsConstraintsHelper;
import org.chromium.chrome.browser.ui.TabObscuringHandler;
import org.chromium.components.browser_ui.modaldialog.TabModalPresenter;
import org.chromium.components.browser_ui.util.BrowserControlsVisibilityDelegate;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.UiUtils;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.util.TokenHolder;
/**
* This presenter creates tab modality by blocking interaction with select UI elements while a
* dialog is visible.
*/
public class ChromeTabModalPresenter
extends TabModalPresenter implements BrowserControlsStateProvider.Observer {
/** The activity displaying the dialogs. */
private final ChromeActivity mChromeActivity;
private final Supplier<TabObscuringHandler> mTabObscuringHandlerSupplier;
private final FullscreenManager mFullscreenManager;
private final BrowserControlsVisibilityManager mBrowserControlsVisibilityManager;
private final TabModalBrowserControlsVisibilityDelegate mVisibilityDelegate;
/** The active tab of which the dialog will be shown on top. */
private Tab mActiveTab;
/** The parent view that contains the dialog container. */
private ViewGroup mContainerParent;
/** Whether the dialog container is brought to the front in its parent. */
private boolean mContainerIsAtFront;
/**
* Whether an enter animation on the dialog container should run when
* {@link #onBrowserControlsFullyVisible} is called.
*/
private boolean mRunEnterAnimationOnCallback;
/**
* The sibling view of the dialog container drawn next in its parent when it should be behind
* browser controls. If BottomSheet is opened or UrlBar is focused, the dialog container should
* be behind the browser controls and the URL suggestions.
*/
private View mDefaultNextSiblingView;
private int mBottomControlsHeight;
private boolean mShouldUpdateContainerLayoutParams;
/** A token held while the dialog manager is obscuring all tabs. */
private int mTabObscuringToken;
/**
* Constructor for initializing dialog container.
* @param chromeActivity The activity displaying the dialogs.
* @param tabObscuringHandler TabObscuringHandler object.
*/
public ChromeTabModalPresenter(
ChromeActivity chromeActivity, Supplier<TabObscuringHandler> tabObscuringHandler) {
super(chromeActivity);
mChromeActivity = chromeActivity;
mTabObscuringHandlerSupplier = tabObscuringHandler;
mFullscreenManager = mChromeActivity.getFullscreenManager();
mBrowserControlsVisibilityManager = mChromeActivity.getBrowserControlsManager();
mBrowserControlsVisibilityManager.addObserver(this);
mVisibilityDelegate = new TabModalBrowserControlsVisibilityDelegate();
mTabObscuringToken = TokenHolder.INVALID_TOKEN;
}
public void destroy() {
mBrowserControlsVisibilityManager.removeObserver(this);
}
/**
* @return The browser controls visibility delegate associated with tab modal dialogs.
*/
public BrowserControlsVisibilityDelegate getBrowserControlsVisibilityDelegate() {
return mVisibilityDelegate;
}
@Override
protected ViewGroup createDialogContainer() {
ViewStub dialogContainerStub =
mChromeActivity.findViewById(R.id.tab_modal_dialog_container_stub);
dialogContainerStub.setLayoutResource(R.layout.modal_dialog_container);
ViewGroup dialogContainer = (ViewGroup) dialogContainerStub.inflate();
dialogContainer.setVisibility(View.GONE);
// Make sure clicks are not consumed by content beneath the container view.
dialogContainer.setClickable(true);
mContainerParent = (ViewGroup) dialogContainer.getParent();
// The default sibling view is the next view of the dialog container stub in main.xml and
// should not be removed from its parent.
mDefaultNextSiblingView =
mChromeActivity.findViewById(R.id.tab_modal_dialog_container_sibling_view);
assert mDefaultNextSiblingView != null;
Resources resources = mChromeActivity.getResources();
MarginLayoutParams params = (MarginLayoutParams) dialogContainer.getLayoutParams();
params.width = ViewGroup.MarginLayoutParams.MATCH_PARENT;
params.height = ViewGroup.MarginLayoutParams.MATCH_PARENT;
params.topMargin = getContainerTopMargin(resources, mBrowserControlsVisibilityManager);
params.bottomMargin = getContainerBottomMargin(mBrowserControlsVisibilityManager);
dialogContainer.setLayoutParams(params);
int scrimVerticalMargin =
resources.getDimensionPixelSize(R.dimen.tab_modal_scrim_vertical_margin);
View scrimView = dialogContainer.findViewById(R.id.scrim);
params = (MarginLayoutParams) scrimView.getLayoutParams();
params.width = MarginLayoutParams.MATCH_PARENT;
params.height = MarginLayoutParams.MATCH_PARENT;
params.topMargin = scrimVerticalMargin;
scrimView.setLayoutParams(params);
return dialogContainer;
}
@Override
protected void showDialogContainer() {
if (mShouldUpdateContainerLayoutParams) {
MarginLayoutParams params = (MarginLayoutParams) getDialogContainer().getLayoutParams();
params.topMargin = getContainerTopMargin(
mChromeActivity.getResources(), mBrowserControlsVisibilityManager);
params.bottomMargin = mBottomControlsHeight;
getDialogContainer().setLayoutParams(params);
mShouldUpdateContainerLayoutParams = false;
}
// Don't show the dialog container before browser controls are guaranteed fully visible.
if (BrowserControlsUtils.areBrowserControlsFullyVisible(
mBrowserControlsVisibilityManager)) {
runEnterAnimation();
} else {
mRunEnterAnimationOnCallback = true;
}
assert mTabObscuringToken == TokenHolder.INVALID_TOKEN;
mTabObscuringToken = mTabObscuringHandlerSupplier.get().obscureAllTabs();
}
@Override
protected void setBrowserControlsAccess(boolean restricted) {
if (mChromeActivity.getToolbarManager() == null) return;
View menuButton = mChromeActivity.getToolbarManager().getMenuButtonView();
if (restricted) {
mActiveTab = mChromeActivity.getActivityTab();
assert mActiveTab
!= null : "Tab modal dialogs should be shown on top of an active tab.";
// Hide contextual search panel so that bottom toolbar will not be
// obscured and back press is not overridden.
ContextualSearchManager contextualSearchManager =
mChromeActivity.getContextualSearchManager();
if (contextualSearchManager != null) {
contextualSearchManager.hideContextualSearch(
OverlayPanel.StateChangeReason.UNKNOWN);
}
// Dismiss the action bar that obscures the dialogs but preserve the text selection.
WebContents webContents = mActiveTab.getWebContents();
if (webContents != null) {
saveOrRestoreTextSelection(webContents, true);
}
// Force toolbar to show and disable overflow menu.
onTabModalDialogStateChanged(true);
mChromeActivity.getToolbarManager().setUrlBarFocus(false, OmniboxFocusReason.UNFOCUS);
menuButton.setEnabled(false);
} else {
// Show the action bar back if it was dismissed when the dialogs were showing.
WebContents webContents = mActiveTab.getWebContents();
if (webContents != null) {
saveOrRestoreTextSelection(webContents, false);
}
onTabModalDialogStateChanged(false);
menuButton.setEnabled(true);
mActiveTab = null;
}
}
@Override
protected void removeDialogView(PropertyModel model) {
mRunEnterAnimationOnCallback = false;
mTabObscuringHandlerSupplier.get().unobscureAllTabs(mTabObscuringToken);
mTabObscuringToken = TokenHolder.INVALID_TOKEN;
super.removeDialogView(model);
}
@Override
public void onControlsOffsetChanged(int topOffset, int topControlsMinHeightOffset,
int bottomOffset, int bottomControlsMinHeightOffset, boolean needsAnimate) {
if (getDialogModel() == null || !mRunEnterAnimationOnCallback
|| !BrowserControlsUtils.areBrowserControlsFullyVisible(
mBrowserControlsVisibilityManager)) {
return;
}
mRunEnterAnimationOnCallback = false;
runEnterAnimation();
}
@Override
public void onBottomControlsHeightChanged(
int bottomControlsHeight, int bottomControlsMinHeight) {
mBottomControlsHeight = bottomControlsHeight;
mShouldUpdateContainerLayoutParams = true;
}
@Override
public void onTopControlsHeightChanged(int topControlsHeight, int topControlsMinHeight) {
mShouldUpdateContainerLayoutParams = true;
}
@Override
public void updateContainerHierarchy(boolean toFront) {
super.updateContainerHierarchy(toFront);
if (toFront == mContainerIsAtFront) return;
mContainerIsAtFront = toFront;
if (toFront) {
getDialogContainer().bringToFront();
} else {
UiUtils.removeViewFromParent(getDialogContainer());
UiUtils.insertBefore(mContainerParent, getDialogContainer(), mDefaultNextSiblingView);
}
}
/**
* Calculate the top margin of the dialog container and the dialog scrim so that the scrim
* doesn't overlap the toolbar.
* @param resources {@link Resources} to use to get the scrim vertical margin.
* @param provider {@link BrowserControlsStateProvider} for browser controls heights.
* @return The container top margin.
*/
public static int getContainerTopMargin(
Resources resources, BrowserControlsStateProvider provider) {
int scrimVerticalMargin =
resources.getDimensionPixelSize(R.dimen.tab_modal_scrim_vertical_margin);
return provider.getTopControlsHeight() - scrimVerticalMargin;
}
/**
* Calculate the bottom margin of the dialog container.
* @param provider {@link BrowserControlsStateProvider} for browser controls heights.
* @return The container bottom margin.
*/
public static int getContainerBottomMargin(BrowserControlsStateProvider provider) {
return provider.getBottomControlsHeight();
}
public static boolean isDialogShowing(Tab tab) {
return TabAttributes.from(tab).get(TabAttributeKeys.MODAL_DIALOG_SHOWING, false);
}
private void onTabModalDialogStateChanged(boolean isShowing) {
TabAttributes.from(mActiveTab).set(TabAttributeKeys.MODAL_DIALOG_SHOWING, isShowing);
mVisibilityDelegate.updateConstraintsForTab(mActiveTab);
// Make sure to exit fullscreen mode before showing the tab modal dialog view.
mFullscreenManager.onExitFullscreen(mActiveTab);
// Also need to update browser control state after dismissal to refresh the constraints.
if (isShowing && areRendererInputEventsIgnored()) {
mBrowserControlsVisibilityManager.showAndroidControls(true);
} else {
TabBrowserControlsConstraintsHelper.update(mActiveTab, BrowserControlsState.SHOWN,
!mBrowserControlsVisibilityManager.offsetOverridden());
}
}
private boolean areRendererInputEventsIgnored() {
return mActiveTab.getWebContents().getMainFrame().areInputEventsIgnored();
}
@VisibleForTesting
ViewGroup getContainerParentForTest() {
return mContainerParent;
}
/**
* Handles browser controls constraints for the TabModal dialogs.
*/
static class TabModalBrowserControlsVisibilityDelegate
extends BrowserControlsVisibilityDelegate {
public TabModalBrowserControlsVisibilityDelegate() {
super(BrowserControlsState.BOTH);
}
/**
* Updates the tab modal browser constraints for the given tab.
*/
public void updateConstraintsForTab(Tab tab) {
if (tab == null) return;
set(isDialogShowing(tab) ? BrowserControlsState.SHOWN : BrowserControlsState.BOTH);
}
}
}