| 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();
|
| + }
|
| +}
|
|
|