Index: chrome/android/java/src/org/chromium/chrome/browser/fullscreen/FullscreenHtmlApiHandler.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/FullscreenHtmlApiHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/FullscreenHtmlApiHandler.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..5f436cf20242aeff1a0f9b53a88267999cb0a0d7 |
--- /dev/null |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/FullscreenHtmlApiHandler.java |
@@ -0,0 +1,384 @@ |
+// 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 static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; |
+import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; |
+import static android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE; |
+ |
+import android.content.res.Resources; |
+import android.os.Build; |
+import android.os.Bundle; |
+import android.os.Handler; |
+import android.os.Message; |
+import android.view.Gravity; |
+import android.view.View; |
+import android.view.View.OnLayoutChangeListener; |
+import android.view.Window; |
+import android.view.WindowManager; |
+ |
+import org.chromium.chrome.R; |
+import org.chromium.chrome.browser.widget.TextBubble; |
+import org.chromium.content.browser.ContentViewCore; |
+ |
+/** |
+ * Handles updating the UI based on requests to the HTML Fullscreen API. |
+ */ |
+public class FullscreenHtmlApiHandler { |
+ private static final int MSG_ID_HIDE_NOTIFICATION_BUBBLE = 1; |
+ private static final int MSG_ID_SET_FULLSCREEN_SYSTEM_UI_FLAGS = 2; |
+ private static final int MSG_ID_CLEAR_LAYOUT_FULLSCREEN_FLAG = 3; |
+ |
+ private static final int MAX_NOTIFICATION_DIMENSION_DP = 600; |
+ |
+ private static final long NOTIFICATION_INITIAL_SHOW_DURATION_MS = 3500; |
+ private static final long NOTIFICATION_SHOW_DURATION_MS = 2500; |
+ // The time we allow the Android notification bar to be shown when it is requested temporarily |
+ // by the Android system (this value is additive on top of the show duration imposed by |
+ // Android). |
+ private static final long ANDROID_CONTROLS_SHOW_DURATION_MS = 200; |
+ // Delay to allow a frame to render between getting the fullscreen layout update and clearing |
+ // the SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN flag. |
+ private static final long CLEAR_LAYOUT_FULLSCREEN_DELAY_MS = 20; |
+ |
+ private static final int NOTIFICATION_BUBBLE_ALPHA = 252; // 255 * 0.99 |
+ |
+ private static boolean sFullscreenNotificationShown; |
+ |
+ private final Window mWindow; |
+ private final Handler mHandler; |
+ private final FullscreenHtmlApiDelegate mDelegate; |
+ private final int mNotificationMaxDimension; |
+ |
+ private final boolean mPersistentFullscreenSupported; |
+ |
+ private ContentViewCore mContentViewCoreInFullscreen; |
+ private boolean mIsPersistentMode; |
+ |
+ private TextBubble mNotificationBubble; |
+ private OnLayoutChangeListener mFullscreenOnLayoutChangeListener; |
+ |
+ /** |
+ * Delegate that allows embedders to react to fullscreen API requests. |
+ */ |
+ public interface FullscreenHtmlApiDelegate { |
+ /** |
+ * @return The Y offset to be applied to the fullscreen notification. |
+ */ |
+ int getNotificationOffsetY(); |
+ |
+ /** |
+ * @return The view that the fullscreen notification will be pinned to. |
+ */ |
+ View getNotificationAnchorView(); |
+ |
+ /** |
+ * Notifies the delegate that entering fullscreen has been requested and allows them |
+ * to hide their controls. |
+ * <p> |
+ * Once the delegate has hidden the their controls, it must call |
+ * {@link FullscreenHtmlApiHandler#enterFullscreen(ContentViewCore)}. |
+ */ |
+ void onEnterFullscreen(); |
+ |
+ /** |
+ * Cancels a pending enter fullscreen request if present. |
+ * @return Whether the request was cancelled. |
+ */ |
+ boolean cancelPendingEnterFullscreen(); |
+ |
+ /** |
+ * Notifies the delegate that the window UI has fully exited fullscreen and gives |
+ * the embedder a chance to update their controls. |
+ * |
+ * @param contentViewCore The ContentViewCore whose fullscreen is being exited. |
+ */ |
+ void onFullscreenExited(ContentViewCore contentViewCore); |
+ |
+ /** |
+ * @return Whether the notification bubble should be shown. For fullscreen video in |
+ * overlay mode, the notification bubble should be disabled. |
+ */ |
+ boolean shouldShowNotificationBubble(); |
+ } |
+ |
+ /** |
+ * Constructs the handler that will manage the UI transitions from the HTML fullscreen API. |
+ * |
+ * @param window The window containing the view going to fullscreen. |
+ * @param delegate The delegate that allows embedders to handle fullscreen transitions. |
+ * @param persistentFullscreenSupported |
+ */ |
+ public FullscreenHtmlApiHandler(Window window, FullscreenHtmlApiDelegate delegate, |
+ boolean persistentFullscreenSupported) { |
+ mWindow = window; |
+ mDelegate = delegate; |
+ mPersistentFullscreenSupported = persistentFullscreenSupported; |
+ |
+ mHandler = new Handler() { |
+ @Override |
+ public void handleMessage(Message msg) { |
+ if (msg == null) return; |
+ switch (msg.what) { |
+ case MSG_ID_HIDE_NOTIFICATION_BUBBLE: |
+ hideNotificationBubble(); |
+ break; |
+ case MSG_ID_SET_FULLSCREEN_SYSTEM_UI_FLAGS: { |
+ assert mIsPersistentMode : "Calling after we exited fullscreen"; |
+ final ContentViewCore contentViewCore = mContentViewCoreInFullscreen; |
+ if (contentViewCore == null) return; |
+ final View contentView = contentViewCore.getContainerView(); |
+ int systemUiVisibility = contentView.getSystemUiVisibility(); |
+ if ((systemUiVisibility & SYSTEM_UI_FLAG_FULLSCREEN) |
+ == SYSTEM_UI_FLAG_FULLSCREEN) { |
+ return; |
+ } |
+ systemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN; |
+ systemUiVisibility |= SYSTEM_UI_FLAG_LOW_PROFILE; |
+ contentView.setSystemUiVisibility(systemUiVisibility); |
+ |
+ // Trigger a update to clear the SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN flag |
+ // once the view has been laid out after this system UI update. Without |
+ // clearing this flag, the keyboard appearing will not trigger a relayout |
+ // of the contents, which prevents updating the overdraw amount to the |
+ // renderer. |
+ contentView.addOnLayoutChangeListener(new OnLayoutChangeListener() { |
+ @Override |
+ public void onLayoutChange(View v, int left, int top, int right, |
+ int bottom, int oldLeft, int oldTop, int oldRight, |
+ int oldBottom) { |
+ sendEmptyMessageDelayed(MSG_ID_CLEAR_LAYOUT_FULLSCREEN_FLAG, |
+ CLEAR_LAYOUT_FULLSCREEN_DELAY_MS); |
+ contentView.removeOnLayoutChangeListener(this); |
+ } |
+ }); |
+ break; |
+ } |
+ case MSG_ID_CLEAR_LAYOUT_FULLSCREEN_FLAG: { |
+ // Change this assert to simply ignoring the message to work around |
+ // http://crbug/365638 |
+ // TODO(aberent): Fix bug |
+ // assert mIsPersistentMode : "Calling after we exited fullscreen"; |
+ if (!mIsPersistentMode) return; |
+ if (mContentViewCoreInFullscreen == null) return; |
+ final View view = mContentViewCoreInFullscreen.getContainerView(); |
+ int systemUiVisibility = view.getSystemUiVisibility(); |
+ if ((systemUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0) { |
+ return; |
+ } |
+ systemUiVisibility &= ~SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; |
+ view.setSystemUiVisibility(systemUiVisibility); |
+ break; |
+ } |
+ default: |
+ assert false : "Unexpected message for ID: " + msg.what; |
+ break; |
+ } |
+ } |
+ }; |
+ |
+ Resources resources = mWindow.getContext().getResources(); |
+ float density = resources.getDisplayMetrics().density; |
+ mNotificationMaxDimension = (int) (density * MAX_NOTIFICATION_DIMENSION_DP); |
+ } |
+ |
+ /** |
+ * Enters or exits persistent fullscreen mode. In this mode, the top controls will be |
+ * permanently hidden until this mode is exited. |
+ * |
+ * @param enabled Whether to enable persistent fullscreen mode. |
+ */ |
+ public void setPersistentFullscreenMode(boolean enabled) { |
+ if (!mPersistentFullscreenSupported) return; |
+ |
+ if (mIsPersistentMode == enabled) return; |
+ |
+ mIsPersistentMode = enabled; |
+ |
+ if (mIsPersistentMode) { |
+ mDelegate.onEnterFullscreen(); |
+ } else { |
+ if (mContentViewCoreInFullscreen != null) { |
+ exitFullscreen(mContentViewCoreInFullscreen); |
+ } else { |
+ if (!mDelegate.cancelPendingEnterFullscreen()) { |
+ assert false : "No content view previously set to fullscreen."; |
+ } |
+ } |
+ mContentViewCoreInFullscreen = null; |
+ } |
+ } |
+ |
+ /** |
+ * @return Whether the application is in persistent fullscreen mode. |
+ * @see #setPersistentFullscreenMode(boolean) |
+ */ |
+ public boolean getPersistentFullscreenMode() { |
+ return mIsPersistentMode; |
+ } |
+ |
+ private void exitFullscreen(final ContentViewCore contentViewCore) { |
+ final View contentView = contentViewCore.getContainerView(); |
+ hideNotificationBubble(); |
+ mHandler.removeMessages(MSG_ID_SET_FULLSCREEN_SYSTEM_UI_FLAGS); |
+ mHandler.removeMessages(MSG_ID_CLEAR_LAYOUT_FULLSCREEN_FLAG); |
+ |
+ int systemUiVisibility = contentView.getSystemUiVisibility(); |
+ systemUiVisibility &= ~SYSTEM_UI_FLAG_LOW_PROFILE; |
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { |
+ systemUiVisibility &= ~SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; |
+ systemUiVisibility &= ~SYSTEM_UI_FLAG_FULLSCREEN; |
+ } else { |
+ mWindow.addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); |
+ mWindow.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); |
+ } |
+ contentView.setSystemUiVisibility(systemUiVisibility); |
+ if (mFullscreenOnLayoutChangeListener != null) { |
+ contentView.removeOnLayoutChangeListener(mFullscreenOnLayoutChangeListener); |
+ } |
+ mFullscreenOnLayoutChangeListener = new OnLayoutChangeListener() { |
+ @Override |
+ public void onLayoutChange(View v, int left, int top, int right, int bottom, |
+ int oldLeft, int oldTop, int oldRight, int oldBottom) { |
+ if ((bottom - top) < (oldBottom - oldTop)) { |
+ mDelegate.onFullscreenExited(contentViewCore); |
+ contentView.removeOnLayoutChangeListener(this); |
+ } |
+ } |
+ }; |
+ contentView.addOnLayoutChangeListener(mFullscreenOnLayoutChangeListener); |
+ contentViewCore.getWebContents().exitFullscreen(); |
+ } |
+ |
+ /** |
+ * Handles hiding the system UI components to allow the content to take up the full screen. |
+ * @param contentViewCore The contentViewCore that is entering fullscreen. |
+ */ |
+ public void enterFullscreen(final ContentViewCore contentViewCore) { |
+ final View contentView = contentViewCore.getContainerView(); |
+ int systemUiVisibility = contentView.getSystemUiVisibility(); |
+ systemUiVisibility |= SYSTEM_UI_FLAG_LOW_PROFILE; |
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { |
+ if ((systemUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) |
+ == SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) { |
+ systemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN; |
+ } else { |
+ systemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; |
+ } |
+ } else { |
+ mWindow.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); |
+ mWindow.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); |
+ } |
+ if (mFullscreenOnLayoutChangeListener != null) { |
+ contentView.removeOnLayoutChangeListener(mFullscreenOnLayoutChangeListener); |
+ } |
+ mFullscreenOnLayoutChangeListener = new OnLayoutChangeListener() { |
+ @Override |
+ public void onLayoutChange(View v, int left, int top, int right, int bottom, |
+ int oldLeft, int oldTop, int oldRight, int oldBottom) { |
+ // On certain sites playing embedded video (http://crbug.com/293782), setting the |
+ // SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN does not always trigger a view-level layout |
+ // with an updated height. To work around this, do not check for an increased |
+ // height and always just trigger the next step of the fullscreen initialization. |
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { |
+ // Posting the message to set the fullscreen flag because setting it |
+ // directly in the onLayoutChange would have no effect. |
+ mHandler.sendEmptyMessage(MSG_ID_SET_FULLSCREEN_SYSTEM_UI_FLAGS); |
+ } |
+ |
+ if ((bottom - top) <= (oldBottom - oldTop)) return; |
+ if (mDelegate.shouldShowNotificationBubble()) { |
+ showNotificationBubble(mWindow.getContext().getResources().getString( |
+ R.string.fullscreen_api_notification)); |
+ } |
+ contentView.removeOnLayoutChangeListener(this); |
+ } |
+ }; |
+ contentView.addOnLayoutChangeListener(mFullscreenOnLayoutChangeListener); |
+ contentView.setSystemUiVisibility(systemUiVisibility); |
+ mContentViewCoreInFullscreen = contentViewCore; |
+ } |
+ |
+ /** |
+ * Creates (if necessary) and returns a notification bubble. |
+ */ |
+ private TextBubble getOrCreateNotificationBubble() { |
+ if (mNotificationBubble == null) { |
+ Bundle b = new Bundle(); |
+ b.putBoolean(TextBubble.BACKGROUND_INTRINSIC_PADDING, true); |
+ b.putBoolean(TextBubble.CENTER, true); |
+ b.putBoolean(TextBubble.UP_DOWN, true); |
+ b.putInt(TextBubble.TEXT_STYLE_ID, android.R.style.TextAppearance_DeviceDefault_Medium); |
+ b.putInt(TextBubble.ANIM_STYLE_ID, R.style.FullscreenNotificationBubble); |
+ mNotificationBubble = new TextBubble(mWindow.getContext(), b); |
+ mNotificationBubble.getBubbleTextView().setGravity(Gravity.CENTER_HORIZONTAL); |
+ mNotificationBubble.getBackground().setAlpha(NOTIFICATION_BUBBLE_ALPHA); |
+ mNotificationBubble.setTouchable(false); |
+ } |
+ return mNotificationBubble; |
+ } |
+ |
+ private void showNotificationBubble(String text) { |
+ getOrCreateNotificationBubble().showTextBubble(text, mDelegate.getNotificationAnchorView(), |
+ mNotificationMaxDimension, mNotificationMaxDimension); |
+ updateBubblePosition(); |
+ |
+ mHandler.removeMessages(MSG_ID_HIDE_NOTIFICATION_BUBBLE); |
+ |
+ long showDuration = NOTIFICATION_INITIAL_SHOW_DURATION_MS; |
+ if (sFullscreenNotificationShown) { |
+ showDuration = NOTIFICATION_SHOW_DURATION_MS; |
+ } |
+ sFullscreenNotificationShown = true; |
+ |
+ mHandler.sendEmptyMessageDelayed(MSG_ID_HIDE_NOTIFICATION_BUBBLE, showDuration); |
+ } |
+ |
+ /** |
+ * Updates the position of the notification bubble based on the current offset. |
+ */ |
+ public void updateBubblePosition() { |
+ if (mNotificationBubble != null && mNotificationBubble.isShowing()) { |
+ mNotificationBubble.setOffsetY(mDelegate.getNotificationOffsetY()); |
+ } |
+ } |
+ |
+ /** |
+ * Hides the notification bubble. |
+ */ |
+ public void hideNotificationBubble() { |
+ if (mNotificationBubble != null && mNotificationBubble.isShowing()) { |
+ mNotificationBubble.dismiss(); |
+ } |
+ } |
+ |
+ /** |
+ * Notified when the system UI visibility for the current ContentView has changed. |
+ * @param visibility The updated UI visibility. |
+ * @see View#getSystemUiVisibility() |
+ */ |
+ public void onContentViewSystemUiVisibilityChange(int visibility) { |
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) return; |
+ |
+ if (mContentViewCoreInFullscreen == null || !mIsPersistentMode) return; |
+ mHandler.sendEmptyMessageDelayed( |
+ MSG_ID_SET_FULLSCREEN_SYSTEM_UI_FLAGS, ANDROID_CONTROLS_SHOW_DURATION_MS); |
+ } |
+ |
+ /** |
+ * Ensure the proper system UI flags are set after the window regains focus. |
+ * @see android.app.Activity#onWindowFocusChanged(boolean) |
+ */ |
+ public void onWindowFocusChanged(boolean hasWindowFocus) { |
+ if (!hasWindowFocus) hideNotificationBubble(); |
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) return; |
+ |
+ mHandler.removeMessages(MSG_ID_SET_FULLSCREEN_SYSTEM_UI_FLAGS); |
+ mHandler.removeMessages(MSG_ID_CLEAR_LAYOUT_FULLSCREEN_FLAG); |
+ if (mContentViewCoreInFullscreen == null || !mIsPersistentMode || !hasWindowFocus) return; |
+ mHandler.sendEmptyMessageDelayed( |
+ MSG_ID_SET_FULLSCREEN_SYSTEM_UI_FLAGS, ANDROID_CONTROLS_SHOW_DURATION_MS); |
+ } |
+} |