// 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 static org.chromium.chrome.browser.compositor.layouts.ChromeAnimation.AnimatableAnimation.createAnimation; import android.content.Context; import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.Interpolator; import org.chromium.base.VisibleForTesting; import org.chromium.base.annotations.SuppressFBWarnings; import org.chromium.chrome.browser.compositor.LayerTitleCache; import org.chromium.chrome.browser.compositor.layouts.ChromeAnimation.Animatable; import org.chromium.chrome.browser.compositor.layouts.ChromeAnimation.Animation; import org.chromium.chrome.browser.compositor.layouts.components.LayoutTab; import org.chromium.chrome.browser.compositor.layouts.components.VirtualView; import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager; import org.chromium.chrome.browser.compositor.layouts.eventfilter.EdgeSwipeEventFilter.ScrollDirection; import org.chromium.chrome.browser.compositor.layouts.eventfilter.EventFilter; import org.chromium.chrome.browser.compositor.overlays.SceneOverlay; import org.chromium.chrome.browser.compositor.scene_layer.SceneLayer; import org.chromium.chrome.browser.compositor.scene_layer.SceneOverlayLayer; import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager; import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tabmodel.TabModel; import org.chromium.chrome.browser.tabmodel.TabModelSelector; import org.chromium.chrome.browser.tabmodel.TabModelUtils; import org.chromium.ui.resources.ResourceManager; import java.util.ArrayList; import java.util.List; /** * Abstract representation of an OpenGL layout tailored to draw tabs. It is a framework used as an * alternative to the Android UI for lower level hardware accelerated rendering. * This layout also pass through all the events that may happen. */ public abstract class Layout implements TabContentManager.ThumbnailChangeListener { /** * The orientation of the device. */ public interface Orientation { public static final int UNSET = 0; public static final int PORTRAIT = 1; public static final int LANDSCAPE = 2; } /** * The sizing requirements of each layout. These flags allow each layout to specify certain * sizing allowances/constraints by combining them. * * TODO(dtrainor): Flesh out support for all of these flag combinations. */ public interface SizingFlags { /** * The toolbar is allowed to be hidden. */ public static final int ALLOW_TOOLBAR_HIDE = 0x1; /** * The toolbar is allowed to be shown. */ public static final int ALLOW_TOOLBAR_SHOW = 0x10; /** * The toolbar is allowed to animate between the transitions. Note that this flag is not * completely supported in all combinations with the other flags. As functionality is * added and required this will be improved. */ public static final int ALLOW_TOOLBAR_ANIMATE = 0x100; /** * The layout requires a fullscreen size regardless of the toolbar position. */ public static final int REQUIRE_FULLSCREEN_SIZE = 0x1000; /** * The layout does not support fullscreen and it should animate the toolbar away. * A helper flag to make it easier to specify restrictions. */ public static final int HELPER_NO_FULLSCREEN_SUPPORT = ALLOW_TOOLBAR_SHOW | ALLOW_TOOLBAR_ANIMATE; /** * The layout supports persistent fullscreen and should hide the toolbar immediately. * A helper flag to make it easier to specify restrictions. */ public static final int HELPER_HIDE_TOOLBAR_IMMEDIATE = ALLOW_TOOLBAR_HIDE; /** * The layout fully supports fullscreen and the toolbar is allowed to show, hide, and * animate. * A helper flag to make it easier to specify restrictions. */ public static final int HELPER_SUPPORTS_FULLSCREEN = ALLOW_TOOLBAR_HIDE | ALLOW_TOOLBAR_SHOW | ALLOW_TOOLBAR_ANIMATE; } // Defines to make the code easier to read. public static final boolean NEED_TITLE = true; public static final boolean NO_TITLE = false; public static final boolean SHOW_CLOSE_BUTTON = true; public static final boolean NO_CLOSE_BUTTON = false; /** Length of the unstalling animation. **/ public static final long UNSTALLED_ANIMATION_DURATION_MS = 500; // Drawing area properties. private float mWidth; private float mHeight; private float mHeightMinusTopControls; /** A {@link Context} instance. */ private Context mContext; /** The current {@link Orientation} of the layout. */ private int mCurrentOrientation; // Tabs protected TabModelSelector mTabModelSelector; protected TabContentManager mTabContentManager; private ChromeAnimation<Animatable<?>> mLayoutAnimations; // Tablet tab strip managers. private final List<SceneOverlay> mSceneOverlays = new ArrayList<SceneOverlay>(); // Helpers private final LayoutUpdateHost mUpdateHost; protected final LayoutRenderHost mRenderHost; private EventFilter mEventFilter; /** The tabs currently being rendered as part of this layout. The tabs are * drawn using the same ordering as this array. */ protected LayoutTab[] mLayoutTabs; // True means that the layout is going to hide as soon as the animation finishes. private boolean mIsHiding; // The next id to show when the layout is hidden, or TabBase#INVALID_TAB_ID if no change. private int mNextTabId = Tab.INVALID_TAB_ID; /** * The {@link Layout} is not usable until sizeChanged is called. * This is convenient this way so we can pre-create the layout before the host is fully defined. * @param context The current Android's context. * @param updateHost The parent {@link LayoutUpdateHost}. * @param renderHost The parent {@link LayoutRenderHost}. * @param eventFilter The {@link EventFilter} this {@link Layout} is listening to. */ public Layout(Context context, LayoutUpdateHost updateHost, LayoutRenderHost renderHost, EventFilter eventFilter) { mContext = context; mUpdateHost = updateHost; mRenderHost = renderHost; mEventFilter = eventFilter; // Invalid sizes mWidth = -1; mHeight = -1; mHeightMinusTopControls = -1; mCurrentOrientation = Orientation.UNSET; } /** * Adds a {@link SceneOverlay} that can potentially be shown on top of this {@link Layout}. The * {@link SceneOverlay}s added to this {@link Layout} will be cascaded in the order they are * added. The {@link SceneOverlay} added first will become the content of the * {@link SceneOverlay} added second, and so on. * @param helper A {@link SceneOverlay} to add as a potential overlay for this {@link Layout}. */ public void addSceneOverlay(SceneOverlay helper) { assert !mSceneOverlays.contains(helper); mSceneOverlays.add(helper); } /** * Cleans up any internal state. This object should not be used after this call. */ public void destroy() { } /** * @return The current {@link Context} instance associated with this {@link Layout}. */ public Context getContext() { return mContext; } /** * @return Whether the {@link Layout} is currently active. */ public boolean isActive() { return mUpdateHost.isActiveLayout(this); } /** * @return A {@link View} that should be the one the user can interact with. This can be * {@code null} if there is no {@link View} representing {@link Tab} content that should * be interacted with at this time. */ public View getViewForInteraction() { if (!shouldDisplayContentOverlay()) return null; Tab tab = mTabModelSelector.getCurrentTab(); if (tab == null) return null; return tab.getView(); } /** * Get a list of virtual views for accessibility. * * @param views A List to populate with virtual views. */ public void getVirtualViews(List<VirtualView> views) { // TODO(dtrainor): Investigate order. for (int i = 0; i < mSceneOverlays.size(); i++) { mSceneOverlays.get(i).getVirtualViews(views); } } /** * Creates a {@link LayoutTab}. * @param id The id of the reference {@link Tab} in the {@link TabModel}. * @param isIncognito Whether the new tab is incognito. * @param showCloseButton True to show and activate a close button on the border. * @param isTitleNeeded Whether a title will be shown. * @return The newly created {@link LayoutTab}. */ public LayoutTab createLayoutTab(int id, boolean isIncognito, boolean showCloseButton, boolean isTitleNeeded) { return createLayoutTab(id, isIncognito, showCloseButton, isTitleNeeded, -1.f, -1.f); } /** * Creates a {@link LayoutTab}. * @param id The id of the reference {@link Tab} in the {@link TabModel}. * @param isIncognito Whether the new tab is incognito. * @param showCloseButton True to show and activate a close button on the border. * @param isTitleNeeded Whether a title will be shown. * @param maxContentWidth The max content width of the tab. Negative numbers will use the * original content width. * @param maxContentHeight The max content height of the tab. Negative numbers will use the * original content height. * @return The newly created {@link LayoutTab}. */ public LayoutTab createLayoutTab(int id, boolean isIncognito, boolean showCloseButton, boolean isTitleNeeded, float maxContentWidth, float maxContentHeight) { LayoutTab layoutTab = mUpdateHost.createLayoutTab( id, isIncognito, showCloseButton, isTitleNeeded, maxContentWidth, maxContentHeight); initLayoutTabFromHost(layoutTab); return layoutTab; } /** * Releases the data we keep for that {@link LayoutTab}. * @param layoutTab The {@link LayoutTab} to release. */ public void releaseTabLayout(LayoutTab layoutTab) { mUpdateHost.releaseTabLayout(layoutTab.getId()); } /** * Update the animation and give chance to cascade the changes. * @param time The current time of the app in ms. * @param dt The delta time between update frames in ms. * @return Whether the layout is done updating. */ public final boolean onUpdate(long time, long dt) { final boolean doneAnimating = onUpdateAnimation(time, false); // Don't update the layout if onUpdateAnimation ended up making a new layout active. if (mUpdateHost.isActiveLayout(this)) updateLayout(time, dt); return doneAnimating; } /** * Layout-specific updates. Cascades the values updated by the animations. * @param time The current time of the app in ms. * @param dt The delta time between update frames in ms. */ protected void updateLayout(long time, long dt) { for (int i = 0; i < mSceneOverlays.size(); i++) { mSceneOverlays.get(i).updateOverlay(time, dt); } } /** * Request that the renderer render a frame (after the current frame). This * should be called whenever a new frame should be rendered. */ public void requestRender() { mRenderHost.requestRender(); } /** * Requests one more frame of refresh for the transforms and changing properties. Primarily, * this is so animations can continue to animate. */ public void requestUpdate() { mUpdateHost.requestUpdate(); } /** * Called when the context and size of the view has changed. * * @param context The current Android's context. */ public void contextChanged(Context context) { mContext = context; LayoutTab.resetDimensionConstants(context); } /** * Called when the size of the viewport has changed. * @param visibleViewport The visible viewport that represents the area on the screen * this {@link Layout} gets to draw to (potentially takes into * account top controls). * @param screenViewport The viewport of the screen. * @param heightMinusTopControls The height the {@link Layout} gets excluding the height of the * top controls. TODO(dtrainor): Look at getting rid of this. * @param orientation The new orientation. Valid values are defined by * {@link Orientation}. */ public final void sizeChanged(RectF visibleViewport, RectF screenViewport, float heightMinusTopControls, int orientation) { // 1. Pull out this Layout's width and height properties based on the viewport. float width = screenViewport.width(); float height = screenViewport.height(); // 2. Check if any Layout-specific properties have changed. boolean layoutPropertiesChanged = mWidth != width || mHeight != height || mHeightMinusTopControls != heightMinusTopControls || mCurrentOrientation != orientation; // 3. Update the internal sizing properties. mWidth = width; mHeight = height; mHeightMinusTopControls = heightMinusTopControls; mCurrentOrientation = orientation; // 4. Notify the actual Layout if necessary. if (layoutPropertiesChanged) { notifySizeChanged(width, height, orientation); } // 5. TODO(dtrainor): Notify the overlay objects. for (int i = 0; i < mSceneOverlays.size(); i++) { mSceneOverlays.get(i).onSizeChanged(width, height, visibleViewport.top, orientation); } } /** * Notifies when the size or the orientation of the view has actually changed. * * @param width The new width in dp. * @param height The new height in dp. * @param orientation The new orientation. */ protected void notifySizeChanged(float width, float height, int orientation) { } /** * Notify the a title has changed. * * @param tabId The id of the tab that has changed. * @param title The new title. */ public void tabTitleChanged(int tabId, String title) { for (int i = 0; i < mSceneOverlays.size(); i++) { mSceneOverlays.get(i).tabTitleChanged(tabId, title); } } /** * Sets the managers needed to for the layout to get information from outside. The managers * are tailored to be called from the GL thread. * * @param modelSelector The {@link TabModelSelector} to be set on the layout. * @param manager The {@link TabContentManager} to get tab display content. */ public void setTabModelSelector(TabModelSelector modelSelector, TabContentManager manager) { if (mTabContentManager != null) mTabContentManager.removeThumbnailChangeListener(this); mTabModelSelector = modelSelector; mTabContentManager = manager; if (mTabContentManager != null) mTabContentManager.addThumbnailChangeListener(this); } /** * @return Information about the sizing requirements of this layout. */ public int getSizingFlags() { return SizingFlags.HELPER_NO_FULLSCREEN_SUPPORT; } /** * Informs this cache of the relevant {@link Tab} {@code id}s that will be used in the * near future. */ protected void updateCacheVisibleIds(List<Integer> priority) { if (mTabContentManager != null) mTabContentManager.updateVisibleIds(priority); } /** * To be called when the layout is starting a transition out of the view mode. * @param nextTabId The id of the next tab. * @param hintAtTabSelection Whether or not the new tab selection should be broadcast as a hint * potentially before this {@link Layout} is done hiding and the * selection occurs. */ public void startHiding(int nextTabId, boolean hintAtTabSelection) { mUpdateHost.startHiding(nextTabId, hintAtTabSelection); mIsHiding = true; mNextTabId = nextTabId; for (int i = 0; i < mSceneOverlays.size(); i++) { mSceneOverlays.get(i).onHideLayout(); } } /** * @return True is the layout is in the process of hiding itself. */ public boolean isHiding() { return mIsHiding; } /** * @return The incognito state of the layout. */ public boolean isIncognito() { return mTabModelSelector.isIncognitoSelected(); } /** * To be called when the transition into the layout is done. */ public void doneShowing() { mUpdateHost.doneShowing(); } /** * To be called when the transition out of the view mode is done. * This is currently called by the renderer when all the animation are done while hiding. */ public void doneHiding() { mIsHiding = false; if (mNextTabId != Tab.INVALID_TAB_ID) { TabModel model = mTabModelSelector.getModelForTabId(mNextTabId); if (model != null) { TabModelUtils.setIndex(model, TabModelUtils.getTabIndexById(model, mNextTabId)); } mNextTabId = Tab.INVALID_TAB_ID; } mUpdateHost.doneHiding(); if (mRenderHost != null && mRenderHost.getResourceManager() != null) { mRenderHost.getResourceManager().clearTintedResourceCache(); } } /** * Called when a tab is getting selected. Typically when exiting the overview mode. * @param time The current time of the app in ms. * @param tabId The id of the selected tab. */ public void onTabSelecting(long time, int tabId) { startHiding(tabId, true); } /** * Initialize the layout to be shown. * @param time The current time of the app in ms. * @param animate Whether to play an entry animation. */ public void show(long time, boolean animate) { mIsHiding = false; mNextTabId = Tab.INVALID_TAB_ID; } /** * Hands the layout an Android view to attach it's views to. * @param container The Android View to attach the layout's views to. */ public void attachViews(ViewGroup container) { } /** * Signal to the Layout to detach it's views from the container. */ public void detachViews() { } /** * Called when the swipe animation get initiated. It gives a chance to initialize everything. * @param time The current time of the app in ms. * @param direction The direction the swipe is in. * @param x The horizontal coordinate the swipe started at in dp. * @param y The vertical coordinate the swipe started at in dp. */ public void swipeStarted(long time, ScrollDirection direction, float x, float y) { } /** * Updates a swipe gesture. * @param time The current time of the app in ms. * @param x The horizontal coordinate the swipe is currently at in dp. * @param y The vertical coordinate the swipe is currently at in dp. * @param dx The horizontal delta since the last update in dp. * @param dy The vertical delta since the last update in dp. * @param tx The horizontal difference between the start and the current position in dp. * @param ty The vertical difference between the start and the current position in dp. */ public void swipeUpdated(long time, float x, float y, float dx, float dy, float tx, float ty) { } /** * Called when the swipe ends; most likely on finger up event. It gives a chance to start * an ending animation to exit the mode gracefully. * @param time The current time of the app in ms. */ public void swipeFinished(long time) { } /** * Called when the user has cancelled a swipe; most likely if they have dragged their finger * back to the starting position. Some handlers will throw swipeFinished() instead. * @param time The current time of the app in ms. */ public void swipeCancelled(long time) { } /** * Fling from a swipe gesture. * @param time The current time of the app in ms. * @param x The horizontal coordinate the swipe is currently at in dp. * @param y The vertical coordinate the swipe is currently at in dp. * @param tx The horizontal difference between the start and the current position in dp. * @param ty The vertical difference between the start and the current position in dp. * @param vx The horizontal velocity of the fling. * @param vy The vertical velocity of the fling. */ public void swipeFlingOccurred(long time, float x, float y, float tx, float ty, float vx, float vy) { } /** * Forces the current animation to finish and broadcasts the proper event. */ protected void forceAnimationToFinish() { if (mLayoutAnimations != null) { mLayoutAnimations.updateAndFinish(); mLayoutAnimations = null; onAnimationFinished(); } } /** * @return The width of the drawing area. */ public float getWidth() { return mWidth; } /** * @return The height of the drawing area. */ public float getHeight() { return mHeight; } /** * @return The height of the drawing area minus the top controls. */ public float getHeightMinusTopControls() { return mHeightMinusTopControls; } /** * @see Orientation * @return The orientation of the screen (portrait or landscape). Values are defined by * {@link Orientation}. */ public int getOrientation() { return mCurrentOrientation; } /** * @return Whether or not any tabs in this layer use the toolbar resource. */ public boolean usesToolbarLayer() { if (mLayoutTabs == null) return false; for (int i = 0; i < mLayoutTabs.length; i++) { if (mLayoutTabs[i].showToolbar()) return true; } return false; } /** * Initializes a {@link LayoutTab} with data from the {@link LayoutUpdateHost}. This function * eventually needs to be called but may be overridden to manage the posting traffic. * * @param layoutTab The {@link LayoutTab} To initialize from a * {@link Tab} on the UI thread. * @return Whether the asynchronous initialization of the {@link LayoutTab} has really * been posted. */ protected boolean initLayoutTabFromHost(LayoutTab layoutTab) { if (layoutTab.isInitFromHostNeeded()) { mUpdateHost.initLayoutTabFromHost(layoutTab.getId()); return true; } return false; } /** * Called on touch drag event. * @param time The current time of the app in ms. * @param x The y coordinate of the end of the drag event. * @param y The y coordinate of the end of the drag event. * @param deltaX The number of pixels dragged in the x direction. * @param deltaY The number of pixels dragged in the y direction. */ public void drag(long time, float x, float y, float deltaX, float deltaY) { } /** * Called on touch fling event. This is called before the onUpOrCancel event. * * @param time The current time of the app in ms. * @param x The y coordinate of the start of the fling event. * @param y The y coordinate of the start of the fling event. * @param velocityX The amount of velocity in the x direction. * @param velocityY The amount of velocity in the y direction. */ public void fling(long time, float x, float y, float velocityX, float velocityY) { } /** * Called on onDown event. * * @param time The time stamp in millisecond of the event. * @param x The x position of the event. * @param y The y position of the event. */ public void onDown(long time, float x, float y) { } /** * Called on long press touch event. * @param time The current time of the app in ms. * @param x The x coordinate of the position of the press event. * @param y The y coordinate of the position of the press event. */ public void onLongPress(long time, float x, float y) { } /** * Called on click. This is called before the onUpOrCancel event. * @param time The current time of the app in ms. * @param x The x coordinate of the position of the click. * @param y The y coordinate of the position of the click. */ public void click(long time, float x, float y) { } /** * Called on up or cancel touch events. This is called after the click and fling event if any. * @param time The current time of the app in ms. */ public void onUpOrCancel(long time) { } /** * Called when at least 2 touch events are detected. * @param time The current time of the app in ms. * @param x0 The x coordinate of the first touch event. * @param y0 The y coordinate of the first touch event. * @param x1 The x coordinate of the second touch event. * @param y1 The y coordinate of the second touch event. * @param firstEvent The pinch is the first of a sequence of pinch events. */ public void onPinch(long time, float x0, float y0, float x1, float y1, boolean firstEvent) { } /** * Called by the LayoutManager when an animation should be killed. */ public void unstallImmediately() { } /** * Called by the LayoutManager when an animation should be killed. * @param tabId The tab that the kill signal is associated with */ public void unstallImmediately(int tabId) { } /** * Called by the LayoutManager when they system back button is pressed. * @return Whether or not the layout consumed the event. */ public boolean onBackPressed() { for (int i = 0; i < mSceneOverlays.size(); i++) { // If the back button was consumed by any overlays, return true. if (mSceneOverlays.get(i).onBackPressed()) return true; } return false; } /** * Called when a tab get selected. Typically when a tab get closed and the new current tab get * selected. * @param time The current time of the app in ms. * @param tabId The id of the selected tab. * @param prevId The id of the previously selected tab. * @param incognito Whether or not the affected model was incognito. */ public void onTabSelected(long time, int tabId, int prevId, boolean incognito) { for (int i = 0; i < mSceneOverlays.size(); i++) { mSceneOverlays.get(i).tabSelected(time, incognito, tabId, prevId); } } /** * Called when a tab is about to be closed. When called, the closing tab will still * be part of the model. * @param time The current time of the app in ms. * @param tabId The id of the tab being closed */ public void onTabClosing(long time, int tabId) { } /** * Called when a tab is being closed. When called, the closing tab will not * be part of the model. * @param time The current time of the app in ms. * @param tabId The id of the tab being closed. * @param nextTabId The id if the tab that is being switched to. * @param incognito Whether or not the affected model was incognito. */ public void onTabClosed(long time, int tabId, int nextTabId, boolean incognito) { for (int i = 0; i < mSceneOverlays.size(); i++) { mSceneOverlays.get(i).tabClosed(time, incognito, tabId); } } /** * Called when all the tabs in the current stack need to be closed. * When called, the tabs will still be part of the model. * @param time The current time of the app in ms. * @param incognito True if this the incognito tab model should close all tabs, false otherwise. */ public void onTabsAllClosing(long time, boolean incognito) { } /** * Called before a tab is created from the top left button. * * @param sourceTabId The id of the source tab. */ public void onTabCreating(int sourceTabId) { } /** * Called when a tab is created from the top left button. * @param time The current time of the app in ms. * @param tabId The id of the newly created tab. * @param tabIndex The index of the newly created tab. * @param sourceTabId The id of the source tab. * @param newIsIncognito Whether the new tab is incognito. * @param background Whether the tab is created in the background. * @param originX The X screen coordinate in dp of the last touch down event that spawned * this tab. * @param originY The Y screen coordinate in dp of the last touch down event that spawned * this tab. */ public void onTabCreated(long time, int tabId, int tabIndex, int sourceTabId, boolean newIsIncognito, boolean background, float originX, float originY) { for (int i = 0; i < mSceneOverlays.size(); i++) { mSceneOverlays.get(i).tabCreated(time, newIsIncognito, tabId, sourceTabId, !background); } } /** * Called when a tab is restored (created FROM_RESTORE). * @param time The current time of the app in ms. * @param tabId The id of the restored tab. */ public void onTabRestored(long time, int tabId) { } /** * Called when the TabModelSelector has been initialized with an accurate tab count. */ public void onTabStateInitialized() { for (int i = 0; i < mSceneOverlays.size(); i++) { mSceneOverlays.get(i).tabStateInitialized(); } } /** * Called when the current tabModel switched (e.g. standard -> incognito). * * @param incognito True if the new model is incognito. */ public void onTabModelSwitched(boolean incognito) { for (int i = 0; i < mSceneOverlays.size(); i++) { mSceneOverlays.get(i).tabModelSwitched(incognito); } } /** * Called when a tab has been moved in the tabModel. * @param time The current time of the app in ms. * @param tabId The id of the Tab. * @param oldIndex The old index of the tab in the tabModel. * @param newIndex The new index of the tab in the tabModel. * @param incognito True if the tab is incognito. */ public void onTabMoved(long time, int tabId, int oldIndex, int newIndex, boolean incognito) { for (int i = 0; i < mSceneOverlays.size(); i++) { mSceneOverlays.get(i).tabMoved(time, incognito, tabId, oldIndex, newIndex); } } /** * Called when a tab has started loading. * @param id The id of the Tab. * @param incognito True if the tab is incognito. */ public void onTabPageLoadStarted(int id, boolean incognito) { for (int i = 0; i < mSceneOverlays.size(); i++) { mSceneOverlays.get(i).tabPageLoadStarted(id, incognito); } } /** * Called when a tab has finished loading. * @param id The id of the Tab. * @param incognito True if the tab is incognito. */ public void onTabPageLoadFinished(int id, boolean incognito) { for (int i = 0; i < mSceneOverlays.size(); i++) { mSceneOverlays.get(i).tabPageLoadFinished(id, incognito); } } /** * Called when a tab has started loading resources. * @param id The id of the Tab. * @param incognito True if the tab is incognito. */ public void onTabLoadStarted(int id, boolean incognito) { for (int i = 0; i < mSceneOverlays.size(); i++) { mSceneOverlays.get(i).tabLoadStarted(id, incognito); } } /** * Called when a tab has stopped loading resources. * @param id The id of the Tab. * @param incognito True if the tab is incognito. */ public void onTabLoadFinished(int id, boolean incognito) { for (int i = 0; i < mSceneOverlays.size(); i++) { mSceneOverlays.get(i).tabLoadFinished(id, incognito); } } /** * Called when a tab close has been undone and the tab has been restored. * @param time The current time of the app in ms. * @param id The id of the Tab. * @param incognito True if the tab is incognito */ public void onTabClosureCancelled(long time, int id, boolean incognito) { for (int i = 0; i < mSceneOverlays.size(); i++) { mSceneOverlays.get(i).tabClosureCancelled(time, incognito, id); } } /** * Called when a tab is finally closed if the action was previously undoable. * @param time The current time of the app in ms. * @param id The id of the Tab. * @param incognito True if the tab is incognito */ public void onTabClosureCommitted(long time, int id, boolean incognito) { } @Override public void onThumbnailChange(int id) { requestUpdate(); } /** * Steps the animation forward and updates all the animated values. * @param time The current time of the app in ms. * @param jumpToEnd Whether to finish the animation. * @return Whether the animation was finished. */ protected boolean onUpdateAnimation(long time, boolean jumpToEnd) { boolean finished = true; if (mLayoutAnimations != null) { if (jumpToEnd) { finished = mLayoutAnimations.finished(); mLayoutAnimations.updateAndFinish(); } else { finished = mLayoutAnimations.update(time); } if (finished || jumpToEnd) { mLayoutAnimations = null; onAnimationFinished(); } } // LayoutTabs may be running their own animations; make sure they are done. for (int i = 0; mLayoutTabs != null && i < mLayoutTabs.length; i++) { finished &= mLayoutTabs[i].onUpdateAnimation(time); } if (!finished) requestUpdate(); return finished; } /** * @return Whether or not there is an animation currently being driven by this {@link Layout}. */ @VisibleForTesting public boolean isLayoutAnimating() { return mLayoutAnimations != null && !mLayoutAnimations.finished(); } /** * Called when layout-specific actions are needed after the animation finishes. */ protected void onAnimationStarted() { } /** * Called when layout-specific actions are needed after the animation finishes. */ protected void onAnimationFinished() { } /** * Creates an {@link org.chromium.chrome.browser.compositor.layouts.ChromeAnimation * .AnimatableAnimation} and adds it to the animation. * Automatically sets the start value at the beginning of the animation. */ protected <T extends Enum<?>> void addToAnimation(Animatable<T> object, T prop, float start, float end, long duration, long startTime) { addToAnimation(object, prop, start, end, duration, startTime, false); } /** * Creates an {@link org.chromium.chrome.browser.compositor.layouts.ChromeAnimation * .AnimatableAnimation} and it to the animation. Uses a deceleration interpolator by default. */ protected <T extends Enum<?>> void addToAnimation(Animatable<T> object, T prop, float start, float end, long duration, long startTime, boolean setStartValueAfterDelay) { addToAnimation(object, prop, start, end, duration, startTime, setStartValueAfterDelay, ChromeAnimation.getDecelerateInterpolator()); } /** * Creates an {@link org.chromium.chrome.browser.compositor.layouts.ChromeAnimation * .AnimatableAnimation} and * adds it to the animation. * * @param <T> The Enum type of the Property being used * @param object The object being animated * @param prop The property being animated * @param start The starting value of the animation * @param end The ending value of the animation * @param duration The duration of the animation in ms * @param startTime The start time in ms * @param setStartValueAfterDelay See {@link Animation#setStartValueAfterStartDelay(boolean)} * @param interpolator The interpolator to use for the animation */ protected <T extends Enum<?>> void addToAnimation(Animatable<T> object, T prop, float start, float end, long duration, long startTime, boolean setStartValueAfterDelay, Interpolator interpolator) { ChromeAnimation.Animation<Animatable<?>> component = createAnimation(object, prop, start, end, duration, startTime, setStartValueAfterDelay, interpolator); addToAnimation(component); } /** * Appends an Animation to the current animation set and starts it immediately. If the set is * already finished or doesn't exist, the animation set is also started. */ protected void addToAnimation(ChromeAnimation.Animation<Animatable<?>> component) { if (mLayoutAnimations == null || mLayoutAnimations.finished()) { onAnimationStarted(); mLayoutAnimations = new ChromeAnimation<Animatable<?>>(); mLayoutAnimations.start(); } component.start(); mLayoutAnimations.add(component); requestUpdate(); } /** * @return whether or not the animation is currently being run. */ protected boolean animationIsRunning() { return mLayoutAnimations != null && !mLayoutAnimations.finished(); } /** * Cancels any animation for the given object and property. * @param object The object being animated. * @param prop The property to search for. */ protected <T extends Enum<?>> void cancelAnimation(Animatable<T> object, T prop) { if (mLayoutAnimations != null) { mLayoutAnimations.cancel(object, prop); } } /** * @return The {@link LayoutTab}s to be drawn. */ @SuppressFBWarnings("EI_EXPOSE_REP") public LayoutTab[] getLayoutTabsToRender() { return mLayoutTabs; } /** * @param id The id of the {@link LayoutTab} to search for. * @return A {@link LayoutTab} represented by a {@link Tab} with an id of {@code id}. */ public LayoutTab getLayoutTab(int id) { if (mLayoutTabs != null) { for (int i = 0; i < mLayoutTabs.length; i++) { if (mLayoutTabs[i].getId() == id) return mLayoutTabs[i]; } } return null; } /** * @return Whether the layout is handling the model updates when a tab is closing. */ public boolean handlesTabClosing() { return false; } /** * @return Whether the layout is handling the model updates when a tab is creating. */ public boolean handlesTabCreating() { if (mLayoutTabs == null || mLayoutTabs.length != 1) return false; for (int i = 0; i < mSceneOverlays.size(); i++) { if (mSceneOverlays.get(i).handlesTabCreating()) { // Prevent animation from happening if the overlay handles creation. startHiding(mLayoutTabs[0].getId(), false); doneHiding(); return true; } } return false; } /** * @return Whether the layout is handling the model updates when closing all the tabs. */ public boolean handlesCloseAll() { return false; } /** * @return True if the content decoration layer should be shown. */ public boolean shouldDisplayContentOverlay() { return false; } /** * @return True if the currently active content view is shown in the normal interactive mode. */ public boolean isTabInteractive() { return false; } /** * @return The number of drawn tabs. This does not count the one culled out. */ @VisibleForTesting public int getLayoutTabsDrawnCount() { return mRenderHost.getLayoutTabsDrawnCount(); } /** * Setting this will only take effect the next time this layout is shown. If it is currently * showing the original filter will still be used. * @param filter */ public void setEventFilter(EventFilter filter) { mEventFilter = filter; } /** * @param e The {@link MotionEvent} to consider. * @param offsets The current touch offsets that should be applied to the * {@link EventFilter}s. * @param isKeyboardShowing Whether or not the keyboard is showing. * @return The {@link EventFilter} the {@link Layout} is listening to. */ public EventFilter findInterceptingEventFilter( MotionEvent e, Point offsets, boolean isKeyboardShowing) { // The last added overlay will be drawn on top of everything else, therefore the last // filter added should have the first chance to intercept any touch events. for (int i = mSceneOverlays.size() - 1; i >= 0; i--) { EventFilter eventFilter = mSceneOverlays.get(i).getEventFilter(); if (eventFilter == null) continue; if (offsets != null) eventFilter.setCurrentMotionEventOffsets(offsets.x, offsets.y); if (eventFilter.onInterceptTouchEvent(e, isKeyboardShowing)) return eventFilter; } if (mEventFilter != null) { if (offsets != null) mEventFilter.setCurrentMotionEventOffsets(offsets.x, offsets.y); if (mEventFilter.onInterceptTouchEvent(e, isKeyboardShowing)) return mEventFilter; } return null; } /** * Build a {@link SceneLayer} if it hasn't already been built, and update it and return it. * * @param viewport A viewport in which to display content. * @param contentViewport The visible section of the viewport. * @param layerTitleCache A layer title cache. * @param tabContentManager A tab content manager. * @param resourceManager A resource manager. * @param fullscreenManager A fullscreen manager. * @return A {@link SceneLayer} that represents the content for this * {@link Layout}. */ public final SceneLayer getUpdatedSceneLayer(Rect viewport, Rect contentViewport, LayerTitleCache layerTitleCache, TabContentManager tabContentManager, ResourceManager resourceManager, ChromeFullscreenManager fullscreenManager) { updateSceneLayer(viewport, contentViewport, layerTitleCache, tabContentManager, resourceManager, fullscreenManager); float offsetPx = fullscreenManager != null ? fullscreenManager.getControlOffset() : 0.f; float dpToPx = getContext().getResources().getDisplayMetrics().density; float offsetDp = offsetPx / dpToPx; SceneLayer content = getSceneLayer(); for (int i = 0; i < mSceneOverlays.size(); i++) { // If the SceneOverlay is not showing, don't bother adding it to the tree. if (!mSceneOverlays.get(i).isSceneOverlayTreeShowing()) continue; SceneOverlayLayer overlayLayer = mSceneOverlays.get(i).getUpdatedSceneOverlayTree( layerTitleCache, resourceManager, offsetDp); overlayLayer.setContentTree(content); content = overlayLayer; } return content; } /** * @return Whether or not to force the top controls Android view to hide. */ public boolean forceHideTopControlsAndroidView() { for (int i = 0; i < mSceneOverlays.size(); i++) { // If any overlay wants to hide tha Android version of the top controls, hide them. if (mSceneOverlays.get(i).shouldHideAndroidTopControls()) return true; } return false; } /** * @return Whether the tabstrip's event filter is enabled. */ public boolean isTabStripEventFilterEnabled() { return true; } /** * Get an instance of {@link SceneLayer}. Any class inheriting {@link Layout} * should override this function in order for other functions to work. * * @return The scene layer for this {@link Layout}. */ protected abstract SceneLayer getSceneLayer(); /** * Update {@link SceneLayer} instance this layout holds. Any class inheriting {@link Layout} * should override this function in order for other functions to work. */ protected void updateSceneLayer(Rect viewport, Rect contentViewport, LayerTitleCache layerTitleCache, TabContentManager tabContentManager, ResourceManager resourceManager, ChromeFullscreenManager fullscreenManager) { } }