Index: chrome/android/java_staging/src/org/chromium/chrome/browser/compositor/layouts/LayoutManager.java |
diff --git a/chrome/android/java_staging/src/org/chromium/chrome/browser/compositor/layouts/LayoutManager.java b/chrome/android/java_staging/src/org/chromium/chrome/browser/compositor/layouts/LayoutManager.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..ed356d7cba6eee1dcbe5d2b1e052690645b79b48 |
--- /dev/null |
+++ b/chrome/android/java_staging/src/org/chromium/chrome/browser/compositor/layouts/LayoutManager.java |
@@ -0,0 +1,513 @@ |
+// 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.graphics.Point; |
+import android.graphics.Rect; |
+import android.graphics.RectF; |
+import android.os.SystemClock; |
+import android.view.MotionEvent; |
+import android.view.View; |
+import android.view.ViewGroup; |
+ |
+import org.chromium.base.ObserverList; |
+import org.chromium.base.TraceEvent; |
+import org.chromium.base.VisibleForTesting; |
+import org.chromium.base.annotations.SuppressFBWarnings; |
+import org.chromium.chrome.browser.compositor.layouts.Layout.Orientation; |
+import org.chromium.chrome.browser.compositor.layouts.Layout.SizingFlags; |
+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.EdgeSwipeHandler; |
+import org.chromium.chrome.browser.compositor.layouts.eventfilter.EventFilter; |
+import org.chromium.chrome.browser.compositor.layouts.eventfilter.EventFilterHost; |
+import org.chromium.chrome.browser.contextualsearch.ContextualSearchManagementDelegate; |
+import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager; |
+import org.chromium.chrome.browser.fullscreen.FullscreenManager; |
+import org.chromium.chrome.browser.tabmodel.TabCreatorManager; |
+import org.chromium.chrome.browser.tabmodel.TabModelSelector; |
+import org.chromium.content.browser.SPenSupport; |
+import org.chromium.ui.resources.dynamics.DynamicResourceLoader; |
+ |
+import java.util.List; |
+ |
+/** |
+ * A class that is responsible for managing an active {@link Layout} to show to the screen. This |
+ * includes lifecycle managment like showing/hiding this {@link Layout}. |
+ */ |
+public abstract class LayoutManager implements LayoutUpdateHost, LayoutProvider, EventFilterHost { |
+ /** Sampling at 60 fps. */ |
+ private static final long FRAME_DELTA_TIME_MS = 16; |
+ |
+ /** Used to convert pixels to dp. */ |
+ protected final float mPxToDp; |
+ |
+ /** The {@link LayoutManagerHost}, who is responsible for showing the active {@link Layout}. */ |
+ protected final LayoutManagerHost mHost; |
+ |
+ /** The last X coordinate of the last {@link MotionEvent#ACTION_DOWN} event. */ |
+ protected int mLastTapX; |
+ |
+ /** The last Y coordinate of the last {@link MotionEvent#ACTION_DOWN} event. */ |
+ protected int mLastTapY; |
+ |
+ // External Dependencies |
+ private TabModelSelector mTabModelSelector; |
+ private ViewGroup mContentContainer; |
+ |
+ // External Observers |
+ private final ObserverList<SceneChangeObserver> mSceneChangeObservers; |
+ |
+ // Current Layout State |
+ private Layout mActiveLayout; |
+ private Layout mNextActiveLayout; |
+ |
+ // Current Event Fitler State |
+ private EventFilter mActiveEventFilter; |
+ |
+ // Internal State |
+ private int mFullscreenToken = FullscreenManager.INVALID_TOKEN; |
+ private boolean mUpdateRequested; |
+ |
+ // Sizing State |
+ private final Rect mLastViewportPx = new Rect(); |
+ private final Rect mLastVisibleViewportPx = new Rect(); |
+ private final Rect mLastFullscreenViewportPx = new Rect(); |
+ protected final RectF mLastViewportDp = new RectF(); |
+ protected final RectF mLastVisibleViewportDp = new RectF(); |
+ protected final RectF mLastFullscreenViewportDp = new RectF(); |
+ |
+ protected float mLastContentWidthDp; |
+ protected float mLastContentHeightDp; |
+ protected float mLastHeightMinusTopControlsDp; |
+ |
+ private final RectF mCachedRectF = new RectF(); |
+ private final Rect mCachedRect = new Rect(); |
+ private final Point mCachedPoint = new Point(); |
+ |
+ /** |
+ * Creates a {@link LayoutManager} instance. |
+ * @param host A {@link LayoutManagerHost} instance. |
+ */ |
+ public LayoutManager(LayoutManagerHost host) { |
+ mHost = host; |
+ mPxToDp = 1.f / mHost.getContext().getResources().getDisplayMetrics().density; |
+ mSceneChangeObservers = new ObserverList<SceneChangeObserver>(); |
+ |
+ int hostWidth = host.getWidth(); |
+ int hostHeight = host.getHeight(); |
+ mLastViewportPx.set(0, 0, hostWidth, hostHeight); |
+ mLastVisibleViewportPx.set(0, 0, hostWidth, hostHeight); |
+ mLastFullscreenViewportPx.set(0, 0, hostWidth, hostHeight); |
+ |
+ mLastContentWidthDp = hostWidth * mPxToDp; |
+ mLastContentHeightDp = hostHeight * mPxToDp; |
+ mLastViewportDp.set(0, 0, mLastContentWidthDp, mLastContentHeightDp); |
+ mLastVisibleViewportDp.set(0, 0, mLastContentWidthDp, mLastContentHeightDp); |
+ mLastFullscreenViewportDp.set(0, 0, mLastContentWidthDp, mLastContentHeightDp); |
+ |
+ mLastHeightMinusTopControlsDp = mLastContentHeightDp; |
+ } |
+ |
+ /** |
+ * @return The actual current time of the app in ms. |
+ */ |
+ public static long time() { |
+ return SystemClock.uptimeMillis(); |
+ } |
+ |
+ /** |
+ * Gives the {@link LayoutManager} a chance to intercept and process touch events from the |
+ * Android {@link View} system. |
+ * @param e The {@link MotionEvent} that might be intercepted. |
+ * @param isKeyboardShowing Whether or not the keyboard is showing. |
+ * @return Whether or not this current touch gesture should be intercepted and |
+ * continually forwarded to this class. |
+ */ |
+ public boolean onInterceptTouchEvent(MotionEvent e, boolean isKeyboardShowing) { |
+ if (mActiveLayout == null) return false; |
+ |
+ if (e.getAction() == MotionEvent.ACTION_DOWN) { |
+ mLastTapX = (int) e.getX(); |
+ mLastTapY = (int) e.getY(); |
+ } |
+ |
+ Point offsets = getMotionOffsets(e); |
+ mActiveEventFilter = |
+ mActiveLayout.findInterceptingEventFilter(e, offsets, isKeyboardShowing); |
+ if (mActiveEventFilter != null) mActiveLayout.unstallImmediately(); |
+ return mActiveEventFilter != null; |
+ } |
+ |
+ /** |
+ * Gives the {@link LayoutManager} a chance to process the touch events from the Android |
+ * {@link View} system. |
+ * @param e A {@link MotionEvent} instance. |
+ * @return Whether or not {@code e} was consumed. |
+ */ |
+ public boolean onTouchEvent(MotionEvent e) { |
+ if (mActiveEventFilter == null) return false; |
+ |
+ boolean consumed = mActiveEventFilter.onTouchEvent(e); |
+ Point offsets = getMotionOffsets(e); |
+ if (offsets != null) mActiveEventFilter.setCurrentMotionEventOffsets(offsets.x, offsets.y); |
+ return consumed; |
+ } |
+ |
+ @Override |
+ public boolean propagateEvent(MotionEvent e) { |
+ if (e == null) return false; |
+ |
+ View view = getActiveLayout().getViewForInteraction(); |
+ if (view == null) return false; |
+ |
+ return view.dispatchTouchEvent(e); |
+ } |
+ |
+ @Override |
+ public int getViewportWidth() { |
+ return mHost.getWidth(); |
+ } |
+ |
+ private Point getMotionOffsets(MotionEvent e) { |
+ int actionMasked = e.getActionMasked(); |
+ if (SPenSupport.isSPenSupported(mHost.getContext())) { |
+ actionMasked = SPenSupport.convertSPenEventAction(actionMasked); |
+ } |
+ |
+ if (actionMasked == MotionEvent.ACTION_DOWN |
+ || actionMasked == MotionEvent.ACTION_HOVER_ENTER) { |
+ getViewportPixel(mCachedRect); |
+ |
+ mCachedPoint.set(-mCachedRect.left, -mCachedRect.top); |
+ return mCachedPoint; |
+ } else if (actionMasked == MotionEvent.ACTION_UP |
+ || actionMasked == MotionEvent.ACTION_CANCEL |
+ || actionMasked == MotionEvent.ACTION_HOVER_EXIT) { |
+ mCachedPoint.set(0, 0); |
+ return mCachedPoint; |
+ } |
+ |
+ return null; |
+ } |
+ |
+ /** |
+ * Updates the state of the active {@link Layout} if needed. This updates the animations and |
+ * cascades the changes to the tabs. |
+ */ |
+ public void onUpdate() { |
+ TraceEvent.begin("LayoutDriver:onUpdate"); |
+ onUpdate(time(), FRAME_DELTA_TIME_MS); |
+ TraceEvent.end("LayoutDriver:onUpdate"); |
+ } |
+ |
+ /** |
+ * Updates the state of the layout. |
+ * @param timeMs The time in milliseconds. |
+ * @param dtMs The delta time since the last update in milliseconds. |
+ * @return Whether or not the {@link LayoutManager} needs more updates. |
+ */ |
+ @VisibleForTesting |
+ public boolean onUpdate(long timeMs, long dtMs) { |
+ if (!mUpdateRequested) return false; |
+ mUpdateRequested = false; |
+ final Layout layout = getActiveLayout(); |
+ if (layout != null && layout.onUpdate(timeMs, dtMs) && layout.isHiding()) { |
+ layout.doneHiding(); |
+ } |
+ return mUpdateRequested; |
+ } |
+ |
+ /** |
+ * Initializes the {@link LayoutManager}. Must be called before using this object. |
+ * @param selector A {@link TabModelSelector} instance. |
+ * @param creator A {@link TabCreatorManager} instance. |
+ * @param content A {@link TabContentManager} instance. |
+ * @param androidContentContainer A {@link ViewGroup} for Android views to be bound to. |
+ * @param contextualSearchDelegate A {@link ContextualSearchDelegate} instance. |
+ * @param dynamicResourceLoader A {@link DynamicResourceLoader} instance. |
+ */ |
+ public void init(TabModelSelector selector, TabCreatorManager creator, |
+ TabContentManager content, ViewGroup androidContentContainer, |
+ ContextualSearchManagementDelegate contextualSearchDelegate, |
+ DynamicResourceLoader dynamicResourceLoader) { |
+ mTabModelSelector = selector; |
+ mContentContainer = androidContentContainer; |
+ |
+ if (mNextActiveLayout != null) startShowing(mNextActiveLayout, true); |
+ } |
+ |
+ /** |
+ * Cleans up and destroys this object. It should not be used after this. |
+ */ |
+ public void destroy() { |
+ mSceneChangeObservers.clear(); |
+ } |
+ |
+ /** |
+ * @param observer Adds {@code observer} to be notified when the active {@code Layout} changes. |
+ */ |
+ public void addSceneChangeObserver(SceneChangeObserver observer) { |
+ mSceneChangeObservers.addObserver(observer); |
+ } |
+ |
+ /** |
+ * @param observer Removes {@code observer}. |
+ */ |
+ public void removeSceneChangeObserver(SceneChangeObserver observer) { |
+ mSceneChangeObservers.removeObserver(observer); |
+ } |
+ |
+ /** |
+ * Called when the viewport has been changed. Override this to be notified when |
+ * {@link #pushNewViewport(Rect, Rect, int)} calls actually change the current viewport. |
+ * @param viewportDp The new viewport in dp. |
+ */ |
+ protected void onViewportChanged(RectF viewportDp) { |
+ if (getActiveLayout() != null) { |
+ getActiveLayout().sizeChanged(viewportDp, mLastVisibleViewportDp, |
+ mLastFullscreenViewportDp, mLastHeightMinusTopControlsDp, getOrientation()); |
+ } |
+ } |
+ |
+ /** |
+ * Should be called from an external source when the viewport changes. {@code viewport} and |
+ * {@code visibleViewport} are different, as the top controls might be covering part of the |
+ * viewport but a {@link Layout} might want to consume the whole space (or not). |
+ * @param viewport The new viewport in px. |
+ * @param visibleViewport The new visible viewport in px. |
+ * @param heightMinusTopControls The height of the viewport minus the top controls. |
+ */ |
+ public final void pushNewViewport( |
+ Rect viewport, Rect visibleViewport, int heightMinusTopControls) { |
+ mLastViewportPx.set(viewport); |
+ mLastVisibleViewportPx.set(visibleViewport); |
+ |
+ mLastViewportDp.set(viewport.left * mPxToDp, viewport.top * mPxToDp, |
+ viewport.right * mPxToDp, viewport.bottom * mPxToDp); |
+ mLastVisibleViewportDp.set(visibleViewport.left * mPxToDp, visibleViewport.top * mPxToDp, |
+ visibleViewport.right * mPxToDp, visibleViewport.bottom * mPxToDp); |
+ mLastFullscreenViewportDp.set(0, 0, viewport.right * mPxToDp, viewport.bottom * mPxToDp); |
+ mLastHeightMinusTopControlsDp = heightMinusTopControls * mPxToDp; |
+ |
+ propagateViewportToActiveLayout(); |
+ } |
+ |
+ /** |
+ * @return The default {@link Layout} to show when {@link Layout}s get hidden and the next |
+ * {@link Layout} to show isn't known. |
+ */ |
+ protected abstract Layout getDefaultLayout(); |
+ |
+ // TODO(dtrainor): Remove these from this control class. Split the interface? |
+ @Override public abstract void initLayoutTabFromHost(final int tabId); |
+ |
+ @Override |
+ public abstract LayoutTab createLayoutTab(int id, boolean incognito, boolean showCloseButton, |
+ boolean isTitleNeeded, float maxContentWidth, float maxContentHeight); |
+ |
+ @Override public abstract void releaseTabLayout(int id); |
+ |
+ /** |
+ * @return The {@link TabModelSelector} instance this class knows about. |
+ */ |
+ protected TabModelSelector getTabModelSelector() { |
+ return mTabModelSelector; |
+ } |
+ |
+ /** |
+ * @return The next {@link Layout} that will be shown. If no {@link Layout} has been set |
+ * since the last time {@link #startShowing(Layout, boolean)} was called, this will be |
+ * {@link #getDefaultLayout()}. |
+ */ |
+ protected Layout getNextLayout() { |
+ return mNextActiveLayout != null ? mNextActiveLayout : getDefaultLayout(); |
+ } |
+ |
+ @Override |
+ public Layout getActiveLayout() { |
+ return mActiveLayout; |
+ } |
+ |
+ @Override |
+ public RectF getViewportDp(RectF rect) { |
+ if (rect == null) rect = new RectF(); |
+ |
+ if (getActiveLayout() == null) { |
+ rect.set(mLastViewportDp); |
+ return rect; |
+ } |
+ |
+ final int flags = getActiveLayout().getSizingFlags(); |
+ if ((flags & SizingFlags.REQUIRE_FULLSCREEN_SIZE) != 0) { |
+ rect.set(mLastFullscreenViewportDp); |
+ } else if ((flags & SizingFlags.ALLOW_TOOLBAR_HIDE) != 0) { |
+ rect.set(mLastViewportDp); |
+ } else { |
+ rect.set(mLastVisibleViewportDp); |
+ } |
+ |
+ return rect; |
+ } |
+ |
+ @Override |
+ public Rect getViewportPixel(Rect rect) { |
+ if (rect == null) rect = new Rect(); |
+ |
+ if (getActiveLayout() == null) { |
+ rect.set(mLastViewportPx); |
+ return rect; |
+ } |
+ |
+ final int flags = getActiveLayout().getSizingFlags(); |
+ if ((flags & SizingFlags.REQUIRE_FULLSCREEN_SIZE) != 0) { |
+ rect.set(mLastFullscreenViewportPx); |
+ } else if ((flags & SizingFlags.ALLOW_TOOLBAR_HIDE) != 0) { |
+ rect.set(mLastViewportPx); |
+ } else { |
+ rect.set(mLastVisibleViewportPx); |
+ } |
+ return rect; |
+ } |
+ |
+ @Override |
+ public ChromeFullscreenManager getFullscreenManager() { |
+ return mHost != null ? mHost.getFullscreenManager() : null; |
+ } |
+ |
+ @Override |
+ public void requestUpdate() { |
+ if (!mUpdateRequested) mHost.requestRender(); |
+ mUpdateRequested = true; |
+ } |
+ |
+ @Override |
+ public void startHiding(int nextTabId, boolean hintAtTabSelection) { |
+ requestUpdate(); |
+ if (hintAtTabSelection) { |
+ for (SceneChangeObserver observer : mSceneChangeObservers) { |
+ observer.onTabSelectionHinted(nextTabId); |
+ } |
+ } |
+ } |
+ |
+ @SuppressFBWarnings("RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE") |
+ @Override |
+ public void doneHiding() { |
+ // TODO: If next layout is default layout clear caches (should this be a sub layout thing?) |
+ |
+ assert mNextActiveLayout != null : "Need to have a next active layout."; |
+ if (mNextActiveLayout != null) { |
+ startShowing(mNextActiveLayout, true); |
+ } |
+ } |
+ |
+ @Override |
+ public void doneShowing() {} |
+ |
+ /** |
+ * Should be called by control logic to show a new {@link Layout}. |
+ * |
+ * TODO(dtrainor, clholgat): Clean up the show logic to guarantee startHiding/doneHiding get |
+ * called. |
+ * |
+ * @param layout The new {@link Layout} to show. |
+ * @param animate Whether or not {@code layout} should animate as it shows. |
+ */ |
+ protected void startShowing(Layout layout, boolean animate) { |
+ assert mTabModelSelector != null : "init() must be called first."; |
+ assert layout != null : "Can't show a null layout."; |
+ |
+ // Set the new layout |
+ setNextLayout(null); |
+ Layout oldLayout = getActiveLayout(); |
+ if (oldLayout != layout) { |
+ if (oldLayout != null) { |
+ oldLayout.detachViews(); |
+ } |
+ layout.contextChanged(mHost.getContext()); |
+ layout.attachViews(mContentContainer); |
+ mActiveLayout = layout; |
+ } |
+ |
+ ChromeFullscreenManager fullscreenManager = mHost.getFullscreenManager(); |
+ if (fullscreenManager != null) { |
+ // Release any old fullscreen token we were holding. |
+ fullscreenManager.hideControlsPersistent(mFullscreenToken); |
+ mFullscreenToken = FullscreenManager.INVALID_TOKEN; |
+ |
+ // Grab a new fullscreen token if this layout can't be in fullscreen. |
+ final int flags = getActiveLayout().getSizingFlags(); |
+ if ((flags & SizingFlags.ALLOW_TOOLBAR_HIDE) == 0) { |
+ mFullscreenToken = fullscreenManager.showControlsPersistent(); |
+ } |
+ |
+ // Hide the toolbar immediately if the layout wants it gone quickly. |
+ fullscreenManager.setTopControlsPermamentlyHidden( |
+ flags == SizingFlags.HELPER_HIDE_TOOLBAR_IMMEDIATE); |
+ } |
+ |
+ propagateViewportToActiveLayout(); |
+ getActiveLayout().show(time(), animate); |
+ mHost.setContentOverlayVisibility(getActiveLayout().shouldDisplayContentOverlay()); |
+ mHost.requestRender(); |
+ |
+ // Notify observers about the new scene. |
+ for (SceneChangeObserver observer : mSceneChangeObservers) { |
+ observer.onSceneChange(getActiveLayout()); |
+ } |
+ } |
+ |
+ /** |
+ * Sets the next {@link Layout} to show after the current {@link Layout} is finished and is done |
+ * hiding. |
+ * @param layout The new {@link Layout} to show. |
+ */ |
+ public void setNextLayout(Layout layout) { |
+ mNextActiveLayout = (layout == null) ? getDefaultLayout() : layout; |
+ } |
+ |
+ @Override |
+ public boolean isActiveLayout(Layout layout) { |
+ return layout == mActiveLayout; |
+ } |
+ |
+ /** |
+ * Get a list of virtual views for accessibility. |
+ * |
+ * @param views A List to populate with virtual views. |
+ */ |
+ public abstract void getVirtualViews(List<VirtualView> views); |
+ |
+ /** |
+ * @return The {@link EdgeSwipeHandler} responsible for processing swipe events for the toolbar. |
+ */ |
+ public abstract EdgeSwipeHandler getTopSwipeHandler(); |
+ |
+ /** |
+ * Should be called when the user presses the back button on the phone. |
+ * @return Whether or not the back button was consumed by the active {@link Layout}. |
+ */ |
+ public abstract boolean onBackPressed(); |
+ |
+ private void propagateViewportToActiveLayout() { |
+ getViewportDp(mCachedRectF); |
+ |
+ float width = mCachedRectF.width(); |
+ float height = mCachedRectF.height(); |
+ mLastContentWidthDp = width; |
+ mLastContentHeightDp = height; |
+ onViewportChanged(mCachedRectF); |
+ } |
+ |
+ private int getOrientation() { |
+ if (mLastContentWidthDp > mLastContentHeightDp) { |
+ return Orientation.LANDSCAPE; |
+ } else { |
+ return Orientation.PORTRAIT; |
+ } |
+ } |
+} |