Commit ed20c69b authored by Jinsuk Kim's avatar Jinsuk Kim Committed by Commit Bot

Android: Refactor gesture navigation objects to be activity-level

HistoryNavigationLayout is a top level layout class of gesture
navigation feature, and is a child view of Tab's
ContentView. This CL moves it one level up in the view hierarchy
so that it becomes a child of CompositorViewHolder instead. This
has a few benefits:

1) Many native pages included HistoryNavigationLayout in its
layout definition. This caused undesirable dependencies on
gesture navigation, blocking component-wise modularization
efforts. Once it is removed from native page layout, it becomes
much easier to build each component as its own target.

2) Gesture navigation-related UI doesn't have to be instantiated
for each tab. A single set of instances can be used for all, each
tab only needing to provide with navigation delegate objects that
have dependency on tab when it goes foreground.

3) When the layout is added to Tab's content view, the
UI (especially arrow's fade-away animation when navigation
starts) is visible as long as its parent view is alive, but
navigation between rendered and native page swaps out the content
view. This keeps the navigation and animation from starting
simultaneously. The refactoring gets rid of this limitation.

Major changes are:

- CompositorViewHolder dispatches touch event to
HistoryNavigationLayout. If the right gesture is detected, lets
it consume the following events.

- HistoryNavigationLayout in native pages are either removed or all
replaced with FrameLayout. This is basically reverting relevant
parts of the past CLs:

https://crrev.com/c/1249530 Implement gesture navigation on Android
https://crrev.com/c/1423477 Android: More native pages become navigable
https://crrev.com/c/1547547 Android: Trigger history navigation with edge swipe
https://crrev.com/c/1554200 Android: Enable overscroll navigation in explore site page

- Rendered pages also make use of
HistoryNavigationLayout (previously SwipeRefreshHandler had its
own NavigationHandler) and reference the NavigationHandler that
the layout provides. This helps consolidate most of the
navigation event handling logic in the layout.

Bug: 1003914
Change-Id: I62ed28bd8f6aab39ca7a47f2e3fdce7a131fbfb6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1961351
Commit-Queue: Jinsuk Kim <jinsukkim@chromium.org>
Reviewed-by: default avatarTed Choc <tedchoc@chromium.org>
Cr-Commit-Position: refs/heads/master@{#732653}
parent e81f9c34
......@@ -757,12 +757,11 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/gesturenav/AndroidUiNavigationGlow.java",
"java/src/org/chromium/chrome/browser/gesturenav/CompositorNavigationGlow.java",
"java/src/org/chromium/chrome/browser/gesturenav/GestureNavMetrics.java",
"java/src/org/chromium/chrome/browser/gesturenav/HistoryNavigationCoordinator.java",
"java/src/org/chromium/chrome/browser/gesturenav/HistoryNavigationDelegate.java",
"java/src/org/chromium/chrome/browser/gesturenav/HistoryNavigationDelegateFactory.java",
"java/src/org/chromium/chrome/browser/gesturenav/HistoryNavigationLayout.java",
"java/src/org/chromium/chrome/browser/gesturenav/NavigationBubble.java",
"java/src/org/chromium/chrome/browser/gesturenav/NavigationGlow.java",
"java/src/org/chromium/chrome/browser/gesturenav/NavigationGlowFactory.java",
"java/src/org/chromium/chrome/browser/gesturenav/NavigationHandler.java",
"java/src/org/chromium/chrome/browser/gesturenav/NavigationSheet.java",
"java/src/org/chromium/chrome/browser/gesturenav/NavigationSheetCoordinator.java",
......
......@@ -96,7 +96,7 @@ class ExploreSurfaceCoordinator implements FeedSurfaceCoordinator.FeedSurfaceDel
(SectionHeaderView) inflater.inflate(R.layout.ss_feed_header, null, false);
}
FeedSurfaceCoordinator feedSurfaceCoordinator = new FeedSurfaceCoordinator(mActivity, null,
null, null, sectionHeaderView, exploreSurfaceActionHandler, isInNightMode, this);
null, sectionHeaderView, exploreSurfaceActionHandler, isInNightMode, this);
feedSurfaceCoordinator.getView().setId(R.id.start_surface_explore_view);
return feedSurfaceCoordinator;
// TODO(crbug.com/982018): Customize surface background for incognito and dark mode.
......
......@@ -90,7 +90,6 @@ public class FeedNewTabPage
SectionHeaderView sectionHeaderView = (SectionHeaderView) inflater.inflate(
R.layout.new_tab_page_snippets_expandable_header, null, false);
mCoordinator = new FeedSurfaceCoordinator(((TabImpl) mTab).getActivity(),
host.createHistoryNavigationDelegate(),
new SnapScrollHelper(mNewTabPageManager, mNewTabPageLayout), mNewTabPageLayout,
sectionHeaderView, actionApi,
((TabImpl) mTab).getActivity().getNightModeStateProvider().isInNightMode(), this);
......
......@@ -15,6 +15,7 @@ import android.os.Build;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ScrollView;
import androidx.annotation.Nullable;
......@@ -35,8 +36,6 @@ import org.chromium.chrome.browser.feed.library.api.host.stream.SnackbarCallback
import org.chromium.chrome.browser.feed.library.api.host.stream.StreamConfiguration;
import org.chromium.chrome.browser.feed.library.api.host.stream.TooltipApi;
import org.chromium.chrome.browser.feed.tooltip.BasicTooltipApi;
import org.chromium.chrome.browser.gesturenav.HistoryNavigationDelegate;
import org.chromium.chrome.browser.gesturenav.HistoryNavigationLayout;
import org.chromium.chrome.browser.native_page.ContextMenuManager;
import org.chromium.chrome.browser.ntp.NewTabPageLayout;
import org.chromium.chrome.browser.ntp.SnapScrollHelper;
......@@ -67,7 +66,7 @@ public class FeedSurfaceCoordinator {
private final FeedSurfaceMediator mMediator;
private UiConfig mUiConfig;
private HistoryNavigationLayout mRootView;
private FrameLayout mRootView;
private ContextMenuManager mContextMenuManager;
// Used when Feed is enabled.
......@@ -223,7 +222,7 @@ public class FeedSurfaceCoordinator {
/**
* Provides the additional capabilities needed for the container view.
*/
private class RootView extends HistoryNavigationLayout {
private class RootView extends FrameLayout {
/**
* @param context The context of the application.
*/
......@@ -265,7 +264,6 @@ public class FeedSurfaceCoordinator {
* Constructs a new FeedSurfaceCoordinator.
*
* @param activity The containing {@link ChromeActivity}.
* @param historyNavigationDelegate The {@link HistoryNavigationDelegate} for the root view.
* @param snapScrollHelper The {@link SnapScrollHelper} for the New Tab Page.
* @param ntpHeader The extra header on top of the feeds for the New Tab Page.
* @param sectionHeaderView The {@link SectionHeaderView} for the feed.
......@@ -274,7 +272,6 @@ public class FeedSurfaceCoordinator {
* @param delegate The constructing {@link FeedSurfaceDelegate}.
*/
public FeedSurfaceCoordinator(ChromeActivity activity,
@Nullable HistoryNavigationDelegate historyNavigationDelegate,
@Nullable SnapScrollHelper snapScrollHelper, @Nullable View ntpHeader,
@Nullable SectionHeaderView sectionHeaderView, ActionApi actionApi,
boolean showDarkBackground, FeedSurfaceDelegate delegate) {
......@@ -292,9 +289,6 @@ public class FeedSurfaceCoordinator {
mRootView = new RootView(mActivity);
mRootView.setPadding(0, resources.getDimensionPixelOffset(R.dimen.tab_strip_height), 0, 0);
if (historyNavigationDelegate != null) {
mRootView.setNavigationDelegate(historyNavigationDelegate);
}
mUiConfig = new UiConfig(mRootView);
// Mediator should be created before any Stream changes.
......
......@@ -3,7 +3,7 @@
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<org.chromium.chrome.browser.gesturenav.HistoryNavigationLayout
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/history_navigation"
......@@ -15,4 +15,4 @@
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</org.chromium.chrome.browser.gesturenav.HistoryNavigationLayout>
</FrameLayout>
......@@ -4,7 +4,7 @@
found in the LICENSE file. -->
<!-- This single-child FrameLayout is needed for its top padding. -->
<org.chromium.chrome.browser.gesturenav.HistoryNavigationLayout
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/history_navigation"
android:layout_width="match_parent"
......@@ -33,4 +33,4 @@
android:scrollbarStyle="outsideOverlay" />
</org.chromium.chrome.browser.ntp.NativePageRootFrameLayout>
</org.chromium.chrome.browser.gesturenav.HistoryNavigationLayout>
</FrameLayout>
......@@ -21,7 +21,7 @@
android:layout_height="@dimen/toolbar_height_no_shadow"
android:background="@color/default_primary_color" />
<org.chromium.chrome.browser.gesturenav.HistoryNavigationLayout
<FrameLayout
android:id="@+id/list_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
......@@ -64,7 +64,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</org.chromium.chrome.browser.gesturenav.HistoryNavigationLayout>
</FrameLayout>
<org.chromium.components.browser_ui.widget.FadingShadowView
android:id="@+id/shadow"
......
......@@ -2165,14 +2165,15 @@ public class ChromeTabbedActivity extends ChromeActivity implements ScreenshotMo
}
private void showFullHistoryOnNavigationSheet(Tab tab) {
// TODO(jinsukkim): Make NavigationSheet a per-activity object using RootUiCoordinator.
// Another instance of NavigationSheet(for gesture navigation) may be running.
if (NavigationSheet.isInstanceShowing(getBottomSheetController())) {
mNavigationSheet = null;
return;
}
mNavigationSheet = NavigationSheet.create(
getWindow().getDecorView().findViewById(android.R.id.content), this,
this::getBottomSheetController, new TabbedSheetDelegate(tab));
this::getBottomSheetController);
mNavigationSheet.setDelegate(new TabbedSheetDelegate(tab));
if (!mNavigationSheet.startAndExpand(/* forward=*/false, /* animate=*/true)) {
mNavigationSheet = null;
} else {
......
......@@ -6,8 +6,6 @@ package org.chromium.chrome.browser;
import android.annotation.SuppressLint;
import android.content.Context;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
......@@ -18,9 +16,6 @@ import org.chromium.base.TraceEvent;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.base.task.PostTask;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.gesturenav.HistoryNavigationDelegate;
import org.chromium.chrome.browser.gesturenav.HistoryNavigationDelegateFactory;
import org.chromium.chrome.browser.gesturenav.NavigationGlowFactory;
import org.chromium.chrome.browser.gesturenav.NavigationHandler;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.Tab;
......@@ -35,8 +30,8 @@ import org.chromium.ui.OverscrollRefreshHandler;
* An overscroll handler implemented in terms a modified version of the Android
* compat library's SwipeRefreshLayout effect.
*/
public class SwipeRefreshHandler extends TabWebContentsUserData
implements OverscrollRefreshHandler, OnAttachStateChangeListener {
public class SwipeRefreshHandler
extends TabWebContentsUserData implements OverscrollRefreshHandler {
private static final Class<SwipeRefreshHandler> USER_DATA_KEY = SwipeRefreshHandler.class;
// Synthetic delay between the {@link #didStopRefreshing()} signal and the
......@@ -73,15 +68,11 @@ public class SwipeRefreshHandler extends TabWebContentsUserData
// Accessibility utterance used to indicate refresh activation.
private String mAccessibilityRefreshString;
// Overscroll Navigation delegate providing info/object constructor.
private HistoryNavigationDelegate mNavigationDelegate =
HistoryNavigationDelegateFactory.DEFAULT;
// Handles overscroll history navigation.
// Handles overscroll history navigation. Gesture events from native layer are forwarded
// to this object. Remains null while navigation feature is disabled due to feature flag,
// system settings (Q and forward), etc.
private NavigationHandler mNavigationHandler;
private NavigationHandler.ActionDelegate mActionDelegate;
public static SwipeRefreshHandler from(Tab tab) {
SwipeRefreshHandler handler = get(tab);
if (handler == null) {
......@@ -117,7 +108,6 @@ public class SwipeRefreshHandler extends TabWebContentsUserData
}
};
mTab.addObserver(mTabObserver);
mNavigationDelegate = HistoryNavigationDelegateFactory.create(tab);
}
private void initSwipeRefreshLayout(final Context context) {
......@@ -156,9 +146,6 @@ public class SwipeRefreshHandler extends TabWebContentsUserData
public void initWebContents(WebContents webContents) {
webContents.setOverscrollRefreshHandler(this);
mContainerView = mTab.getContentView();
mContainerView.addOnAttachStateChangeListener(this);
mNavigationDelegate.setWindowInsetsChangeObserver(
mContainerView, () -> updateNavigationHandler());
setEnabled(true);
}
......@@ -166,25 +153,11 @@ public class SwipeRefreshHandler extends TabWebContentsUserData
@Override
public void cleanupWebContents(WebContents webContents) {
if (mSwipeRefreshLayout != null) detachSwipeRefreshLayoutIfNecessary();
mContainerView.removeOnAttachStateChangeListener(this);
mNavigationDelegate.setWindowInsetsChangeObserver(mContainerView, null);
mContainerView = null;
if (mNavigationHandler != null) {
mNavigationHandler.destroy();
mNavigationHandler = null;
mActionDelegate = null;
}
mNavigationHandler = null;
setEnabled(false);
}
@Override
public void onViewAttachedToWindow(View v) {
updateNavigationHandler();
}
@Override
public void onViewDetachedFromWindow(View v) {}
@Override
public void destroyInternal() {
if (mSwipeRefreshLayout != null) {
......@@ -215,36 +188,22 @@ public class SwipeRefreshHandler extends TabWebContentsUserData
return mSwipeRefreshLayout.start();
} else if (type == OverscrollAction.HISTORY_NAVIGATION) {
if (mNavigationHandler != null) {
boolean navigable = mActionDelegate.canNavigate(navigateForward);
boolean showGlow = navigateForward && !mTab.canGoForward();
mNavigationHandler.onDown(); // Simulates the initial onDown event.
if (navigable) {
mNavigationHandler.showArrowWidget(navigateForward);
} else if (showGlow) {
mNavigationHandler.showGlow(startX, startY);
}
return navigable || showGlow;
boolean navigable = mNavigationHandler.navigate(navigateForward, startX, startY);
boolean showGlow = navigateForward && !mTab.canGoForward();
return showGlow || navigable;
}
}
mSwipeType = OverscrollAction.NONE;
return false;
}
private void updateNavigationHandler() {
if (mNavigationDelegate.isNavigationEnabled(mContainerView)) {
if (mNavigationHandler == null) {
mActionDelegate = mNavigationDelegate.createActionDelegate();
mNavigationHandler = new NavigationHandler(mContainerView, mTab.getContext(),
mNavigationDelegate,
NavigationGlowFactory.forRenderedPage(
mContainerView, mTab.getWebContents()));
}
} else {
if (mNavigationHandler != null) {
mNavigationHandler.destroy();
mNavigationHandler = null;
}
}
/**
* Sets {@link NavigationHandler} object.
* @param layout {@link NavigationHandler} object.
*/
public void setNavigationHandler(NavigationHandler navigationHandler) {
mNavigationHandler = navigationHandler;
}
@Override
......
......@@ -25,7 +25,6 @@ import org.chromium.chrome.browser.bookmarks.BookmarkBridge.BookmarkItem;
import org.chromium.chrome.browser.bookmarks.BookmarkBridge.BookmarkModelObserver;
import org.chromium.chrome.browser.favicon.LargeIconBridge;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.gesturenav.HistoryNavigationDelegate;
import org.chromium.chrome.browser.native_page.BasicNativePage;
import org.chromium.chrome.browser.partnerbookmarks.PartnerBookmarksReader;
import org.chromium.chrome.browser.profiles.Profile;
......@@ -357,14 +356,6 @@ public class BookmarkManager
mNativePage = nativePage;
}
/**
* Sets the delegate object needed for history navigation logic.
* @param delegate {@link HistoryNavigationDelegate} object.
*/
public void setHistoryNavigationDelegate(HistoryNavigationDelegate delegate) {
mSelectableListLayout.setHistoryNavigationDelegate(delegate);
}
/**
* @return Current URL representing the UI state of bookmark manager. If no state has been shown
* yet in this session, on phone return last used state stored in preference; on tablet
......@@ -630,4 +621,4 @@ public class BookmarkManager
public static void preventLoadingForTesting(boolean preventLoading) {
sPreventLoadingForTesting = preventLoading;
}
}
\ No newline at end of file
}
......@@ -29,7 +29,6 @@ public class BookmarkPage extends BasicNativePage {
mManager = new BookmarkManager(activity, false, activity.getSnackbarManager());
mManager.setBasicNativePage(this);
mManager.setHistoryNavigationDelegate(host.createHistoryNavigationDelegate());
mTitle = host.getContext().getResources().getString(R.string.bookmarks);
initWithView(mManager.getView());
......
......@@ -32,6 +32,7 @@ import android.widget.FrameLayout;
import androidx.annotation.Nullable;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.ObserverList;
import org.chromium.base.SysUtils;
import org.chromium.base.TraceEvent;
import org.chromium.base.compat.ApiHelperForN;
......@@ -87,6 +88,27 @@ public class CompositorViewHolder extends FrameLayout
CompositorViewResizer.Observer {
private static final long SYSTEM_UI_VIEWPORT_UPDATE_DELAY_MS = 500;
/**
* Observer interface for any object that needs to process touch events.
*/
public interface TouchEventObserver {
/**
* Determine if touch events should be forwarded to the observing object.
* Should return {@link true} if the object decided to consume the events.
* @param e {@link MotionEvent} object to process.
* @return {@code true} if the observer will process touch events going forward.
*/
boolean shouldInterceptTouchEvent(MotionEvent e);
/**
* Handle touch events.
* @param e {@link MotionEvent} object to process.
*/
void handleTouchEvent(MotionEvent e);
}
private ObserverList<TouchEventObserver> mTouchEventObservers = new ObserverList<>();
private EventOffsetHandler mEventOffsetHandler;
private boolean mIsKeyboardShowing;
......@@ -558,10 +580,28 @@ public class CompositorViewHolder extends FrameLayout
return mInvalidator;
}
/**
* Add observer that needs to listen and process touch events.
* @param o {@link TouchEventObserver} object.
*/
public void addTouchEventObserver(TouchEventObserver o) {
mTouchEventObservers.addObserver(o);
}
/**
* Remove observer that needs to listen and process touch events.
* @param o {@link TouchEventObserver} object.
*/
public void removeTouchEventObserver(TouchEventObserver o) {
mTouchEventObservers.removeObserver(o);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
super.onInterceptTouchEvent(e);
for (TouchEventObserver o : mTouchEventObservers) {
if (o.shouldInterceptTouchEvent(e)) return true;
}
if (mLayoutManager == null) return false;
mEventOffsetHandler.onInterceptTouchEvent(e);
......@@ -605,6 +645,7 @@ public class CompositorViewHolder extends FrameLayout
@Override
public boolean dispatchTouchEvent(MotionEvent e) {
updateLastActiveTouchEvent(e);
for (TouchEventObserver o : mTouchEventObservers) o.handleTouchEvent(e);
return super.dispatchTouchEvent(e);
}
......
......@@ -47,7 +47,6 @@ public class DownloadPage extends BasicNativePage implements DownloadManagerCoor
activity.getModalDialogManager());
mDownloadCoordinator.addObserver(this);
mDownloadCoordinator.setHistoryNavigationDelegate(host.createHistoryNavigationDelegate());
mTitle = activity.getString(R.string.menu_downloads);
// #destroy() unregisters the ActivityStateListener to avoid checking for externally removed
......
......@@ -6,8 +6,6 @@ package org.chromium.chrome.browser.download.home;
import android.view.View;
import org.chromium.chrome.browser.gesturenav.HistoryNavigationDelegate;
/**
* A coordinator that represents the main download manager UI page. This visually shows a list of
* downloaded items and allows the user to interact with those items.
......@@ -42,10 +40,4 @@ public interface DownloadManagerCoordinator {
/** Stops notifying {@code observer} of url state changes. */
void removeObserver(Observer observer);
/**
* Sets the {@link HistoryNavigationDelegate} object that takes care of history navigation.
* @param delegate The delegate instance the history navigation logic needs.
*/
void setHistoryNavigationDelegate(HistoryNavigationDelegate delegate);
}
......@@ -7,6 +7,7 @@ package org.chromium.chrome.browser.download.home;
import android.app.Activity;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import org.chromium.base.ApiCompatibilityUtils;
......@@ -20,8 +21,6 @@ import org.chromium.chrome.browser.download.home.list.ListItem;
import org.chromium.chrome.browser.download.home.snackbars.DeleteUndoCoordinator;
import org.chromium.chrome.browser.download.home.toolbar.ToolbarCoordinator;
import org.chromium.chrome.browser.download.items.OfflineContentAggregatorFactory;
import org.chromium.chrome.browser.gesturenav.HistoryNavigationDelegate;
import org.chromium.chrome.browser.gesturenav.HistoryNavigationLayout;
import org.chromium.chrome.browser.settings.SettingsLauncher;
import org.chromium.chrome.browser.settings.download.DownloadSettings;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
......@@ -44,11 +43,10 @@ class DownloadManagerCoordinatorImpl
private final ToolbarCoordinator mToolbarCoordinator;
private final SelectionDelegate<ListItem> mSelectionDelegate;
private SelectionDelegate.SelectionObserver<ListItem> mNavigationCanceller;
private final Activity mActivity;
private HistoryNavigationLayout mMainView;
private ViewGroup mMainView;
private boolean mMuteFilterChanges;
......@@ -75,7 +73,7 @@ class DownloadManagerCoordinatorImpl
* TODO(crbug.com/880468) : Investigate if it is better to do in XML.
*/
private void initializeView() {
mMainView = new HistoryNavigationLayout(mActivity);
mMainView = new FrameLayout(mActivity);
mMainView.setBackgroundColor(ApiCompatibilityUtils.getColor(
mActivity.getResources(), R.color.modern_primary_color));
......@@ -85,10 +83,6 @@ class DownloadManagerCoordinatorImpl
mActivity.getResources().getDimensionPixelOffset(R.dimen.toolbar_height_no_shadow),
0, 0);
mMainView.addView(mListCoordinator.getView(), listParams);
mNavigationCanceller = (selectedItems) -> {
if (!selectedItems.isEmpty()) mMainView.release();
};
mSelectionDelegate.addObserver(mNavigationCanceller);
FrameLayout.LayoutParams toolbarParams = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT);
......@@ -118,7 +112,6 @@ class DownloadManagerCoordinatorImpl
mDeleteCoordinator.destroy();
mListCoordinator.destroy();
mToolbarCoordinator.destroy();
mSelectionDelegate.removeObserver(mNavigationCanceller);
}
@Override
......@@ -150,11 +143,6 @@ class DownloadManagerCoordinatorImpl
mObservers.removeObserver(observer);
}
@Override
public void setHistoryNavigationDelegate(HistoryNavigationDelegate delegate) {
mMainView.setNavigationDelegate(delegate);
}
// ToolbarActionDelegate implementation.
@Override
public void close() {
......
......@@ -11,6 +11,7 @@ import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Base64;
import android.view.ViewGroup;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
......@@ -22,7 +23,6 @@ import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.favicon.RoundedIconGenerator;
import org.chromium.chrome.browser.gesturenav.HistoryNavigationLayout;
import org.chromium.chrome.browser.native_page.BasicNativePage;
import org.chromium.chrome.browser.native_page.ContextMenuManager;
import org.chromium.chrome.browser.native_page.NativePageHost;
......@@ -96,7 +96,7 @@ public class ExploreSitesPage extends BasicNativePage {
private Tab mTab;
private TabObserver mTabObserver;
private Profile mProfile;
private HistoryNavigationLayout mView;
private ViewGroup mView;
private RecyclerView mRecyclerView;
private StableScrollLayoutManager mLayoutManager;
private String mTitle;
......@@ -177,7 +177,7 @@ public class ExploreSitesPage extends BasicNativePage {
mTab = tab;
mTitle = activity.getString(R.string.explore_sites_title);
mView = (HistoryNavigationLayout) activity.getLayoutInflater().inflate(
mView = (ViewGroup) activity.getLayoutInflater().inflate(
R.layout.explore_sites_page_layout, null);
mProfile = ((TabImpl) mTab).getProfile();
......@@ -246,7 +246,6 @@ public class ExploreSitesPage extends BasicNativePage {
CategoryCardAdapter adapterDelegate = new CategoryCardAdapter(
mModel, mLayoutManager, iconGenerator, mContextMenuManager, navDelegate, mProfile);
mView.setNavigationDelegate(host.createHistoryNavigationDelegate());
mRecyclerView = mView.findViewById(R.id.explore_sites_category_recycler);
CategoryCardViewHolderFactory factory = createCategoryCardViewHolderFactory();
......
......@@ -20,7 +20,7 @@ public class AndroidUiNavigationGlow extends NavigationGlow {
* Amount of time we wait before {@link GlowView} gets detached from parent view
* after the glow effect is completed.
*/
private final static int REMOVE_RUNNABLE_DELAY_MS = 500;
private static final int REMOVE_RUNNABLE_DELAY_MS = 500;
private final Runnable mRemoveGlowViewRunnable;
......
// 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.gesturenav;
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Build;
import android.view.View;
import org.chromium.base.supplier.Supplier;
import org.chromium.chrome.browser.ActivityTabProvider;
import org.chromium.chrome.browser.ActivityTabProvider.ActivityTabTabObserver;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.InsetObserverView;
import org.chromium.chrome.browser.SwipeRefreshHandler;
import org.chromium.chrome.browser.compositor.CompositorViewHolder;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.lifecycle.Destroyable;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabImpl;
import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetController;
import org.chromium.content_public.browser.WebContents;
/**
* Coordinator object for gesture navigation.
*/
public class HistoryNavigationCoordinator
implements InsetObserverView.WindowInsetObserver, Destroyable {
private CompositorViewHolder mCompositorViewHolder;
private HistoryNavigationLayout mNavigationLayout;
private InsetObserverView mInsetObserverView;
private ActivityTabTabObserver mActivityTabObserver;
private ActivityLifecycleDispatcher mActivityLifecycleDispatcher;
private Tab mTab;
/**
* Initializes the navigation layout and internal objects.
* @param compositorViewHolder Parent view for navigation layout.
* @param tabProvider Activity tab provider.
* @param insetObserverView View that provides information about the inset and inset
* capabilities of the device.
*/
public void init(ActivityLifecycleDispatcher lifecycleDispatcher,
CompositorViewHolder compositorViewHolder, ActivityTabProvider tabProvider,
InsetObserverView insetObserverView) {
mNavigationLayout = new HistoryNavigationLayout(compositorViewHolder.getContext());
mCompositorViewHolder = compositorViewHolder;
mActivityLifecycleDispatcher = lifecycleDispatcher;
lifecycleDispatcher.register(this);
compositorViewHolder.addView(mNavigationLayout);
compositorViewHolder.addTouchEventObserver(mNavigationLayout);
mActivityTabObserver = new ActivityTabProvider.ActivityTabTabObserver(tabProvider) {
@Override
protected void onObservingDifferentTab(Tab tab) {
if (mTab == tab) return;
onTabSwitched(tab);
}
@Override
public void onContentChanged(Tab tab) {
initNavigationHandler(
tab, createDelegate(tab), tab.getWebContents(), tab.isNativePage());
}
};
// We wouldn't hear about the first tab until the content changed or we switched tabs
// if tabProvider.get() != null. Do here what we do when tab switching happens.
if (tabProvider.get() != null) onTabSwitched(tabProvider.get());
mNavigationLayout.setVisibility(View.INVISIBLE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
mInsetObserverView = insetObserverView;
insetObserverView.addObserver(this);
}
}
/**
* Creates {@link HistoryNavigationDelegate} for native/rendered pages on Tab.
*/
private static HistoryNavigationDelegate createDelegate(Tab tab) {
if (!isFeatureFlagEnabled() || ((TabImpl) tab).getActivity() == null) {
return HistoryNavigationDelegate.DEFAULT;
}
return new HistoryNavigationDelegate() {
// TODO(jinsukkim): Avoid getting activity from tab.
private final Supplier<BottomSheetController> mController = () -> {
ChromeActivity activity = ((TabImpl) tab).getActivity();
return activity == null || activity.isActivityFinishingOrDestroyed()
? null
: activity.getBottomSheetController();
};
@Override
public NavigationHandler.ActionDelegate createActionDelegate() {
return new TabbedActionDelegate(tab);
}
@Override
public NavigationSheet.Delegate createSheetDelegate() {
return new TabbedSheetDelegate(tab);
}
@Override
public Supplier<BottomSheetController> getBottomSheetController() {
return mController;
}
};
}
private static boolean isFeatureFlagEnabled() {
return ChromeFeatureList.isEnabled(ChromeFeatureList.OVERSCROLL_HISTORY_NAVIGATION);
}
@Override
public void onInsetChanged(int left, int top, int right, int bottom) {
// Resets navigation handler when the feature becomes disabled.
if (!isNavigationEnabled(mCompositorViewHolder)) {
initNavigationHandler(mTab, HistoryNavigationDelegate.DEFAULT, null, false);
}
}
private void onTabSwitched(Tab tab) {
WebContents webContents = tab != null ? tab.getWebContents() : null;
HistoryNavigationDelegate delegate =
webContents != null && isNavigationEnabled(mCompositorViewHolder)
? createDelegate(tab)
: HistoryNavigationDelegate.DEFAULT;
// Also resets NavigationHandler when there's no tab (going into TabSwitcher).
if (tab == null || webContents != null) {
initNavigationHandler(
tab, delegate, webContents, tab != null ? tab.isNativePage() : false);
}
}
/**
* @param view {@link View} object to obtain the navigation setting from.
* @return {@code true} if overscroll navigation is allowed to run on this page.
*/
private static boolean isNavigationEnabled(View view) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) return true;
Insets insets = view.getRootWindowInsets().getSystemGestureInsets();
return insets.left == 0 && insets.right == 0;
}
private void initNavigationHandler(Tab tab, HistoryNavigationDelegate delegate,
WebContents webContents, boolean isNativePage) {
assert mNavigationLayout != null;
mNavigationLayout.initNavigationHandler(delegate, webContents, isNativePage);
if (mTab != tab) {
if (mTab != null) SwipeRefreshHandler.from(mTab).setNavigationHandler(null);
if (tab != null) {
SwipeRefreshHandler.from(tab).setNavigationHandler(
mNavigationLayout.getNavigationHandler());
}
mTab = tab;
}
}
@Override
public void onSafeAreaChanged(Rect area) {}
@Override
public void destroy() {
if (mActivityTabObserver != null) {
mActivityTabObserver.destroy();
mActivityTabObserver = null;
}
if (mInsetObserverView != null) {
mInsetObserverView.removeObserver(this);
mInsetObserverView = null;
}
if (mCompositorViewHolder != null && mNavigationLayout != null) {
mCompositorViewHolder.removeTouchEventObserver(mNavigationLayout);
mCompositorViewHolder = null;
}
if (mNavigationLayout != null) {
mNavigationLayout.destroy();
mNavigationLayout = null;
}
if (mActivityLifecycleDispatcher != null) {
mActivityLifecycleDispatcher.unregister(this);
mActivityLifecycleDispatcher = null;
}
}
}
......@@ -4,8 +4,6 @@
package org.chromium.chrome.browser.gesturenav;
import android.view.View;
import org.chromium.base.supplier.Supplier;
import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetController;
......@@ -23,22 +21,29 @@ public interface HistoryNavigationDelegate {
*/
NavigationSheet.Delegate createSheetDelegate();
/**
* @param view {@link View} object to obtain the navigation setting from.
* @return {@code true} if overscroll navigation is allowed to run on this page.
*/
boolean isNavigationEnabled(View view);
/**
* @return {@link BottomSheetController} object.
*/
Supplier<BottomSheetController> getBottomSheetController();
/**
* Observe window insets change to update navigation configutation dynamically.
* @param view {@link View} to observe the insets change on.
* @param runnable {@link Runnable} to execute when insets change is detected.
* Pass {@code null} to reset the observation.
* Default {@link HistoryNavigationDelegate} that does not support navigation.
*/
void setWindowInsetsChangeObserver(View view, Runnable runnable);
public static final HistoryNavigationDelegate DEFAULT = new HistoryNavigationDelegate() {
@Override
public NavigationHandler.ActionDelegate createActionDelegate() {
return null;
}
@Override
public NavigationSheet.Delegate createSheetDelegate() {
return null;
}
@Override
public Supplier<BottomSheetController> getBottomSheetController() {
assert false : "Should never be called";
return null;
}
};
}
// 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.Insets;
import android.os.Build;
import android.view.View;
import android.view.WindowInsets;
import org.chromium.base.supplier.Supplier;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabImpl;
import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetController;
/**
* Factory class for {@link HistoryNavigationDelegate}.
*/
public class HistoryNavigationDelegateFactory {
/**
* Default {@link HistoryNavigationDelegate} that does not support navigation. Mainly
* used for SnackbarActivity-based UI on the phone to disable the feature.
*/
public static final HistoryNavigationDelegate DEFAULT = new HistoryNavigationDelegate() {
@Override
public NavigationHandler.ActionDelegate createActionDelegate() {
return null;
}
@Override
public NavigationSheet.Delegate createSheetDelegate() {
return null;
}
@Override
public Supplier<BottomSheetController> getBottomSheetController() {
assert false : "Should never be called";
return null;
}
@Override
public boolean isNavigationEnabled(View view) {
return false;
}
@Override
public void setWindowInsetsChangeObserver(View view, Runnable runnable) {}
};
private HistoryNavigationDelegateFactory() {}
/**
* Creates {@link HistoryNavigationDelegate} for native/rendered pages on Tab.
*/
public static HistoryNavigationDelegate create(Tab tab) {
if (!isFeatureFlagEnabled() || ((TabImpl) tab).getActivity() == null) return DEFAULT;
return new HistoryNavigationDelegate() {
// TODO(jinsukkim): Avoid getting activity from tab.
private final Supplier<BottomSheetController> mController = () -> {
ChromeActivity activity = ((TabImpl) tab).getActivity();
return activity == null || activity.isActivityFinishingOrDestroyed()
? null
: activity.getBottomSheetController();
};
private Runnable mInsetsChangeRunnable;
@Override
public NavigationHandler.ActionDelegate createActionDelegate() {
return new TabbedActionDelegate(tab);
}
@Override
public NavigationSheet.Delegate createSheetDelegate() {
return new TabbedSheetDelegate(tab);
}
@Override
public Supplier<BottomSheetController> getBottomSheetController() {
return mController;
}
@Override
public boolean isNavigationEnabled(View view) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) return true;
Insets insets = view.getRootWindowInsets().getSystemGestureInsets();
return insets.left == 0 && insets.right == 0;
}
@Override
public void setWindowInsetsChangeObserver(View view, Runnable runnable) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) return;
mInsetsChangeRunnable = runnable;
view.setOnApplyWindowInsetsListener(
runnable != null ? this::onApplyWindowInsets : null);
}
private WindowInsets onApplyWindowInsets(View view, WindowInsets insets) {
assert Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
mInsetsChangeRunnable.run();
return insets;
}
};
}
private static boolean isFeatureFlagEnabled() {
return ChromeFeatureList.isEnabled(ChromeFeatureList.OVERSCROLL_HISTORY_NAVIGATION);
}
}
......@@ -5,88 +5,123 @@
package org.chromium.chrome.browser.gesturenav;
import android.content.Context;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import org.chromium.chrome.browser.compositor.CompositorViewHolder.TouchEventObserver;
import org.chromium.content_public.browser.WebContents;
/**
* FrameLayout that supports side-wise slide gesture for history navigation. Inheriting
* class may need to override {@link #wasLastSideSwipeGestureConsumed()} if
* {@link #onTouchEvent} cannot be relied upon to know whether the side-swipe related
* event was handled. Namely {@link android.support.v7.widget.RecyclerView}) always
* claims to handle touch events.
* TODO(jinsukkim): Write a test verifying UI logic.
* FrameLayout that supports side-wise slide gesture for history navigation.
*/
public class HistoryNavigationLayout extends FrameLayout {
public class HistoryNavigationLayout
extends FrameLayout implements TouchEventObserver, ViewGroup.OnHierarchyChangeListener {
private GestureDetector mDetector;
private NavigationHandler mNavigationHandler;
private HistoryNavigationDelegate mDelegate = HistoryNavigationDelegateFactory.DEFAULT;
private HistoryNavigationDelegate mDelegate;
private WebContents mWebContents;
private boolean mIsNativePage;
private NavigationGlow mJavaGlowEffect;
private NavigationGlow mCompositorGlowEffect;
public HistoryNavigationLayout(Context context) {
this(context, null);
}
public HistoryNavigationLayout(Context context, AttributeSet attrs) {
super(context, attrs);
super(context);
setOnHierarchyChangeListener(this);
}
/**
* Initializes navigation logic and internal objects if navigation is enabled.
* @param delegate {@link HistoryNavigationDelegate} providing info and a factory method.
*/
public void setNavigationDelegate(HistoryNavigationDelegate delegate) {
mDelegate = delegate;
// Navigation is potentially enabled only when the delegate is set.
delegate.setWindowInsetsChangeObserver(this, () -> updateNavigationHandler());
@Override
public void onChildViewAdded(View parent, View child) {
if (getVisibility() != View.VISIBLE) setVisibility(View.VISIBLE);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
updateNavigationHandler();
public void onChildViewRemoved(View parent, View child) {
// TODO(jinsukkim): Replace INVISIBLE with GONE to avoid performing layout/measurements.
if (getChildCount() == 0) setVisibility(View.INVISIBLE);
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
// TODO(jinsukkim): There are callsites enabling HistoryNavigationLayout but
// failing to call |setNavigationDelegate| (or |setTab| before renaming).
// Find when it can happen.
if (mNavigationHandler != null) mNavigationHandler.reset();
}
@Override
public boolean dispatchTouchEvent(MotionEvent e) {
if (mNavigationHandler != null) {
public boolean shouldInterceptTouchEvent(MotionEvent e) {
// Forward gesture events only for native pages. Rendered pages receive events
// from SwipeRefreshHandler.
if (!mIsNativePage) return false;
return mNavigationHandler != null && mNavigationHandler.isActive();
}
@Override
public void handleTouchEvent(MotionEvent e) {
if (mNavigationHandler != null && mIsNativePage) {
mDetector.onTouchEvent(e);
mNavigationHandler.onTouchEvent(e.getAction());
}
return super.dispatchTouchEvent(e);
}
private void updateNavigationHandler() {
if (mDelegate.isNavigationEnabled(this)) {
if (mNavigationHandler == null) {
mDetector = new GestureDetector(getContext(), new SideNavGestureListener());
mNavigationHandler = new NavigationHandler(
this, getContext(), mDelegate, NavigationGlowFactory.forJavaLayer(this));
}
/**
* Initialize {@link NavigationHandler} object.
* @param delegate {@link HistoryNavigationDelegate} providing info and a factory method.
* @param webContents A new WebContents object.
* @param isNativePage {@code true} if the content is a native page.
*/
public void initNavigationHandler(
HistoryNavigationDelegate delegate, WebContents webContents, boolean isNativePage) {
if (mNavigationHandler == null) {
mDetector = new GestureDetector(getContext(), new SideNavGestureListener());
mNavigationHandler = new NavigationHandler(this, getContext(), this::getGlowEffect);
}
if (mDelegate != delegate) {
mNavigationHandler.setDelegate(delegate);
mDelegate = delegate;
}
if (isNativePage == mIsNativePage && mWebContents == webContents) return;
if (mWebContents != webContents) resetCompositorGlow();
mWebContents = webContents;
mIsNativePage = isNativePage;
}
/**
* Create {@link NavigationGlow} object lazily.
*/
private NavigationGlow getGlowEffect() {
if (mIsNativePage) {
if (mJavaGlowEffect == null) mJavaGlowEffect = new AndroidUiNavigationGlow(this);
return mJavaGlowEffect;
} else {
mDetector = null;
if (mNavigationHandler != null) {
mNavigationHandler.destroy();
mNavigationHandler = null;
if (mCompositorGlowEffect == null) {
mCompositorGlowEffect = new CompositorNavigationGlow(this, mWebContents);
}
return mCompositorGlowEffect;
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
// Do not propagate touch events down to children if navigation UI was triggered.
if (mDetector != null && mNavigationHandler.isActive()) return true;
return super.onInterceptTouchEvent(e);
/**
* Reset CompositorGlowEffect for new a WebContents. Destroy the current one
* (for its native object) so it can be created again lazily.
*/
private void resetCompositorGlow() {
if (mCompositorGlowEffect != null) {
mCompositorGlowEffect.destroy();
mCompositorGlowEffect = null;
}
}
/** @return Current {@link HistoryNavigationDelegate} object. */
public HistoryNavigationDelegate getDelegate() {
return mDelegate;
}
/** @return Current {@link NavigationHandler} object. */
public NavigationHandler getNavigationHandler() {
return mNavigationHandler;
}
private class SideNavGestureListener extends GestureDetector.SimpleOnGestureListener {
......@@ -97,8 +132,7 @@ public class HistoryNavigationLayout extends FrameLayout {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
// |onScroll| needs handling only after the state moves away from |NONE|. This helps
// invoke |wasLastSideSwipeGestureConsumed| which may be expensive less often.
// |onScroll| needs handling only after the state moved away from |NONE|.
if (mNavigationHandler.isStopped()) return true;
return mNavigationHandler.onScroll(
......@@ -112,4 +146,16 @@ public class HistoryNavigationLayout extends FrameLayout {
public void release() {
if (mNavigationHandler != null) mNavigationHandler.release(false);
}
/**
* Performs cleanup upon destruction.
*/
void destroy() {
resetCompositorGlow();
mDelegate = HistoryNavigationDelegate.DEFAULT;
if (mNavigationHandler != null) {
mNavigationHandler.setDelegate(mDelegate);
mNavigationHandler = null;
}
}
}
// 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.view.ViewGroup;
import org.chromium.base.supplier.Supplier;
import org.chromium.content_public.browser.WebContents;
/**
* Factory class that provides {@link NavigationGlow} according to the actual surface
* the glow effect is rendered on.
*/
public class NavigationGlowFactory {
/**
* @pararm parentView Parent view where the glow view gets attached to.
* @pararm webContents WebContents whose native view's cc layer will be used
* for rendering glow effect.
* @return Supplier for {@link NavigationGlow} object for rendered pages.
*/
public static Supplier<NavigationGlow> forRenderedPage(
ViewGroup parentView, WebContents webContents) {
return () -> new CompositorNavigationGlow(parentView, webContents);
}
/**
* @pararm parentView Parent view where the glow view gets attached to.
* @return Supplier for {@link NavigationGlow} object for pages where glow effect can be
* rendered on the parent android view.
*/
public static Supplier<NavigationGlow> forJavaLayer(ViewGroup parentView) {
return () -> new AndroidUiNavigationGlow(parentView);
}
}
......@@ -46,12 +46,11 @@ public class NavigationHandler {
private final ViewGroup mParentView;
private final Context mContext;
private final Supplier<NavigationGlow> mGlowEffectSupplier;
private final HistoryNavigationDelegate mDelegate;
private final ActionDelegate mActionDelegate;
private HistoryNavigationDelegate mDelegate;
private ActionDelegate mActionDelegate;
private NavigationGlow mGlowEffect;
private Supplier<NavigationGlow> mGlowEffectSupplier;
private @GestureState int mState = GestureState.NONE;
......@@ -95,13 +94,11 @@ public class NavigationHandler {
boolean willBackExitApp();
}
public NavigationHandler(ViewGroup parentView, Context context,
HistoryNavigationDelegate delegate, Supplier<NavigationGlow> glowEffectSupplier) {
public NavigationHandler(
ViewGroup parentView, Context context, Supplier<NavigationGlow> glowEffect) {
mParentView = parentView;
mContext = context;
mDelegate = delegate;
mActionDelegate = delegate.createActionDelegate();
mGlowEffectSupplier = glowEffectSupplier;
mGlowEffectSupplier = glowEffect;
mEdgeWidthPx = EDGE_WIDTH_DP * parentView.getResources().getDisplayMetrics().density;
}
......@@ -114,7 +111,6 @@ public class NavigationHandler {
cancelStopNavigatingRunnable();
mSideSlideLayout.post(getStopNavigatingRunnable());
});
mSideSlideLayout.setOnResetListener(() -> {
if (mDetachLayoutRunnable != null) return;
mDetachLayoutRunnable = () -> {
......@@ -124,10 +120,21 @@ public class NavigationHandler {
mSideSlideLayout.post(mDetachLayoutRunnable);
});
mNavigationSheet = NavigationSheet.isEnabled()
? NavigationSheet.create(mParentView, mContext,
mDelegate.getBottomSheetController(), mDelegate.createSheetDelegate())
: NavigationSheet.DUMMY;
mNavigationSheet = NavigationSheet.isEnabled() ? NavigationSheet.create(
mParentView, mContext, mDelegate.getBottomSheetController())
: NavigationSheet.DUMMY;
mNavigationSheet.setDelegate(mDelegate.createSheetDelegate());
}
/**
* Sets {@link HistoryNavigationDelegate} object.
* Also creates new delegates, for horizontal gesture and bottom sheet processing.
* @param {@link HistoryNavigationDelegate} object.
*/
void setDelegate(HistoryNavigationDelegate delegate) {
mDelegate = delegate;
mActionDelegate = delegate.createActionDelegate();
if (mNavigationSheet != null) mNavigationSheet.setDelegate(delegate.createSheetDelegate());
}
/**
......@@ -138,8 +145,8 @@ public class NavigationHandler {
if (mState == GestureState.DRAGGED && mSideSlideLayout != null) {
mSideSlideLayout.release(mNavigationSheet.isHidden());
mNavigationSheet.release();
} else if (mState == GestureState.GLOW && mGlowEffect != null) {
mGlowEffect.release();
} else if (mState == GestureState.GLOW && mGlowEffectSupplier != null) {
mGlowEffectSupplier.get().release();
}
}
}
......@@ -167,15 +174,7 @@ public class NavigationHandler {
if (mState == GestureState.STARTED) {
if (shouldTriggerUi(startX, distanceX, distanceY)) {
boolean forward = distanceX > 0;
if (mActionDelegate.canNavigate(forward)) {
showArrowWidget(forward);
} else {
// |forward| should be true if we get here, since navigating back
// is always possible.
assert forward;
showGlow(endX, endY);
}
navigate(distanceX > 0, endX, endY);
}
if (!isActive()) mState = GestureState.NONE;
}
......@@ -188,11 +187,29 @@ public class NavigationHandler {
&& (sX < mEdgeWidthPx || (mParentView.getWidth() - mEdgeWidthPx) < sX);
}
/**
* Shows UI in response to gesture events.
* @param forward Direction of the swipe gesture. {@code true} if forward; else back.
* @param x The X position of the event.
* @param y The Y position of the event.
* @return {@code true} if the navigation can be triggered.
*/
public boolean navigate(boolean forward, float x, float y) {
assert mActionDelegate != null;
boolean navigable = mActionDelegate.canNavigate(forward);
if (navigable) {
showArrowWidget(forward);
} else {
showGlow(x, y);
}
return navigable;
}
/**
* Start showing arrow widget for navigation back/forward.
* @param forward {@code true} if navigating forward.
*/
public void showArrowWidget(boolean forward) {
private void showArrowWidget(boolean forward) {
if (mState != GestureState.STARTED) reset();
if (mSideSlideLayout == null) createLayout();
mSideSlideLayout.setEnabled(true);
......@@ -211,10 +228,9 @@ public class NavigationHandler {
* @param startX X coordinate of the touch event at the beginning.
* @param startY Y coordinate of the touch event at the beginning.
*/
public void showGlow(float startX, float startY) {
private void showGlow(float startX, float startY) {
if (mState != GestureState.STARTED) reset();
if (mGlowEffect == null) mGlowEffect = mGlowEffectSupplier.get();
mGlowEffect.prepare(startX, startY);
mGlowEffectSupplier.get().prepare(startX, startY);
mState = GestureState.GLOW;
}
......@@ -252,8 +268,8 @@ public class NavigationHandler {
mSideSlideLayout.hideArrow();
mState = GestureState.NONE;
}
} else if (mState == GestureState.GLOW && mGlowEffect != null) {
mGlowEffect.onScroll(-delta);
} else if (mState == GestureState.GLOW) {
mGlowEffectSupplier.get().onScroll(-delta);
}
}
......@@ -283,8 +299,8 @@ public class NavigationHandler {
cancelStopNavigatingRunnable();
mSideSlideLayout.release(allowNav && mNavigationSheet.isHidden());
mNavigationSheet.release();
} else if (mState == GestureState.GLOW && mGlowEffect != null) {
mGlowEffect.release();
} else if (mState == GestureState.GLOW) {
mGlowEffectSupplier.get().release();
}
}
......@@ -295,19 +311,12 @@ public class NavigationHandler {
if (mState == GestureState.DRAGGED && mSideSlideLayout != null) {
cancelStopNavigatingRunnable();
mSideSlideLayout.reset();
} else if (mState == GestureState.GLOW && mGlowEffect != null) {
mGlowEffect.reset();
} else if (mState == GestureState.GLOW) {
mGlowEffectSupplier.get().reset();
}
mState = GestureState.NONE;
}
/**
* Performs cleanup upon destruction.
*/
public void destroy() {
if (mGlowEffect != null) mGlowEffect.destroy();
}
private void cancelStopNavigatingRunnable() {
if (mStopNavigatingRunnable != null) {
mSideSlideLayout.removeCallbacks(mStopNavigatingRunnable);
......
......@@ -40,13 +40,11 @@ public interface NavigationSheet {
* @param rootView Root view whose dimension is used for the sheet.
* @param context {@link Context} used to retrieve resources.
* @param bottomSheetController {@link BottomSheetController} object.
* @param delegate Delegate used by navigation sheet to perform actions.
* @return NavigationSheet object.
*/
public static NavigationSheet create(View rootView, Context context,
Supplier<BottomSheetController> bottomSheetController,
NavigationSheet.Delegate delegate) {
return new NavigationSheetCoordinator(rootView, context, bottomSheetController, delegate);
public static NavigationSheet create(
View rootView, Context context, Supplier<BottomSheetController> bottomSheetController) {
return new NavigationSheetCoordinator(rootView, context, bottomSheetController);
}
/**
......@@ -72,6 +70,9 @@ public interface NavigationSheet {
* Dummy object that does nothing. Saves lots of null checks.
*/
static final NavigationSheet DUMMY = new NavigationSheet() {
@Override
public void setDelegate(Delegate delegate) {}
@Override
public void start(boolean forward, boolean showCloseIndicator) {}
......@@ -100,6 +101,12 @@ public interface NavigationSheet {
}
};
/**
* Set a new {@link Delegate} object whenever the dependency is updated.
* @param delegate Delegate used by navigation sheet to perform actions.
*/
void setDelegate(Delegate delegate);
/**
* Get the navigation sheet ready as the gesture starts.
* @param forward {@code true} if we're navigating forward.
......
......@@ -60,7 +60,6 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet
private final View mToolbarView;
private final LayoutInflater mLayoutInflater;
private final Supplier<BottomSheetController> mBottomSheetController;
private final NavigationSheet.Delegate mDelegate;
private final NavigationSheetMediator mMediator;
private final BottomSheetObserver mSheetObserver = new EmptyBottomSheetObserver() {
@Override
......@@ -80,6 +79,8 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet
private final int mContentPadding;
private final View mParentView;
private NavigationSheet.Delegate mDelegate;
private static class NavigationItemViewBinder {
public static void bind(PropertyModel model, View view, PropertyKey propertyKey) {
if (ItemProperties.ICON == propertyKey) {
......@@ -113,11 +114,10 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet
/**
* Construct a new NavigationSheet.
*/
NavigationSheetCoordinator(View parent, Context context,
Supplier<BottomSheetController> bottomSheetController, Delegate delegate) {
NavigationSheetCoordinator(
View parent, Context context, Supplier<BottomSheetController> bottomSheetController) {
mParentView = parent;
mBottomSheetController = bottomSheetController;
mDelegate = delegate;
mLayoutInflater = LayoutInflater.from(context);
mToolbarView = mLayoutInflater.inflate(R.layout.navigation_sheet_toolbar, null);
mMediator = new NavigationSheetMediator(context, mModelList, (position, index) -> {
......@@ -183,6 +183,11 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet
// NavigationSheet
@Override
public void setDelegate(NavigationSheet.Delegate delegate) {
mDelegate = delegate;
}
@Override
public void start(boolean forward, boolean showCloseIndicator) {
if (mBottomSheetController.get() == null) return;
......
......@@ -53,7 +53,7 @@ public class SideSlideLayout extends ViewGroup {
private static final float DECELERATE_INTERPOLATION_FACTOR = 2f;
private static final int SCALE_DOWN_DURATION_MS = 400;
private static final int SCALE_DOWN_DURATION_MS = 600;
private static final int ANIMATE_TO_START_DURATION_MS = 500;
// Minimum number of pull updates necessary to trigger a side nav.
......@@ -115,11 +115,7 @@ public class SideSlideLayout extends ViewGroup {
public void onAnimationEnd(Animation animation) {
mArrowView.setFaded(false, false);
mArrowView.setVisibility(View.INVISIBLE);
if (mNavigating) {
if (mListener != null) mListener.onNavigate(mIsForward);
} else {
reset();
}
if (!mNavigating) reset();
hideCloseIndicator();
}
};
......@@ -195,6 +191,9 @@ public class SideSlideLayout extends ViewGroup {
}
private void startHidingAnimation(AnimationListener listener) {
// Start animation and navigation simultaneously.
if (mNavigating && mListener != null) mListener.onNavigate(mIsForward);
// ScaleAnimation needs to be created again if the arrow widget width changes over time
// (due to turning on/off close indicator) to set the right x pivot point.
if (mHidingAnimation == null || mAnimationViewWidth != mArrowViewWidth) {
......
......@@ -31,7 +31,6 @@ import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.document.ChromeLauncherActivity;
import org.chromium.chrome.browser.favicon.LargeIconBridge;
import org.chromium.chrome.browser.gesturenav.HistoryNavigationDelegate;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.preferences.PrefChangeRegistrar;
import org.chromium.chrome.browser.preferences.PrefChangeRegistrar.PrefObserver;
......@@ -296,14 +295,6 @@ public class HistoryManager implements OnMenuItemClickListener, SignInStateObser
mPrefChangeRegistrar.destroy();
}
/**
* Sets the delegate object needed for history navigation logic.
* @param delegate {@link HistoryNavigationDelegate} object.
*/
public void setHistoryNavigationDelegate(HistoryNavigationDelegate delegate) {
mSelectableListLayout.setHistoryNavigationDelegate(delegate);
}
/**
* Called when the user presses the back key. This is only going to be called
* when the history UI is shown in a separate activity rather inside a tab.
......
......@@ -33,7 +33,6 @@ public class HistoryPage extends BasicNativePage {
mHistoryManager = new HistoryManager(activity, false, activity.getSnackbarManager(),
activity.getCurrentTabModel().isIncognito());
mTitle = host.getContext().getResources().getString(R.string.menu_history);
mHistoryManager.setHistoryNavigationDelegate(host.createHistoryNavigationDelegate());
initWithView(mHistoryManager.getView());
}
......
......@@ -20,8 +20,6 @@ import org.chromium.chrome.browser.explore_sites.ExploreSitesPage;
import org.chromium.chrome.browser.feed.FeedNewTabPage;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
import org.chromium.chrome.browser.gesturenav.HistoryNavigationDelegate;
import org.chromium.chrome.browser.gesturenav.HistoryNavigationDelegateFactory;
import org.chromium.chrome.browser.history.HistoryPage;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.ntp.IncognitoNewTabPage;
......@@ -247,11 +245,6 @@ public class NativePageFactory {
return mTab == TabModelSelector.from(mTab).getCurrentTab();
}
@Override
public HistoryNavigationDelegate createHistoryNavigationDelegate() {
return HistoryNavigationDelegateFactory.create(mTab);
}
@Override
public DestroyableObservableSupplier<Rect> createDefaultMarginSupplier() {
return new BrowserControlsMarginSupplier(mFullscreenManager);
......
......@@ -8,7 +8,6 @@ import android.content.Context;
import android.graphics.Rect;
import org.chromium.base.supplier.DestroyableObservableSupplier;
import org.chromium.chrome.browser.gesturenav.HistoryNavigationDelegate;
import org.chromium.content_public.browser.LoadUrlParams;
/**
......@@ -38,12 +37,6 @@ public interface NativePageHost {
/** @return whether the hosted native page is currently visible. */
boolean isVisible();
/**
* Creates a delegate object needed for history navigation logic.
* @return {@link HistoryNavigationDelegate} implementation.
*/
HistoryNavigationDelegate createHistoryNavigationDelegate();
/**
* Creates a default margin supplier. Once created, the NativePage is responsible for calling
* {@link DestroyableObservableSupplier#destroy()} to clean-up the supplier once it is no longer
......
......@@ -84,7 +84,6 @@ public class IncognitoNewTabPage
mIncognitoNewTabPageView =
(IncognitoNewTabPageView) inflater.inflate(R.layout.new_tab_page_incognito, null);
mIncognitoNewTabPageView.initialize(mIncognitoNewTabPageManager);
mIncognitoNewTabPageView.setNavigationDelegate(host.createHistoryNavigationDelegate());
TextView newTabIncognitoHeader =
mIncognitoNewTabPageView.findViewById(R.id.new_tab_incognito_title);
......
......@@ -8,16 +8,16 @@ import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.gesturenav.HistoryNavigationLayout;
import org.chromium.ui.base.ViewUtils;
/**
* The New Tab Page for use in the incognito profile.
*/
public class IncognitoNewTabPageView extends HistoryNavigationLayout {
public class IncognitoNewTabPageView extends FrameLayout {
private IncognitoNewTabPageManager mManager;
private boolean mFirstShow = true;
private NewTabPageScrollView mScrollView;
......
......@@ -12,6 +12,7 @@ import android.support.v7.widget.RecyclerView.AdapterDataObserver;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.FrameLayout;
import androidx.annotation.VisibleForTesting;
......@@ -20,8 +21,6 @@ import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
import org.chromium.chrome.browser.compositor.layouts.content.InvalidationAwareThumbnailProvider;
import org.chromium.chrome.browser.gesturenav.HistoryNavigationDelegateFactory;
import org.chromium.chrome.browser.gesturenav.HistoryNavigationLayout;
import org.chromium.chrome.browser.native_page.ContextMenuManager;
import org.chromium.chrome.browser.ntp.cards.NewTabPageAdapter;
import org.chromium.chrome.browser.ntp.cards.NewTabPageRecyclerView;
......@@ -40,7 +39,7 @@ import org.chromium.ui.base.ViewUtils;
* The native new tab page, represented by some basic data such as title and url, and an Android
* View that displays the page.
*/
public class NewTabPageView extends HistoryNavigationLayout {
public class NewTabPageView extends FrameLayout {
private static final String TAG = "NewTabPageView";
private NewTabPageRecyclerView mRecyclerView;
......@@ -131,7 +130,6 @@ public class NewTabPageView extends HistoryNavigationLayout {
mRecyclerView::setTouchEnabled, closeContextMenuCallback,
NewTabPage.CONTEXT_MENU_USER_ACTION_PREFIX);
mTab.getWindowAndroid().addContextMenuCloseListener(mContextMenuManager);
setNavigationDelegate(HistoryNavigationDelegateFactory.create(mTab));
OverviewModeBehavior overviewModeBehavior =
((TabImpl) tab).getActivity() instanceof ChromeTabbedActivity
......
......@@ -14,6 +14,7 @@ import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ExpandableListView;
import org.chromium.base.ActivityState;
......@@ -23,7 +24,6 @@ import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.compositor.layouts.content.InvalidationAwareThumbnailProvider;
import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
import org.chromium.chrome.browser.gesturenav.HistoryNavigationLayout;
import org.chromium.chrome.browser.native_page.NativePage;
import org.chromium.chrome.browser.native_page.NativePageHost;
import org.chromium.chrome.browser.util.UrlConstants;
......@@ -45,7 +45,7 @@ public class RecentTabsPage
private final ChromeFullscreenManager mFullscreenManager;
private final ExpandableListView mListView;
private final String mTitle;
private final HistoryNavigationLayout mView;
private final ViewGroup mView;
private RecentTabsManager mRecentTabsManager;
private RecentTabsRowAdapter mAdapter;
......@@ -91,7 +91,7 @@ public class RecentTabsPage
mTitle = resources.getString(R.string.recent_tabs);
mRecentTabsManager.setUpdatedCallback(this);
LayoutInflater inflater = LayoutInflater.from(activity);
mView = (HistoryNavigationLayout) inflater.inflate(R.layout.recent_tabs_page, null);
mView = (ViewGroup) inflater.inflate(R.layout.recent_tabs_page, null);
mListView = (ExpandableListView) mView.findViewById(R.id.odp_listview);
mAdapter = new RecentTabsRowAdapter(activity, recentTabsManager);
mListView.setAdapter(mAdapter);
......@@ -114,7 +114,6 @@ public class RecentTabsPage
mFullscreenManager = null;
}
mView.setNavigationDelegate(mPageHost.createHistoryNavigationDelegate());
onUpdated();
}
......
......@@ -18,6 +18,7 @@ import org.chromium.chrome.browser.datareduction.DataReductionPromoScreen;
import org.chromium.chrome.browser.firstrun.FirstRunStatus;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.FeatureUtilities;
import org.chromium.chrome.browser.gesturenav.HistoryNavigationCoordinator;
import org.chromium.chrome.browser.language.LanguageAskPrompt;
import org.chromium.chrome.browser.lifecycle.NativeInitObserver;
import org.chromium.chrome.browser.locale.LocaleManager;
......@@ -48,6 +49,7 @@ public class TabbedRootUiCoordinator extends RootUiCoordinator implements Native
private StatusIndicatorCoordinator mStatusIndicatorCoordinator;
private StatusIndicatorCoordinator.StatusIndicatorObserver mStatusIndicatorObserver;
private @Nullable ToolbarButtonInProductHelpController mToolbarButtonInProductHelpController;
private HistoryNavigationCoordinator mHistoryNavigationCoordinator;
private boolean mIntentWithEffect;
/**
......@@ -95,6 +97,10 @@ public class TabbedRootUiCoordinator extends RootUiCoordinator implements Native
if (mImmersiveModeManager != null) {
getToolbarManager().setImmersiveModeManager(mImmersiveModeManager);
}
mHistoryNavigationCoordinator = new HistoryNavigationCoordinator();
mHistoryNavigationCoordinator.init(mActivity.getLifecycleDispatcher(),
mActivity.getCompositorViewHolder(), mActivity.getActivityTabProvider(),
mActivity.getInsetObserverView());
}
/**
......
......@@ -26,8 +26,6 @@ import androidx.annotation.VisibleForTesting;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.gesturenav.HistoryNavigationDelegate;
import org.chromium.chrome.browser.gesturenav.HistoryNavigationLayout;
import org.chromium.chrome.browser.widget.selection.SelectionDelegate.SelectionObserver;
import org.chromium.components.browser_ui.widget.FadingShadow;
import org.chromium.components.browser_ui.widget.FadingShadowView;
......@@ -286,18 +284,6 @@ public class SelectableListLayout<E>
@Override
public void onSelectionStateChange(List<E> selectedItems) {
setToolbarShadowVisibility();
if (!selectedItems.isEmpty()) {
((HistoryNavigationLayout) findViewById(R.id.list_content)).release();
}
}
/**
* Sets the delegate object needed for history navigation logic.
* @param delegate {@link HistoryNavigationDelegate} object.
*/
public void setHistoryNavigationDelegate(HistoryNavigationDelegate delegate) {
HistoryNavigationLayout layout = (HistoryNavigationLayout) findViewById(R.id.list_content);
layout.setNavigationDelegate(delegate);
}
/**
......
......@@ -195,8 +195,8 @@ public class NavigationSheetTest {
Tab tab = mActivityTestRule.getActivity().getActivityTabProvider().get();
NavigationSheet navigationSheet =
NavigationSheet.create(tab.getContentView(), mActivityTestRule.getActivity(),
mActivityTestRule.getActivity()::getBottomSheetController,
new TestSheetDelegate(controller));
mActivityTestRule.getActivity()::getBottomSheetController);
navigationSheet.setDelegate(new TestSheetDelegate(controller));
navigationSheet.startAndExpand(false, false);
return navigationSheet;
});
......
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