Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(53)

Unified Diff: chrome/android/java/src/org/chromium/chrome/browser/fullscreen/ChromeFullscreenManager.java

Issue 810853003: Upstream FullscreenManager (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Update comments Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/android/java/src/org/chromium/chrome/browser/fullscreen/ChromeFullscreenManager.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/ChromeFullscreenManager.java b/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/ChromeFullscreenManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..974ff2037ff4793325de81dfde61a45289f14e5c
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/ChromeFullscreenManager.java
@@ -0,0 +1,721 @@
+// Copyright 2014 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.fullscreen;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.app.Activity;
+import android.content.res.Resources;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.Property;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.Window;
+import android.widget.FrameLayout;
+
+import org.chromium.base.ActivityState;
+import org.chromium.base.ApiCompatibilityUtils;
+import org.chromium.base.ApplicationStatus;
+import org.chromium.base.ApplicationStatus.ActivityStateListener;
+import org.chromium.base.BaseChromiumApplication;
+import org.chromium.base.BaseChromiumApplication.WindowFocusChangedListener;
+import org.chromium.base.TraceEvent;
+import org.chromium.base.VisibleForTesting;
+import org.chromium.chrome.browser.Tab;
+import org.chromium.chrome.browser.fullscreen.FullscreenHtmlApiHandler.FullscreenHtmlApiDelegate;
+import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.content.browser.ContentViewCore;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+/**
+ * A class that manages control and content views to create the fullscreen mode.
+ */
+public class ChromeFullscreenManager
+ extends FullscreenManager implements ActivityStateListener, WindowFocusChangedListener {
+ // Minimum showtime of the toolbar (in ms).
+ private static final long MINIMUM_SHOW_DURATION_MS = 3000;
+
+ // Maximum length of the slide in/out animation of the toolbar (in ms).
+ private static final long MAX_ANIMATION_DURATION_MS = 500;
+
+ private static final int MSG_ID_CONTROLS_REQUEST_LAYOUT = 1;
+ private static final int MSG_ID_HIDE_CONTROLS = 2;
+
+ private final HashSet<Integer> mPersistentControlTokens = new HashSet<Integer>();
+
+ private final Activity mActivity;
+ private final Window mWindow;
+ private final Handler mHandler;
+ private final int mControlContainerHeight;
+
+ private View mControlContainer;
+
+ private long mMinShowNotificationMs = MINIMUM_SHOW_DURATION_MS;
+ private long mMaxAnimationDurationMs = MAX_ANIMATION_DURATION_MS;
+
+ private final boolean mEnabled;
+
+ private float mBrowserControlOffset = Float.NaN;
+ private float mRendererControlOffset = Float.NaN;
+ private float mRendererContentOffset;
+ private float mPreviousContentOffset;
+ private float mControlOffset;
+ private float mPreviousControlOffset;
+ private boolean mIsEnteringPersistentModeState;
+
+ private boolean mInGesture;
+ private boolean mContentViewScrolling;
+
+ private int mPersistentControlsCurrentToken;
+ private long mCurrentShowTime;
+
+ private ObjectAnimator mControlAnimation;
+ private boolean mCurrentAnimationIsShowing;
+
+ private boolean mDisableBrowserOverride;
+
+ private boolean mTopControlsPermanentlyHidden;
+ private boolean mTopControlsAndroidViewHidden;
+
+ private final ArrayList<FullscreenListener> mListeners = new ArrayList<FullscreenListener>();
+
+ /**
+ * A listener that gets notified of changes to the fullscreen state.
+ */
+ public interface FullscreenListener {
+ /**
+ * Called whenever the content's offset changes.
+ * @param offset The new offset of the content from the top of the screen.
+ */
+ public void onContentOffsetChanged(float offset);
+
+ /**
+ * Called whenever the content's visible offset changes.
+ * @param offset The new offset of the visible content from the top of the screen.
+ */
+ public void onVisibleContentOffsetChanged(float offset);
+
+ /**
+ * Called when a ContentVideoView is created/destroyed.
+ * @param enabled Whether to enter or leave overlay video mode.
+ */
+ public void onToggleOverlayVideoMode(boolean enabled);
+ }
+
+ private class ControlsOffsetProperty extends Property<ChromeFullscreenManager, Float> {
+ public ControlsOffsetProperty() {
+ super(Float.class, "controlsOffset");
+ }
+
+ @Override
+ public Float get(ChromeFullscreenManager object) {
+ return getControlOffset();
+ }
+
+ @Override
+ public void set(ChromeFullscreenManager manager, Float offset) {
+ if (mDisableBrowserOverride) return;
+ float browserOffset = offset.floatValue();
+ if (Float.compare(mBrowserControlOffset, browserOffset) == 0) return;
+ mBrowserControlOffset = browserOffset;
+ manager.updateControlOffset();
+ manager.updateVisuals();
+ }
+ }
+
+ private final Runnable mUpdateVisibilityRunnable = new Runnable() {
+ @Override
+ public void run() {
+ int visibility = shouldShowAndroidControls() ? View.VISIBLE : View.INVISIBLE;
+ if (mControlContainer.getVisibility() == visibility) return;
+ // requestLayout is required to trigger a new gatherTransparentRegion(), which
+ // only occurs together with a layout and let's SurfaceFlinger trim overlays.
+ // This may be almost equivalent to using View.GONE, but we still use View.INVISIBLE
+ // since drawing caches etc. won't be destroyed, and the layout may be less expensive.
+ mControlContainer.setVisibility(visibility);
+ mControlContainer.requestLayout();
+ }
+ };
+
+ /**
+ * Creates an instance of the fullscreen mode manager.
+ * @param activity The activity that supports fullscreen.
+ * @param controlContainer Container holding the controls (Toolbar).
+ * @param enabled Whether fullscreen is globally enabled.
+ * @param modelSelector The model selector providing access to the current tab.
+ */
+ public ChromeFullscreenManager(Activity activity, View controlContainer, boolean enabled,
+ boolean persistentFullscreenSupported, TabModelSelector modelSelector,
+ int resControlContainerHeight) {
+ super(activity.getWindow(), modelSelector, enabled, persistentFullscreenSupported);
+
+ mActivity = activity;
+ ApplicationStatus.registerStateListenerForActivity(this, activity);
+ ((BaseChromiumApplication) activity.getApplication())
+ .registerWindowFocusChangedListener(this);
+
+ mWindow = activity.getWindow();
+ mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg == null) return;
+ switch (msg.what) {
+ case MSG_ID_CONTROLS_REQUEST_LAYOUT:
+ mControlContainer.requestLayout();
+ break;
+ case MSG_ID_HIDE_CONTROLS:
+ update(false);
+ break;
+ default:
+ assert false : "Unexpected message for ID: " + msg.what;
+ break;
+ }
+ }
+ };
+ setControlContainer(controlContainer);
+ Resources resources = mWindow.getContext().getResources();
+ mControlContainerHeight = resources.getDimensionPixelSize(resControlContainerHeight);
+ mRendererContentOffset = mControlContainerHeight;
+ mEnabled = enabled;
+ updateControlOffset();
+ }
+
+ /**
+ * @return Whether or not fullscreen is enabled.
+ */
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ /**
+ * Set the control container that is being hidden and shown when manipulating the fullscreen
+ * state.
+ * @param controlContainer The container at the top of the screen that contains the controls.
+ */
+ public void setControlContainer(View controlContainer) {
+ assert controlContainer != null;
+ mControlContainer = controlContainer;
+ }
+
+ @Override
+ public void onActivityStateChange(Activity activity, int newState) {
+ if (newState == ActivityState.STOPPED) {
+ // Exit fullscreen in onStop to ensure the system UI flags are set correctly when
+ // showing again (on JB MR2+ builds, the omnibox would be covered by the
+ // notification bar when this was done in onStart()).
+ setPersistentFullscreenMode(false);
+ } else if (newState == ActivityState.STARTED) {
+ showControlsTransient();
+ } else if (newState == ActivityState.DESTROYED) {
+ ApplicationStatus.unregisterActivityStateListener(this);
+ ((BaseChromiumApplication) mWindow.getContext().getApplicationContext())
+ .unregisterWindowFocusChangedListener(this);
+ }
+ }
+
+ @Override
+ public void onWindowFocusChanged(Activity activity, boolean hasFocus) {
+ if (mActivity == activity) onWindowFocusChanged(hasFocus);
+ }
+
+ @Override
+ protected FullscreenHtmlApiDelegate createApiDelegate() {
+ return new FullscreenHtmlApiDelegate() {
+ @Override
+ public View getNotificationAnchorView() {
+ return mControlContainer;
+ }
+
+ @Override
+ public int getNotificationOffsetY() {
+ return (int) getControlOffset();
+ }
+
+ @Override
+ public void onEnterFullscreen() {
+ mIsEnteringPersistentModeState = true;
+ }
+
+ @Override
+ public boolean cancelPendingEnterFullscreen() {
+ boolean wasPending = mIsEnteringPersistentModeState;
+ mIsEnteringPersistentModeState = false;
+ return wasPending;
+ }
+
+ @Override
+ public void onFullscreenExited(ContentViewCore contentViewCore) {
+ contentViewCore.getWebContents().updateTopControlsState(false, true, true);
+ contentViewCore.getWebContents().updateTopControlsState(true, true, false);
+ }
+
+ @Override
+ public boolean shouldShowNotificationBubble() {
+ return !isOverlayVideoMode();
+ }
+ };
+ }
+
+ /**
+ * Disables the ability for the browser to override the renderer provided top controls
+ * position for testing.
+ */
+ @VisibleForTesting
+ public void disableBrowserOverrideForTest() {
+ mDisableBrowserOverride = true;
+ mPersistentControlTokens.clear();
+ mHandler.removeMessages(MSG_ID_HIDE_CONTROLS);
+ if (mControlAnimation != null) {
+ mControlAnimation.cancel();
+ mControlAnimation = null;
+ }
+ mBrowserControlOffset = Float.NaN;
+ updateVisuals();
+ }
+
+ /**
+ * Allows tests to override the animation durations for faster tests.
+ * @param minShowDuration The minimum time the controls must be shown.
+ * @param maxAnimationDuration The maximum animation time to show/hide the controls.
+ */
+ @VisibleForTesting
+ public void setAnimationDurationsForTest(long minShowDuration, long maxAnimationDuration) {
+ mMinShowNotificationMs = minShowDuration;
+ mMaxAnimationDurationMs = maxAnimationDuration;
+ }
+
+ @Override
+ public void showControlsTransient() {
+ if (mPersistentControlTokens.isEmpty()) update(true);
+ }
+
+ @Override
+ public int showControlsPersistent() {
+ int token = mPersistentControlsCurrentToken++;
+ mPersistentControlTokens.add(token);
+ if (mPersistentControlTokens.size() == 1) update(true);
+ return token;
+ }
+
+ @Override
+ public int showControlsPersistentAndClearOldToken(int oldToken) {
+ if (oldToken != INVALID_TOKEN) mPersistentControlTokens.remove(oldToken);
+ return showControlsPersistent();
+ }
+
+ @Override
+ public void hideControlsPersistent(int token) {
+ if (mPersistentControlTokens.remove(token) && mPersistentControlTokens.isEmpty()) {
+ update(false);
+ }
+ }
+
+ /**
+ * @param remove Whether or not to forcefully remove the toolbar.
+ */
+ public void setTopControlsPermamentlyHidden(boolean remove) {
+ if (remove == mTopControlsPermanentlyHidden) return;
+ mTopControlsPermanentlyHidden = remove;
+ updateVisuals();
+ }
+
+ /**
+ * @return Whether or not the toolbar is forcefully being removed.
+ */
+ public boolean areTopControlsPermanentlyHidden() {
+ return mTopControlsPermanentlyHidden;
+ }
+
+ /**
+ * @return Whether the top controls should be drawn as a texture.
+ */
+ public boolean drawControlsAsTexture() {
+ return getControlOffset() > -mControlContainerHeight;
+ }
+
+ /**
+ * @return The height of the top controls in pixels.
+ */
+ public int getTopControlsHeight() {
+ return mEnabled ? mControlContainerHeight : 0;
+ }
+
+ @Override
+ public float getContentOffset() {
+ if (!mEnabled || mTopControlsPermanentlyHidden) return 0;
+ return rendererContentOffset();
+ }
+
+ /**
+ * @return The offset of the controls from the top of the screen.
+ */
+ public float getControlOffset() {
+ if (!mEnabled) return 0;
+ if (mTopControlsPermanentlyHidden) return -getTopControlsHeight();
+ return mControlOffset;
+ }
+
+ @SuppressWarnings("SelfEquality")
+ private void updateControlOffset() {
+ float offset = 0;
+ // Inline Float.isNan with "x != x":
+ final boolean isNaNBrowserControlOffset = mBrowserControlOffset != mBrowserControlOffset;
+ final float rendererControlOffset = rendererControlOffset();
+ final boolean isNaNRendererControlOffset = rendererControlOffset != rendererControlOffset;
+ if (!isNaNBrowserControlOffset || !isNaNRendererControlOffset) {
+ offset = Math.max(
+ isNaNBrowserControlOffset ? -mControlContainerHeight : mBrowserControlOffset,
+ isNaNRendererControlOffset ? -mControlContainerHeight : rendererControlOffset);
+ }
+ mControlOffset = offset;
+ }
+
+ @Override
+ public void setOverlayVideoMode(boolean enabled) {
+ super.setOverlayVideoMode(enabled);
+
+ for (int i = 0; i < mListeners.size(); i++) {
+ mListeners.get(i).onToggleOverlayVideoMode(enabled);
+ }
+ }
+
+ /**
+ * @return Whether the browser has a control offset override.
+ */
+ @VisibleForTesting
+ public boolean hasBrowserControlOffsetOverride() {
+ return !Float.isNaN(mBrowserControlOffset) || mControlAnimation != null
+ || !mPersistentControlTokens.isEmpty();
+ }
+
+ /**
+ * Returns how tall the opaque portion of the control container is.
+ */
+ public float controlContainerHeight() {
+ return mControlContainerHeight;
+ }
+
+ private float rendererContentOffset() {
+ if (!mEnabled) return mControlContainerHeight;
+ return mRendererContentOffset;
+ }
+
+ private float rendererControlOffset() {
+ if (!mEnabled) return 0;
+ return mRendererControlOffset;
+ }
+
+ /**
+ * @return The visible offset of the content from the top of the screen.
+ */
+ public float getVisibleContentOffset() {
+ if (!mEnabled) return 0;
+ return mControlContainerHeight + getControlOffset();
+ }
+
+ /**
+ * @param listener The {@link FullscreenListener} to be notified of fullscreen changes.
+ */
+ public void addListener(FullscreenListener listener) {
+ if (!mListeners.contains(listener)) mListeners.add(listener);
+ }
+
+ /**
+ * @param listener The {@link FullscreenListener} to no longer be notified of fullscreen
+ * changes.
+ */
+ public void removeListener(FullscreenListener listener) {
+ mListeners.remove(listener);
+ }
+
+ /**
+ * Updates the content view's viewport size to have it render the content correctly.
+ *
+ * @param viewCore The ContentViewCore to update.
+ */
+ public void updateContentViewViewportSize(ContentViewCore viewCore) {
+ if (!mEnabled || viewCore == null) return;
+ if (mInGesture || mContentViewScrolling) return;
+
+ // Update content viewport size only when the top controls are not animating.
+ int contentOffset = (int) rendererContentOffset();
+ if (contentOffset != 0 && contentOffset != mControlContainerHeight) return;
+ viewCore.setTopControlsHeight(mControlContainerHeight, contentOffset > 0);
+ }
+
+ @Override
+ public void updateContentViewChildrenState() {
+ ContentViewCore contentViewCore = getActiveContentViewCore();
+ if (contentViewCore == null || !mEnabled) return;
+ ViewGroup view = contentViewCore.getContainerView();
+
+ float topViewsTranslation = (getControlOffset() + mControlContainerHeight);
+ applyTranslationToTopChildViews(view, topViewsTranslation);
+ applyMarginToFullChildViews(view, topViewsTranslation);
+ updateContentViewViewportSize(contentViewCore);
+ }
+
+ /**
+ * Utility routine for ensuring visibility updates are synchronized with
+ * animation, preventing message loop stalls due to untimely invalidation.
+ */
+ private void scheduleVisibilityUpdate() {
+ final int desiredVisibility = shouldShowAndroidControls() ? View.VISIBLE : View.INVISIBLE;
+ if (mControlContainer.getVisibility() == desiredVisibility) return;
+ mControlContainer.removeCallbacks(mUpdateVisibilityRunnable);
+ ApiCompatibilityUtils.postOnAnimation(mControlContainer, mUpdateVisibilityRunnable);
+ }
+
+ private void updateVisuals() {
+ if (!mEnabled) return;
+
+ TraceEvent.begin("FullscreenManager:updateVisuals");
+
+ float offset = getControlOffset();
+ if (Float.compare(mPreviousControlOffset, offset) != 0) {
+ mPreviousControlOffset = offset;
+ getHtmlApiHandler().updateBubblePosition();
+
+ scheduleVisibilityUpdate();
+ if (shouldShowAndroidControls()) mControlContainer.setTranslationY(getControlOffset());
+
+ // In ICS, the toolbar can appear clipped when compositor content is not being drawn
+ // beneath it (at the top of the page, during side swipe). Requesting a layout clears
+ // up the issue (see crbug.com/172631).
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+ if (!mHandler.hasMessages(MSG_ID_CONTROLS_REQUEST_LAYOUT)) {
+ mHandler.sendEmptyMessage(MSG_ID_CONTROLS_REQUEST_LAYOUT);
+ }
+ }
+ for (int i = 0; i < mListeners.size(); i++) {
+ mListeners.get(i).onVisibleContentOffsetChanged(getVisibleContentOffset());
+ }
+ }
+
+ final ContentViewCore contentViewCore = getActiveContentViewCore();
+ if (contentViewCore != null && offset == -mControlContainerHeight
+ && mIsEnteringPersistentModeState) {
+ getHtmlApiHandler().enterFullscreen(contentViewCore);
+ mIsEnteringPersistentModeState = false;
+ }
+
+ updateContentViewChildrenState();
+
+ float contentOffset = getContentOffset();
+ if (Float.compare(mPreviousContentOffset, contentOffset) != 0) {
+ for (int i = 0; i < mListeners.size(); i++) {
+ mListeners.get(i).onContentOffsetChanged(contentOffset);
+ }
+ mPreviousContentOffset = contentOffset;
+ }
+
+ TraceEvent.end("FullscreenManager:updateVisuals");
+ }
+
+ /**
+ * @param hide Whether or not to force the top controls Android view to hide. If this is
+ * {@code false} the top controls Android view will show/hide based on position, if
+ * it is {@code true} the top controls Android view will always be hidden.
+ */
+ public void setHideTopControlsAndroidView(boolean hide) {
+ if (mTopControlsAndroidViewHidden == hide) return;
+ mTopControlsAndroidViewHidden = hide;
+ scheduleVisibilityUpdate();
+ }
+
+ private boolean shouldShowAndroidControls() {
+ if (mTopControlsAndroidViewHidden) return false;
+
+ boolean showControls = getControlOffset() == 0;
+ ContentViewCore contentViewCore = getActiveContentViewCore();
+ if (contentViewCore == null) return showControls;
+ ViewGroup contentView = contentViewCore.getContainerView();
+
+ for (int i = 0; i < contentView.getChildCount(); i++) {
+ View child = contentView.getChildAt(i);
+ if (!(child.getLayoutParams() instanceof FrameLayout.LayoutParams)) continue;
+
+ FrameLayout.LayoutParams layoutParams =
+ (FrameLayout.LayoutParams) child.getLayoutParams();
+ if (Gravity.TOP == (layoutParams.gravity & Gravity.FILL_VERTICAL)) {
+ showControls = true;
+ break;
+ }
+ }
+
+ showControls |= !mPersistentControlTokens.isEmpty();
+
+ return showControls;
+ }
+
+ private void applyMarginToFullChildViews(ViewGroup contentView, float margin) {
+ for (int i = 0; i < contentView.getChildCount(); i++) {
+ View child = contentView.getChildAt(i);
+ if (!(child.getLayoutParams() instanceof FrameLayout.LayoutParams)) continue;
+ FrameLayout.LayoutParams layoutParams =
+ (FrameLayout.LayoutParams) child.getLayoutParams();
+
+ if (layoutParams.height == LayoutParams.MATCH_PARENT
+ && layoutParams.topMargin != (int) margin) {
+ layoutParams.topMargin = (int) margin;
+ child.requestLayout();
+ TraceEvent.instant("FullscreenManager:child.requestLayout()");
+ }
+ }
+ }
+
+ private void applyTranslationToTopChildViews(ViewGroup contentView, float translation) {
+ for (int i = 0; i < contentView.getChildCount(); i++) {
+ View child = contentView.getChildAt(i);
+ if (!(child.getLayoutParams() instanceof FrameLayout.LayoutParams)) continue;
+
+ FrameLayout.LayoutParams layoutParams =
+ (FrameLayout.LayoutParams) child.getLayoutParams();
+ if (Gravity.TOP == (layoutParams.gravity & Gravity.FILL_VERTICAL)) {
+ child.setTranslationY(translation);
+ TraceEvent.instant("FullscreenManager:child.setTranslationY()");
+ }
+ }
+ }
+
+ private ContentViewCore getActiveContentViewCore() {
+ Tab tab = getTabModelSelector().getCurrentTab();
+ return tab != null ? tab.getContentViewCore() : null;
+ }
+
+ @Override
+ public void setPositionsForTabToNonFullscreen() {
+ setPositionsForTab(0, mControlContainerHeight);
+ }
+
+ @Override
+ public void setPositionsForTab(float controlsOffset, float contentOffset) {
+ if (!mEnabled) return;
+ float rendererControlOffset =
+ Math.round(Math.max(controlsOffset, -mControlContainerHeight));
+ float rendererContentOffset = Math.min(
+ Math.round(contentOffset), rendererControlOffset + mControlContainerHeight);
+
+ if (Float.compare(rendererControlOffset, mRendererControlOffset) == 0
+ && Float.compare(rendererContentOffset, mRendererContentOffset) == 0) {
+ return;
+ }
+
+ mRendererControlOffset = rendererControlOffset;
+ mRendererContentOffset = rendererContentOffset;
+ updateControlOffset();
+
+ if (mControlAnimation == null) updateVisuals();
+ }
+
+ /**
+ * @param e The dispatched motion event
+ * @return Whether or not this motion event is in the top control container area and should be
+ * consumed.
+ */
+ public boolean onInterceptMotionEvent(MotionEvent e) {
+ return mEnabled && e.getY() < getControlOffset() + mControlContainerHeight
+ && !mTopControlsAndroidViewHidden;
+ }
+
+ /**
+ * Notifies the fullscreen manager that a motion event has occurred.
+ * @param e The dispatched motion event.
+ */
+ public void onMotionEvent(MotionEvent e) {
+ if (!mEnabled) return;
+ int eventAction = e.getActionMasked();
+ if (eventAction == MotionEvent.ACTION_DOWN
+ || eventAction == MotionEvent.ACTION_POINTER_DOWN) {
+ mInGesture = true;
+ getHtmlApiHandler().hideNotificationBubble();
+ } else if (eventAction == MotionEvent.ACTION_CANCEL
+ || eventAction == MotionEvent.ACTION_UP) {
+ mInGesture = false;
+ updateVisuals();
+ }
+ }
+
+ private void update(boolean show) {
+ // On forced show/hide, reset the flags that may suppress ContentView resize.
+ // As this method is also called when tab is switched, this also cleanup the scrolling
+ // flag set based on the previous ContentView's scrolling state.
+ mInGesture = false;
+ mContentViewScrolling = false;
+
+ if (!mEnabled) return;
+
+ if (show) mCurrentShowTime = SystemClock.uptimeMillis();
+
+ boolean postHideMessage = false;
+ if (!show) {
+ if (mControlAnimation != null && mCurrentAnimationIsShowing) {
+ postHideMessage = true;
+ } else {
+ long timeDelta = SystemClock.uptimeMillis() - mCurrentShowTime;
+ animateIfNecessary(false, Math.max(mMinShowNotificationMs - timeDelta, 0));
+ }
+ } else {
+ animateIfNecessary(true, 0);
+ if (mPersistentControlTokens.isEmpty()) postHideMessage = true;
+ }
+
+ mHandler.removeMessages(MSG_ID_HIDE_CONTROLS);
+ if (postHideMessage) {
+ long timeDelta = SystemClock.uptimeMillis() - mCurrentShowTime;
+ mHandler.sendEmptyMessageDelayed(
+ MSG_ID_HIDE_CONTROLS, Math.max(mMinShowNotificationMs - timeDelta, 0));
+ }
+ }
+
+ private void animateIfNecessary(final boolean show, long startDelay) {
+ if (mControlAnimation != null) {
+ if (!mControlAnimation.isRunning() || mCurrentAnimationIsShowing != show) {
+ mControlAnimation.cancel();
+ mControlAnimation = null;
+ } else {
+ return;
+ }
+ }
+
+ float destination = show ? 0 : -mControlContainerHeight;
+ long duration = (long) (mMaxAnimationDurationMs
+ * Math.abs((destination - getControlOffset()) / mControlContainerHeight));
+ mControlAnimation = ObjectAnimator.ofFloat(this, new ControlsOffsetProperty(), destination);
+ mControlAnimation.addListener(new AnimatorListenerAdapter() {
+ private boolean mCanceled = false;
+
+ @Override
+ public void onAnimationCancel(Animator anim) {
+ mCanceled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (!show && !mCanceled) mBrowserControlOffset = Float.NaN;
+ mControlAnimation = null;
+ }
+ });
+ mControlAnimation.setStartDelay(startDelay);
+ mControlAnimation.setDuration(duration);
+ mControlAnimation.start();
+ mCurrentAnimationIsShowing = show;
+ }
+
+ @Override
+ public void onContentViewScrollingStateChanged(boolean scrolling) {
+ mContentViewScrolling = scrolling;
+ if (!scrolling) updateVisuals();
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698