Commit 3aaf65aa authored by Evan Stade's avatar Evan Stade Committed by Commit Bot

Refactor TabModalPresenter into //components for sharing with WebLayer.

The shared parts of TabModalPresenter, which include layout inflation,
animations, and some accessibility, are moved into //components.
Chrome-specific specialization is delegated via abstract methods.

Bug: 1045499, 1025256
Change-Id: I2e37561338344495c6ee5cf5e0606c0f76e2c6d1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2048846
Commit-Queue: Evan Stade <estade@chromium.org>
Reviewed-by: default avatarTed Choc <tedchoc@chromium.org>
Cr-Commit-Position: refs/heads/master@{#742766}
parent 59952f4f
...@@ -934,8 +934,8 @@ chrome_java_sources = [ ...@@ -934,8 +934,8 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/metrics/VariationsSession.java", "java/src/org/chromium/chrome/browser/metrics/VariationsSession.java",
"java/src/org/chromium/chrome/browser/metrics/WebApkSplashscreenMetrics.java", "java/src/org/chromium/chrome/browser/metrics/WebApkSplashscreenMetrics.java",
"java/src/org/chromium/chrome/browser/metrics/WebApkUma.java", "java/src/org/chromium/chrome/browser/metrics/WebApkUma.java",
"java/src/org/chromium/chrome/browser/modaldialog/ChromeTabModalPresenter.java",
"java/src/org/chromium/chrome/browser/modaldialog/TabModalLifetimeHandler.java", "java/src/org/chromium/chrome/browser/modaldialog/TabModalLifetimeHandler.java",
"java/src/org/chromium/chrome/browser/modaldialog/TabModalPresenter.java",
"java/src/org/chromium/chrome/browser/modules/ModuleInstallUi.java", "java/src/org/chromium/chrome/browser/modules/ModuleInstallUi.java",
"java/src/org/chromium/chrome/browser/mojo/ChromeInterfaceRegistrar.java", "java/src/org/chromium/chrome/browser/mojo/ChromeInterfaceRegistrar.java",
"java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceChromeTabbedActivity.java", "java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceChromeTabbedActivity.java",
......
...@@ -236,8 +236,8 @@ chrome_test_java_sources = [ ...@@ -236,8 +236,8 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/metrics/MainIntentBehaviorMetricsIntegrationTest.java", "javatests/src/org/chromium/chrome/browser/metrics/MainIntentBehaviorMetricsIntegrationTest.java",
"javatests/src/org/chromium/chrome/browser/metrics/PageLoadMetricsTest.java", "javatests/src/org/chromium/chrome/browser/metrics/PageLoadMetricsTest.java",
"javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java", "javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java",
"javatests/src/org/chromium/chrome/browser/modaldialog/ChromeTabModalPresenterTest.java",
"javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogViewRenderTest.java", "javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogViewRenderTest.java",
"javatests/src/org/chromium/chrome/browser/modaldialog/TabModalPresenterTest.java",
"javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowIntegrationTest.java", "javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowIntegrationTest.java",
"javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowTestHelper.java", "javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowTestHelper.java",
"javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtilsTest.java", "javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtilsTest.java",
......
...@@ -92,8 +92,8 @@ import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher; ...@@ -92,8 +92,8 @@ import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.locale.LocaleManager; import org.chromium.chrome.browser.locale.LocaleManager;
import org.chromium.chrome.browser.metrics.LaunchMetrics; import org.chromium.chrome.browser.metrics.LaunchMetrics;
import org.chromium.chrome.browser.metrics.MainIntentBehaviorMetrics; import org.chromium.chrome.browser.metrics.MainIntentBehaviorMetrics;
import org.chromium.chrome.browser.modaldialog.ChromeTabModalPresenter;
import org.chromium.chrome.browser.modaldialog.TabModalLifetimeHandler; import org.chromium.chrome.browser.modaldialog.TabModalLifetimeHandler;
import org.chromium.chrome.browser.modaldialog.TabModalPresenter;
import org.chromium.chrome.browser.multiwindow.MultiInstanceChromeTabbedActivity; import org.chromium.chrome.browser.multiwindow.MultiInstanceChromeTabbedActivity;
import org.chromium.chrome.browser.multiwindow.MultiInstanceManager; import org.chromium.chrome.browser.multiwindow.MultiInstanceManager;
import org.chromium.chrome.browser.native_page.NativePageAssassin; import org.chromium.chrome.browser.native_page.NativePageAssassin;
...@@ -2186,7 +2186,7 @@ public class ChromeTabbedActivity extends ChromeActivity implements Accessibilit ...@@ -2186,7 +2186,7 @@ public class ChromeTabbedActivity extends ChromeActivity implements Accessibilit
// If the current active tab is showing a tab modal dialog, an app menu shouldn't be shown // If the current active tab is showing a tab modal dialog, an app menu shouldn't be shown
// in any cases, e.g. when a hardware menu button is clicked. // in any cases, e.g. when a hardware menu button is clicked.
Tab tab = getActivityTab(); Tab tab = getActivityTab();
if (tab != null && TabModalPresenter.isDialogShowing(tab)) return false; if (tab != null && ChromeTabModalPresenter.isDialogShowing(tab)) return false;
return super.canShowAppMenu(); return super.canShowAppMenu();
} }
......
...@@ -47,7 +47,7 @@ public class TabModalLifetimeHandler implements NativeInitObserver, Destroyable ...@@ -47,7 +47,7 @@ public class TabModalLifetimeHandler implements NativeInitObserver, Destroyable
private final ModalDialogManager mManager; private final ModalDialogManager mManager;
private final ComposedBrowserControlsVisibilityDelegate mAppVisibilityDelegate; private final ComposedBrowserControlsVisibilityDelegate mAppVisibilityDelegate;
private final Supplier<TabObscuringHandler> mTabObscuringHandlerSupplier; private final Supplier<TabObscuringHandler> mTabObscuringHandlerSupplier;
private TabModalPresenter mPresenter; private ChromeTabModalPresenter mPresenter;
private TabModelSelectorTabModelObserver mTabModelObserver; private TabModelSelectorTabModelObserver mTabModelObserver;
private Tab mActiveTab; private Tab mActiveTab;
private int mTabModalSuspendedToken; private int mTabModalSuspendedToken;
...@@ -91,7 +91,7 @@ public class TabModalLifetimeHandler implements NativeInitObserver, Destroyable ...@@ -91,7 +91,7 @@ public class TabModalLifetimeHandler implements NativeInitObserver, Destroyable
@Override @Override
public void onFinishNativeInitialization() { public void onFinishNativeInitialization() {
mPresenter = new TabModalPresenter(mActivity, mTabObscuringHandlerSupplier); mPresenter = new ChromeTabModalPresenter(mActivity, mTabObscuringHandlerSupplier);
mAppVisibilityDelegate.addDelegate(mPresenter.getBrowserControlsVisibilityDelegate()); mAppVisibilityDelegate.addDelegate(mPresenter.getBrowserControlsVisibilityDelegate());
mManager.registerPresenter(mPresenter, ModalDialogType.TAB); mManager.registerPresenter(mPresenter, ModalDialogType.TAB);
......
...@@ -15,7 +15,7 @@ import org.chromium.base.Callback; ...@@ -15,7 +15,7 @@ import org.chromium.base.Callback;
import org.chromium.base.task.PostTask; import org.chromium.base.task.PostTask;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager; import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
import org.chromium.chrome.browser.modaldialog.TabModalPresenter; import org.chromium.chrome.browser.modaldialog.ChromeTabModalPresenter;
import org.chromium.content_public.browser.UiThreadTaskTraits; import org.chromium.content_public.browser.UiThreadTaskTraits;
import org.chromium.ui.modaldialog.DialogDismissalCause; import org.chromium.ui.modaldialog.DialogDismissalCause;
import org.chromium.ui.modaldialog.ModalDialogManager; import org.chromium.ui.modaldialog.ModalDialogManager;
...@@ -97,8 +97,9 @@ class PasswordManagerDialogMediator implements View.OnLayoutChangeListener { ...@@ -97,8 +97,9 @@ class PasswordManagerDialogMediator implements View.OnLayoutChangeListener {
private boolean hasSufficientSpaceForIllustration(int heightPx) { private boolean hasSufficientSpaceForIllustration(int heightPx) {
// If |mResources| is null, it means that the dialog was not initialized yet. // If |mResources| is null, it means that the dialog was not initialized yet.
if (mResources == null) return false; if (mResources == null) return false;
heightPx -= TabModalPresenter.getContainerTopMargin(mResources, mContainerHeightResource); heightPx -=
heightPx -= TabModalPresenter.getContainerBottomMargin(mFullscreenManager); ChromeTabModalPresenter.getContainerTopMargin(mResources, mContainerHeightResource);
heightPx -= ChromeTabModalPresenter.getContainerBottomMargin(mFullscreenManager);
return heightPx >= mResources.getDimensionPixelSize( return heightPx >= mResources.getDimensionPixelSize(
R.dimen.password_manager_dialog_min_vertical_space_to_show_illustration); R.dimen.password_manager_dialog_min_vertical_space_to_show_illustration);
} }
......
...@@ -65,11 +65,11 @@ import org.chromium.ui.modelutil.PropertyModel; ...@@ -65,11 +65,11 @@ import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.test.util.UiRestriction; import org.chromium.ui.test.util.UiRestriction;
/** /**
* Tests for {@link TabModalPresenter}. * Tests for {@link ChromeTabModalPresenter}.
*/ */
@RunWith(ChromeJUnit4ClassRunner.class) @RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class TabModalPresenterTest { public class ChromeTabModalPresenterTest {
private class TestObserver extends EmptyTabObserver private class TestObserver extends EmptyTabObserver
implements UrlFocusChangeListener, ModalDialogTestUtils.TestDialogDismissedObserver { implements UrlFocusChangeListener, ModalDialogTestUtils.TestDialogDismissedObserver {
public final CallbackHelper onUrlFocusChangedCallback = new CallbackHelper(); public final CallbackHelper onUrlFocusChangedCallback = new CallbackHelper();
...@@ -98,7 +98,7 @@ public class TabModalPresenterTest { ...@@ -98,7 +98,7 @@ public class TabModalPresenterTest {
private ChromeTabbedActivity mActivity; private ChromeTabbedActivity mActivity;
private ModalDialogManager mManager; private ModalDialogManager mManager;
private TabModalPresenter mTabModalPresenter; private ChromeTabModalPresenter mTabModalPresenter;
private TestObserver mTestObserver; private TestObserver mTestObserver;
private Integer mExpectedDismissalCause; private Integer mExpectedDismissalCause;
...@@ -112,7 +112,8 @@ public class TabModalPresenterTest { ...@@ -112,7 +112,8 @@ public class TabModalPresenterTest {
.getToolbarLayoutForTesting() .getToolbarLayoutForTesting()
.getLocationBar() .getLocationBar()
.addUrlFocusChangeListener(mTestObserver); .addUrlFocusChangeListener(mTestObserver);
mTabModalPresenter = (TabModalPresenter) mManager.getPresenterForTest(ModalDialogType.TAB); mTabModalPresenter =
(ChromeTabModalPresenter) mManager.getPresenterForTest(ModalDialogType.TAB);
mTabModalPresenter.disableAnimationForTest(); mTabModalPresenter.disableAnimationForTest();
} }
...@@ -125,8 +126,8 @@ public class TabModalPresenterTest { ...@@ -125,8 +126,8 @@ public class TabModalPresenterTest {
createDialog(mActivity, mActivity.getModalDialogManager(), "1", null); createDialog(mActivity, mActivity.getModalDialogManager(), "1", null);
showDialog(mManager, dialog1, ModalDialogType.TAB); showDialog(mManager, dialog1, ModalDialogType.TAB);
TabModalPresenter presenter = ChromeTabModalPresenter presenter =
(TabModalPresenter) mManager.getPresenterForTest(ModalDialogType.TAB); (ChromeTabModalPresenter) mManager.getPresenterForTest(ModalDialogType.TAB);
final View dialogContainer = presenter.getDialogContainerForTest(); final View dialogContainer = presenter.getDialogContainerForTest();
final View controlContainer = mActivity.findViewById(R.id.control_container); final View controlContainer = mActivity.findViewById(R.id.control_container);
final ViewGroup containerParent = presenter.getContainerParentForTest(); final ViewGroup containerParent = presenter.getContainerParentForTest();
......
...@@ -9,12 +9,14 @@ android_library("java") { ...@@ -9,12 +9,14 @@ android_library("java") {
"java/src/org/chromium/components/browser_ui/modaldialog/AppModalPresenter.java", "java/src/org/chromium/components/browser_ui/modaldialog/AppModalPresenter.java",
"java/src/org/chromium/components/browser_ui/modaldialog/ModalDialogView.java", "java/src/org/chromium/components/browser_ui/modaldialog/ModalDialogView.java",
"java/src/org/chromium/components/browser_ui/modaldialog/ModalDialogViewBinder.java", "java/src/org/chromium/components/browser_ui/modaldialog/ModalDialogViewBinder.java",
"java/src/org/chromium/components/browser_ui/modaldialog/TabModalPresenter.java",
] ]
deps = [ deps = [
":java_resources", ":java_resources",
"//base:base_java", "//base:base_java",
"//components/browser_ui/widget/android:java", "//components/browser_ui/widget/android:java",
"//content/public/android:content_java",
"//third_party/android_deps:android_support_v7_appcompat_java", "//third_party/android_deps:android_support_v7_appcompat_java",
"//third_party/android_deps:com_android_support_design_java", "//third_party/android_deps:com_android_support_design_java",
"//ui/android:ui_java", "//ui/android:ui_java",
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
<FrameLayout <FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="UnusedResources"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
...@@ -14,4 +16,4 @@ ...@@ -14,4 +16,4 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/modal_dialog_scrim_color" /> android:background="@color/modal_dialog_scrim_color" />
</FrameLayout> </FrameLayout>
\ No newline at end of file
// Copyright 2020 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.components.browser_ui.modaldialog;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import org.chromium.content_public.browser.SelectionPopupController;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.interpolators.BakedBezierInterpolator;
import org.chromium.ui.modaldialog.DialogDismissalCause;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
/**
* A base class for presenting a single tab modal dialog.
*
* Several abstract methods allow embedder-specific specializations.
*/
public abstract class TabModalPresenter extends ModalDialogManager.Presenter {
private static final int ENTER_EXIT_ANIMATION_DURATION_MS = 200;
private final Context mContext;
private ViewGroup mDialogContainer;
private ModalDialogView mDialogView;
private PropertyModelChangeProcessor<PropertyModel, ModalDialogView, PropertyKey>
mModelChangeProcessor;
/** Whether the action bar on selected text is temporarily cleared for showing dialogs. */
private boolean mDidClearTextControls;
/** Enter and exit animation duration that can be overwritten in tests. */
private int mEnterExitAnimationDurationMs;
private class ViewBinder extends ModalDialogViewBinder {
@Override
public void bind(PropertyModel model, ModalDialogView view, PropertyKey propertyKey) {
if (ModalDialogProperties.CANCEL_ON_TOUCH_OUTSIDE == propertyKey) {
assert mDialogContainer != null;
if (model.get(ModalDialogProperties.CANCEL_ON_TOUCH_OUTSIDE)) {
mDialogContainer.setOnClickListener((v) -> {
dismissCurrentDialog(DialogDismissalCause.NAVIGATE_BACK_OR_TOUCH_OUTSIDE);
});
} else {
mDialogContainer.setOnClickListener(null);
}
} else {
super.bind(model, view, propertyKey);
}
}
}
/**
* Constructor for initializing dialog container.
* @param context The context for inflating UI.
*/
public TabModalPresenter(Context context) {
mContext = context;
mEnterExitAnimationDurationMs = ENTER_EXIT_ANIMATION_DURATION_MS;
}
/**
* @return a ViewGroup that will host {@link TabModalPresenter#mDialogView}.
*/
protected abstract ViewGroup createDialogContainer();
/**
* Called when {@link TabModalPresenter#mDialogContainer} should be displayed.
*/
protected abstract void showDialogContainer();
/**
* Set whether the browser controls access should be restricted.
*
* This is called any time a dialog view is being shown or hidden and should update browser
* state, e.g. breaking fullscreen or disabling certain browser controls as necessary.
*
* @param restricted Whether the browser controls access should be restricted.
*/
protected abstract void setBrowserControlsAccess(boolean restricted);
/**
* @return the container previously returned by {@link TabModalPresenter#createDialogContainer}.
*/
protected ViewGroup getDialogContainer() {
return mDialogContainer;
}
@Override
protected void addDialogView(PropertyModel model) {
if (mDialogContainer == null) mDialogContainer = createDialogContainer();
int style = model.get(ModalDialogProperties.PRIMARY_BUTTON_FILLED)
? R.style.Theme_Chromium_ModalDialog_FilledPrimaryButton
: R.style.Theme_Chromium_ModalDialog_TextPrimaryButton;
mDialogView =
(ModalDialogView) LayoutInflater.from(new ContextThemeWrapper(mContext, style))
.inflate(R.layout.modal_dialog_view, null);
mModelChangeProcessor =
PropertyModelChangeProcessor.create(model, mDialogView, new ViewBinder());
setBrowserControlsAccess(true);
showDialogContainer();
}
@Override
protected void removeDialogView(PropertyModel model) {
setBrowserControlsAccess(false);
// The dialog view may not have been added to the container yet, e.g. if the enter animation
// has not yet started.
if (ViewCompat.isAttachedToWindow(mDialogView)) {
runExitAnimation();
}
if (mModelChangeProcessor != null) {
mModelChangeProcessor.destroy();
mModelChangeProcessor = null;
}
mDialogView = null;
}
/**
* Change view hierarchy for the dialog container to be either the front most or beneath the
* toolbar.
*
* @param toFront Whether the dialog container should be brought to the front.
*/
public void updateContainerHierarchy(boolean toFront) {
if (toFront) {
mDialogView.announceForAccessibility(getContentDescription(getDialogModel()));
mDialogView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
mDialogView.requestFocus();
} else {
mDialogView.clearFocus();
mDialogView.setImportantForAccessibility(
View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
}
}
/**
* Dismisses the text selection action bar that would otherwise obscure a visible dialog, but
* preserves the text selection.
*
* @param webContents the WebContents that the dialog is showing over.
* @param save true if a dialog is showing and text selection should be saved; false if a dialog
* is hiding and text selection should be restored.
*/
protected void saveOrRestoreTextSelection(@NonNull WebContents webContents, boolean save) {
if (save) {
// Dismiss the action bar that obscures the dialogs but preserve the text selection.
SelectionPopupController controller =
SelectionPopupController.fromWebContents(webContents);
controller.setPreserveSelectionOnNextLossOfFocus(true);
webContents.getViewAndroidDelegate().getContainerView().clearFocus();
controller.updateTextSelectionUI(false);
mDidClearTextControls = true;
} else if (mDidClearTextControls) {
// Show the action bar back if it was dismissed when the dialogs were showing.
mDidClearTextControls = false;
SelectionPopupController.fromWebContents(webContents).updateTextSelectionUI(true);
}
}
/**
* Inserts {@link TabModalPresenter#mDialogView} into {@link TabModalPresenter#mDialogContainer}
* and animates the container into view.
*
* Exposed to subclasses as they may want to control the exact start time of the animation.
*/
protected void runEnterAnimation() {
mDialogContainer.animate().cancel();
FrameLayout.LayoutParams params =
new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER);
mDialogView.setBackgroundResource(org.chromium.ui.R.drawable.popup_bg_tinted);
mDialogContainer.addView(mDialogView, params);
mDialogContainer.setAlpha(0f);
mDialogContainer.setVisibility(View.VISIBLE);
mDialogContainer.animate()
.setDuration(mEnterExitAnimationDurationMs)
.alpha(1f)
.setInterpolator(BakedBezierInterpolator.FADE_IN_CURVE)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
updateContainerHierarchy(true);
}
})
.start();
}
private void runExitAnimation() {
final View dialogView = mDialogView;
// Clear focus so that keyboard can hide accordingly while entering tab switcher.
dialogView.clearFocus();
mDialogContainer.animate().cancel();
mDialogContainer.animate()
.setDuration(mEnterExitAnimationDurationMs)
.alpha(0f)
.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mDialogContainer.setVisibility(View.GONE);
mDialogContainer.removeView(dialogView);
}
})
.start();
}
@VisibleForTesting
public View getDialogContainerForTest() {
return mDialogContainer;
}
@VisibleForTesting
public void disableAnimationForTest() {
mEnterExitAnimationDurationMs = 0;
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment