// Copyright 2015 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.compositor.layouts; import android.app.Activity; import android.content.Context; import android.view.ViewGroup; import org.chromium.base.ObserverList; import org.chromium.chrome.browser.ChromeApplication; import org.chromium.chrome.browser.compositor.TitleCache; import org.chromium.chrome.browser.compositor.layouts.components.LayoutTab; import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager; import org.chromium.chrome.browser.compositor.layouts.eventfilter.BlackHoleEventFilter; import org.chromium.chrome.browser.compositor.layouts.eventfilter.GestureEventFilter; import org.chromium.chrome.browser.compositor.layouts.eventfilter.GestureHandler; import org.chromium.chrome.browser.compositor.layouts.phone.StackLayout; import org.chromium.chrome.browser.compositor.overlays.SceneOverlay; import org.chromium.chrome.browser.contextualsearch.ContextualSearchManagementDelegate; import org.chromium.chrome.browser.device.DeviceClassManager; import org.chromium.chrome.browser.dom_distiller.ReaderModeManagerDelegate; import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tabmodel.TabCreatorManager; import org.chromium.chrome.browser.tabmodel.TabModelSelector; import org.chromium.chrome.browser.widget.OverviewListLayout; import org.chromium.content.browser.ContentViewCore; import org.chromium.ui.resources.dynamics.DynamicResourceLoader; /** * A {@link Layout} controller for enabling tab switcher on document mode. * Note that lots of fields and methods here intentionally duplicate those of LayoutManagerChrome * without further refactoring them because because this is for a UX experiment, and * we might scrap this as a whole. * See https://crbug.com/520327 for more. */ public class LayoutManagerDocumentTabSwitcher extends LayoutManagerDocument implements OverviewModeBehavior { /** A {@link Layout} that should be used when the user is swiping sideways on the toolbar. */ private final OverviewListLayout mOverviewListLayout; private final StackLayout mOverviewLayout; // Event Filter Handlers /** A {@link GestureHandler} that will delegate all events to {@link #getActiveLayout()}. */ private final BlackHoleEventFilter mBlackHoleEventFilter; private final GestureEventFilter mGestureEventFilter; private TitleCache mTitleCache; private final ObserverList<OverviewModeObserver> mOverviewModeObservers = new ObserverList<OverviewModeObserver>(); /** * Creates a {@link LayoutManagerDocumentTabSwitcher} instance. * @param host A {@link LayoutManagerHost} instance. */ public LayoutManagerDocumentTabSwitcher(LayoutManagerHost host) { super(host); Context context = host.getContext(); LayoutRenderHost renderHost = host.getLayoutRenderHost(); mBlackHoleEventFilter = new BlackHoleEventFilter(context, this); mGestureEventFilter = new GestureEventFilter(context, this, mGestureHandler); mOverviewListLayout = new OverviewListLayout(context, this, renderHost, mBlackHoleEventFilter); mOverviewLayout = new StackLayout(context, this, renderHost, mGestureEventFilter); } @Override public void init(TabModelSelector selector, TabCreatorManager creator, TabContentManager content, ViewGroup androidContentContainer, ContextualSearchManagementDelegate contextualSearchDelegate, ReaderModeManagerDelegate readerModeManagerDelegate, DynamicResourceLoader dynamicResourceLoader) { super.init(selector, creator, content, androidContentContainer, contextualSearchDelegate, readerModeManagerDelegate, dynamicResourceLoader); mTitleCache = mHost.getTitleCache(); TabModelSelector documentTabSelector = ChromeApplication.getDocumentTabModelSelector(); mOverviewListLayout.setTabModelSelector(documentTabSelector, content); mOverviewLayout.setTabModelSelector(documentTabSelector, content); // TODO(changwan): do we really need this? startShowing(getDefaultLayout(), false); } @Override public void destroy() { super.destroy(); if (mOverviewListLayout != null) mOverviewListLayout.destroy(); if (mOverviewLayout != null) mOverviewLayout.destroy(); } /** * Adds the {@link SceneOverlay} across all {@link Layout}s owned by this class. * @param helper A {@link SceneOverlay} instance. */ @Override protected void addGlobalSceneOverlay(SceneOverlay helper) { super.addGlobalSceneOverlay(helper); if (mOverviewListLayout != null) { mOverviewListLayout.addSceneOverlay(helper); } if (mOverviewLayout != null) { mOverviewLayout.addSceneOverlay(helper); } } @Override public void initLayoutTabFromHost(final int tabId) { if (mTitleCache != null) { mTitleCache.remove(tabId); } super.initLayoutTabFromHost(tabId); } @Override public void releaseTabLayout(int id) { super.releaseTabLayout(id); if (mTitleCache != null) mTitleCache.remove(id); } @Override public void doneHiding() { super.doneHiding(); // Remove transition animation when switching to another tab, in accordance to // moveToFront() in ActivityDelegate. Activity activity = (Activity) mHost.getContext(); if (activity != null) { activity.overridePendingTransition(0, 0); } } @Override protected void startShowing(Layout layout, boolean animate) { super.startShowing(layout, animate); Layout layoutBeingShown = getActiveLayout(); // Check if we should notify OverviewModeObservers. if (isOverviewLayout(layoutBeingShown)) { boolean showToolbar = false; for (OverviewModeObserver observer : mOverviewModeObservers) { observer.onOverviewModeStartedShowing(showToolbar); } } } @Override public void startHiding(int nextTabId, boolean hintAtTabSelection) { super.startHiding(nextTabId, hintAtTabSelection); Layout layoutBeingHidden = getActiveLayout(); if (isOverviewLayout(layoutBeingHidden)) { boolean showToolbar = true; if (layoutBeingHidden == mOverviewLayout) { final LayoutTab tab = layoutBeingHidden.getLayoutTab(nextTabId); // Note: this value is reversed in LayoutManagerChrome#startHiding. showToolbar = tab != null ? tab.showToolbar() : true; } boolean delayAnimation = false; for (OverviewModeObserver observer : mOverviewModeObservers) { observer.onOverviewModeStartedHiding(showToolbar, delayAnimation); } } } private boolean isOverviewLayout(Layout layout) { return layout != null && (layout == mOverviewLayout || layout == mOverviewListLayout); } @Override public void addOverviewModeObserver(OverviewModeObserver listener) { mOverviewModeObservers.addObserver(listener); } @Override public void removeOverviewModeObserver(OverviewModeObserver listener) { mOverviewModeObservers.removeObserver(listener); } @Override public boolean overviewVisible() { Layout activeLayout = getActiveLayout(); return isOverviewLayout(activeLayout) && !activeLayout.isHiding(); } public void toggleOverview() { Tab tab = getTabModelSelector().getCurrentTab(); ContentViewCore contentViewCore = tab != null ? tab.getContentViewCore() : null; if (!overviewVisible()) { mHost.hideKeyboard(new Runnable() { @Override public void run() { showOverview(true); } }); if (contentViewCore != null) { contentViewCore.setAccessibilityState(false); } } else { Layout activeLayout = getActiveLayout(); if (activeLayout instanceof StackLayout) { ((StackLayout) activeLayout).commitOutstandingModelState(LayoutManager.time()); } if (getTabModelSelector().getCurrentModel().getCount() != 0) { // Don't hide overview if current tab stack is empty() hideOverview(true); // hideOverview could change the current tab. Update the local variables. tab = getTabModelSelector().getCurrentTab(); contentViewCore = tab != null ? tab.getContentViewCore() : null; if (contentViewCore != null) { contentViewCore.setAccessibilityState(true); } } } } /** * @return Whether or not to use the accessibility layout. */ private boolean useAccessibilityLayout() { return DeviceClassManager.isAccessibilityModeEnabled(mHost.getContext()) || DeviceClassManager.enableAccessibilityLayout(); } /** * Show the overview {@link Layout}. This is generally a {@link Layout} that visibly represents * all of the {@link Tab}s opened by the user. * @param animate Whether or not to animate the transition to overview mode. */ private void showOverview(boolean animate) { boolean useAccessibility = useAccessibilityLayout(); boolean accessibilityIsVisible = useAccessibility && getActiveLayout() == mOverviewListLayout; boolean normalIsVisible = getActiveLayout() == mOverviewLayout && mOverviewLayout != null; // We only want to use the AccessibilityOverviewLayout if the following are all valid: // 1. We're already showing the AccessibilityOverviewLayout OR we're using accessibility. // 2. We're not already showing the normal OverviewLayout (or we are on a tablet, in which // case the normal layout is always visible). if ((accessibilityIsVisible || useAccessibility) && !normalIsVisible) { startShowing(mOverviewListLayout, animate); } else if (mOverviewLayout != null) { startShowing(mOverviewLayout, animate); } } /** * Hides the current {@link Layout}, returning to the default {@link Layout}. * @param animate Whether or not to animate the transition to the default {@link Layout}. */ private void hideOverview(boolean animate) { Layout activeLayout = getActiveLayout(); if (activeLayout != null && !activeLayout.isHiding()) { if (animate) { activeLayout.onTabSelecting(time(), Tab.INVALID_TAB_ID); } else { startHiding(Tab.INVALID_TAB_ID, false); doneHiding(); } } } }