Commit 76a9169f authored by Scott Violet's avatar Scott Violet Committed by Commit Bot

weblayer: adds BrowserControlsOffsetCallback

This adds a callback that is notified the offset the top/bottom
view changes. This allows the embedder to update the visual in
response to the offsets changing.

Change-Id: I1f2f8c8318b8250ac74fad5eaedeb59ca7454cf5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2440977
Commit-Queue: Scott Violet <sky@chromium.org>
Reviewed-by: default avatarBo <boliu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#813325}
parent 9a49d977
...@@ -68,6 +68,7 @@ android_library("weblayer_java_tests") { ...@@ -68,6 +68,7 @@ android_library("weblayer_java_tests") {
android_library("weblayer_private_java_tests") { android_library("weblayer_private_java_tests") {
testonly = true testonly = true
sources = [ sources = [
"src/org/chromium/weblayer/test/BrowserControlsOffsetCallbackTest.java",
"src/org/chromium/weblayer/test/BrowserControlsTest.java", "src/org/chromium/weblayer/test/BrowserControlsTest.java",
"src/org/chromium/weblayer/test/FullscreenCallbackPrivateTest.java", "src/org/chromium/weblayer/test/FullscreenCallbackPrivateTest.java",
"src/org/chromium/weblayer/test/GeolocationTest.java", "src/org/chromium/weblayer/test/GeolocationTest.java",
......
// 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.weblayer.test;
import android.graphics.Point;
import android.os.SystemClock;
import android.util.Range;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
import androidx.test.filters.SmallTest;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.DisabledTest;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.weblayer.BrowserControlsOffsetCallback;
import org.chromium.weblayer.shell.InstrumentationActivity;
import java.util.ArrayList;
import java.util.List;
/**
* Test for ScrollOffsetCallback.
*/
@RunWith(WebLayerJUnit4ClassRunner.class)
@CommandLineFlags.Add("enable-features=ImmediatelyHideBrowserControlsForTest")
public class BrowserControlsOffsetCallbackTest {
@Rule
public InstrumentationActivityTestRule mActivityTestRule =
new InstrumentationActivityTestRule();
private static final class BrowserControlsOffsetCallbackImpl
extends BrowserControlsOffsetCallback {
// All offsets are added to either |mTopOffsets| or |mBottomOffsets|.
public List<Integer> mTopOffsets = new ArrayList<>();
public List<Integer> mBottomOffsets = new ArrayList<>();
public CallbackHelper mCallbackHelper = new CallbackHelper();
// If non-null an offset and an offset lies in the specified range
// mCallbackHelper.notifyCalled() is invoked.
public Range<Integer> mTopTriggerRange;
public Range<Integer> mBottomTriggerRange;
@Override
public void onTopViewOffsetChanged(int offset) {
mTopOffsets.add(offset);
if (mTopTriggerRange != null && mTopTriggerRange.contains(offset)) {
mTopTriggerRange = null;
mCallbackHelper.notifyCalled();
}
}
@Override
public void onBottomViewOffsetChanged(int offset) {
mBottomOffsets.add(offset);
if (mBottomTriggerRange != null && mBottomTriggerRange.contains(offset)) {
mBottomTriggerRange = null;
mCallbackHelper.notifyCalled();
}
}
}
private BrowserControlsOffsetCallbackImpl mBrowserControlsOffsetCallback =
new BrowserControlsOffsetCallbackImpl();
private BrowserControlsHelper mBrowserControlsHelper;
private Point mCurrentDragPoint;
private boolean mInDrag;
@Before
public void setUp() throws Throwable {
final String url = mActivityTestRule.getTestDataURL("tall_page.html");
InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(url);
TestThreadUtils.runOnUiThreadBlocking(() -> {
activity.getBrowser().registerBrowserControlsOffsetCallback(
mBrowserControlsOffsetCallback);
});
mBrowserControlsHelper =
BrowserControlsHelper.createAndBlockUntilBrowserControlsInitializedInSetUp(
activity);
}
private void startDrag() {
View view = mActivityTestRule.getActivity().getWindow().getDecorView();
Assert.assertFalse(mInDrag);
mInDrag = true;
view.post(() -> {
mCurrentDragPoint = new Point(view.getWidth() / 2, view.getHeight() / 2);
long eventTime = SystemClock.uptimeMillis();
view.dispatchTouchEvent(MotionEvent.obtain(eventTime, eventTime,
MotionEvent.ACTION_DOWN, mCurrentDragPoint.x, mCurrentDragPoint.y, 0));
});
}
private void dragBy(final int deltaY) {
View view = mActivityTestRule.getActivity().getWindow().getDecorView();
Assert.assertTrue(mInDrag);
view.post(() -> {
long eventTime = SystemClock.uptimeMillis();
mCurrentDragPoint.y += deltaY;
view.dispatchTouchEvent(MotionEvent.obtain(eventTime, eventTime,
MotionEvent.ACTION_MOVE, mCurrentDragPoint.x, mCurrentDragPoint.y, 0));
});
}
@Test
@SmallTest
@DisabledTest(message = "TODO(sky): enable")
public void testTopScroll() throws Exception {
int topViewHeight = mBrowserControlsHelper.getTopViewHeight();
CallbackHelper callbackHelper = mBrowserControlsOffsetCallback.mCallbackHelper;
// Scroll half the height.
mBrowserControlsOffsetCallback.mTopTriggerRange = new Range(-topViewHeight + 2, -2);
startDrag();
dragBy(-topViewHeight / 2);
int callCount = 0;
callbackHelper.waitForCallback(callCount++);
TestThreadUtils.runOnUiThreadBlocking(() -> {
mBrowserControlsOffsetCallback.mTopOffsets.clear();
mBrowserControlsOffsetCallback.mTopTriggerRange =
new Range(-topViewHeight, -topViewHeight);
});
// Scroll a lot to ensure top view completely hides.
dragBy(-topViewHeight);
callbackHelper.waitForCallback(callCount++);
TestThreadUtils.runOnUiThreadBlocking(() -> {
mBrowserControlsOffsetCallback.mTopOffsets.clear();
mBrowserControlsOffsetCallback.mTopTriggerRange = new Range(-topViewHeight + 2, -2);
});
// Scroll up half the height to trigger showing again.
dragBy(topViewHeight / 2);
callbackHelper.waitForCallback(callCount++);
TestThreadUtils.runOnUiThreadBlocking(() -> {
mBrowserControlsOffsetCallback.mTopOffsets.clear();
mBrowserControlsOffsetCallback.mTopTriggerRange = new Range(0, 0);
});
// And enough to be fully visible.
dragBy(topViewHeight);
callbackHelper.waitForCallback(callCount++);
}
@Test
@SmallTest
@DisabledTest(message = "TODO(sky): enable")
public void testBottomScroll() throws Exception {
CallbackHelper callbackHelper = mBrowserControlsOffsetCallback.mCallbackHelper;
int topViewHeight = mBrowserControlsHelper.getTopViewHeight();
InstrumentationActivity activity = mActivityTestRule.getActivity();
View bottomView = TestThreadUtils.runOnUiThreadBlocking(() -> {
TextView view = new TextView(activity);
view.setText("BOTTOM");
activity.getBrowser().setBottomView(view);
return view;
});
mBrowserControlsHelper.waitForBrowserControlsViewToBeVisible(bottomView);
int bottomViewHeight =
TestThreadUtils.runOnUiThreadBlocking(() -> { return bottomView.getHeight(); });
Assert.assertTrue(bottomViewHeight > 0);
// The amount necessary to scroll is the sum of the two views. This is because the page
// height is reduced by the sum of these two.
int maxViewsHeight = topViewHeight + bottomViewHeight;
// Wait for cc to see the bottom height. This is very important, as scrolling is gated by
// cc getting the bottom height.
mBrowserControlsHelper.waitForBrowserControlsMetadataState(topViewHeight, bottomViewHeight);
// Move by the size of the controls, which should hide both.
TestThreadUtils.runOnUiThreadBlocking(() -> {
mBrowserControlsOffsetCallback.mTopTriggerRange =
new Range(-topViewHeight, -topViewHeight);
mBrowserControlsOffsetCallback.mBottomTriggerRange =
new Range(bottomViewHeight, bottomViewHeight);
});
EventUtils.simulateDragFromCenterOfView(
activity.getWindow().getDecorView(), 0, -maxViewsHeight);
int callCount = 0;
// 2 is for the top and bottom.
callbackHelper.waitForCallback(callCount, 2);
callCount += 2;
// Move so top and bottom controls are shown again.
TestThreadUtils.runOnUiThreadBlocking(() -> {
mBrowserControlsOffsetCallback.mTopTriggerRange = new Range(0, 0);
mBrowserControlsOffsetCallback.mBottomTriggerRange = new Range(0, 0);
});
EventUtils.simulateDragFromCenterOfView(
activity.getWindow().getDecorView(), 0, maxViewsHeight);
// 2 is for the top and bottom.
callbackHelper.waitForCallback(callCount, 2);
callCount += 2;
}
}
...@@ -161,6 +161,11 @@ class BrowserControlsContainerView extends FrameLayout { ...@@ -161,6 +161,11 @@ class BrowserControlsContainerView extends FrameLayout {
* Requests that the browser controls visibility state be changed. * Requests that the browser controls visibility state be changed.
*/ */
void setAnimationConstraint(@BrowserControlsState int constraint); void setAnimationConstraint(@BrowserControlsState int constraint);
/**
* Called when the offset of the controls changes.
*/
void onOffsetsChanged(boolean isTop, int controlsOffset);
} }
BrowserControlsContainerView(Context context, ContentViewRenderView contentViewRenderView, BrowserControlsContainerView(Context context, ContentViewRenderView contentViewRenderView,
...@@ -498,6 +503,7 @@ class BrowserControlsContainerView extends FrameLayout { ...@@ -498,6 +503,7 @@ class BrowserControlsContainerView extends FrameLayout {
BrowserControlsContainerViewJni.get().setBottomControlsOffset( BrowserControlsContainerViewJni.get().setBottomControlsOffset(
mNativeBrowserControlsContainerView); mNativeBrowserControlsContainerView);
} }
mDelegate.onOffsetsChanged(mIsTop, mControlsOffset);
} }
private void reportHeightChange() { private void reportHeightChange() {
......
...@@ -85,6 +85,7 @@ public class BrowserImpl extends IBrowser.Stub implements View.OnAttachStateChan ...@@ -85,6 +85,7 @@ public class BrowserImpl extends IBrowser.Stub implements View.OnAttachStateChan
private Boolean mDarkThemeEnabled; private Boolean mDarkThemeEnabled;
private Float mFontScale; private Float mFontScale;
private boolean mViewAttachedToWindow; private boolean mViewAttachedToWindow;
private boolean mNotifyOnBrowserControlsOffsetsChanged;
// Created in the constructor from saved state and used in setClient(). // Created in the constructor from saved state and used in setClient().
private PersistenceInfo mPersistenceInfo; private PersistenceInfo mPersistenceInfo;
...@@ -478,6 +479,21 @@ public class BrowserImpl extends IBrowser.Stub implements View.OnAttachStateChan ...@@ -478,6 +479,21 @@ public class BrowserImpl extends IBrowser.Stub implements View.OnAttachStateChan
return mUrlBarController; return mUrlBarController;
} }
@Override
public void setBrowserControlsOffsetsEnabled(boolean enable) {
mNotifyOnBrowserControlsOffsetsChanged = enable;
}
public void onBrowserControlsOffsetsChanged(TabImpl tab, boolean isTop, int controlsOffset) {
if (mNotifyOnBrowserControlsOffsetsChanged && tab == getActiveTab()) {
try {
mClient.onBrowserControlsOffsetsChanged(isTop, controlsOffset);
} catch (RemoteException e) {
throw new APICallException(e);
}
}
}
public View getFragmentView() { public View getFragmentView() {
return getViewController().getView(); return getViewController().getView();
} }
......
...@@ -282,6 +282,12 @@ public final class BrowserViewController ...@@ -282,6 +282,12 @@ public final class BrowserViewController
ImplControlsVisibilityReason.ANIMATION, constraint); ImplControlsVisibilityReason.ANIMATION, constraint);
} }
@Override
public void onOffsetsChanged(boolean isTop, int controlsOffset) {
if (mTab == null) return;
mTab.getBrowser().onBrowserControlsOffsetsChanged(mTab, isTop, controlsOffset);
}
@Override @Override
public void onGestureStateChanged() { public void onGestureStateChanged() {
// This is called from |mGestureStateTracker|. // This is called from |mGestureStateTracker|.
......
...@@ -40,4 +40,7 @@ interface IBrowser { ...@@ -40,4 +40,7 @@ interface IBrowser {
void setTopViewAndScrollingBehavior(in IObjectWrapper view, in int minHeight, void setTopViewAndScrollingBehavior(in IObjectWrapper view, in int minHeight,
in boolean onlyExpandControlsAtPageTop, in boolean onlyExpandControlsAtPageTop,
in boolean animate) = 12; in boolean animate) = 12;
// Added in 87.
void setBrowserControlsOffsetsEnabled(in boolean enable) = 13;
} }
...@@ -14,4 +14,6 @@ interface IBrowserClient { ...@@ -14,4 +14,6 @@ interface IBrowserClient {
// Added in 87. // Added in 87.
IRemoteFragment createMediaRouteDialogFragment() = 3; IRemoteFragment createMediaRouteDialogFragment() = 3;
void onBrowserControlsOffsetsChanged(in boolean isTop,
in int controlsOffset) = 4;
} }
...@@ -37,6 +37,7 @@ android_library("java") { ...@@ -37,6 +37,7 @@ android_library("java") {
sources = [ sources = [
"org/chromium/weblayer/BroadcastReceiver.java", "org/chromium/weblayer/BroadcastReceiver.java",
"org/chromium/weblayer/Browser.java", "org/chromium/weblayer/Browser.java",
"org/chromium/weblayer/BrowserControlsOffsetCallback.java",
"org/chromium/weblayer/BrowserFragment.java", "org/chromium/weblayer/BrowserFragment.java",
"org/chromium/weblayer/BrowsingDataType.java", "org/chromium/weblayer/BrowsingDataType.java",
"org/chromium/weblayer/Callback.java", "org/chromium/weblayer/Callback.java",
......
...@@ -35,17 +35,21 @@ public class Browser { ...@@ -35,17 +35,21 @@ public class Browser {
private final ObserverList<TabListCallback> mTabListCallbacks; private final ObserverList<TabListCallback> mTabListCallbacks;
private final UrlBarController mUrlBarController; private final UrlBarController mUrlBarController;
private final ObserverList<BrowserControlsOffsetCallback> mBrowserControlsOffsetCallbacks;
// Constructor for test mocking. // Constructor for test mocking.
protected Browser() { protected Browser() {
mImpl = null; mImpl = null;
mTabListCallbacks = null; mTabListCallbacks = null;
mUrlBarController = null; mUrlBarController = null;
mBrowserControlsOffsetCallbacks = null;
} }
Browser(IBrowser impl, BrowserFragment fragment) { Browser(IBrowser impl, BrowserFragment fragment) {
mImpl = impl; mImpl = impl;
mFragment = fragment; mFragment = fragment;
mTabListCallbacks = new ObserverList<TabListCallback>(); mTabListCallbacks = new ObserverList<TabListCallback>();
mBrowserControlsOffsetCallbacks = new ObserverList<BrowserControlsOffsetCallback>();
try { try {
mImpl.setClient(new BrowserClientImpl()); mImpl.setClient(new BrowserClientImpl());
...@@ -282,6 +286,54 @@ public class Browser { ...@@ -282,6 +286,54 @@ public class Browser {
} }
} }
/**
* Registers {@link callback} to be notified when the offset of the top or bottom view changes.
*
* @param callback The BrowserControlsOffsetCallback to notify
*
* @since 87
*/
public void registerBrowserControlsOffsetCallback(
@NonNull BrowserControlsOffsetCallback callback) {
ThreadCheck.ensureOnUiThread();
throwIfDestroyed();
if (WebLayer.getSupportedMajorVersionInternal() < 87) {
throw new UnsupportedOperationException();
}
if (mBrowserControlsOffsetCallbacks.isEmpty()) {
try {
mImpl.setBrowserControlsOffsetsEnabled(true);
} catch (RemoteException e) {
throw new APICallException(e);
}
}
mBrowserControlsOffsetCallbacks.addObserver(callback);
}
/**
* Removes a BrowserControlsOffsetCallback that was added using {@link
* registerBrowserControlsOffsetCallback}.
*
* @param callback The BrowserControlsOffsetCallback to remove.
*
* @since 87
*/
public void unregisterScrollOffsetCallback(@NonNull BrowserControlsOffsetCallback callback) {
ThreadCheck.ensureOnUiThread();
throwIfDestroyed();
if (WebLayer.getSupportedMajorVersionInternal() < 87) {
throw new UnsupportedOperationException();
}
mBrowserControlsOffsetCallbacks.removeObserver(callback);
if (mBrowserControlsOffsetCallbacks.isEmpty()) {
try {
mImpl.setBrowserControlsOffsetsEnabled(false);
} catch (RemoteException e) {
throw new APICallException(e);
}
}
}
/** /**
* Creates a new tab attached to this browser. This will call {@link TabListCallback#onTabAdded} * Creates a new tab attached to this browser. This will call {@link TabListCallback#onTabAdded}
* with the new tab. * with the new tab.
...@@ -404,5 +456,16 @@ public class Browser { ...@@ -404,5 +456,16 @@ public class Browser {
StrictModeWorkaround.apply(); StrictModeWorkaround.apply();
return MediaRouteDialogFragment.create(mFragment); return MediaRouteDialogFragment.create(mFragment);
} }
@Override
public void onBrowserControlsOffsetsChanged(boolean isTop, int offset) {
for (BrowserControlsOffsetCallback callback : mBrowserControlsOffsetCallbacks) {
if (isTop) {
callback.onTopViewOffsetChanged(offset);
} else {
callback.onBottomViewOffsetChanged(offset);
}
}
}
} }
} }
// 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.weblayer;
/**
* Callback notified when the vertical location of the top or bottom View changes.
*
* WebLayer maintains a snapshot (bitmap) of the browser controls. During a scroll, the snapshot is
* scrolled and not the actual View. A ramification of this is any updates to the View during a
* scroll are not reflected to the user. If the contents of the controls needs to change during a
* scroll, than an empty View should be set as the top/bottom control and the real View should be
* positioned based on the offsets supplied to the callback.
*
* @since 87
*/
public abstract class BrowserControlsOffsetCallback {
/**
* Called when the vertical location of the top view changes. The value varies from 0
* (completely shown) to -(height - minHeight), where height is the preferred height of the view
* and minHeight is the minimum height supplied to {@link Tab#setTopView}.
*
* If the top view is removed, this is called with a value of 0.
*
* @param offset The vertical offset.
*/
public void onTopViewOffsetChanged(int offset) {}
/**
* Called when the vertical location of the bottom view changes. The value varies from 0
* (completely shown) to the preferred height of the bottom view.
*
* If the bottom view is removed, this is called with a value of 0.
*
* @param offset The vertical offset.
*/
public void onBottomViewOffsetChanged(int offset) {}
}
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