Commit 7642971e authored by Jinsuk Kim's avatar Jinsuk Kim Committed by Commit Bot

Android: Use NavigationSheet for navigation popup menu

This CL replaces NavigationPopup with NavigationSheet for the history
popup menu impl triggered by long press on back button. This allows
the gesture navigation sheet and navigation popup to have same UI.

The only difference between them is that, on long press on back, the
sheet fully expands to show all the items.

Now NavigationPopup class is used by back/forward button on tablet only.

Bug: 999377
Change-Id: Ib68de11f0e5ac5357723ffbd1401c7859393a4ad
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1792687
Commit-Queue: Jinsuk Kim <jinsukkim@chromium.org>
Reviewed-by: default avatarTed Choc <tedchoc@chromium.org>
Cr-Commit-Position: refs/heads/master@{#698805}
parent 445aee22
......@@ -181,6 +181,7 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/gcore/MockConnectedTask.java",
"javatests/src/org/chromium/chrome/browser/gcore/MockConnectedTaskTest.java",
"javatests/src/org/chromium/chrome/browser/gesturenav/NavigationHandlerTest.java",
"javatests/src/org/chromium/chrome/browser/gesturenav/NavigationSheetTest.java",
"javatests/src/org/chromium/chrome/browser/gsa/GSAAccountChangeListenerTest.java",
"javatests/src/org/chromium/chrome/browser/hardware_acceleration/ChromeTabbedActivityHWATest.java",
"javatests/src/org/chromium/chrome/browser/hardware_acceleration/CustomTabActivityHWATest.java",
......
......@@ -81,6 +81,8 @@ import org.chromium.chrome.browser.feed.FeedProcessScopeFactory;
import org.chromium.chrome.browser.firstrun.FirstRunSignInProcessor;
import org.chromium.chrome.browser.firstrun.FirstRunStatus;
import org.chromium.chrome.browser.fullscreen.ComposedBrowserControlsVisibilityDelegate;
import org.chromium.chrome.browser.gesturenav.NavigationSheet;
import org.chromium.chrome.browser.gesturenav.TabbedSheetDelegate;
import org.chromium.chrome.browser.incognito.IncognitoNotificationManager;
import org.chromium.chrome.browser.incognito.IncognitoTabHost;
import org.chromium.chrome.browser.incognito.IncognitoTabHostRegistry;
......@@ -151,6 +153,7 @@ import org.chromium.chrome.browser.util.FeatureUtilities;
import org.chromium.chrome.browser.util.IntentUtils;
import org.chromium.chrome.browser.util.UrlConstants;
import org.chromium.chrome.browser.vr.VrModuleProvider;
import org.chromium.chrome.browser.widget.bottomsheet.EmptyBottomSheetObserver;
import org.chromium.chrome.features.start_surface.StartSurface;
import org.chromium.components.feature_engagement.EventConstants;
import org.chromium.components.feature_engagement.FeatureConstants;
......@@ -280,6 +283,7 @@ public class ChromeTabbedActivity extends ChromeActivity implements ScreenshotMo
private Runnable mShowHistoryRunnable;
private NavigationPopup mNavigationPopup;
private NavigationSheet mNavigationSheet;
/**
* Keeps track of whether or not a specific tab was created based on the startup intent.
......@@ -2187,10 +2191,21 @@ public class ChromeTabbedActivity extends ChromeActivity implements ScreenshotMo
if (keyCode == KeyEvent.KEYCODE_BACK && !isTablet()) {
mHandler.removeCallbacks(mShowHistoryRunnable);
mShowHistoryRunnable = null;
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M
&& event.getEventTime() - event.getDownTime()
>= ViewConfiguration.getLongPressTimeout()) {
return true;
}
}
return super.onKeyUp(keyCode, event);
}
@VisibleForTesting
public NavigationSheet getNavigationSheetForTesting() {
ThreadUtils.assertOnUiThread();
return mNavigationSheet;
}
@VisibleForTesting
public NavigationPopup getNavigationPopupForTesting() {
ThreadUtils.assertOnUiThread();
......@@ -2198,7 +2213,7 @@ public class ChromeTabbedActivity extends ChromeActivity implements ScreenshotMo
}
@VisibleForTesting
public boolean hasPendingNavigationPopupForTesting() {
public boolean hasPendingNavigationRunnableForTesting() {
ThreadUtils.assertOnUiThread();
return mShowHistoryRunnable != null;
}
......@@ -2206,12 +2221,34 @@ public class ChromeTabbedActivity extends ChromeActivity implements ScreenshotMo
private void showFullHistoryForTab() {
Tab tab = getActivityTab();
if (tab == null || tab.getWebContents() == null || !tab.isUserInteractable()) return;
if (NavigationSheet.isEnabled()) {
showFullHistoryOnNavigationSheet(tab);
} else {
mNavigationPopup = new NavigationPopup(tab.getProfile(), this,
tab.getWebContents().getNavigationController(),
NavigationPopup.Type.ANDROID_SYSTEM_BACK);
mNavigationPopup.setOnDismissCallback(() -> mNavigationPopup = null);
mNavigationPopup.show(findViewById(R.id.navigation_popup_anchor_stub));
}
}
mNavigationPopup = new NavigationPopup(tab.getProfile(), this,
tab.getWebContents().getNavigationController(),
NavigationPopup.Type.ANDROID_SYSTEM_BACK);
mNavigationPopup.setOnDismissCallback(() -> mNavigationPopup = null);
mNavigationPopup.show(findViewById(R.id.navigation_popup_anchor_stub));
private void showFullHistoryOnNavigationSheet(Tab tab) {
// TODO(jinsukkim): Make NavigationSheet a per-activity object using RootUiCoordinator.
if (NavigationSheet.isInstanceShowing(getBottomSheetController())) {
mNavigationSheet = null;
return;
}
mNavigationSheet = NavigationSheet.create(
getWindow().getDecorView().findViewById(android.R.id.content),
this::getBottomSheetController, new TabbedSheetDelegate(tab));
mNavigationSheet.startAndExpand(/* forward=*/false, /* animate=*/true);
getBottomSheet().addObserver(new EmptyBottomSheetObserver() {
@Override
public void onSheetClosed(int reason) {
getBottomSheet().removeObserver(this);
mNavigationSheet = null;
}
});
}
@Override
......
......@@ -243,9 +243,7 @@ public class SwipeRefreshHandler extends TabWebContentsUserData
if (mNavigationDelegate.isNavigationEnabled(mContainerView)) {
if (mNavigationHandler == null) {
mActionDelegate = mNavigationDelegate.createActionDelegate();
mNavigationHandler = new NavigationHandler(mContainerView,
mNavigationDelegate.getBottomSheetController(), mActionDelegate,
mNavigationDelegate.createSheetDelegate(),
mNavigationHandler = new NavigationHandler(mContainerView, mNavigationDelegate,
NavigationGlowFactory.forRenderedPage(
mContainerView, mTab.getWebContents()));
}
......
......@@ -77,8 +77,7 @@ public class HistoryNavigationDelegateFactory {
@Override
public Supplier<BottomSheetController> getBottomSheetController() {
// TODO(jinsukkim): Consider getting the controller not via Tab. Maybe move this
// create method to TabDelegateFactory to avoid this.
// TODO(jinsukkim): Avoid getting activity from tab.
return tab.getActivity()::getBottomSheetController;
}
......
......@@ -70,9 +70,8 @@ public class HistoryNavigationLayout extends FrameLayout {
if (mDelegate.isNavigationEnabled(this)) {
if (mNavigationHandler == null) {
mDetector = new GestureDetector(getContext(), new SideNavGestureListener());
mNavigationHandler = new NavigationHandler(this,
mDelegate.getBottomSheetController(), mDelegate.createActionDelegate(),
mDelegate.createSheetDelegate(), NavigationGlowFactory.forJavaLayer(this));
mNavigationHandler = new NavigationHandler(
this, mDelegate, NavigationGlowFactory.forJavaLayer(this));
}
} else {
mDetector = null;
......
......@@ -14,7 +14,6 @@ import androidx.annotation.IntDef;
import org.chromium.base.Supplier;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetController;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
......@@ -46,10 +45,9 @@ public class NavigationHandler {
private final ViewGroup mParentView;
private final Supplier<NavigationGlow> mGlowEffectSupplier;
private final Supplier<BottomSheetController> mBottomSheetController;
private final ActionDelegate mDelegate;
private final NavigationSheet.Delegate mSheetDelegate;
private final HistoryNavigationDelegate mDelegate;
private final ActionDelegate mActionDelegate;
private NavigationGlow mGlowEffect;
......@@ -90,13 +88,11 @@ public class NavigationHandler {
boolean willBackExitApp();
}
public NavigationHandler(ViewGroup parentView,
Supplier<BottomSheetController> bottomSheetController, ActionDelegate delegate,
NavigationSheet.Delegate sheetDelegate, Supplier<NavigationGlow> glowEffectSupplier) {
public NavigationHandler(ViewGroup parentView, HistoryNavigationDelegate delegate,
Supplier<NavigationGlow> glowEffectSupplier) {
mParentView = parentView;
mBottomSheetController = bottomSheetController;
mDelegate = delegate;
mSheetDelegate = sheetDelegate;
mActionDelegate = delegate.createActionDelegate();
mGlowEffectSupplier = glowEffectSupplier;
mEdgeWidthPx = EDGE_WIDTH_DP * parentView.getResources().getDisplayMetrics().density;
}
......@@ -106,7 +102,7 @@ public class NavigationHandler {
mSideSlideLayout.setLayoutParams(
new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
mSideSlideLayout.setOnNavigationListener((forward) -> {
mDelegate.navigate(forward);
mActionDelegate.navigate(forward);
cancelStopNavigatingRunnable();
mSideSlideLayout.post(getStopNavigatingRunnable());
});
......@@ -120,9 +116,10 @@ public class NavigationHandler {
mSideSlideLayout.post(mDetachLayoutRunnable);
});
mNavigationSheet = NavigationSheet.isEnabled() ? new NavigationSheetCoordinator(
mParentView, mBottomSheetController, mSheetDelegate)
: NavigationSheet.DUMMY;
mNavigationSheet = NavigationSheet.isEnabled()
? NavigationSheet.create(mParentView, mDelegate.getBottomSheetController(),
mDelegate.createSheetDelegate())
: NavigationSheet.DUMMY;
}
/**
......@@ -163,7 +160,7 @@ public class NavigationHandler {
if (mState == GestureState.STARTED) {
if (shouldTriggerUi(startX, distanceX, distanceY)) {
boolean forward = distanceX > 0;
if (mDelegate.canNavigate(forward)) {
if (mActionDelegate.canNavigate(forward)) {
showArrowWidget(forward);
} else {
// |forward| should be true if we get here, since navigating back
......@@ -215,7 +212,7 @@ public class NavigationHandler {
private boolean shouldShowCloseIndicator(boolean forward) {
// Some tabs, upon back at the beginning of the history stack, should be just closed
// than closing the entire app. In such case we do not show the close indicator.
return !forward && mDelegate.willBackExitApp();
return !forward && mActionDelegate.willBackExitApp();
}
/**
......
......@@ -4,13 +4,18 @@
package org.chromium.chrome.browser.gesturenav;
import android.view.View;
import org.chromium.base.Supplier;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet;
import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetController;
import org.chromium.content_public.browser.NavigationHistory;
/**
* Interface that defines the methods for controlling Navigation sheet.
*/
interface NavigationSheet {
public interface NavigationSheet {
// Field trial variation key that enables navigation sheet.
static final String NAVIGATION_SHEET_ENABLED_KEY = "overscroll_history_navigation_bottom_sheet";
......@@ -30,13 +35,37 @@ interface NavigationSheet {
void navigateToIndex(int index);
}
/**
* Create {@link NavigationSheet} object.
* @param rootView Root view whose dimension is used for the sheet.
* @param bottomSheetController {@link BottomSheetController} object.
* @param delegate Delegate used by navigation sheet to perform actions.
* @return NavigationSheet object.
*/
public static NavigationSheet create(View rootView,
Supplier<BottomSheetController> bottomSheetController,
NavigationSheet.Delegate delegate) {
return new NavigationSheetCoordinator(rootView, bottomSheetController, delegate);
}
/**
* @return {@code true} if navigation sheet is enabled.
*/
static boolean isEnabled() {
return ChromeFeatureList.getFieldTrialParamByFeatureAsBoolean(
ChromeFeatureList.OVERSCROLL_HISTORY_NAVIGATION, NAVIGATION_SHEET_ENABLED_KEY,
false);
return ChromeFeatureList.isInitialized()
&& ChromeFeatureList.getFieldTrialParamByFeatureAsBoolean(
ChromeFeatureList.OVERSCROLL_HISTORY_NAVIGATION,
NAVIGATION_SHEET_ENABLED_KEY, false);
}
/**
* @return {@code true} if another instance of NavigationSheet is already showing.
*/
public static boolean isInstanceShowing(BottomSheetController controller) {
if (controller == null) return false;
BottomSheet sheet = controller.getBottomSheet();
return (sheet.getCurrentSheetContent() instanceof NavigationSheetCoordinator)
&& sheet.isSheetOpen();
}
/**
......@@ -46,6 +75,9 @@ interface NavigationSheet {
@Override
public void start(boolean forward, boolean showCloseIndicator) {}
@Override
public void startAndExpand(boolean forward, boolean animate) {}
@Override
public void onScroll(float delta, float overscroll, boolean willNavigate) {}
......@@ -71,6 +103,13 @@ interface NavigationSheet {
*/
void start(boolean forward, boolean showCloseIndicator);
/**
* Fully expand the navigation sheet from the beginning.
* @param forward {@code true} if this is for forward navigation.
* @param animate {@code true} to enable animation.
*/
void startAndExpand(boolean forward, boolean animate);
/**
* Process swipe gesture and update the navigation sheet state.
* @param delta Scroll delta from the previous scroll.
......
......@@ -41,11 +41,11 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet
// Amount of time to hold the finger still to trigger navigation bottom sheet.
// with a long swipe. This ensures fling gestures from edge won't invoke the sheet.
private final static int LONG_SWIPE_HOLD_DELAY_MS = 50;
private static final int LONG_SWIPE_HOLD_DELAY_MS = 50;
// Amount of time to hold the finger still to trigger navigation bottom sheet
// with a short swipe.
private final static int SHORT_SWIPE_HOLD_DELAY_MS = 400;
private static final int SHORT_SWIPE_HOLD_DELAY_MS = 400;
// Amount of distance to trigger navigation sheet with a long swipe.
// Actual amount is capped so it is at most half the screen width.
......@@ -104,6 +104,10 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet
// Metrics. True if sheet has ever been triggered (in peeked state) for an edge swipe.
private boolean mSheetTriggered;
// Set to {@code true} for each trigger when the sheet should fully expand with
// no peek/half state.
private boolean mFullyExpand;
/**
* Construct a new NavigationSheet.
*/
......@@ -133,7 +137,7 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet
ListView listview = (ListView) mContentView.findViewById(R.id.navigation_entries);
listview.setAdapter(mModelAdapter);
mOpenSheetRunnable = () -> {
if (isHidden()) openSheet();
if (isHidden()) openSheet(false, true);
};
mLongSwipePeekThreshold = Math.min(
context.getResources().getDisplayMetrics().density * LONG_SWIPE_PEEK_THRESHOLD_DP,
......@@ -149,14 +153,15 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet
}
// Transition to either peeked or expanded state.
private void openSheet() {
private void openSheet(boolean fullyExpand, boolean animate) {
NavigationHistory history = mDelegate.getHistory(mForward);
mMediator.populateEntries(history);
mContentView.requestListViewLayout();
mBottomSheetController.get().requestShowContent(this, true);
mBottomSheetController.get().getBottomSheet().addObserver(mSheetObserver);
mSheetTriggered = true;
if (history.getEntryCount() <= SKIP_PEEK_COUNT) expandSheet();
mFullyExpand = fullyExpand;
if (fullyExpand || history.getEntryCount() <= SKIP_PEEK_COUNT) expandSheet();
}
private void expandSheet() {
......@@ -174,6 +179,12 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet
mSheetTriggered = false;
}
@Override
public void startAndExpand(boolean forward, boolean animate) {
start(forward, /* showCloseIndicator= */ false);
openSheet(/* fullyExpand= */ true, animate);
}
@Override
public void onScroll(float delta, float overscroll, boolean willNavigate) {
if (mBottomSheetController.get() == null) return;
......@@ -292,7 +303,8 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet
@Override
public float getCustomHalfRatio() {
return getCappedHeightRatio(mParentView.getHeight() / 2 + mItemHeight / 2);
return mFullyExpand ? getCustomFullRatio()
: getCappedHeightRatio(mParentView.getHeight() / 2 + mItemHeight / 2);
}
@Override
......
......@@ -28,10 +28,10 @@ import org.chromium.chrome.R;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.NavigationController;
import org.chromium.content_public.browser.NavigationEntry;
import org.chromium.content_public.browser.NavigationHistory;
import org.chromium.content_public.browser.test.mock.MockNavigationController;
import org.chromium.content_public.browser.test.util.Criteria;
import org.chromium.content_public.browser.test.util.CriteriaHelper;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
......@@ -77,7 +77,7 @@ public class NavigationPopupTest {
}
}
private static class TestNavigationController implements NavigationController {
private static class TestNavigationController extends MockNavigationController {
private final TestNavigationHistory mHistory;
private int mNavigatedIndex = INVALID_NAVIGATION_INDEX;
......@@ -89,107 +89,6 @@ public class NavigationPopupTest {
5, UrlUtils.encodeHtmlDataUri("<html>1</html>"), null, null, null, null, 0, 0));
}
@Override
public boolean canGoBack() {
return false;
}
@Override
public boolean canGoForward() {
return false;
}
@Override
public boolean canGoToOffset(int offset) {
return false;
}
@Override
public void goToOffset(int offset) {
}
@Override
public void goBack() {
}
@Override
public void goForward() {
}
@Override
public boolean isInitialNavigation() {
return false;
}
@Override
public void loadIfNecessary() {
}
@Override
public boolean needsReload() {
return false;
}
@Override
public void setNeedsReload() {}
@Override
public void reload(boolean checkForRepost) {
}
@Override
public void reloadBypassingCache(boolean checkForRepost) {
}
@Override
public void cancelPendingReload() {
}
@Override
public void continuePendingReload() {
}
@Override
public void loadUrl(LoadUrlParams params) {
}
@Override
public void clearHistory() {
}
@Override
public NavigationHistory getNavigationHistory() {
return null;
}
@Override
public void clearSslPreferences() {
}
@Override
public boolean getUseDesktopUserAgent() {
return false;
}
@Override
public void setUseDesktopUserAgent(boolean override, boolean reloadOnChange) {
}
@Override
public NavigationEntry getEntryAtIndex(int index) {
return null;
}
@Override
public NavigationEntry getVisibleEntry() {
return null;
}
@Override
public NavigationEntry getPendingEntry() {
return null;
}
@Override
public NavigationHistory getDirectedNavigationHistory(boolean isForward, int itemLimit) {
return mHistory;
......@@ -199,29 +98,6 @@ public class NavigationPopupTest {
public void goToNavigationIndex(int index) {
mNavigatedIndex = index;
}
@Override
public int getLastCommittedEntryIndex() {
return -1;
}
@Override
public boolean removeEntryAtIndex(int index) {
return false;
}
@Override
public String getEntryExtraData(int index, String key) {
return null;
}
@Override
public void setEntryExtraData(int index, String key, String value) {}
@Override
public boolean isEntryMarkedToBeSkipped(int index) {
return false;
}
}
@Test
......@@ -287,7 +163,7 @@ public class NavigationPopupTest {
TestThreadUtils.runOnUiThreadBlocking(
() -> { mActivityTestRule.getActivity().onKeyDown(KeyEvent.KEYCODE_BACK, event); });
CriteriaHelper.pollUiThread(
() -> mActivityTestRule.getActivity().hasPendingNavigationPopupForTesting());
() -> mActivityTestRule.getActivity().hasPendingNavigationRunnableForTesting());
// Wait for the long press timeout to trigger and show the navigation popup.
CriteriaHelper.pollUiThread(
......@@ -304,13 +180,13 @@ public class NavigationPopupTest {
mActivityTestRule.getActivity().onKeyDown(KeyEvent.KEYCODE_BACK, event);
});
CriteriaHelper.pollUiThread(
() -> mActivityTestRule.getActivity().hasPendingNavigationPopupForTesting());
() -> mActivityTestRule.getActivity().hasPendingNavigationRunnableForTesting());
TestThreadUtils.runOnUiThreadBlocking(() -> {
KeyEvent event = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
mActivityTestRule.getActivity().onKeyUp(KeyEvent.KEYCODE_BACK, event);
});
CriteriaHelper.pollUiThread(
() -> !mActivityTestRule.getActivity().hasPendingNavigationPopupForTesting());
() -> !mActivityTestRule.getActivity().hasPendingNavigationRunnableForTesting());
// Ensure no navigation popup is showing.
Assert.assertNull(TestThreadUtils.runOnUiThreadBlocking(
......
// Copyright 2019 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.gesturenav;
import android.graphics.Bitmap;
import android.support.test.filters.MediumTest;
import android.support.test.filters.SmallTest;
import android.view.KeyEvent;
import android.widget.ListView;
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.CommandLineFlags;
import org.chromium.base.test.util.Restriction;
import org.chromium.base.test.util.UrlUtils;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.gesturenav.NavigationSheetMediator.ItemProperties;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.content_public.browser.NavigationController;
import org.chromium.content_public.browser.NavigationEntry;
import org.chromium.content_public.browser.NavigationHistory;
import org.chromium.content_public.browser.test.mock.MockNavigationController;
import org.chromium.content_public.browser.test.util.Criteria;
import org.chromium.content_public.browser.test.util.CriteriaHelper;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.content_public.browser.test.util.TouchCommon;
import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
import org.chromium.ui.test.util.UiRestriction;
import java.util.concurrent.ExecutionException;
/**
* Tests for the gesture navigation sheet.
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class NavigationSheetTest {
@Rule
public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
private static final int INVALID_NAVIGATION_INDEX = -1;
private static final int NAVIGATION_INDEX_1 = 1;
private static final int NAVIGATION_INDEX_2 = 5;
@Before
public void setUp() throws Exception {
mActivityTestRule.startMainActivityOnBlankPage();
}
private static class TestNavigationEntry extends NavigationEntry {
public TestNavigationEntry(int index, String url, String virtualUrl, String originalUrl,
String title, Bitmap favicon, int transition, long timestamp) {
super(index, url, virtualUrl, originalUrl, /*referrerUrl=*/null, title, favicon,
transition, timestamp);
}
}
private static class TestNavigationController extends MockNavigationController {
private final NavigationHistory mHistory;
private int mNavigatedIndex = INVALID_NAVIGATION_INDEX;
public TestNavigationController() {
mHistory = new NavigationHistory();
mHistory.addEntry(new TestNavigationEntry(
NAVIGATION_INDEX_1, "about:blank", null, null, "About Blank", null, 0, 0));
mHistory.addEntry(new TestNavigationEntry(NAVIGATION_INDEX_2,
UrlUtils.encodeHtmlDataUri("<html>1</html>"), null, null, null, null, 0, 0));
}
@Override
public NavigationHistory getDirectedNavigationHistory(boolean isForward, int itemLimit) {
return mHistory;
}
@Override
public void goToNavigationIndex(int index) {
mNavigatedIndex = index;
}
}
private class TestSheetDelegate implements NavigationSheet.Delegate {
private static final int MAXIMUM_HISTORY_ITEMS = 8;
private final NavigationController mNavigationController;
public TestSheetDelegate(NavigationController controller) {
mNavigationController = controller;
}
@Override
public NavigationHistory getHistory(boolean forward) {
return mNavigationController.getDirectedNavigationHistory(
forward, MAXIMUM_HISTORY_ITEMS);
}
@Override
public void navigateToIndex(int index) {
mNavigationController.goToNavigationIndex(index);
}
}
@Test
@MediumTest
public void testFaviconFetching() throws ExecutionException {
TestNavigationController controller = new TestNavigationController();
NavigationSheetCoordinator sheet = (NavigationSheetCoordinator) showPopup(controller);
ListView listview = sheet.getContentView().findViewById(R.id.navigation_entries);
CriteriaHelper.pollUiThread(new Criteria("All favicons did not get updated.") {
@Override
public boolean isSatisfied() {
for (int i = 0; i < controller.mHistory.getEntryCount(); i++) {
ListItem item = (ListItem) listview.getAdapter().getItem(i);
if (item.model.get(ItemProperties.ICON) == null) return false;
}
return true;
}
});
}
@Test
@SmallTest
public void testItemSelection() throws ExecutionException {
TestNavigationController controller = new TestNavigationController();
NavigationSheetCoordinator sheet = (NavigationSheetCoordinator) showPopup(controller);
ListView listview = sheet.getContentView().findViewById(R.id.navigation_entries);
CriteriaHelper.pollUiThread(sheet::isExpanded);
Assert.assertEquals(INVALID_NAVIGATION_INDEX, controller.mNavigatedIndex);
TouchCommon.singleClickView(listview.getChildAt(1));
CriteriaHelper.pollUiThread(sheet::isHidden);
CriteriaHelper.pollUiThread(
Criteria.equals(NAVIGATION_INDEX_2, () -> controller.mNavigatedIndex));
}
@Test
@MediumTest
@Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
@CommandLineFlags.Add({"enable-features=OverscrollHistoryNavigation<GestureNavigation",
"force-fieldtrials=GestureNavigation/Enabled",
"force-fieldtrial-params=GestureNavigation.Enabled:"
+ "overscroll_history_navigation_bottom_sheet/true"})
public void
testLongPressBackTriggering() throws ExecutionException {
KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
ChromeTabbedActivity activity = mActivityTestRule.getActivity();
TestThreadUtils.runOnUiThreadBlocking(
() -> { activity.onKeyDown(KeyEvent.KEYCODE_BACK, event); });
CriteriaHelper.pollUiThread(activity::hasPendingNavigationRunnableForTesting);
// Wait for the long press timeout to trigger and show the navigation popup.
CriteriaHelper.pollUiThread(() -> activity.getNavigationSheetForTesting() != null);
}
@Test
@SmallTest
@Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
@CommandLineFlags.Add({"enable-features=OverscrollHistoryNavigation<GestureNavigation",
"force-fieldtrials=GestureNavigation/Enabled",
"force-fieldtrial-params=GestureNavigation.Enabled:"
+ "overscroll_history_navigation_bottom_sheet/true"})
public void
testLongPressBackTriggering_Cancellation() throws ExecutionException {
ChromeTabbedActivity activity = mActivityTestRule.getActivity();
TestThreadUtils.runOnUiThreadBlocking(() -> {
KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
activity.onKeyDown(KeyEvent.KEYCODE_BACK, event);
});
CriteriaHelper.pollUiThread(activity::hasPendingNavigationRunnableForTesting);
TestThreadUtils.runOnUiThreadBlocking(() -> {
KeyEvent event = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
activity.onKeyUp(KeyEvent.KEYCODE_BACK, event);
});
CriteriaHelper.pollUiThread(() -> !activity.hasPendingNavigationRunnableForTesting());
// Ensure no navigation popup is showing.
Assert.assertNull(
TestThreadUtils.runOnUiThreadBlocking(activity::getNavigationSheetForTesting));
}
private NavigationSheet showPopup(NavigationController controller) throws ExecutionException {
return TestThreadUtils.runOnUiThreadBlocking(() -> {
Tab tab = mActivityTestRule.getActivity().getActivityTabProvider().get();
NavigationSheet navigationSheet = NavigationSheet.create(tab.getContentView(),
mActivityTestRule.getActivity()::getBottomSheetController,
new TestSheetDelegate(controller));
navigationSheet.startAndExpand(false, false);
return navigationSheet;
});
}
}
......@@ -34,6 +34,7 @@ android_library("content_java_test_support") {
"javatests/src/org/chromium/content_public/browser/test/InterstitialPageDelegateAndroid.java",
"javatests/src/org/chromium/content_public/browser/test/NativeLibraryTestRule.java",
"javatests/src/org/chromium/content_public/browser/test/RenderFrameHostTestExt.java",
"javatests/src/org/chromium/content_public/browser/test/mock/MockNavigationController.java",
"javatests/src/org/chromium/content_public/browser/test/mock/MockRenderFrameHost.java",
"javatests/src/org/chromium/content_public/browser/test/mock/MockWebContents.java",
"javatests/src/org/chromium/content_public/browser/test/util/BackgroundSyncNetworkUtils.java",
......
// Copyright 2019 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.content_public.browser.test.mock;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.NavigationController;
import org.chromium.content_public.browser.NavigationEntry;
import org.chromium.content_public.browser.NavigationHistory;
/**
* Mock NavigationController implementation for Test.
*/
public class MockNavigationController implements NavigationController {
@Override
public boolean canGoBack() {
return false;
}
@Override
public boolean canGoForward() {
return false;
}
@Override
public boolean canGoToOffset(int offset) {
return false;
}
@Override
public void goToOffset(int offset) {}
@Override
public void goBack() {}
@Override
public void goForward() {}
@Override
public boolean isInitialNavigation() {
return false;
}
@Override
public void loadIfNecessary() {}
@Override
public boolean needsReload() {
return false;
}
@Override
public void setNeedsReload() {}
@Override
public void reload(boolean checkForRepost) {}
@Override
public void reloadBypassingCache(boolean checkForRepost) {}
@Override
public void cancelPendingReload() {}
@Override
public void continuePendingReload() {}
@Override
public void loadUrl(LoadUrlParams params) {}
@Override
public void clearHistory() {}
@Override
public NavigationHistory getNavigationHistory() {
return null;
}
@Override
public void clearSslPreferences() {}
@Override
public boolean getUseDesktopUserAgent() {
return false;
}
@Override
public void setUseDesktopUserAgent(boolean override, boolean reloadOnChange) {}
@Override
public NavigationEntry getEntryAtIndex(int index) {
return null;
}
@Override
public NavigationEntry getVisibleEntry() {
return null;
}
@Override
public NavigationEntry getPendingEntry() {
return null;
}
@Override
public NavigationHistory getDirectedNavigationHistory(boolean isForward, int itemLimit) {
return null;
}
@Override
public void goToNavigationIndex(int index) {}
@Override
public int getLastCommittedEntryIndex() {
return -1;
}
@Override
public boolean removeEntryAtIndex(int index) {
return false;
}
@Override
public String getEntryExtraData(int index, String key) {
return null;
}
@Override
public void setEntryExtraData(int index, String key, String value) {}
@Override
public boolean isEntryMarkedToBeSkipped(int index) {
return false;
}
}
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