Commit eb6596e7 authored by Evan Stade's avatar Evan Stade Committed by Commit Bot

Refactor DisplayCutoutController to prepare for componentization.

DisplayCutoutController is split into DisplayCutoutController
(destined for //components) and DisplayCutoutTabHelper (which stays
in //chrome). Relocation/reuse will occur in a follow up CL.

TBR=dtrainor@chromium.org
Bug: 1095714

Change-Id: Ie119069ae4756baf73acc23d655665fce1d4c035
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2296888
Commit-Queue: Evan Stade <estade@chromium.org>
Reviewed-by: default avatarBecca Hughes <beccahughes@chromium.org>
Cr-Commit-Position: refs/heads/master@{#790402}
parent 530cf097
......@@ -513,6 +513,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/directactions/MenuDirectActionHandler.java",
"java/src/org/chromium/chrome/browser/directactions/SimpleDirectActionHandler.java",
"java/src/org/chromium/chrome/browser/display_cutout/DisplayCutoutController.java",
"java/src/org/chromium/chrome/browser/display_cutout/DisplayCutoutTabHelper.java",
"java/src/org/chromium/chrome/browser/document/ChromeIntentUtil.java",
"java/src/org/chromium/chrome/browser/document/ChromeLauncherActivity.java",
"java/src/org/chromium/chrome/browser/document/DocumentWebContentsDelegate.java",
......
......@@ -14,29 +14,21 @@ import android.view.WindowManager.LayoutParams;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.UserData;
import org.chromium.base.UserDataHost;
import org.chromium.blink.mojom.ViewportFit;
import org.chromium.chrome.browser.ChromeActivity;
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.components.browser_ui.widget.InsetObserverView;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.WebContentsObserver;
import org.chromium.ui.base.WindowAndroid;
/**
* Controls the display cutout state for the tab.
* Controls the display safe area for a {@link WebContents} and the cutout mode for an {@link
* Activity} window.
*
* The WebContents is updated with the safe area continuously, as long as {@link
* Delegate#getAttachedActivity()} returns a non-null value. The cutout mode is set on the
* Activity's window only in P+, and only when the associated WebContents is fullscreen.
*/
public class DisplayCutoutController implements InsetObserverView.WindowInsetObserver, UserData {
private static final Class<DisplayCutoutController> USER_DATA_KEY =
DisplayCutoutController.class;
/** The tab that this controller belongs to. */
private Tab mTab;
public class DisplayCutoutController implements InsetObserverView.WindowInsetObserver {
/** {@link Window} of the current {@link Activity}. */
private Window mWindow;
......@@ -49,73 +41,47 @@ public class DisplayCutoutController implements InsetObserverView.WindowInsetObs
*/
private @Nullable InsetObserverView mInsetObserverView;
/** 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.
maybeUpdateLayout();
}
@Override
public void onInteractabilityChanged(Tab tab, boolean interactable) {
// Force a layout update if the tab is now in the foreground.
maybeUpdateLayout();
}
@Override
public void onActivityAttachmentChanged(Tab tab, @Nullable WindowAndroid window) {
assert tab == mTab;
if (window != null) {
maybeAddInsetObserver(tab.getWindowAndroid().getActivity().get());
} else {
maybeRemoveInsetObserver();
}
}
};
public static DisplayCutoutController from(Tab tab) {
UserDataHost host = tab.getUserDataHost();
DisplayCutoutController controller = host.getUserData(USER_DATA_KEY);
return controller == null
? host.setUserData(USER_DATA_KEY, new DisplayCutoutController(tab))
: controller;
/** An interface for providing embedder-specific behavior to the controller. */
interface Delegate {
/** Returns the activity this controller is associated with, if there is one. */
@Nullable
Activity getAttachedActivity();
/**
* Returns the {@link WebContents} this controller should update the safe area for, if
* there is one.
*/
@Nullable
WebContents getWebContents();
/** Returns the view this controller uses for safe area updates, if there is one. */
@Nullable
InsetObserverView getInsetObserverView();
/** Returns whether the user can interact with the associated WebContents/UI element. */
boolean isInteractable();
}
private final Delegate mDelegate;
/**
* Constructs a new DisplayCutoutController for a specific tab.
* @param tab The tab that this controller belongs to.
*/
@VisibleForTesting
DisplayCutoutController(Tab tab) {
mTab = tab;
tab.addObserver(mTabObserver);
maybeAddInsetObserver(tab.getWindowAndroid().getActivity().get());
public DisplayCutoutController(Delegate delegate) {
mDelegate = delegate;
maybeAddInsetObserver();
}
/**
* Add an observer to {@link InsetObserverView} if we have not already added
* one.
*/
private void maybeAddInsetObserver(Activity activity) {
/** Add an observer to {@link InsetObserverView} if we have not already added one. */
void maybeAddInsetObserver() {
Activity activity = mDelegate.getAttachedActivity();
if (mInsetObserverView != null || activity == null) return;
mInsetObserverView = ((ChromeActivity) activity).getInsetObserverView();
mInsetObserverView = mDelegate.getInsetObserverView();
if (mInsetObserverView == null) return;
mInsetObserverView.addObserver(this);
mWindow = activity.getWindow();
}
/**
* Remove the observer added to {@link InsetObserverView} if we have added
* one.
*/
private void maybeRemoveInsetObserver() {
/** Remove the observer added to {@link InsetObserverView} if we have added one. */
void maybeRemoveInsetObserver() {
if (mInsetObserverView == null) return;
mInsetObserverView.removeObserver(this);
......@@ -123,9 +89,7 @@ public class DisplayCutoutController implements InsetObserverView.WindowInsetObs
mWindow = null;
}
@Override
public void destroy() {
mTab.removeObserver(mTabObserver);
maybeRemoveInsetObserver();
}
......@@ -134,6 +98,10 @@ public class DisplayCutoutController implements InsetObserverView.WindowInsetObs
* @param value The new viewport fit value.
*/
public void setViewportFit(@WebContentsObserver.ViewportFitType int value) {
if (value != ViewportFit.AUTO) {
assert mDelegate.getWebContents().isFullscreenForCurrentTab();
}
if (value == mViewportFit) return;
mViewportFit = value;
......@@ -143,7 +111,7 @@ public class DisplayCutoutController implements InsetObserverView.WindowInsetObs
/** Implements {@link WindowInsetsObserver}. */
@Override
public void onSafeAreaChanged(Rect area) {
WebContents webContents = mTab.getWebContents();
WebContents webContents = mDelegate.getWebContents();
if (webContents == null) return;
float dipScale = getDipScale();
......@@ -163,18 +131,13 @@ public class DisplayCutoutController implements InsetObserverView.WindowInsetObs
* @param dipScale The devices dip scale as an integer.
* @return The CSS pixel value adjusted for scale.
*/
private int adjustInsetForScale(int inset, float dipScale) {
private static int adjustInsetForScale(int inset, float dipScale) {
return (int) Math.ceil(inset / dipScale);
}
@VisibleForTesting
static void initForTesting(UserDataHost host, DisplayCutoutController controller) {
host.setUserData(USER_DATA_KEY, controller);
}
@VisibleForTesting
protected float getDipScale() {
return mTab.getWindowAndroid().getDisplay().getDipScale();
return mDelegate.getWebContents().getTopLevelNativeWindow().getDisplay().getDipScale();
}
/**
......@@ -186,7 +149,7 @@ public class DisplayCutoutController implements InsetObserverView.WindowInsetObs
@TargetApi(Build.VERSION_CODES.P)
protected int getDisplayCutoutMode() {
// If we are not interactable then force the default mode.
if (!mTab.isUserInteractable()) {
if (!mDelegate.isInteractable()) {
return LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
}
......@@ -223,4 +186,12 @@ public class DisplayCutoutController implements InsetObserverView.WindowInsetObs
attributes.layoutInDisplayCutoutMode = getDisplayCutoutMode();
setWindowAttributes(attributes);
}
void onActivityAttachmentChanged(@Nullable WindowAndroid window) {
if (window == null) {
maybeRemoveInsetObserver();
} else {
maybeAddInsetObserver();
}
}
}
// 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.chrome.browser.display_cutout;
import android.app.Activity;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.UserData;
import org.chromium.base.UserDataHost;
import org.chromium.chrome.browser.ChromeActivity;
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.components.browser_ui.widget.InsetObserverView;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.WebContentsObserver;
import org.chromium.ui.base.WindowAndroid;
/**
* Wraps a {@link DisplayCutoutController} for a Chrome {@link Tab}.
*
* 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 InsetObserverView getInsetObserverView() {
Activity activity = getAttachedActivity();
return activity == null ? null : ((ChromeActivity) activity).getInsetObserverView();
}
@Override
public boolean isInteractable() {
return mTab.isUserInteractable();
}
}
/**
* 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 = new DisplayCutoutController(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();
}
@VisibleForTesting
static void initForTesting(Tab tab, DisplayCutoutController controller) {
DisplayCutoutTabHelper tabHelper = new DisplayCutoutTabHelper(tab);
tabHelper.mCutoutController = controller;
tab.getUserDataHost().setUserData(USER_DATA_KEY, tabHelper);
}
}
......@@ -76,5 +76,8 @@ specific_include_rules = {
],
'TabViewManagerImpl\.java': [
"+chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BrowserControlsMarginSupplier.java",
],
'TabWebContentsObserver\.java': [
"+chrome/android/java/src/org/chromium/chrome/browser/display_cutout/DisplayCutoutTabHelper.java",
]
}
......@@ -20,7 +20,7 @@ import org.chromium.base.ObserverList.RewindableIterator;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.chrome.browser.AppHooks;
import org.chromium.chrome.browser.SwipeRefreshHandler;
import org.chromium.chrome.browser.display_cutout.DisplayCutoutController;
import org.chromium.chrome.browser.display_cutout.DisplayCutoutTabHelper;
import org.chromium.chrome.browser.media.MediaCaptureNotificationService;
import org.chromium.chrome.browser.policy.PolicyAuditor;
import org.chromium.chrome.browser.policy.PolicyAuditor.AuditEvent;
......@@ -347,7 +347,7 @@ public class TabWebContentsObserver extends TabWebContentsUserData {
@Override
public void viewportFitChanged(@WebContentsObserver.ViewportFitType int value) {
DisplayCutoutController.from(mTab).setViewportFit(value);
DisplayCutoutTabHelper.from(mTab).setViewportFit(value);
}
@Override
......
......@@ -59,8 +59,8 @@ public class DisplayCutoutTestRule<T extends ChromeActivity> extends ChromeActiv
private boolean mDeviceHasCutout = true;
private float mDipScale = 1;
TestDisplayCutoutController(Tab tab) {
super(tab);
TestDisplayCutoutController(DisplayCutoutController.Delegate delegate) {
super(delegate);
}
@Override
......@@ -159,10 +159,11 @@ public class DisplayCutoutTestRule<T extends ChromeActivity> extends ChromeActiv
protected void setUp() {
mTab = getActivity().getActivityTab();
mTestController = new TestDisplayCutoutController(mTab);
mTestController = new TestDisplayCutoutController(
new DisplayCutoutTabHelper.ChromeDisplayCutoutDelegate(mTab));
TestThreadUtils.runOnUiThreadBlocking(
() -> DisplayCutoutController.initForTesting(
mTab.getUserDataHost(), mTestController));
() -> DisplayCutoutTabHelper.initForTesting(mTab, mTestController));
mListener = new FullscreenToggleObserver();
getActivity().getFullscreenManager().addObserver(mListener);
......
......@@ -31,6 +31,7 @@ import org.chromium.chrome.browser.tab.TabImpl;
import org.chromium.chrome.browser.tab.TabObserver;
import org.chromium.chrome.browser.tab.TabSelectionType;
import org.chromium.components.browser_ui.widget.InsetObserverView;
import org.chromium.content_public.browser.WebContents;
import org.chromium.testing.local.LocalRobolectricTestRunner;
import org.chromium.ui.base.WindowAndroid;
......@@ -45,6 +46,9 @@ public class DisplayCutoutControllerTest {
@Mock
private TabImpl mTab;
@Mock
private WebContents mWebContents;
@Mock
private WindowAndroid mWindowAndroid;
......@@ -60,7 +64,8 @@ public class DisplayCutoutControllerTest {
@Mock
private InsetObserverView mInsetObserver;
private DisplayCutoutController mDisplayCutoutController;
private DisplayCutoutTabHelper mDisplayCutoutTabHelper;
private DisplayCutoutController mController;
private WeakReference<Activity> mActivityRef;
......@@ -75,28 +80,32 @@ public class DisplayCutoutControllerTest {
when(mChromeActivity.getWindow()).thenReturn(mWindow);
when(mWindow.getAttributes()).thenReturn(new LayoutParams());
when(mTab.getWindowAndroid()).thenReturn(mWindowAndroid);
when(mTab.getWebContents()).thenReturn(mWebContents);
when(mWebContents.isFullscreenForCurrentTab()).thenReturn(true);
when(mWindowAndroid.getActivity()).thenReturn(mActivityRef);
when(mChromeActivity.getInsetObserverView()).thenReturn(mInsetObserver);
mDisplayCutoutController = spy(new DisplayCutoutController(mTab));
mDisplayCutoutTabHelper = spy(new DisplayCutoutTabHelper(mTab));
mController = spy(mDisplayCutoutTabHelper.mCutoutController);
mDisplayCutoutTabHelper.mCutoutController = mController;
}
@Test
@SmallTest
public void testViewportFitUpdate() {
verify(mDisplayCutoutController, never()).maybeUpdateLayout();
verify(mController, never()).maybeUpdateLayout();
mDisplayCutoutController.setViewportFit(ViewportFit.COVER);
verify(mDisplayCutoutController).maybeUpdateLayout();
mDisplayCutoutTabHelper.setViewportFit(ViewportFit.COVER);
verify(mController).maybeUpdateLayout();
}
@Test
@SmallTest
public void testViewportFitUpdateNotChanged() {
verify(mDisplayCutoutController, never()).maybeUpdateLayout();
verify(mController, never()).maybeUpdateLayout();
mDisplayCutoutController.setViewportFit(ViewportFit.AUTO);
verify(mDisplayCutoutController, never()).maybeUpdateLayout();
mDisplayCutoutTabHelper.setViewportFit(ViewportFit.AUTO);
verify(mController, never()).maybeUpdateLayout();
}
@Test
......@@ -104,9 +113,9 @@ public class DisplayCutoutControllerTest {
public void testCutoutModeWhenAutoAndInteractable() {
when(mTab.isUserInteractable()).thenReturn(true);
mDisplayCutoutController.setViewportFit(ViewportFit.AUTO);
mDisplayCutoutTabHelper.setViewportFit(ViewportFit.AUTO);
Assert.assertEquals(LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT,
mDisplayCutoutController.getDisplayCutoutMode());
mController.getDisplayCutoutMode());
}
@Test
......@@ -114,9 +123,9 @@ public class DisplayCutoutControllerTest {
public void testCutoutModeWhenCoverAndInteractable() {
when(mTab.isUserInteractable()).thenReturn(true);
mDisplayCutoutController.setViewportFit(ViewportFit.COVER);
mDisplayCutoutTabHelper.setViewportFit(ViewportFit.COVER);
Assert.assertEquals(LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES,
mDisplayCutoutController.getDisplayCutoutMode());
mController.getDisplayCutoutMode());
}
@Test
......@@ -124,9 +133,9 @@ public class DisplayCutoutControllerTest {
public void testCutoutModeWhenCoverForcedAndInteractable() {
when(mTab.isUserInteractable()).thenReturn(true);
mDisplayCutoutController.setViewportFit(ViewportFit.COVER_FORCED_BY_USER_AGENT);
mDisplayCutoutTabHelper.setViewportFit(ViewportFit.COVER_FORCED_BY_USER_AGENT);
Assert.assertEquals(LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES,
mDisplayCutoutController.getDisplayCutoutMode());
mController.getDisplayCutoutMode());
}
@Test
......@@ -134,41 +143,41 @@ public class DisplayCutoutControllerTest {
public void testCutoutModeWhenContainAndInteractable() {
when(mTab.isUserInteractable()).thenReturn(true);
mDisplayCutoutController.setViewportFit(ViewportFit.CONTAIN);
mDisplayCutoutTabHelper.setViewportFit(ViewportFit.CONTAIN);
Assert.assertEquals(LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER,
mDisplayCutoutController.getDisplayCutoutMode());
mController.getDisplayCutoutMode());
}
@Test
@SmallTest
public void testCutoutModeWhenAutoAndNotInteractable() {
mDisplayCutoutController.setViewportFit(ViewportFit.AUTO);
mDisplayCutoutTabHelper.setViewportFit(ViewportFit.AUTO);
Assert.assertEquals(LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT,
mDisplayCutoutController.getDisplayCutoutMode());
mController.getDisplayCutoutMode());
}
@Test
@SmallTest
public void testCutoutModeWhenCoverAndNotInteractable() {
mDisplayCutoutController.setViewportFit(ViewportFit.COVER);
mDisplayCutoutTabHelper.setViewportFit(ViewportFit.COVER);
Assert.assertEquals(LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT,
mDisplayCutoutController.getDisplayCutoutMode());
mController.getDisplayCutoutMode());
}
@Test
@SmallTest
public void testCutoutModeWhenCoverForcedAndNotInteractable() {
mDisplayCutoutController.setViewportFit(ViewportFit.COVER_FORCED_BY_USER_AGENT);
mDisplayCutoutTabHelper.setViewportFit(ViewportFit.COVER_FORCED_BY_USER_AGENT);
Assert.assertEquals(LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT,
mDisplayCutoutController.getDisplayCutoutMode());
mController.getDisplayCutoutMode());
}
@Test
@SmallTest
public void testCutoutModeWhenContainAndNotInteractable() {
mDisplayCutoutController.setViewportFit(ViewportFit.CONTAIN);
mDisplayCutoutTabHelper.setViewportFit(ViewportFit.CONTAIN);
Assert.assertEquals(LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT,
mDisplayCutoutController.getDisplayCutoutMode());
mController.getDisplayCutoutMode());
}
@Test
......@@ -176,7 +185,7 @@ public class DisplayCutoutControllerTest {
public void testLayoutOnInteractability_True() {
// In this test we are checking for a side effect of maybeUpdateLayout.
// This is because the tab observer holds a reference to the original
// mDisplayCutoutController and not the spied one.
// mDisplayCutoutTabHelper and not the spied one.
verify(mTab).addObserver(mTabObserverCaptor.capture());
reset(mTab);
......@@ -189,7 +198,7 @@ public class DisplayCutoutControllerTest {
public void testLayoutOnInteractability_False() {
// In this test we are checking for a side effect of maybeUpdateLayout.
// This is because the tab observer holds a reference to the original
// mDisplayCutoutController and not the spied one.
// mDisplayCutoutTabHelper and not the spied one.
verify(mTab).addObserver(mTabObserverCaptor.capture());
reset(mTab);
......@@ -214,7 +223,7 @@ public class DisplayCutoutControllerTest {
public void testLayoutOnShown() {
// In this test we are checking for a side effect of maybeUpdateLayout.
// This is because the tab observer holds a reference to the original
// mDisplayCutoutController and not the spied one.
// mDisplayCutoutTabHelper and not the spied one.
verify(mTab).addObserver(mTabObserverCaptor.capture());
reset(mTab);
......
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