OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 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.fullscreen; |
| 6 |
| 7 import android.animation.Animator; |
| 8 import android.animation.AnimatorListenerAdapter; |
| 9 import android.animation.ObjectAnimator; |
| 10 import android.app.Activity; |
| 11 import android.content.res.Resources; |
| 12 import android.os.Build; |
| 13 import android.os.Handler; |
| 14 import android.os.Message; |
| 15 import android.os.SystemClock; |
| 16 import android.util.Property; |
| 17 import android.view.Gravity; |
| 18 import android.view.MotionEvent; |
| 19 import android.view.View; |
| 20 import android.view.ViewGroup; |
| 21 import android.view.ViewGroup.LayoutParams; |
| 22 import android.view.Window; |
| 23 import android.widget.FrameLayout; |
| 24 |
| 25 import org.chromium.base.ActivityState; |
| 26 import org.chromium.base.ApiCompatibilityUtils; |
| 27 import org.chromium.base.ApplicationStatus; |
| 28 import org.chromium.base.ApplicationStatus.ActivityStateListener; |
| 29 import org.chromium.base.BaseChromiumApplication; |
| 30 import org.chromium.base.BaseChromiumApplication.WindowFocusChangedListener; |
| 31 import org.chromium.base.TraceEvent; |
| 32 import org.chromium.base.VisibleForTesting; |
| 33 import org.chromium.chrome.browser.Tab; |
| 34 import org.chromium.chrome.browser.fullscreen.FullscreenHtmlApiHandler.Fullscree
nHtmlApiDelegate; |
| 35 import org.chromium.chrome.browser.tabmodel.TabModelSelector; |
| 36 import org.chromium.content.browser.ContentViewCore; |
| 37 |
| 38 import java.util.ArrayList; |
| 39 import java.util.HashSet; |
| 40 |
| 41 /** |
| 42 * A class that manages control and content views to create the fullscreen mode. |
| 43 */ |
| 44 public class ChromeFullscreenManager |
| 45 extends FullscreenManager implements ActivityStateListener, WindowFocusC
hangedListener { |
| 46 // Minimum showtime of the toolbar (in ms). |
| 47 private static final long MINIMUM_SHOW_DURATION_MS = 3000; |
| 48 |
| 49 // Maximum length of the slide in/out animation of the toolbar (in ms). |
| 50 private static final long MAX_ANIMATION_DURATION_MS = 500; |
| 51 |
| 52 private static final int MSG_ID_CONTROLS_REQUEST_LAYOUT = 1; |
| 53 private static final int MSG_ID_HIDE_CONTROLS = 2; |
| 54 |
| 55 private final HashSet<Integer> mPersistentControlTokens = new HashSet<Intege
r>(); |
| 56 |
| 57 private final Activity mActivity; |
| 58 private final Window mWindow; |
| 59 private final Handler mHandler; |
| 60 private final int mControlContainerHeight; |
| 61 |
| 62 private View mControlContainer; |
| 63 |
| 64 private long mMinShowNotificationMs = MINIMUM_SHOW_DURATION_MS; |
| 65 private long mMaxAnimationDurationMs = MAX_ANIMATION_DURATION_MS; |
| 66 |
| 67 private final boolean mEnabled; |
| 68 |
| 69 private float mBrowserControlOffset = Float.NaN; |
| 70 private float mRendererControlOffset = Float.NaN; |
| 71 private float mRendererContentOffset; |
| 72 private float mPreviousContentOffset; |
| 73 private float mControlOffset; |
| 74 private float mPreviousControlOffset; |
| 75 private boolean mIsEnteringPersistentModeState; |
| 76 |
| 77 private boolean mInGesture; |
| 78 private boolean mContentViewScrolling; |
| 79 |
| 80 private int mPersistentControlsCurrentToken; |
| 81 private long mCurrentShowTime; |
| 82 |
| 83 private ObjectAnimator mControlAnimation; |
| 84 private boolean mCurrentAnimationIsShowing; |
| 85 |
| 86 private boolean mDisableBrowserOverride; |
| 87 |
| 88 private boolean mTopControlsPermanentlyHidden; |
| 89 private boolean mTopControlsAndroidViewHidden; |
| 90 |
| 91 private final ArrayList<FullscreenListener> mListeners = new ArrayList<Fulls
creenListener>(); |
| 92 |
| 93 /** |
| 94 * A listener that gets notified of changes to the fullscreen state. |
| 95 */ |
| 96 public interface FullscreenListener { |
| 97 /** |
| 98 * Called whenever the content's offset changes. |
| 99 * @param offset The new offset of the content from the top of the scree
n. |
| 100 */ |
| 101 public void onContentOffsetChanged(float offset); |
| 102 |
| 103 /** |
| 104 * Called whenever the content's visible offset changes. |
| 105 * @param offset The new offset of the visible content from the top of t
he screen. |
| 106 */ |
| 107 public void onVisibleContentOffsetChanged(float offset); |
| 108 |
| 109 /** |
| 110 * Called when a ContentVideoView is created/destroyed. |
| 111 * @param enabled Whether to enter or leave overlay video mode. |
| 112 */ |
| 113 public void onToggleOverlayVideoMode(boolean enabled); |
| 114 } |
| 115 |
| 116 private class ControlsOffsetProperty extends Property<ChromeFullscreenManage
r, Float> { |
| 117 public ControlsOffsetProperty() { |
| 118 super(Float.class, "controlsOffset"); |
| 119 } |
| 120 |
| 121 @Override |
| 122 public Float get(ChromeFullscreenManager object) { |
| 123 return getControlOffset(); |
| 124 } |
| 125 |
| 126 @Override |
| 127 public void set(ChromeFullscreenManager manager, Float offset) { |
| 128 if (mDisableBrowserOverride) return; |
| 129 float browserOffset = offset.floatValue(); |
| 130 if (Float.compare(mBrowserControlOffset, browserOffset) == 0) return
; |
| 131 mBrowserControlOffset = browserOffset; |
| 132 manager.updateControlOffset(); |
| 133 manager.updateVisuals(); |
| 134 } |
| 135 } |
| 136 |
| 137 private final Runnable mUpdateVisibilityRunnable = new Runnable() { |
| 138 @Override |
| 139 public void run() { |
| 140 int visibility = shouldShowAndroidControls() ? View.VISIBLE : View.I
NVISIBLE; |
| 141 if (mControlContainer.getVisibility() == visibility) return; |
| 142 // requestLayout is required to trigger a new gatherTransparentRegio
n(), which |
| 143 // only occurs together with a layout and let's SurfaceFlinger trim
overlays. |
| 144 // This may be almost equivalent to using View.GONE, but we still us
e View.INVISIBLE |
| 145 // since drawing caches etc. won't be destroyed, and the layout may
be less expensive. |
| 146 mControlContainer.setVisibility(visibility); |
| 147 mControlContainer.requestLayout(); |
| 148 } |
| 149 }; |
| 150 |
| 151 /** |
| 152 * Creates an instance of the fullscreen mode manager. |
| 153 * @param activity The activity that supports fullscreen. |
| 154 * @param controlContainer Container holding the controls (Toolbar). |
| 155 * @param enabled Whether fullscreen is globally enabled. |
| 156 * @param modelSelector The model selector providing access to the current t
ab. |
| 157 */ |
| 158 public ChromeFullscreenManager(Activity activity, View controlContainer, boo
lean enabled, |
| 159 boolean persistentFullscreenSupported, TabModelSelector modelSelecto
r, |
| 160 int resControlContainerHeight) { |
| 161 super(activity.getWindow(), modelSelector, enabled, persistentFullscreen
Supported); |
| 162 |
| 163 mActivity = activity; |
| 164 ApplicationStatus.registerStateListenerForActivity(this, activity); |
| 165 ((BaseChromiumApplication) activity.getApplication()) |
| 166 .registerWindowFocusChangedListener(this); |
| 167 |
| 168 mWindow = activity.getWindow(); |
| 169 mHandler = new Handler() { |
| 170 @Override |
| 171 public void handleMessage(Message msg) { |
| 172 if (msg == null) return; |
| 173 switch (msg.what) { |
| 174 case MSG_ID_CONTROLS_REQUEST_LAYOUT: |
| 175 mControlContainer.requestLayout(); |
| 176 break; |
| 177 case MSG_ID_HIDE_CONTROLS: |
| 178 update(false); |
| 179 break; |
| 180 default: |
| 181 assert false : "Unexpected message for ID: " + msg.what; |
| 182 break; |
| 183 } |
| 184 } |
| 185 }; |
| 186 setControlContainer(controlContainer); |
| 187 Resources resources = mWindow.getContext().getResources(); |
| 188 mControlContainerHeight = resources.getDimensionPixelSize(resControlCont
ainerHeight); |
| 189 mRendererContentOffset = mControlContainerHeight; |
| 190 mEnabled = enabled; |
| 191 updateControlOffset(); |
| 192 } |
| 193 |
| 194 /** |
| 195 * @return Whether or not fullscreen is enabled. |
| 196 */ |
| 197 public boolean isEnabled() { |
| 198 return mEnabled; |
| 199 } |
| 200 |
| 201 /** |
| 202 * Set the control container that is being hidden and shown when manipulatin
g the fullscreen |
| 203 * state. |
| 204 * @param controlContainer The container at the top of the screen that conta
ins the controls. |
| 205 */ |
| 206 public void setControlContainer(View controlContainer) { |
| 207 assert controlContainer != null; |
| 208 mControlContainer = controlContainer; |
| 209 } |
| 210 |
| 211 @Override |
| 212 public void onActivityStateChange(Activity activity, int newState) { |
| 213 if (newState == ActivityState.STOPPED) { |
| 214 // Exit fullscreen in onStop to ensure the system UI flags are set c
orrectly when |
| 215 // showing again (on JB MR2+ builds, the omnibox would be covered by
the |
| 216 // notification bar when this was done in onStart()). |
| 217 setPersistentFullscreenMode(false); |
| 218 } else if (newState == ActivityState.STARTED) { |
| 219 showControlsTransient(); |
| 220 } else if (newState == ActivityState.DESTROYED) { |
| 221 ApplicationStatus.unregisterActivityStateListener(this); |
| 222 ((BaseChromiumApplication) mWindow.getContext().getApplicationContex
t()) |
| 223 .unregisterWindowFocusChangedListener(this); |
| 224 } |
| 225 } |
| 226 |
| 227 @Override |
| 228 public void onWindowFocusChanged(Activity activity, boolean hasFocus) { |
| 229 if (mActivity == activity) onWindowFocusChanged(hasFocus); |
| 230 } |
| 231 |
| 232 @Override |
| 233 protected FullscreenHtmlApiDelegate createApiDelegate() { |
| 234 return new FullscreenHtmlApiDelegate() { |
| 235 @Override |
| 236 public View getNotificationAnchorView() { |
| 237 return mControlContainer; |
| 238 } |
| 239 |
| 240 @Override |
| 241 public int getNotificationOffsetY() { |
| 242 return (int) getControlOffset(); |
| 243 } |
| 244 |
| 245 @Override |
| 246 public void onEnterFullscreen() { |
| 247 mIsEnteringPersistentModeState = true; |
| 248 } |
| 249 |
| 250 @Override |
| 251 public boolean cancelPendingEnterFullscreen() { |
| 252 boolean wasPending = mIsEnteringPersistentModeState; |
| 253 mIsEnteringPersistentModeState = false; |
| 254 return wasPending; |
| 255 } |
| 256 |
| 257 @Override |
| 258 public void onFullscreenExited(ContentViewCore contentViewCore) { |
| 259 contentViewCore.getWebContents().updateTopControlsState(false, t
rue, true); |
| 260 contentViewCore.getWebContents().updateTopControlsState(true, tr
ue, false); |
| 261 } |
| 262 |
| 263 @Override |
| 264 public boolean shouldShowNotificationBubble() { |
| 265 return !isOverlayVideoMode(); |
| 266 } |
| 267 }; |
| 268 } |
| 269 |
| 270 /** |
| 271 * Disables the ability for the browser to override the renderer provided to
p controls |
| 272 * position for testing. |
| 273 */ |
| 274 @VisibleForTesting |
| 275 public void disableBrowserOverrideForTest() { |
| 276 mDisableBrowserOverride = true; |
| 277 mPersistentControlTokens.clear(); |
| 278 mHandler.removeMessages(MSG_ID_HIDE_CONTROLS); |
| 279 if (mControlAnimation != null) { |
| 280 mControlAnimation.cancel(); |
| 281 mControlAnimation = null; |
| 282 } |
| 283 mBrowserControlOffset = Float.NaN; |
| 284 updateVisuals(); |
| 285 } |
| 286 |
| 287 /** |
| 288 * Allows tests to override the animation durations for faster tests. |
| 289 * @param minShowDuration The minimum time the controls must be shown. |
| 290 * @param maxAnimationDuration The maximum animation time to show/hide the c
ontrols. |
| 291 */ |
| 292 @VisibleForTesting |
| 293 public void setAnimationDurationsForTest(long minShowDuration, long maxAnima
tionDuration) { |
| 294 mMinShowNotificationMs = minShowDuration; |
| 295 mMaxAnimationDurationMs = maxAnimationDuration; |
| 296 } |
| 297 |
| 298 @Override |
| 299 public void showControlsTransient() { |
| 300 if (mPersistentControlTokens.isEmpty()) update(true); |
| 301 } |
| 302 |
| 303 @Override |
| 304 public int showControlsPersistent() { |
| 305 int token = mPersistentControlsCurrentToken++; |
| 306 mPersistentControlTokens.add(token); |
| 307 if (mPersistentControlTokens.size() == 1) update(true); |
| 308 return token; |
| 309 } |
| 310 |
| 311 @Override |
| 312 public int showControlsPersistentAndClearOldToken(int oldToken) { |
| 313 if (oldToken != INVALID_TOKEN) mPersistentControlTokens.remove(oldToken)
; |
| 314 return showControlsPersistent(); |
| 315 } |
| 316 |
| 317 @Override |
| 318 public void hideControlsPersistent(int token) { |
| 319 if (mPersistentControlTokens.remove(token) && mPersistentControlTokens.i
sEmpty()) { |
| 320 update(false); |
| 321 } |
| 322 } |
| 323 |
| 324 /** |
| 325 * @param remove Whether or not to forcefully remove the toolbar. |
| 326 */ |
| 327 public void setTopControlsPermamentlyHidden(boolean remove) { |
| 328 if (remove == mTopControlsPermanentlyHidden) return; |
| 329 mTopControlsPermanentlyHidden = remove; |
| 330 updateVisuals(); |
| 331 } |
| 332 |
| 333 /** |
| 334 * @return Whether or not the toolbar is forcefully being removed. |
| 335 */ |
| 336 public boolean areTopControlsPermanentlyHidden() { |
| 337 return mTopControlsPermanentlyHidden; |
| 338 } |
| 339 |
| 340 /** |
| 341 * @return Whether the top controls should be drawn as a texture. |
| 342 */ |
| 343 public boolean drawControlsAsTexture() { |
| 344 return getControlOffset() > -mControlContainerHeight; |
| 345 } |
| 346 |
| 347 /** |
| 348 * @return The height of the top controls in pixels. |
| 349 */ |
| 350 public int getTopControlsHeight() { |
| 351 return mEnabled ? mControlContainerHeight : 0; |
| 352 } |
| 353 |
| 354 @Override |
| 355 public float getContentOffset() { |
| 356 if (!mEnabled || mTopControlsPermanentlyHidden) return 0; |
| 357 return rendererContentOffset(); |
| 358 } |
| 359 |
| 360 /** |
| 361 * @return The offset of the controls from the top of the screen. |
| 362 */ |
| 363 public float getControlOffset() { |
| 364 if (!mEnabled) return 0; |
| 365 if (mTopControlsPermanentlyHidden) return -getTopControlsHeight(); |
| 366 return mControlOffset; |
| 367 } |
| 368 |
| 369 @SuppressWarnings("SelfEquality") |
| 370 private void updateControlOffset() { |
| 371 float offset = 0; |
| 372 // Inline Float.isNan with "x != x": |
| 373 final boolean isNaNBrowserControlOffset = mBrowserControlOffset != mBrow
serControlOffset; |
| 374 final float rendererControlOffset = rendererControlOffset(); |
| 375 final boolean isNaNRendererControlOffset = rendererControlOffset != rend
ererControlOffset; |
| 376 if (!isNaNBrowserControlOffset || !isNaNRendererControlOffset) { |
| 377 offset = Math.max( |
| 378 isNaNBrowserControlOffset ? -mControlContainerHeight : mBrow
serControlOffset, |
| 379 isNaNRendererControlOffset ? -mControlContainerHeight : rend
ererControlOffset); |
| 380 } |
| 381 mControlOffset = offset; |
| 382 } |
| 383 |
| 384 @Override |
| 385 public void setOverlayVideoMode(boolean enabled) { |
| 386 super.setOverlayVideoMode(enabled); |
| 387 |
| 388 for (int i = 0; i < mListeners.size(); i++) { |
| 389 mListeners.get(i).onToggleOverlayVideoMode(enabled); |
| 390 } |
| 391 } |
| 392 |
| 393 /** |
| 394 * @return Whether the browser has a control offset override. |
| 395 */ |
| 396 @VisibleForTesting |
| 397 public boolean hasBrowserControlOffsetOverride() { |
| 398 return !Float.isNaN(mBrowserControlOffset) || mControlAnimation != null |
| 399 || !mPersistentControlTokens.isEmpty(); |
| 400 } |
| 401 |
| 402 /** |
| 403 * Returns how tall the opaque portion of the control container is. |
| 404 */ |
| 405 public float controlContainerHeight() { |
| 406 return mControlContainerHeight; |
| 407 } |
| 408 |
| 409 private float rendererContentOffset() { |
| 410 if (!mEnabled) return mControlContainerHeight; |
| 411 return mRendererContentOffset; |
| 412 } |
| 413 |
| 414 private float rendererControlOffset() { |
| 415 if (!mEnabled) return 0; |
| 416 return mRendererControlOffset; |
| 417 } |
| 418 |
| 419 /** |
| 420 * @return The visible offset of the content from the top of the screen. |
| 421 */ |
| 422 public float getVisibleContentOffset() { |
| 423 if (!mEnabled) return 0; |
| 424 return mControlContainerHeight + getControlOffset(); |
| 425 } |
| 426 |
| 427 /** |
| 428 * @param listener The {@link FullscreenListener} to be notified of fullscre
en changes. |
| 429 */ |
| 430 public void addListener(FullscreenListener listener) { |
| 431 if (!mListeners.contains(listener)) mListeners.add(listener); |
| 432 } |
| 433 |
| 434 /** |
| 435 * @param listener The {@link FullscreenListener} to no longer be notified o
f fullscreen |
| 436 * changes. |
| 437 */ |
| 438 public void removeListener(FullscreenListener listener) { |
| 439 mListeners.remove(listener); |
| 440 } |
| 441 |
| 442 /** |
| 443 * Updates the content view's viewport size to have it render the content co
rrectly. |
| 444 * |
| 445 * @param viewCore The ContentViewCore to update. |
| 446 */ |
| 447 public void updateContentViewViewportSize(ContentViewCore viewCore) { |
| 448 if (!mEnabled || viewCore == null) return; |
| 449 if (mInGesture || mContentViewScrolling) return; |
| 450 |
| 451 // Update content viewport size only when the top controls are not anima
ting. |
| 452 int contentOffset = (int) rendererContentOffset(); |
| 453 if (contentOffset != 0 && contentOffset != mControlContainerHeight) retu
rn; |
| 454 viewCore.setTopControlsHeight(mControlContainerHeight, contentOffset > 0
); |
| 455 } |
| 456 |
| 457 @Override |
| 458 public void updateContentViewChildrenState() { |
| 459 ContentViewCore contentViewCore = getActiveContentViewCore(); |
| 460 if (contentViewCore == null || !mEnabled) return; |
| 461 ViewGroup view = contentViewCore.getContainerView(); |
| 462 |
| 463 float topViewsTranslation = (getControlOffset() + mControlContainerHeigh
t); |
| 464 applyTranslationToTopChildViews(view, topViewsTranslation); |
| 465 applyMarginToFullChildViews(view, topViewsTranslation); |
| 466 updateContentViewViewportSize(contentViewCore); |
| 467 } |
| 468 |
| 469 /** |
| 470 * Utility routine for ensuring visibility updates are synchronized with |
| 471 * animation, preventing message loop stalls due to untimely invalidation. |
| 472 */ |
| 473 private void scheduleVisibilityUpdate() { |
| 474 final int desiredVisibility = shouldShowAndroidControls() ? View.VISIBLE
: View.INVISIBLE; |
| 475 if (mControlContainer.getVisibility() == desiredVisibility) return; |
| 476 mControlContainer.removeCallbacks(mUpdateVisibilityRunnable); |
| 477 ApiCompatibilityUtils.postOnAnimation(mControlContainer, mUpdateVisibili
tyRunnable); |
| 478 } |
| 479 |
| 480 private void updateVisuals() { |
| 481 if (!mEnabled) return; |
| 482 |
| 483 TraceEvent.begin("FullscreenManager:updateVisuals"); |
| 484 |
| 485 float offset = getControlOffset(); |
| 486 if (Float.compare(mPreviousControlOffset, offset) != 0) { |
| 487 mPreviousControlOffset = offset; |
| 488 getHtmlApiHandler().updateBubblePosition(); |
| 489 |
| 490 scheduleVisibilityUpdate(); |
| 491 if (shouldShowAndroidControls()) mControlContainer.setTranslationY(g
etControlOffset()); |
| 492 |
| 493 // In ICS, the toolbar can appear clipped when compositor content is
not being drawn |
| 494 // beneath it (at the top of the page, during side swipe). Requesti
ng a layout clears |
| 495 // up the issue (see crbug.com/172631). |
| 496 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { |
| 497 if (!mHandler.hasMessages(MSG_ID_CONTROLS_REQUEST_LAYOUT)) { |
| 498 mHandler.sendEmptyMessage(MSG_ID_CONTROLS_REQUEST_LAYOUT); |
| 499 } |
| 500 } |
| 501 for (int i = 0; i < mListeners.size(); i++) { |
| 502 mListeners.get(i).onVisibleContentOffsetChanged(getVisibleConten
tOffset()); |
| 503 } |
| 504 } |
| 505 |
| 506 final ContentViewCore contentViewCore = getActiveContentViewCore(); |
| 507 if (contentViewCore != null && offset == -mControlContainerHeight |
| 508 && mIsEnteringPersistentModeState) { |
| 509 getHtmlApiHandler().enterFullscreen(contentViewCore); |
| 510 mIsEnteringPersistentModeState = false; |
| 511 } |
| 512 |
| 513 updateContentViewChildrenState(); |
| 514 |
| 515 float contentOffset = getContentOffset(); |
| 516 if (Float.compare(mPreviousContentOffset, contentOffset) != 0) { |
| 517 for (int i = 0; i < mListeners.size(); i++) { |
| 518 mListeners.get(i).onContentOffsetChanged(contentOffset); |
| 519 } |
| 520 mPreviousContentOffset = contentOffset; |
| 521 } |
| 522 |
| 523 TraceEvent.end("FullscreenManager:updateVisuals"); |
| 524 } |
| 525 |
| 526 /** |
| 527 * @param hide Whether or not to force the top controls Android view to hide
. If this is |
| 528 * {@code false} the top controls Android view will show/hide ba
sed on position, if |
| 529 * it is {@code true} the top controls Android view will always
be hidden. |
| 530 */ |
| 531 public void setHideTopControlsAndroidView(boolean hide) { |
| 532 if (mTopControlsAndroidViewHidden == hide) return; |
| 533 mTopControlsAndroidViewHidden = hide; |
| 534 scheduleVisibilityUpdate(); |
| 535 } |
| 536 |
| 537 private boolean shouldShowAndroidControls() { |
| 538 if (mTopControlsAndroidViewHidden) return false; |
| 539 |
| 540 boolean showControls = getControlOffset() == 0; |
| 541 ContentViewCore contentViewCore = getActiveContentViewCore(); |
| 542 if (contentViewCore == null) return showControls; |
| 543 ViewGroup contentView = contentViewCore.getContainerView(); |
| 544 |
| 545 for (int i = 0; i < contentView.getChildCount(); i++) { |
| 546 View child = contentView.getChildAt(i); |
| 547 if (!(child.getLayoutParams() instanceof FrameLayout.LayoutParams))
continue; |
| 548 |
| 549 FrameLayout.LayoutParams layoutParams = |
| 550 (FrameLayout.LayoutParams) child.getLayoutParams(); |
| 551 if (Gravity.TOP == (layoutParams.gravity & Gravity.FILL_VERTICAL)) { |
| 552 showControls = true; |
| 553 break; |
| 554 } |
| 555 } |
| 556 |
| 557 showControls |= !mPersistentControlTokens.isEmpty(); |
| 558 |
| 559 return showControls; |
| 560 } |
| 561 |
| 562 private void applyMarginToFullChildViews(ViewGroup contentView, float margin
) { |
| 563 for (int i = 0; i < contentView.getChildCount(); i++) { |
| 564 View child = contentView.getChildAt(i); |
| 565 if (!(child.getLayoutParams() instanceof FrameLayout.LayoutParams))
continue; |
| 566 FrameLayout.LayoutParams layoutParams = |
| 567 (FrameLayout.LayoutParams) child.getLayoutParams(); |
| 568 |
| 569 if (layoutParams.height == LayoutParams.MATCH_PARENT |
| 570 && layoutParams.topMargin != (int) margin) { |
| 571 layoutParams.topMargin = (int) margin; |
| 572 child.requestLayout(); |
| 573 TraceEvent.instant("FullscreenManager:child.requestLayout()"); |
| 574 } |
| 575 } |
| 576 } |
| 577 |
| 578 private void applyTranslationToTopChildViews(ViewGroup contentView, float tr
anslation) { |
| 579 for (int i = 0; i < contentView.getChildCount(); i++) { |
| 580 View child = contentView.getChildAt(i); |
| 581 if (!(child.getLayoutParams() instanceof FrameLayout.LayoutParams))
continue; |
| 582 |
| 583 FrameLayout.LayoutParams layoutParams = |
| 584 (FrameLayout.LayoutParams) child.getLayoutParams(); |
| 585 if (Gravity.TOP == (layoutParams.gravity & Gravity.FILL_VERTICAL)) { |
| 586 child.setTranslationY(translation); |
| 587 TraceEvent.instant("FullscreenManager:child.setTranslationY()"); |
| 588 } |
| 589 } |
| 590 } |
| 591 |
| 592 private ContentViewCore getActiveContentViewCore() { |
| 593 Tab tab = getTabModelSelector().getCurrentTab(); |
| 594 return tab != null ? tab.getContentViewCore() : null; |
| 595 } |
| 596 |
| 597 @Override |
| 598 public void setPositionsForTabToNonFullscreen() { |
| 599 setPositionsForTab(0, mControlContainerHeight); |
| 600 } |
| 601 |
| 602 @Override |
| 603 public void setPositionsForTab(float controlsOffset, float contentOffset) { |
| 604 if (!mEnabled) return; |
| 605 float rendererControlOffset = |
| 606 Math.round(Math.max(controlsOffset, -mControlContainerHeight)); |
| 607 float rendererContentOffset = Math.min( |
| 608 Math.round(contentOffset), rendererControlOffset + mControlConta
inerHeight); |
| 609 |
| 610 if (Float.compare(rendererControlOffset, mRendererControlOffset) == 0 |
| 611 && Float.compare(rendererContentOffset, mRendererContentOffset)
== 0) { |
| 612 return; |
| 613 } |
| 614 |
| 615 mRendererControlOffset = rendererControlOffset; |
| 616 mRendererContentOffset = rendererContentOffset; |
| 617 updateControlOffset(); |
| 618 |
| 619 if (mControlAnimation == null) updateVisuals(); |
| 620 } |
| 621 |
| 622 /** |
| 623 * @param e The dispatched motion event |
| 624 * @return Whether or not this motion event is in the top control container
area and should be |
| 625 * consumed. |
| 626 */ |
| 627 public boolean onInterceptMotionEvent(MotionEvent e) { |
| 628 return mEnabled && e.getY() < getControlOffset() + mControlContainerHeig
ht |
| 629 && !mTopControlsAndroidViewHidden; |
| 630 } |
| 631 |
| 632 /** |
| 633 * Notifies the fullscreen manager that a motion event has occurred. |
| 634 * @param e The dispatched motion event. |
| 635 */ |
| 636 public void onMotionEvent(MotionEvent e) { |
| 637 if (!mEnabled) return; |
| 638 int eventAction = e.getActionMasked(); |
| 639 if (eventAction == MotionEvent.ACTION_DOWN |
| 640 || eventAction == MotionEvent.ACTION_POINTER_DOWN) { |
| 641 mInGesture = true; |
| 642 getHtmlApiHandler().hideNotificationBubble(); |
| 643 } else if (eventAction == MotionEvent.ACTION_CANCEL |
| 644 || eventAction == MotionEvent.ACTION_UP) { |
| 645 mInGesture = false; |
| 646 updateVisuals(); |
| 647 } |
| 648 } |
| 649 |
| 650 private void update(boolean show) { |
| 651 // On forced show/hide, reset the flags that may suppress ContentView re
size. |
| 652 // As this method is also called when tab is switched, this also cleanup
the scrolling |
| 653 // flag set based on the previous ContentView's scrolling state. |
| 654 mInGesture = false; |
| 655 mContentViewScrolling = false; |
| 656 |
| 657 if (!mEnabled) return; |
| 658 |
| 659 if (show) mCurrentShowTime = SystemClock.uptimeMillis(); |
| 660 |
| 661 boolean postHideMessage = false; |
| 662 if (!show) { |
| 663 if (mControlAnimation != null && mCurrentAnimationIsShowing) { |
| 664 postHideMessage = true; |
| 665 } else { |
| 666 long timeDelta = SystemClock.uptimeMillis() - mCurrentShowTime; |
| 667 animateIfNecessary(false, Math.max(mMinShowNotificationMs - time
Delta, 0)); |
| 668 } |
| 669 } else { |
| 670 animateIfNecessary(true, 0); |
| 671 if (mPersistentControlTokens.isEmpty()) postHideMessage = true; |
| 672 } |
| 673 |
| 674 mHandler.removeMessages(MSG_ID_HIDE_CONTROLS); |
| 675 if (postHideMessage) { |
| 676 long timeDelta = SystemClock.uptimeMillis() - mCurrentShowTime; |
| 677 mHandler.sendEmptyMessageDelayed( |
| 678 MSG_ID_HIDE_CONTROLS, Math.max(mMinShowNotificationMs - time
Delta, 0)); |
| 679 } |
| 680 } |
| 681 |
| 682 private void animateIfNecessary(final boolean show, long startDelay) { |
| 683 if (mControlAnimation != null) { |
| 684 if (!mControlAnimation.isRunning() || mCurrentAnimationIsShowing !=
show) { |
| 685 mControlAnimation.cancel(); |
| 686 mControlAnimation = null; |
| 687 } else { |
| 688 return; |
| 689 } |
| 690 } |
| 691 |
| 692 float destination = show ? 0 : -mControlContainerHeight; |
| 693 long duration = (long) (mMaxAnimationDurationMs |
| 694 * Math.abs((destination - getControlOffset()) / mControlContaine
rHeight)); |
| 695 mControlAnimation = ObjectAnimator.ofFloat(this, new ControlsOffsetPrope
rty(), destination); |
| 696 mControlAnimation.addListener(new AnimatorListenerAdapter() { |
| 697 private boolean mCanceled = false; |
| 698 |
| 699 @Override |
| 700 public void onAnimationCancel(Animator anim) { |
| 701 mCanceled = true; |
| 702 } |
| 703 |
| 704 @Override |
| 705 public void onAnimationEnd(Animator animation) { |
| 706 if (!show && !mCanceled) mBrowserControlOffset = Float.NaN; |
| 707 mControlAnimation = null; |
| 708 } |
| 709 }); |
| 710 mControlAnimation.setStartDelay(startDelay); |
| 711 mControlAnimation.setDuration(duration); |
| 712 mControlAnimation.start(); |
| 713 mCurrentAnimationIsShowing = show; |
| 714 } |
| 715 |
| 716 @Override |
| 717 public void onContentViewScrollingStateChanged(boolean scrolling) { |
| 718 mContentViewScrolling = scrolling; |
| 719 if (!scrolling) updateVisuals(); |
| 720 } |
| 721 } |
OLD | NEW |