blob: b98ec616ba3a9d28239798d7a24d6b4f55eaa137 [file] [log] [blame]
// 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.overlays.strip;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.RectF;
import android.os.SystemClock;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.compositor.LayerTitleCache;
import org.chromium.chrome.browser.compositor.layouts.LayoutManager;
import org.chromium.chrome.browser.compositor.layouts.LayoutRenderHost;
import org.chromium.chrome.browser.compositor.layouts.LayoutUpdateHost;
import org.chromium.chrome.browser.compositor.layouts.components.CompositorButton;
import org.chromium.chrome.browser.compositor.layouts.components.CompositorButton.CompositorOnClickHandler;
import org.chromium.chrome.browser.compositor.layouts.components.VirtualView;
import org.chromium.chrome.browser.compositor.layouts.eventfilter.AreaGestureEventFilter;
import org.chromium.chrome.browser.compositor.layouts.eventfilter.EventFilter;
import org.chromium.chrome.browser.compositor.layouts.eventfilter.GestureHandler;
import org.chromium.chrome.browser.compositor.overlays.SceneOverlay;
import org.chromium.chrome.browser.compositor.scene_layer.SceneOverlayLayer;
import org.chromium.chrome.browser.compositor.scene_layer.TabStripSceneLayer;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabModelObserver;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabObserver;
import org.chromium.ui.base.LocalizationUtils;
import org.chromium.ui.resources.ResourceManager;
import java.util.List;
/**
* This class handles managing which {@link StripLayoutHelper} is currently active and dispatches
* all input and model events to the proper destination.
*/
public class StripLayoutHelperManager implements SceneOverlay {
// Caching Variables
private final RectF mStripFilterArea = new RectF();
// Model selector buttons constants.
private static final float MODEL_SELECTOR_BUTTON_Y_OFFSET_DP = 10.f;
private static final float MODEL_SELECTOR_BUTTON_END_PADDING_DP = 6.f;
private static final float MODEL_SELECTOR_BUTTON_START_PADDING_DP = 3.f;
private static final float MODEL_SELECTOR_BUTTON_WIDTH_DP = 24.f;
private static final float MODEL_SELECTOR_BUTTON_HEIGHT_DP = 24.f;
// External influences
private TabModelSelector mTabModelSelector;
private final LayoutUpdateHost mUpdateHost;
// Event Filters
private final AreaGestureEventFilter mEventFilter;
// Internal state
private boolean mIsIncognito;
private final StripLayoutHelper mNormalHelper;
private final StripLayoutHelper mIncognitoHelper;
// UI State
private float mWidth; // in dp units
private final float mHeight; // in dp units
private int mOrientation;
private final CompositorButton mModelSelectorButton;
private TabStripSceneLayer mTabStripTreeProvider;
private TabStripEventHandler mTabStripEventHandler;
private class TabStripEventHandler implements GestureHandler {
@Override
public void onDown(float x, float y, boolean fromMouse, int buttons) {
if (mModelSelectorButton.onDown(x, y)) return;
getActiveStripLayoutHelper().onDown(time(), x, y, fromMouse, buttons);
}
@Override
public void onUpOrCancel() {
if (mModelSelectorButton.onUpOrCancel() && mTabModelSelector != null) {
getActiveStripLayoutHelper().finishAnimation();
if (!mModelSelectorButton.isVisible()) return;
mTabModelSelector.selectModel(!mTabModelSelector.isIncognitoSelected());
return;
}
getActiveStripLayoutHelper().onUpOrCancel(time());
}
@Override
public void drag(float x, float y, float dx, float dy, float tx, float ty) {
mModelSelectorButton.drag(x, y);
getActiveStripLayoutHelper().drag(time(), x, y, dx, dy, tx, ty);
}
@Override
public void click(float x, float y, boolean fromMouse, int buttons) {
long time = time();
if (mModelSelectorButton.click(x, y)) {
mModelSelectorButton.handleClick(time);
return;
}
getActiveStripLayoutHelper().click(time(), x, y, fromMouse, buttons);
}
@Override
public void fling(float x, float y, float velocityX, float velocityY) {
getActiveStripLayoutHelper().fling(time(), x, y, velocityX, velocityY);
}
@Override
public void onLongPress(float x, float y) {
getActiveStripLayoutHelper().onLongPress(time(), x, y);
}
@Override
public void onPinch(float x0, float y0, float x1, float y1, boolean firstEvent) {
// Not implemented.
}
private long time() {
return LayoutManager.time();
}
}
/**
* Creates an instance of the {@link StripLayoutHelperManager}.
* @param context The current Android {@link Context}.
* @param updateHost The parent {@link LayoutUpdateHost}.
* @param renderHost The {@link LayoutRenderHost}.
*/
public StripLayoutHelperManager(
Context context, LayoutUpdateHost updateHost, LayoutRenderHost renderHost) {
mUpdateHost = updateHost;
mTabStripTreeProvider = new TabStripSceneLayer(context);
mTabStripEventHandler = new TabStripEventHandler();
mEventFilter =
new AreaGestureEventFilter(context, mTabStripEventHandler, null, false, false);
mNormalHelper = new StripLayoutHelper(context, updateHost, renderHost, false);
mIncognitoHelper = new StripLayoutHelper(context, updateHost, renderHost, true);
CompositorOnClickHandler selectorClickHandler = new CompositorOnClickHandler() {
@Override
public void onClick(long time) {
handleModelSelectorButtonClick();
}
};
mModelSelectorButton = new CompositorButton(context, MODEL_SELECTOR_BUTTON_WIDTH_DP,
MODEL_SELECTOR_BUTTON_HEIGHT_DP, selectorClickHandler);
mModelSelectorButton.setIncognito(false);
mModelSelectorButton.setVisible(false);
// Pressed resources are the same as the unpressed resources.
mModelSelectorButton.setResources(R.drawable.btn_tabstrip_switch_normal,
R.drawable.btn_tabstrip_switch_normal, R.drawable.btn_tabstrip_switch_incognito,
R.drawable.btn_tabstrip_switch_incognito);
mModelSelectorButton.setY(MODEL_SELECTOR_BUTTON_Y_OFFSET_DP);
Resources res = context.getResources();
mHeight = res.getDimension(R.dimen.tab_strip_height) / res.getDisplayMetrics().density;
boolean useAlternativeIncognitoStrings =
ChromeFeatureList.isEnabled(ChromeFeatureList.INCOGNITO_STRINGS);
mModelSelectorButton.setAccessibilityDescription(
res.getString(useAlternativeIncognitoStrings
? R.string.accessibility_tabstrip_btn_private_toggle_standard
: R.string.accessibility_tabstrip_btn_incognito_toggle_standard),
res.getString(useAlternativeIncognitoStrings
? R.string.accessibility_tabstrip_btn_private_toggle_private
: R.string.accessibility_tabstrip_btn_incognito_toggle_incognito));
onContextChanged(context);
}
/**
* Cleans up internal state.
*/
public void destroy() {
mTabStripTreeProvider.destroy();
mTabStripTreeProvider = null;
mIncognitoHelper.destroy();
mNormalHelper.destroy();
}
private void handleModelSelectorButtonClick() {
if (mTabModelSelector == null) return;
getActiveStripLayoutHelper().finishAnimation();
if (!mModelSelectorButton.isVisible()) return;
mTabModelSelector.selectModel(!mTabModelSelector.isIncognitoSelected());
}
@VisibleForTesting
public void simulateClick(float x, float y, boolean fromMouse, int buttons) {
mTabStripEventHandler.click(x, y, fromMouse, buttons);
}
@VisibleForTesting
public void simulateLongPress(float x, float y) {
mTabStripEventHandler.onLongPress(x, y);
}
@Override
public SceneOverlayLayer getUpdatedSceneOverlayTree(RectF viewport, RectF visibleViewport,
LayerTitleCache layerTitleCache, ResourceManager resourceManager, float yOffset) {
assert mTabStripTreeProvider != null;
Tab selectedTab = mTabModelSelector.getCurrentModel().getTabAt(
mTabModelSelector.getCurrentModel().index());
int selectedTabId = selectedTab == null ? TabModel.INVALID_TAB_INDEX : selectedTab.getId();
mTabStripTreeProvider.pushAndUpdateStrip(this, layerTitleCache, resourceManager,
getActiveStripLayoutHelper().getStripLayoutTabsToRender(), yOffset,
selectedTabId);
return mTabStripTreeProvider;
}
@Override
public boolean isSceneOverlayTreeShowing() {
// TODO(mdjones): This matches existing behavior but can be improved to return false if
// the browser controls offset is equal to the browser controls height.
return true;
}
@Override
public EventFilter getEventFilter() {
return mEventFilter;
}
@Override
public void onSizeChanged(
float width, float height, float visibleViewportOffsetY, int orientation) {
mWidth = width;
mOrientation = orientation;
if (!LocalizationUtils.isLayoutRtl()) {
mModelSelectorButton.setX(
mWidth - MODEL_SELECTOR_BUTTON_WIDTH_DP - MODEL_SELECTOR_BUTTON_END_PADDING_DP);
} else {
mModelSelectorButton.setX(MODEL_SELECTOR_BUTTON_END_PADDING_DP);
}
mNormalHelper.onSizeChanged(mWidth, mHeight);
mIncognitoHelper.onSizeChanged(mWidth, mHeight);
mStripFilterArea.set(0, 0, mWidth, Math.min(getHeight(), visibleViewportOffsetY));
mEventFilter.setEventArea(mStripFilterArea);
}
public CompositorButton getNewTabButton() {
return getActiveStripLayoutHelper().getNewTabButton();
}
public CompositorButton getModelSelectorButton() {
return mModelSelectorButton;
}
@Override
public void getVirtualViews(List<VirtualView> views) {
if (mModelSelectorButton.isVisible()) views.add(mModelSelectorButton);
getActiveStripLayoutHelper().getVirtualViews(views);
}
@Override
public boolean shouldHideAndroidBrowserControls() {
return false;
}
/**
* @return The opacity to use for the fade on the left side of the tab strip.
*/
public float getLeftFadeOpacity() {
return getActiveStripLayoutHelper().getLeftFadeOpacity();
}
/**
* @return The opacity to use for the fade on the right side of the tab strip.
*/
public float getRightFadeOpacity() {
return getActiveStripLayoutHelper().getRightFadeOpacity();
}
/**
* @return The brightness of background tabs in the tabstrip.
*/
public float getBackgroundTabBrightness() {
return getActiveStripLayoutHelper().getBackgroundTabBrightness();
}
/**
* @return The brightness of the entire tabstrip.
*/
public float getBrightness() {
return getActiveStripLayoutHelper().getBrightness();
}
/**
* Sets the {@link TabModelSelector} that this {@link StripLayoutHelperManager} will visually
* represent, and various objects associated with it.
* @param modelSelector The {@link TabModelSelector} to visually represent.
* @param tabCreatorManager The {@link TabCreatorManager}, used to create new tabs.
*/
public void setTabModelSelector(TabModelSelector modelSelector,
TabCreatorManager tabCreatorManager) {
if (mTabModelSelector == modelSelector) return;
mTabModelSelector = modelSelector;
mNormalHelper.setTabModel(mTabModelSelector.getModel(false),
tabCreatorManager.getTabCreator(false));
mIncognitoHelper.setTabModel(mTabModelSelector.getModel(true),
tabCreatorManager.getTabCreator(true));
tabModelSwitched(mTabModelSelector.isIncognitoSelected());
new TabModelSelectorTabModelObserver(modelSelector) {
/**
* @return The actual current time of the app in ms.
*/
public long time() {
return SystemClock.uptimeMillis();
}
@Override
public void tabRemoved(Tab tab) {
getStripLayoutHelper(tab.isIncognito()).tabClosed(time(), tab.getId());
updateModelSwitcherButton();
}
@Override
public void didMoveTab(Tab tab, int newIndex, int curIndex) {
getStripLayoutHelper(tab.isIncognito())
.tabMoved(time(), tab.getId(), curIndex, newIndex);
}
@Override
public void tabClosureUndone(Tab tab) {
getStripLayoutHelper(tab.isIncognito()).tabClosureCancelled(time(), tab.getId());
updateModelSwitcherButton();
}
@Override
public void tabPendingClosure(Tab tab) {
getStripLayoutHelper(tab.isIncognito()).tabClosed(time(), tab.getId());
updateModelSwitcherButton();
}
@Override
public void didCloseTab(int tabId, boolean incognito) {
getStripLayoutHelper(incognito).tabClosed(time(), tabId);
updateModelSwitcherButton();
}
@Override
public void didSelectTab(Tab tab, @TabModel.TabSelectionType int type, int lastId) {
if (tab.getId() == lastId) return;
getStripLayoutHelper(tab.isIncognito()).tabSelected(time(), tab.getId(), lastId);
}
};
new TabModelSelectorTabObserver(modelSelector) {
@Override
public void onLoadStarted(Tab tab, boolean toDifferentDocument) {
getStripLayoutHelper(tab.isIncognito()).tabLoadStarted(tab.getId());
}
@Override
public void onLoadStopped(Tab tab, boolean toDifferentDocument) {
getStripLayoutHelper(tab.isIncognito()).tabLoadFinished(tab.getId());
}
@Override
public void onPageLoadStarted(Tab tab, String url) {
getStripLayoutHelper(tab.isIncognito()).tabPageLoadStarted(tab.getId());
}
@Override
public void onPageLoadFinished(Tab tab) {
getStripLayoutHelper(tab.isIncognito()).tabPageLoadFinished(tab.getId());
}
@Override
public void onPageLoadFailed(Tab tab, int errorCode) {
getStripLayoutHelper(tab.isIncognito()).tabPageLoadFinished(tab.getId());
}
@Override
public void onCrash(Tab tab, boolean sadTabShown) {
getStripLayoutHelper(tab.isIncognito()).tabPageLoadFinished(tab.getId());
}
};
}
@Override
public void tabTitleChanged(int tabId, String title) {
getActiveStripLayoutHelper().tabTitleChanged(tabId, title);
}
public float getHeight() {
return mHeight;
}
public float getWidth() {
return mWidth;
}
public int getOrientation() {
return mOrientation;
}
/**
* Updates all internal resources and dimensions.
* @param context The current Android {@link Context}.
*/
public void onContextChanged(Context context) {
mNormalHelper.onContextChanged(context);
mIncognitoHelper.onContextChanged(context);
}
@Override
public boolean updateOverlay(long time, long dt) {
getInactiveStripLayoutHelper().finishAnimation();
return getActiveStripLayoutHelper().updateLayout(time, dt);
}
@Override
public boolean onBackPressed() {
return false;
}
@Override
public void onHideLayout() {}
@Override
public boolean handlesTabCreating() {
return false;
}
@Override
public void tabStateInitialized() {
updateModelSwitcherButton();
}
@Override
public void tabModelSwitched(boolean incognito) {
if (incognito == mIsIncognito) return;
mIsIncognito = incognito;
mIncognitoHelper.tabModelSelected(mIsIncognito);
mNormalHelper.tabModelSelected(!mIsIncognito);
updateModelSwitcherButton();
mUpdateHost.requestUpdate();
}
private void updateModelSwitcherButton() {
mModelSelectorButton.setIncognito(mIsIncognito);
if (mTabModelSelector != null) {
boolean isVisible = mTabModelSelector.getModel(true).getCount() != 0;
mModelSelectorButton.setVisible(isVisible);
float endMargin = isVisible
? MODEL_SELECTOR_BUTTON_WIDTH_DP + MODEL_SELECTOR_BUTTON_END_PADDING_DP
+ MODEL_SELECTOR_BUTTON_START_PADDING_DP
: 0.0f;
mNormalHelper.setEndMargin(endMargin);
mIncognitoHelper.setEndMargin(endMargin);
}
}
@Override
public void tabCreated(long time, boolean incognito, int id, int prevId, boolean selected) {
getStripLayoutHelper(incognito).tabCreated(time, id, prevId, selected);
}
/**
* @param incognito Whether or not you want the incognito StripLayoutHelper
* @return The requested StripLayoutHelper.
*/
@VisibleForTesting
public StripLayoutHelper getStripLayoutHelper(boolean incognito) {
return incognito ? mIncognitoHelper : mNormalHelper;
}
/**
* @return The currently visible strip layout helper.
*/
@VisibleForTesting
public StripLayoutHelper getActiveStripLayoutHelper() {
return getStripLayoutHelper(mIsIncognito);
}
private StripLayoutHelper getInactiveStripLayoutHelper() {
return mIsIncognito ? mNormalHelper : mIncognitoHelper;
}
}