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 static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; |
| 8 import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; |
| 9 import static android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE; |
| 10 |
| 11 import android.content.res.Resources; |
| 12 import android.os.Build; |
| 13 import android.os.Bundle; |
| 14 import android.os.Handler; |
| 15 import android.os.Message; |
| 16 import android.view.Gravity; |
| 17 import android.view.View; |
| 18 import android.view.View.OnLayoutChangeListener; |
| 19 import android.view.Window; |
| 20 import android.view.WindowManager; |
| 21 |
| 22 import org.chromium.chrome.R; |
| 23 import org.chromium.chrome.browser.widget.TextBubble; |
| 24 import org.chromium.content.browser.ContentViewCore; |
| 25 |
| 26 /** |
| 27 * Handles updating the UI based on requests to the HTML Fullscreen API. |
| 28 */ |
| 29 public class FullscreenHtmlApiHandler { |
| 30 private static final int MSG_ID_HIDE_NOTIFICATION_BUBBLE = 1; |
| 31 private static final int MSG_ID_SET_FULLSCREEN_SYSTEM_UI_FLAGS = 2; |
| 32 private static final int MSG_ID_CLEAR_LAYOUT_FULLSCREEN_FLAG = 3; |
| 33 |
| 34 private static final int MAX_NOTIFICATION_DIMENSION_DP = 600; |
| 35 |
| 36 private static final long NOTIFICATION_INITIAL_SHOW_DURATION_MS = 3500; |
| 37 private static final long NOTIFICATION_SHOW_DURATION_MS = 2500; |
| 38 // The time we allow the Android notification bar to be shown when it is req
uested temporarily |
| 39 // by the Android system (this value is additive on top of the show duration
imposed by |
| 40 // Android). |
| 41 private static final long ANDROID_CONTROLS_SHOW_DURATION_MS = 200; |
| 42 // Delay to allow a frame to render between getting the fullscreen layout up
date and clearing |
| 43 // the SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN flag. |
| 44 private static final long CLEAR_LAYOUT_FULLSCREEN_DELAY_MS = 20; |
| 45 |
| 46 private static final int NOTIFICATION_BUBBLE_ALPHA = 252; // 255 * 0.99 |
| 47 |
| 48 private static boolean sFullscreenNotificationShown; |
| 49 |
| 50 private final Window mWindow; |
| 51 private final Handler mHandler; |
| 52 private final FullscreenHtmlApiDelegate mDelegate; |
| 53 private final int mNotificationMaxDimension; |
| 54 |
| 55 private final boolean mPersistentFullscreenSupported; |
| 56 |
| 57 private ContentViewCore mContentViewCoreInFullscreen; |
| 58 private boolean mIsPersistentMode; |
| 59 |
| 60 private TextBubble mNotificationBubble; |
| 61 private OnLayoutChangeListener mFullscreenOnLayoutChangeListener; |
| 62 |
| 63 /** |
| 64 * Delegate that allows embedders to react to fullscreen API requests. |
| 65 */ |
| 66 public interface FullscreenHtmlApiDelegate { |
| 67 /** |
| 68 * @return The Y offset to be applied to the fullscreen notification. |
| 69 */ |
| 70 int getNotificationOffsetY(); |
| 71 |
| 72 /** |
| 73 * @return The view that the fullscreen notification will be pinned to. |
| 74 */ |
| 75 View getNotificationAnchorView(); |
| 76 |
| 77 /** |
| 78 * Notifies the delegate that entering fullscreen has been requested and
allows them |
| 79 * to hide their controls. |
| 80 * <p> |
| 81 * Once the delegate has hidden the their controls, it must call |
| 82 * {@link FullscreenHtmlApiHandler#enterFullscreen(ContentViewCore)}. |
| 83 */ |
| 84 void onEnterFullscreen(); |
| 85 |
| 86 /** |
| 87 * Cancels a pending enter fullscreen request if present. |
| 88 * @return Whether the request was cancelled. |
| 89 */ |
| 90 boolean cancelPendingEnterFullscreen(); |
| 91 |
| 92 /** |
| 93 * Notifies the delegate that the window UI has fully exited fullscreen
and gives |
| 94 * the embedder a chance to update their controls. |
| 95 * |
| 96 * @param contentViewCore The ContentViewCore whose fullscreen is being
exited. |
| 97 */ |
| 98 void onFullscreenExited(ContentViewCore contentViewCore); |
| 99 |
| 100 /** |
| 101 * @return Whether the notification bubble should be shown. For fullscre
en video in |
| 102 * overlay mode, the notification bubble should be disabled. |
| 103 */ |
| 104 boolean shouldShowNotificationBubble(); |
| 105 } |
| 106 |
| 107 /** |
| 108 * Constructs the handler that will manage the UI transitions from the HTML
fullscreen API. |
| 109 * |
| 110 * @param window The window containing the view going to fullscreen. |
| 111 * @param delegate The delegate that allows embedders to handle fullscreen t
ransitions. |
| 112 * @param persistentFullscreenSupported |
| 113 */ |
| 114 public FullscreenHtmlApiHandler(Window window, FullscreenHtmlApiDelegate del
egate, |
| 115 boolean persistentFullscreenSupported) { |
| 116 mWindow = window; |
| 117 mDelegate = delegate; |
| 118 mPersistentFullscreenSupported = persistentFullscreenSupported; |
| 119 |
| 120 mHandler = new Handler() { |
| 121 @Override |
| 122 public void handleMessage(Message msg) { |
| 123 if (msg == null) return; |
| 124 switch (msg.what) { |
| 125 case MSG_ID_HIDE_NOTIFICATION_BUBBLE: |
| 126 hideNotificationBubble(); |
| 127 break; |
| 128 case MSG_ID_SET_FULLSCREEN_SYSTEM_UI_FLAGS: { |
| 129 assert mIsPersistentMode : "Calling after we exited full
screen"; |
| 130 final ContentViewCore contentViewCore = mContentViewCore
InFullscreen; |
| 131 if (contentViewCore == null) return; |
| 132 final View contentView = contentViewCore.getContainerVie
w(); |
| 133 int systemUiVisibility = contentView.getSystemUiVisibili
ty(); |
| 134 if ((systemUiVisibility & SYSTEM_UI_FLAG_FULLSCREEN) |
| 135 == SYSTEM_UI_FLAG_FULLSCREEN) { |
| 136 return; |
| 137 } |
| 138 systemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN; |
| 139 systemUiVisibility |= SYSTEM_UI_FLAG_LOW_PROFILE; |
| 140 contentView.setSystemUiVisibility(systemUiVisibility); |
| 141 |
| 142 // Trigger a update to clear the SYSTEM_UI_FLAG_LAYOUT_F
ULLSCREEN flag |
| 143 // once the view has been laid out after this system UI
update. Without |
| 144 // clearing this flag, the keyboard appearing will not t
rigger a relayout |
| 145 // of the contents, which prevents updating the overdraw
amount to the |
| 146 // renderer. |
| 147 contentView.addOnLayoutChangeListener(new OnLayoutChange
Listener() { |
| 148 @Override |
| 149 public void onLayoutChange(View v, int left, int top
, int right, |
| 150 int bottom, int oldLeft, int oldTop, int old
Right, |
| 151 int oldBottom) { |
| 152 sendEmptyMessageDelayed(MSG_ID_CLEAR_LAYOUT_FULL
SCREEN_FLAG, |
| 153 CLEAR_LAYOUT_FULLSCREEN_DELAY_MS); |
| 154 contentView.removeOnLayoutChangeListener(this); |
| 155 } |
| 156 }); |
| 157 break; |
| 158 } |
| 159 case MSG_ID_CLEAR_LAYOUT_FULLSCREEN_FLAG: { |
| 160 // Change this assert to simply ignoring the message to
work around |
| 161 // http://crbug/365638 |
| 162 // TODO(aberent): Fix bug |
| 163 // assert mIsPersistentMode : "Calling after we exited f
ullscreen"; |
| 164 if (!mIsPersistentMode) return; |
| 165 if (mContentViewCoreInFullscreen == null) return; |
| 166 final View view = mContentViewCoreInFullscreen.getContai
nerView(); |
| 167 int systemUiVisibility = view.getSystemUiVisibility(); |
| 168 if ((systemUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCRE
EN) == 0) { |
| 169 return; |
| 170 } |
| 171 systemUiVisibility &= ~SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; |
| 172 view.setSystemUiVisibility(systemUiVisibility); |
| 173 break; |
| 174 } |
| 175 default: |
| 176 assert false : "Unexpected message for ID: " + msg.what; |
| 177 break; |
| 178 } |
| 179 } |
| 180 }; |
| 181 |
| 182 Resources resources = mWindow.getContext().getResources(); |
| 183 float density = resources.getDisplayMetrics().density; |
| 184 mNotificationMaxDimension = (int) (density * MAX_NOTIFICATION_DIMENSION_
DP); |
| 185 } |
| 186 |
| 187 /** |
| 188 * Enters or exits persistent fullscreen mode. In this mode, the top contro
ls will be |
| 189 * permanently hidden until this mode is exited. |
| 190 * |
| 191 * @param enabled Whether to enable persistent fullscreen mode. |
| 192 */ |
| 193 public void setPersistentFullscreenMode(boolean enabled) { |
| 194 if (!mPersistentFullscreenSupported) return; |
| 195 |
| 196 if (mIsPersistentMode == enabled) return; |
| 197 |
| 198 mIsPersistentMode = enabled; |
| 199 |
| 200 if (mIsPersistentMode) { |
| 201 mDelegate.onEnterFullscreen(); |
| 202 } else { |
| 203 if (mContentViewCoreInFullscreen != null) { |
| 204 exitFullscreen(mContentViewCoreInFullscreen); |
| 205 } else { |
| 206 if (!mDelegate.cancelPendingEnterFullscreen()) { |
| 207 assert false : "No content view previously set to fullscreen
."; |
| 208 } |
| 209 } |
| 210 mContentViewCoreInFullscreen = null; |
| 211 } |
| 212 } |
| 213 |
| 214 /** |
| 215 * @return Whether the application is in persistent fullscreen mode. |
| 216 * @see #setPersistentFullscreenMode(boolean) |
| 217 */ |
| 218 public boolean getPersistentFullscreenMode() { |
| 219 return mIsPersistentMode; |
| 220 } |
| 221 |
| 222 private void exitFullscreen(final ContentViewCore contentViewCore) { |
| 223 final View contentView = contentViewCore.getContainerView(); |
| 224 hideNotificationBubble(); |
| 225 mHandler.removeMessages(MSG_ID_SET_FULLSCREEN_SYSTEM_UI_FLAGS); |
| 226 mHandler.removeMessages(MSG_ID_CLEAR_LAYOUT_FULLSCREEN_FLAG); |
| 227 |
| 228 int systemUiVisibility = contentView.getSystemUiVisibility(); |
| 229 systemUiVisibility &= ~SYSTEM_UI_FLAG_LOW_PROFILE; |
| 230 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { |
| 231 systemUiVisibility &= ~SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; |
| 232 systemUiVisibility &= ~SYSTEM_UI_FLAG_FULLSCREEN; |
| 233 } else { |
| 234 mWindow.addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREE
N); |
| 235 mWindow.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); |
| 236 } |
| 237 contentView.setSystemUiVisibility(systemUiVisibility); |
| 238 if (mFullscreenOnLayoutChangeListener != null) { |
| 239 contentView.removeOnLayoutChangeListener(mFullscreenOnLayoutChangeLi
stener); |
| 240 } |
| 241 mFullscreenOnLayoutChangeListener = new OnLayoutChangeListener() { |
| 242 @Override |
| 243 public void onLayoutChange(View v, int left, int top, int right, int
bottom, |
| 244 int oldLeft, int oldTop, int oldRight, int oldBottom) { |
| 245 if ((bottom - top) < (oldBottom - oldTop)) { |
| 246 mDelegate.onFullscreenExited(contentViewCore); |
| 247 contentView.removeOnLayoutChangeListener(this); |
| 248 } |
| 249 } |
| 250 }; |
| 251 contentView.addOnLayoutChangeListener(mFullscreenOnLayoutChangeListener)
; |
| 252 contentViewCore.getWebContents().exitFullscreen(); |
| 253 } |
| 254 |
| 255 /** |
| 256 * Handles hiding the system UI components to allow the content to take up t
he full screen. |
| 257 * @param contentViewCore The contentViewCore that is entering fullscreen. |
| 258 */ |
| 259 public void enterFullscreen(final ContentViewCore contentViewCore) { |
| 260 final View contentView = contentViewCore.getContainerView(); |
| 261 int systemUiVisibility = contentView.getSystemUiVisibility(); |
| 262 systemUiVisibility |= SYSTEM_UI_FLAG_LOW_PROFILE; |
| 263 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { |
| 264 if ((systemUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) |
| 265 == SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) { |
| 266 systemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN; |
| 267 } else { |
| 268 systemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; |
| 269 } |
| 270 } else { |
| 271 mWindow.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); |
| 272 mWindow.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCR
EEN); |
| 273 } |
| 274 if (mFullscreenOnLayoutChangeListener != null) { |
| 275 contentView.removeOnLayoutChangeListener(mFullscreenOnLayoutChangeLi
stener); |
| 276 } |
| 277 mFullscreenOnLayoutChangeListener = new OnLayoutChangeListener() { |
| 278 @Override |
| 279 public void onLayoutChange(View v, int left, int top, int right, int
bottom, |
| 280 int oldLeft, int oldTop, int oldRight, int oldBottom) { |
| 281 // On certain sites playing embedded video (http://crbug.com/293
782), setting the |
| 282 // SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN does not always trigger a vi
ew-level layout |
| 283 // with an updated height. To work around this, do not check fo
r an increased |
| 284 // height and always just trigger the next step of the fullscree
n initialization. |
| 285 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2)
{ |
| 286 // Posting the message to set the fullscreen flag because se
tting it |
| 287 // directly in the onLayoutChange would have no effect. |
| 288 mHandler.sendEmptyMessage(MSG_ID_SET_FULLSCREEN_SYSTEM_UI_FL
AGS); |
| 289 } |
| 290 |
| 291 if ((bottom - top) <= (oldBottom - oldTop)) return; |
| 292 if (mDelegate.shouldShowNotificationBubble()) { |
| 293 showNotificationBubble(mWindow.getContext().getResources().g
etString( |
| 294 R.string.fullscreen_api_notification)); |
| 295 } |
| 296 contentView.removeOnLayoutChangeListener(this); |
| 297 } |
| 298 }; |
| 299 contentView.addOnLayoutChangeListener(mFullscreenOnLayoutChangeListener)
; |
| 300 contentView.setSystemUiVisibility(systemUiVisibility); |
| 301 mContentViewCoreInFullscreen = contentViewCore; |
| 302 } |
| 303 |
| 304 /** |
| 305 * Creates (if necessary) and returns a notification bubble. |
| 306 */ |
| 307 private TextBubble getOrCreateNotificationBubble() { |
| 308 if (mNotificationBubble == null) { |
| 309 Bundle b = new Bundle(); |
| 310 b.putBoolean(TextBubble.BACKGROUND_INTRINSIC_PADDING, true); |
| 311 b.putBoolean(TextBubble.CENTER, true); |
| 312 b.putBoolean(TextBubble.UP_DOWN, true); |
| 313 b.putInt(TextBubble.TEXT_STYLE_ID, android.R.style.TextAppearance_De
viceDefault_Medium); |
| 314 b.putInt(TextBubble.ANIM_STYLE_ID, R.style.FullscreenNotificationBub
ble); |
| 315 mNotificationBubble = new TextBubble(mWindow.getContext(), b); |
| 316 mNotificationBubble.getBubbleTextView().setGravity(Gravity.CENTER_HO
RIZONTAL); |
| 317 mNotificationBubble.getBackground().setAlpha(NOTIFICATION_BUBBLE_ALP
HA); |
| 318 mNotificationBubble.setTouchable(false); |
| 319 } |
| 320 return mNotificationBubble; |
| 321 } |
| 322 |
| 323 private void showNotificationBubble(String text) { |
| 324 getOrCreateNotificationBubble().showTextBubble(text, mDelegate.getNotifi
cationAnchorView(), |
| 325 mNotificationMaxDimension, mNotificationMaxDimension); |
| 326 updateBubblePosition(); |
| 327 |
| 328 mHandler.removeMessages(MSG_ID_HIDE_NOTIFICATION_BUBBLE); |
| 329 |
| 330 long showDuration = NOTIFICATION_INITIAL_SHOW_DURATION_MS; |
| 331 if (sFullscreenNotificationShown) { |
| 332 showDuration = NOTIFICATION_SHOW_DURATION_MS; |
| 333 } |
| 334 sFullscreenNotificationShown = true; |
| 335 |
| 336 mHandler.sendEmptyMessageDelayed(MSG_ID_HIDE_NOTIFICATION_BUBBLE, showDu
ration); |
| 337 } |
| 338 |
| 339 /** |
| 340 * Updates the position of the notification bubble based on the current offs
et. |
| 341 */ |
| 342 public void updateBubblePosition() { |
| 343 if (mNotificationBubble != null && mNotificationBubble.isShowing()) { |
| 344 mNotificationBubble.setOffsetY(mDelegate.getNotificationOffsetY()); |
| 345 } |
| 346 } |
| 347 |
| 348 /** |
| 349 * Hides the notification bubble. |
| 350 */ |
| 351 public void hideNotificationBubble() { |
| 352 if (mNotificationBubble != null && mNotificationBubble.isShowing()) { |
| 353 mNotificationBubble.dismiss(); |
| 354 } |
| 355 } |
| 356 |
| 357 /** |
| 358 * Notified when the system UI visibility for the current ContentView has ch
anged. |
| 359 * @param visibility The updated UI visibility. |
| 360 * @see View#getSystemUiVisibility() |
| 361 */ |
| 362 public void onContentViewSystemUiVisibilityChange(int visibility) { |
| 363 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) return; |
| 364 |
| 365 if (mContentViewCoreInFullscreen == null || !mIsPersistentMode) return; |
| 366 mHandler.sendEmptyMessageDelayed( |
| 367 MSG_ID_SET_FULLSCREEN_SYSTEM_UI_FLAGS, ANDROID_CONTROLS_SHOW_DUR
ATION_MS); |
| 368 } |
| 369 |
| 370 /** |
| 371 * Ensure the proper system UI flags are set after the window regains focus. |
| 372 * @see android.app.Activity#onWindowFocusChanged(boolean) |
| 373 */ |
| 374 public void onWindowFocusChanged(boolean hasWindowFocus) { |
| 375 if (!hasWindowFocus) hideNotificationBubble(); |
| 376 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) return; |
| 377 |
| 378 mHandler.removeMessages(MSG_ID_SET_FULLSCREEN_SYSTEM_UI_FLAGS); |
| 379 mHandler.removeMessages(MSG_ID_CLEAR_LAYOUT_FULLSCREEN_FLAG); |
| 380 if (mContentViewCoreInFullscreen == null || !mIsPersistentMode || !hasWi
ndowFocus) return; |
| 381 mHandler.sendEmptyMessageDelayed( |
| 382 MSG_ID_SET_FULLSCREEN_SYSTEM_UI_FLAGS, ANDROID_CONTROLS_SHOW_DUR
ATION_MS); |
| 383 } |
| 384 } |
OLD | NEW |