OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.chrome.browser.compositor.layouts; |
| 6 |
| 7 import android.graphics.Point; |
| 8 import android.graphics.Rect; |
| 9 import android.graphics.RectF; |
| 10 import android.os.SystemClock; |
| 11 import android.view.MotionEvent; |
| 12 import android.view.View; |
| 13 import android.view.ViewGroup; |
| 14 |
| 15 import org.chromium.base.ObserverList; |
| 16 import org.chromium.base.TraceEvent; |
| 17 import org.chromium.base.VisibleForTesting; |
| 18 import org.chromium.base.annotations.SuppressFBWarnings; |
| 19 import org.chromium.chrome.browser.compositor.layouts.Layout.Orientation; |
| 20 import org.chromium.chrome.browser.compositor.layouts.Layout.SizingFlags; |
| 21 import org.chromium.chrome.browser.compositor.layouts.components.LayoutTab; |
| 22 import org.chromium.chrome.browser.compositor.layouts.components.VirtualView; |
| 23 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager; |
| 24 import org.chromium.chrome.browser.compositor.layouts.eventfilter.EdgeSwipeHandl
er; |
| 25 import org.chromium.chrome.browser.compositor.layouts.eventfilter.EventFilter; |
| 26 import org.chromium.chrome.browser.compositor.layouts.eventfilter.EventFilterHos
t; |
| 27 import org.chromium.chrome.browser.contextualsearch.ContextualSearchManagementDe
legate; |
| 28 import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager; |
| 29 import org.chromium.chrome.browser.fullscreen.FullscreenManager; |
| 30 import org.chromium.chrome.browser.tabmodel.TabCreatorManager; |
| 31 import org.chromium.chrome.browser.tabmodel.TabModelSelector; |
| 32 import org.chromium.content.browser.SPenSupport; |
| 33 import org.chromium.ui.resources.dynamics.DynamicResourceLoader; |
| 34 |
| 35 import java.util.List; |
| 36 |
| 37 /** |
| 38 * A class that is responsible for managing an active {@link Layout} to show to
the screen. This |
| 39 * includes lifecycle managment like showing/hiding this {@link Layout}. |
| 40 */ |
| 41 public abstract class LayoutManager implements LayoutUpdateHost, LayoutProvider,
EventFilterHost { |
| 42 /** Sampling at 60 fps. */ |
| 43 private static final long FRAME_DELTA_TIME_MS = 16; |
| 44 |
| 45 /** Used to convert pixels to dp. */ |
| 46 protected final float mPxToDp; |
| 47 |
| 48 /** The {@link LayoutManagerHost}, who is responsible for showing the active
{@link Layout}. */ |
| 49 protected final LayoutManagerHost mHost; |
| 50 |
| 51 /** The last X coordinate of the last {@link MotionEvent#ACTION_DOWN} event.
*/ |
| 52 protected int mLastTapX; |
| 53 |
| 54 /** The last Y coordinate of the last {@link MotionEvent#ACTION_DOWN} event.
*/ |
| 55 protected int mLastTapY; |
| 56 |
| 57 // External Dependencies |
| 58 private TabModelSelector mTabModelSelector; |
| 59 private ViewGroup mContentContainer; |
| 60 |
| 61 // External Observers |
| 62 private final ObserverList<SceneChangeObserver> mSceneChangeObservers; |
| 63 |
| 64 // Current Layout State |
| 65 private Layout mActiveLayout; |
| 66 private Layout mNextActiveLayout; |
| 67 |
| 68 // Current Event Fitler State |
| 69 private EventFilter mActiveEventFilter; |
| 70 |
| 71 // Internal State |
| 72 private int mFullscreenToken = FullscreenManager.INVALID_TOKEN; |
| 73 private boolean mUpdateRequested; |
| 74 |
| 75 // Sizing State |
| 76 private final Rect mLastViewportPx = new Rect(); |
| 77 private final Rect mLastVisibleViewportPx = new Rect(); |
| 78 private final Rect mLastFullscreenViewportPx = new Rect(); |
| 79 protected final RectF mLastViewportDp = new RectF(); |
| 80 protected final RectF mLastVisibleViewportDp = new RectF(); |
| 81 protected final RectF mLastFullscreenViewportDp = new RectF(); |
| 82 |
| 83 protected float mLastContentWidthDp; |
| 84 protected float mLastContentHeightDp; |
| 85 protected float mLastHeightMinusTopControlsDp; |
| 86 |
| 87 private final RectF mCachedRectF = new RectF(); |
| 88 private final Rect mCachedRect = new Rect(); |
| 89 private final Point mCachedPoint = new Point(); |
| 90 |
| 91 /** |
| 92 * Creates a {@link LayoutManager} instance. |
| 93 * @param host A {@link LayoutManagerHost} instance. |
| 94 */ |
| 95 public LayoutManager(LayoutManagerHost host) { |
| 96 mHost = host; |
| 97 mPxToDp = 1.f / mHost.getContext().getResources().getDisplayMetrics().de
nsity; |
| 98 mSceneChangeObservers = new ObserverList<SceneChangeObserver>(); |
| 99 |
| 100 int hostWidth = host.getWidth(); |
| 101 int hostHeight = host.getHeight(); |
| 102 mLastViewportPx.set(0, 0, hostWidth, hostHeight); |
| 103 mLastVisibleViewportPx.set(0, 0, hostWidth, hostHeight); |
| 104 mLastFullscreenViewportPx.set(0, 0, hostWidth, hostHeight); |
| 105 |
| 106 mLastContentWidthDp = hostWidth * mPxToDp; |
| 107 mLastContentHeightDp = hostHeight * mPxToDp; |
| 108 mLastViewportDp.set(0, 0, mLastContentWidthDp, mLastContentHeightDp); |
| 109 mLastVisibleViewportDp.set(0, 0, mLastContentWidthDp, mLastContentHeight
Dp); |
| 110 mLastFullscreenViewportDp.set(0, 0, mLastContentWidthDp, mLastContentHei
ghtDp); |
| 111 |
| 112 mLastHeightMinusTopControlsDp = mLastContentHeightDp; |
| 113 } |
| 114 |
| 115 /** |
| 116 * @return The actual current time of the app in ms. |
| 117 */ |
| 118 public static long time() { |
| 119 return SystemClock.uptimeMillis(); |
| 120 } |
| 121 |
| 122 /** |
| 123 * Gives the {@link LayoutManager} a chance to intercept and process touch e
vents from the |
| 124 * Android {@link View} system. |
| 125 * @param e The {@link MotionEvent} that might be intercepte
d. |
| 126 * @param isKeyboardShowing Whether or not the keyboard is showing. |
| 127 * @return Whether or not this current touch gesture should
be intercepted and |
| 128 * continually forwarded to this class. |
| 129 */ |
| 130 public boolean onInterceptTouchEvent(MotionEvent e, boolean isKeyboardShowin
g) { |
| 131 if (mActiveLayout == null) return false; |
| 132 |
| 133 if (e.getAction() == MotionEvent.ACTION_DOWN) { |
| 134 mLastTapX = (int) e.getX(); |
| 135 mLastTapY = (int) e.getY(); |
| 136 } |
| 137 |
| 138 Point offsets = getMotionOffsets(e); |
| 139 mActiveEventFilter = |
| 140 mActiveLayout.findInterceptingEventFilter(e, offsets, isKeyboard
Showing); |
| 141 if (mActiveEventFilter != null) mActiveLayout.unstallImmediately(); |
| 142 return mActiveEventFilter != null; |
| 143 } |
| 144 |
| 145 /** |
| 146 * Gives the {@link LayoutManager} a chance to process the touch events from
the Android |
| 147 * {@link View} system. |
| 148 * @param e A {@link MotionEvent} instance. |
| 149 * @return Whether or not {@code e} was consumed. |
| 150 */ |
| 151 public boolean onTouchEvent(MotionEvent e) { |
| 152 if (mActiveEventFilter == null) return false; |
| 153 |
| 154 boolean consumed = mActiveEventFilter.onTouchEvent(e); |
| 155 Point offsets = getMotionOffsets(e); |
| 156 if (offsets != null) mActiveEventFilter.setCurrentMotionEventOffsets(off
sets.x, offsets.y); |
| 157 return consumed; |
| 158 } |
| 159 |
| 160 @Override |
| 161 public boolean propagateEvent(MotionEvent e) { |
| 162 if (e == null) return false; |
| 163 |
| 164 View view = getActiveLayout().getViewForInteraction(); |
| 165 if (view == null) return false; |
| 166 |
| 167 return view.dispatchTouchEvent(e); |
| 168 } |
| 169 |
| 170 @Override |
| 171 public int getViewportWidth() { |
| 172 return mHost.getWidth(); |
| 173 } |
| 174 |
| 175 private Point getMotionOffsets(MotionEvent e) { |
| 176 int actionMasked = e.getActionMasked(); |
| 177 if (SPenSupport.isSPenSupported(mHost.getContext())) { |
| 178 actionMasked = SPenSupport.convertSPenEventAction(actionMasked); |
| 179 } |
| 180 |
| 181 if (actionMasked == MotionEvent.ACTION_DOWN |
| 182 || actionMasked == MotionEvent.ACTION_HOVER_ENTER) { |
| 183 getViewportPixel(mCachedRect); |
| 184 |
| 185 mCachedPoint.set(-mCachedRect.left, -mCachedRect.top); |
| 186 return mCachedPoint; |
| 187 } else if (actionMasked == MotionEvent.ACTION_UP |
| 188 || actionMasked == MotionEvent.ACTION_CANCEL |
| 189 || actionMasked == MotionEvent.ACTION_HOVER_EXIT) { |
| 190 mCachedPoint.set(0, 0); |
| 191 return mCachedPoint; |
| 192 } |
| 193 |
| 194 return null; |
| 195 } |
| 196 |
| 197 /** |
| 198 * Updates the state of the active {@link Layout} if needed. This updates t
he animations and |
| 199 * cascades the changes to the tabs. |
| 200 */ |
| 201 public void onUpdate() { |
| 202 TraceEvent.begin("LayoutDriver:onUpdate"); |
| 203 onUpdate(time(), FRAME_DELTA_TIME_MS); |
| 204 TraceEvent.end("LayoutDriver:onUpdate"); |
| 205 } |
| 206 |
| 207 /** |
| 208 * Updates the state of the layout. |
| 209 * @param timeMs The time in milliseconds. |
| 210 * @param dtMs The delta time since the last update in milliseconds. |
| 211 * @return Whether or not the {@link LayoutManager} needs more updates
. |
| 212 */ |
| 213 @VisibleForTesting |
| 214 public boolean onUpdate(long timeMs, long dtMs) { |
| 215 if (!mUpdateRequested) return false; |
| 216 mUpdateRequested = false; |
| 217 final Layout layout = getActiveLayout(); |
| 218 if (layout != null && layout.onUpdate(timeMs, dtMs) && layout.isHiding()
) { |
| 219 layout.doneHiding(); |
| 220 } |
| 221 return mUpdateRequested; |
| 222 } |
| 223 |
| 224 /** |
| 225 * Initializes the {@link LayoutManager}. Must be called before using this
object. |
| 226 * @param selector A {@link TabModelSelector} instance. |
| 227 * @param creator A {@link TabCreatorManager} instance. |
| 228 * @param content A {@link TabContentManager} instance. |
| 229 * @param androidContentContainer A {@link ViewGroup} for Android views to
be bound to. |
| 230 * @param contextualSearchDelegate A {@link ContextualSearchDelegate} instan
ce. |
| 231 * @param dynamicResourceLoader A {@link DynamicResourceLoader} instance. |
| 232 */ |
| 233 public void init(TabModelSelector selector, TabCreatorManager creator, |
| 234 TabContentManager content, ViewGroup androidContentContainer, |
| 235 ContextualSearchManagementDelegate contextualSearchDelegate, |
| 236 DynamicResourceLoader dynamicResourceLoader) { |
| 237 mTabModelSelector = selector; |
| 238 mContentContainer = androidContentContainer; |
| 239 |
| 240 if (mNextActiveLayout != null) startShowing(mNextActiveLayout, true); |
| 241 } |
| 242 |
| 243 /** |
| 244 * Cleans up and destroys this object. It should not be used after this. |
| 245 */ |
| 246 public void destroy() { |
| 247 mSceneChangeObservers.clear(); |
| 248 } |
| 249 |
| 250 /** |
| 251 * @param observer Adds {@code observer} to be notified when the active {@co
de Layout} changes. |
| 252 */ |
| 253 public void addSceneChangeObserver(SceneChangeObserver observer) { |
| 254 mSceneChangeObservers.addObserver(observer); |
| 255 } |
| 256 |
| 257 /** |
| 258 * @param observer Removes {@code observer}. |
| 259 */ |
| 260 public void removeSceneChangeObserver(SceneChangeObserver observer) { |
| 261 mSceneChangeObservers.removeObserver(observer); |
| 262 } |
| 263 |
| 264 /** |
| 265 * Called when the viewport has been changed. Override this to be notified
when |
| 266 * {@link #pushNewViewport(Rect, Rect, int)} calls actually change the curre
nt viewport. |
| 267 * @param viewportDp The new viewport in dp. |
| 268 */ |
| 269 protected void onViewportChanged(RectF viewportDp) { |
| 270 if (getActiveLayout() != null) { |
| 271 getActiveLayout().sizeChanged(viewportDp, mLastVisibleViewportDp, |
| 272 mLastFullscreenViewportDp, mLastHeightMinusTopControlsDp, ge
tOrientation()); |
| 273 } |
| 274 } |
| 275 |
| 276 /** |
| 277 * Should be called from an external source when the viewport changes. {@co
de viewport} and |
| 278 * {@code visibleViewport} are different, as the top controls might be cover
ing part of the |
| 279 * viewport but a {@link Layout} might want to consume the whole space (or n
ot). |
| 280 * @param viewport The new viewport in px. |
| 281 * @param visibleViewport The new visible viewport in px. |
| 282 * @param heightMinusTopControls The height of the viewport minus the top co
ntrols. |
| 283 */ |
| 284 public final void pushNewViewport( |
| 285 Rect viewport, Rect visibleViewport, int heightMinusTopControls) { |
| 286 mLastViewportPx.set(viewport); |
| 287 mLastVisibleViewportPx.set(visibleViewport); |
| 288 |
| 289 mLastViewportDp.set(viewport.left * mPxToDp, viewport.top * mPxToDp, |
| 290 viewport.right * mPxToDp, viewport.bottom * mPxToDp); |
| 291 mLastVisibleViewportDp.set(visibleViewport.left * mPxToDp, visibleViewpo
rt.top * mPxToDp, |
| 292 visibleViewport.right * mPxToDp, visibleViewport.bottom * mPxToD
p); |
| 293 mLastFullscreenViewportDp.set(0, 0, viewport.right * mPxToDp, viewport.b
ottom * mPxToDp); |
| 294 mLastHeightMinusTopControlsDp = heightMinusTopControls * mPxToDp; |
| 295 |
| 296 propagateViewportToActiveLayout(); |
| 297 } |
| 298 |
| 299 /** |
| 300 * @return The default {@link Layout} to show when {@link Layout}s get hidde
n and the next |
| 301 * {@link Layout} to show isn't known. |
| 302 */ |
| 303 protected abstract Layout getDefaultLayout(); |
| 304 |
| 305 // TODO(dtrainor): Remove these from this control class. Split the interfac
e? |
| 306 @Override public abstract void initLayoutTabFromHost(final int tabId); |
| 307 |
| 308 @Override |
| 309 public abstract LayoutTab createLayoutTab(int id, boolean incognito, boolean
showCloseButton, |
| 310 boolean isTitleNeeded, float maxContentWidth, float maxContentHeight
); |
| 311 |
| 312 @Override public abstract void releaseTabLayout(int id); |
| 313 |
| 314 /** |
| 315 * @return The {@link TabModelSelector} instance this class knows about. |
| 316 */ |
| 317 protected TabModelSelector getTabModelSelector() { |
| 318 return mTabModelSelector; |
| 319 } |
| 320 |
| 321 /** |
| 322 * @return The next {@link Layout} that will be shown. If no {@link Layout}
has been set |
| 323 * since the last time {@link #startShowing(Layout, boolean)} was ca
lled, this will be |
| 324 * {@link #getDefaultLayout()}. |
| 325 */ |
| 326 protected Layout getNextLayout() { |
| 327 return mNextActiveLayout != null ? mNextActiveLayout : getDefaultLayout(
); |
| 328 } |
| 329 |
| 330 @Override |
| 331 public Layout getActiveLayout() { |
| 332 return mActiveLayout; |
| 333 } |
| 334 |
| 335 @Override |
| 336 public RectF getViewportDp(RectF rect) { |
| 337 if (rect == null) rect = new RectF(); |
| 338 |
| 339 if (getActiveLayout() == null) { |
| 340 rect.set(mLastViewportDp); |
| 341 return rect; |
| 342 } |
| 343 |
| 344 final int flags = getActiveLayout().getSizingFlags(); |
| 345 if ((flags & SizingFlags.REQUIRE_FULLSCREEN_SIZE) != 0) { |
| 346 rect.set(mLastFullscreenViewportDp); |
| 347 } else if ((flags & SizingFlags.ALLOW_TOOLBAR_HIDE) != 0) { |
| 348 rect.set(mLastViewportDp); |
| 349 } else { |
| 350 rect.set(mLastVisibleViewportDp); |
| 351 } |
| 352 |
| 353 return rect; |
| 354 } |
| 355 |
| 356 @Override |
| 357 public Rect getViewportPixel(Rect rect) { |
| 358 if (rect == null) rect = new Rect(); |
| 359 |
| 360 if (getActiveLayout() == null) { |
| 361 rect.set(mLastViewportPx); |
| 362 return rect; |
| 363 } |
| 364 |
| 365 final int flags = getActiveLayout().getSizingFlags(); |
| 366 if ((flags & SizingFlags.REQUIRE_FULLSCREEN_SIZE) != 0) { |
| 367 rect.set(mLastFullscreenViewportPx); |
| 368 } else if ((flags & SizingFlags.ALLOW_TOOLBAR_HIDE) != 0) { |
| 369 rect.set(mLastViewportPx); |
| 370 } else { |
| 371 rect.set(mLastVisibleViewportPx); |
| 372 } |
| 373 return rect; |
| 374 } |
| 375 |
| 376 @Override |
| 377 public ChromeFullscreenManager getFullscreenManager() { |
| 378 return mHost != null ? mHost.getFullscreenManager() : null; |
| 379 } |
| 380 |
| 381 @Override |
| 382 public void requestUpdate() { |
| 383 if (!mUpdateRequested) mHost.requestRender(); |
| 384 mUpdateRequested = true; |
| 385 } |
| 386 |
| 387 @Override |
| 388 public void startHiding(int nextTabId, boolean hintAtTabSelection) { |
| 389 requestUpdate(); |
| 390 if (hintAtTabSelection) { |
| 391 for (SceneChangeObserver observer : mSceneChangeObservers) { |
| 392 observer.onTabSelectionHinted(nextTabId); |
| 393 } |
| 394 } |
| 395 } |
| 396 |
| 397 @SuppressFBWarnings("RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE") |
| 398 @Override |
| 399 public void doneHiding() { |
| 400 // TODO: If next layout is default layout clear caches (should this be a
sub layout thing?) |
| 401 |
| 402 assert mNextActiveLayout != null : "Need to have a next active layout."; |
| 403 if (mNextActiveLayout != null) { |
| 404 startShowing(mNextActiveLayout, true); |
| 405 } |
| 406 } |
| 407 |
| 408 @Override |
| 409 public void doneShowing() {} |
| 410 |
| 411 /** |
| 412 * Should be called by control logic to show a new {@link Layout}. |
| 413 * |
| 414 * TODO(dtrainor, clholgat): Clean up the show logic to guarantee startHidin
g/doneHiding get |
| 415 * called. |
| 416 * |
| 417 * @param layout The new {@link Layout} to show. |
| 418 * @param animate Whether or not {@code layout} should animate as it shows. |
| 419 */ |
| 420 protected void startShowing(Layout layout, boolean animate) { |
| 421 assert mTabModelSelector != null : "init() must be called first."; |
| 422 assert layout != null : "Can't show a null layout."; |
| 423 |
| 424 // Set the new layout |
| 425 setNextLayout(null); |
| 426 Layout oldLayout = getActiveLayout(); |
| 427 if (oldLayout != layout) { |
| 428 if (oldLayout != null) { |
| 429 oldLayout.detachViews(); |
| 430 } |
| 431 layout.contextChanged(mHost.getContext()); |
| 432 layout.attachViews(mContentContainer); |
| 433 mActiveLayout = layout; |
| 434 } |
| 435 |
| 436 ChromeFullscreenManager fullscreenManager = mHost.getFullscreenManager()
; |
| 437 if (fullscreenManager != null) { |
| 438 // Release any old fullscreen token we were holding. |
| 439 fullscreenManager.hideControlsPersistent(mFullscreenToken); |
| 440 mFullscreenToken = FullscreenManager.INVALID_TOKEN; |
| 441 |
| 442 // Grab a new fullscreen token if this layout can't be in fullscreen
. |
| 443 final int flags = getActiveLayout().getSizingFlags(); |
| 444 if ((flags & SizingFlags.ALLOW_TOOLBAR_HIDE) == 0) { |
| 445 mFullscreenToken = fullscreenManager.showControlsPersistent(); |
| 446 } |
| 447 |
| 448 // Hide the toolbar immediately if the layout wants it gone quickly. |
| 449 fullscreenManager.setTopControlsPermamentlyHidden( |
| 450 flags == SizingFlags.HELPER_HIDE_TOOLBAR_IMMEDIATE); |
| 451 } |
| 452 |
| 453 propagateViewportToActiveLayout(); |
| 454 getActiveLayout().show(time(), animate); |
| 455 mHost.setContentOverlayVisibility(getActiveLayout().shouldDisplayContent
Overlay()); |
| 456 mHost.requestRender(); |
| 457 |
| 458 // Notify observers about the new scene. |
| 459 for (SceneChangeObserver observer : mSceneChangeObservers) { |
| 460 observer.onSceneChange(getActiveLayout()); |
| 461 } |
| 462 } |
| 463 |
| 464 /** |
| 465 * Sets the next {@link Layout} to show after the current {@link Layout} is
finished and is done |
| 466 * hiding. |
| 467 * @param layout The new {@link Layout} to show. |
| 468 */ |
| 469 public void setNextLayout(Layout layout) { |
| 470 mNextActiveLayout = (layout == null) ? getDefaultLayout() : layout; |
| 471 } |
| 472 |
| 473 @Override |
| 474 public boolean isActiveLayout(Layout layout) { |
| 475 return layout == mActiveLayout; |
| 476 } |
| 477 |
| 478 /** |
| 479 * Get a list of virtual views for accessibility. |
| 480 * |
| 481 * @param views A List to populate with virtual views. |
| 482 */ |
| 483 public abstract void getVirtualViews(List<VirtualView> views); |
| 484 |
| 485 /** |
| 486 * @return The {@link EdgeSwipeHandler} responsible for processing swipe eve
nts for the toolbar. |
| 487 */ |
| 488 public abstract EdgeSwipeHandler getTopSwipeHandler(); |
| 489 |
| 490 /** |
| 491 * Should be called when the user presses the back button on the phone. |
| 492 * @return Whether or not the back button was consumed by the active {@link
Layout}. |
| 493 */ |
| 494 public abstract boolean onBackPressed(); |
| 495 |
| 496 private void propagateViewportToActiveLayout() { |
| 497 getViewportDp(mCachedRectF); |
| 498 |
| 499 float width = mCachedRectF.width(); |
| 500 float height = mCachedRectF.height(); |
| 501 mLastContentWidthDp = width; |
| 502 mLastContentHeightDp = height; |
| 503 onViewportChanged(mCachedRectF); |
| 504 } |
| 505 |
| 506 private int getOrientation() { |
| 507 if (mLastContentWidthDp > mLastContentHeightDp) { |
| 508 return Orientation.LANDSCAPE; |
| 509 } else { |
| 510 return Orientation.PORTRAIT; |
| 511 } |
| 512 } |
| 513 } |
OLD | NEW |