OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 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.compositor; |
| 6 |
| 7 import android.app.Activity; |
| 8 import android.content.Context; |
| 9 import android.graphics.Color; |
| 10 import android.graphics.PixelFormat; |
| 11 import android.graphics.Rect; |
| 12 import android.view.MotionEvent; |
| 13 import android.view.Surface; |
| 14 import android.view.SurfaceHolder; |
| 15 import android.view.SurfaceView; |
| 16 import android.view.View; |
| 17 |
| 18 import com.google.android.apps.chrome.R; |
| 19 |
| 20 import org.chromium.base.CalledByNative; |
| 21 import org.chromium.base.CommandLine; |
| 22 import org.chromium.base.JNINamespace; |
| 23 import org.chromium.base.Log; |
| 24 import org.chromium.base.TraceEvent; |
| 25 import org.chromium.base.VisibleForTesting; |
| 26 import org.chromium.chrome.browser.ChromeSwitches; |
| 27 import org.chromium.chrome.browser.ChromiumApplication; |
| 28 import org.chromium.chrome.browser.compositor.layouts.Layout; |
| 29 import org.chromium.chrome.browser.compositor.layouts.Layout.SizingFlags; |
| 30 import org.chromium.chrome.browser.compositor.layouts.LayoutProvider; |
| 31 import org.chromium.chrome.browser.compositor.layouts.LayoutRenderHost; |
| 32 import org.chromium.chrome.browser.compositor.layouts.components.LayoutTab; |
| 33 import org.chromium.chrome.browser.compositor.layouts.content.ContentOffsetProvi
der; |
| 34 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager; |
| 35 import org.chromium.chrome.browser.compositor.resources.StaticResourcePreloads; |
| 36 import org.chromium.chrome.browser.compositor.scene_layer.SceneLayer; |
| 37 import org.chromium.chrome.browser.device.DeviceClassManager; |
| 38 import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager; |
| 39 import org.chromium.chrome.browser.tabmodel.TabModelBase; |
| 40 import org.chromium.content.browser.ContentReadbackHandler; |
| 41 import org.chromium.ui.base.DeviceFormFactor; |
| 42 import org.chromium.ui.base.WindowAndroid; |
| 43 import org.chromium.ui.resources.AndroidResourceType; |
| 44 import org.chromium.ui.resources.ResourceManager; |
| 45 |
| 46 /** |
| 47 * The is the {@link View} displaying the ui compositor results; including webpa
ges and tabswitcher. |
| 48 */ |
| 49 @JNINamespace("chrome::android") |
| 50 public class CompositorView |
| 51 extends SurfaceView implements ContentOffsetProvider, SurfaceHolder.Call
back2 { |
| 52 private static final String TAG = "CompositorView"; |
| 53 |
| 54 // Cache objects that should not be created every frame |
| 55 private final Rect mCacheViewport = new Rect(); |
| 56 private final Rect mCacheAppRect = new Rect(); |
| 57 private final Rect mCacheVisibleViewport = new Rect(); |
| 58 private final int[] mCacheViewPosition = new int[2]; |
| 59 |
| 60 private long mNativeCompositorView; |
| 61 private final LayoutRenderHost mRenderHost; |
| 62 private boolean mEnableTabletTabStack; |
| 63 private int mPreviousWindowTop = -1; |
| 64 |
| 65 private int mLastLayerCount; |
| 66 |
| 67 // Resource Management |
| 68 private ResourceManager mResourceManager; |
| 69 |
| 70 // Lazily populated as it is needed. |
| 71 private View mRootActivityView; |
| 72 private WindowAndroid mWindowAndroid; |
| 73 private LayerTitleCache mLayerTitleCache; |
| 74 private TabContentManager mTabContentManager; |
| 75 |
| 76 private View mRootView; |
| 77 private int mSurfaceWidth; |
| 78 private int mSurfaceHeight; |
| 79 private boolean mPreloadedResources; |
| 80 |
| 81 private ContentReadbackHandler mContentReadbackHandler; |
| 82 |
| 83 // The current SurfaceView pixel format. Defaults to OPAQUE. |
| 84 private int mCurrentPixelFormat = PixelFormat.OPAQUE; |
| 85 |
| 86 /** |
| 87 * Creates a {@link CompositorView}. This can be called only after the nativ
e library is |
| 88 * properly loaded. |
| 89 * @param c The Context to create this {@link CompositorView} in. |
| 90 * @param host The renderer host owning this view. |
| 91 */ |
| 92 public CompositorView(Context c, LayoutRenderHost host) { |
| 93 super(c); |
| 94 mRenderHost = host; |
| 95 resetFlags(); |
| 96 setVisibility(View.INVISIBLE); |
| 97 setZOrderMediaOverlay(true); |
| 98 mContentReadbackHandler = new ContentReadbackHandler() { |
| 99 @Override |
| 100 protected boolean readyForReadback() { |
| 101 return mNativeCompositorView != 0; |
| 102 } |
| 103 }; |
| 104 } |
| 105 |
| 106 /** |
| 107 * @param view The root view of the hierarchy. |
| 108 */ |
| 109 public void setRootView(View view) { |
| 110 mRootView = view; |
| 111 } |
| 112 |
| 113 /** |
| 114 * Reset the commandline flags. This gets called after we switch over to the |
| 115 * native command line. |
| 116 */ |
| 117 public void resetFlags() { |
| 118 CommandLine commandLine = CommandLine.getInstance(); |
| 119 mEnableTabletTabStack = commandLine.hasSwitch(ChromeSwitches.ENABLE_TABL
ET_TAB_STACK) |
| 120 && DeviceFormFactor.isTablet(getContext()); |
| 121 } |
| 122 |
| 123 @Override |
| 124 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
| 125 if (mRootView != null) { |
| 126 mRootView.getWindowVisibleDisplayFrame(mCacheAppRect); |
| 127 |
| 128 // Check whether the top position of the window has changed as we al
ways must |
| 129 // resize in that case to the specified height spec. On certain ver
sions of |
| 130 // Android when you change the top position (i.e. by leaving fullscr
een) and |
| 131 // do not shrink the SurfaceView, it will appear to be pinned to the
top of |
| 132 // the screen under the notification bar and all touch offsets will
be wrong |
| 133 // as well as a gap will appear at the bottom of the screen. |
| 134 int windowTop = mCacheAppRect.top; |
| 135 boolean topChanged = windowTop != mPreviousWindowTop; |
| 136 mPreviousWindowTop = windowTop; |
| 137 |
| 138 Activity activity = mWindowAndroid != null ? mWindowAndroid.getActiv
ity().get() : null; |
| 139 ChromiumApplication application = |
| 140 (ChromiumApplication) getContext().getApplicationContext(); |
| 141 boolean isMultiWindow = application.isMultiWindow(activity); |
| 142 |
| 143 // If the measured width is the same as the allowed width (i.e. the
orientation has |
| 144 // not changed) and multi-window mode is off, use the largest measur
ed height seen thus |
| 145 // far. This will prevent surface resizes as a result of showing th
e keyboard. |
| 146 if (!topChanged && !isMultiWindow |
| 147 && getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpe
c) |
| 148 && getMeasuredHeight() > MeasureSpec.getSize(heightMeasureSp
ec)) { |
| 149 heightMeasureSpec = |
| 150 MeasureSpec.makeMeasureSpec(getMeasuredHeight(), Measure
Spec.EXACTLY); |
| 151 } |
| 152 } |
| 153 super.onMeasure(widthMeasureSpec, heightMeasureSpec); |
| 154 } |
| 155 |
| 156 @Override |
| 157 protected void onLayout(boolean changed, int left, int top, int right, int b
ottom) { |
| 158 super.onLayout(changed, left, top, right, bottom); |
| 159 mRenderHost.onOverdrawBottomHeightChanged(getOverdrawBottomHeight()); |
| 160 } |
| 161 |
| 162 @Override |
| 163 protected void onDetachedFromWindow() { |
| 164 super.onDetachedFromWindow(); |
| 165 mPreviousWindowTop = -1; |
| 166 } |
| 167 |
| 168 /** |
| 169 * @return The content readback handler. |
| 170 */ |
| 171 public ContentReadbackHandler getContentReadbackHandler() { |
| 172 return mContentReadbackHandler; |
| 173 } |
| 174 |
| 175 /** |
| 176 * @return The ResourceManager. |
| 177 */ |
| 178 public ResourceManager getResourceManager() { |
| 179 return mResourceManager; |
| 180 } |
| 181 |
| 182 /** |
| 183 * @return The amount the surface view is overdrawing the window bounds. |
| 184 */ |
| 185 public int getOverdrawBottomHeight() { |
| 186 if (mRootActivityView == null) { |
| 187 mRootActivityView = mRootView.findViewById(android.R.id.content); |
| 188 } |
| 189 if (mRootActivityView != null) { |
| 190 int compositorHeight = getHeight(); |
| 191 int rootViewHeight = mRootActivityView.getHeight(); |
| 192 return Math.max(0, compositorHeight - rootViewHeight); |
| 193 } |
| 194 return 0; |
| 195 } |
| 196 |
| 197 /** |
| 198 * Should be called for cleanup when the CompositorView instance is no longe
r used. |
| 199 */ |
| 200 public void shutDown() { |
| 201 getHolder().removeCallback(this); |
| 202 mContentReadbackHandler.destroy(); |
| 203 mContentReadbackHandler = null; |
| 204 if (mNativeCompositorView != 0) nativeDestroy(mNativeCompositorView); |
| 205 mNativeCompositorView = 0; |
| 206 } |
| 207 |
| 208 /** |
| 209 * Initializes the {@link CompositorView}'s native parts (e.g. the rendering
parts). |
| 210 * @param lowMemDevice If this is a low memory device. |
| 211 * @param windowAndroid A {@link WindowAndroid} instance. |
| 212 * @param layerTitleCache A {@link LayerTitleCache} instance. |
| 213 * @param tabContentManager A {@link TabContentManager} instance. |
| 214 */ |
| 215 public void initNativeCompositor(boolean lowMemDevice, WindowAndroid windowA
ndroid, |
| 216 LayerTitleCache layerTitleCache, TabContentManager tabContentManager
) { |
| 217 mWindowAndroid = windowAndroid; |
| 218 mLayerTitleCache = layerTitleCache; |
| 219 mTabContentManager = tabContentManager; |
| 220 |
| 221 mNativeCompositorView = |
| 222 nativeInit(lowMemDevice, getResources().getColor(R.color.tab_swi
tcher_background), |
| 223 windowAndroid.getNativePointer(), layerTitleCache, tabCo
ntentManager); |
| 224 |
| 225 assert !getHolder().getSurface().isValid() |
| 226 : "Surface created before native library loaded."; |
| 227 getHolder().addCallback(this); |
| 228 |
| 229 // Cover the black surface before it has valid content. |
| 230 setBackgroundColor(Color.WHITE); |
| 231 setVisibility(View.VISIBLE); |
| 232 |
| 233 // Grab the Resource Manager |
| 234 mResourceManager = nativeGetResourceManager(mNativeCompositorView); |
| 235 |
| 236 mContentReadbackHandler.initNativeContentReadbackHandler(); |
| 237 } |
| 238 |
| 239 @Override |
| 240 public boolean onTouchEvent(MotionEvent e) { |
| 241 return super.onTouchEvent(e); |
| 242 } |
| 243 |
| 244 /** |
| 245 * Enables/disables overlay video mode. Affects alpha blending on this view. |
| 246 * @param enabled Whether to enter or leave overlay video mode. |
| 247 */ |
| 248 public void setOverlayVideoMode(boolean enabled) { |
| 249 mCurrentPixelFormat = enabled ? PixelFormat.TRANSLUCENT : PixelFormat.OP
AQUE; |
| 250 getHolder().setFormat(mCurrentPixelFormat); |
| 251 nativeSetOverlayVideoMode(mNativeCompositorView, enabled); |
| 252 } |
| 253 |
| 254 @Override |
| 255 public void surfaceChanged(SurfaceHolder holder, int format, int width, int
height) { |
| 256 if (mNativeCompositorView == 0) return; |
| 257 nativeSurfaceChanged(mNativeCompositorView, format, width, height, holde
r.getSurface()); |
| 258 mRenderHost.onPhysicalBackingSizeChanged(width, height); |
| 259 mSurfaceWidth = width; |
| 260 mSurfaceHeight = height; |
| 261 } |
| 262 |
| 263 @Override |
| 264 public void surfaceCreated(SurfaceHolder holder) { |
| 265 if (mNativeCompositorView == 0) return; |
| 266 nativeSurfaceCreated(mNativeCompositorView); |
| 267 mRenderHost.onSurfaceCreated(); |
| 268 } |
| 269 |
| 270 @Override |
| 271 public void surfaceDestroyed(SurfaceHolder holder) { |
| 272 if (mNativeCompositorView == 0) return; |
| 273 nativeSurfaceDestroyed(mNativeCompositorView); |
| 274 } |
| 275 |
| 276 @Override |
| 277 public void surfaceRedrawNeeded(SurfaceHolder holder) {} |
| 278 |
| 279 @CalledByNative |
| 280 private void onCompositorLayout() { |
| 281 mRenderHost.onCompositorLayout(); |
| 282 } |
| 283 |
| 284 /* |
| 285 * On JellyBean there is a known bug where a crashed producer process |
| 286 * (i.e. GPU process) does not properly disconnect from the BufferQueue, |
| 287 * which means we won't be able to reconnect to it ever again. |
| 288 * This workaround forces the creation of a new Surface. |
| 289 */ |
| 290 @CalledByNative |
| 291 private void onJellyBeanSurfaceDisconnectWorkaround(boolean inOverlayMode) { |
| 292 // There is a bug in JellyBean because of which we will not be able to |
| 293 // reconnect to the existing Surface after we launch a new GPU process. |
| 294 // We simply trick the JB Android code to allocate a new Surface. |
| 295 // It does a strict comparison between the current format and the reques
ted |
| 296 // one, even if they are the same in practice. Furthermore, the format |
| 297 // does not matter here since the producer-side EGL config overwrites it |
| 298 // (but transparency might matter). |
| 299 switch (mCurrentPixelFormat) { |
| 300 case PixelFormat.OPAQUE: |
| 301 mCurrentPixelFormat = PixelFormat.RGBA_8888; |
| 302 break; |
| 303 case PixelFormat.RGBA_8888: |
| 304 mCurrentPixelFormat = inOverlayMode |
| 305 ? PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; |
| 306 break; |
| 307 case PixelFormat.TRANSLUCENT: |
| 308 mCurrentPixelFormat = PixelFormat.RGBA_8888; |
| 309 break; |
| 310 default: |
| 311 assert false; |
| 312 Log.e(TAG, "Unknown current pixel format."); |
| 313 } |
| 314 getHolder().setFormat(mCurrentPixelFormat); |
| 315 } |
| 316 |
| 317 /** |
| 318 * Request compositor view to render a frame. |
| 319 */ |
| 320 public void requestRender() { |
| 321 if (mNativeCompositorView != 0) nativeSetNeedsComposite(mNativeComposito
rView); |
| 322 } |
| 323 |
| 324 @CalledByNative |
| 325 private void onSwapBuffersCompleted(int pendingSwapBuffersCount) { |
| 326 // Clear the color used to cover the uninitialized surface. |
| 327 if (getBackground() != null) { |
| 328 post(new Runnable() { |
| 329 @Override |
| 330 public void run() { |
| 331 setBackgroundResource(0); |
| 332 } |
| 333 }); |
| 334 } |
| 335 |
| 336 mRenderHost.onSwapBuffersCompleted(pendingSwapBuffersCount); |
| 337 } |
| 338 |
| 339 private void updateToolbarLayer(LayoutProvider provider, boolean forRotation
) { |
| 340 if (forRotation || !DeviceClassManager.enableFullscreen()) return; |
| 341 |
| 342 ChromeFullscreenManager fullscreenManager = provider.getFullscreenManage
r(); |
| 343 if (fullscreenManager == null) return; |
| 344 |
| 345 float offset = fullscreenManager.getControlOffset(); |
| 346 boolean useTexture = fullscreenManager.drawControlsAsTexture() || offset
== 0; |
| 347 |
| 348 float dpToPx = getContext().getResources().getDisplayMetrics().density; |
| 349 float layoutOffsetDp = provider.getActiveLayout().getTopControlsOffset(o
ffset / dpToPx); |
| 350 boolean validLayoutOffset = !Float.isNaN(layoutOffsetDp); |
| 351 |
| 352 if (validLayoutOffset) { |
| 353 offset = layoutOffsetDp * dpToPx; |
| 354 useTexture = true; |
| 355 } |
| 356 |
| 357 fullscreenManager.setHideTopControlsAndroidView(validLayoutOffset && lay
outOffsetDp != 0.f); |
| 358 |
| 359 int flags = provider.getActiveLayout().getSizingFlags(); |
| 360 if ((flags & SizingFlags.REQUIRE_FULLSCREEN_SIZE) != 0 |
| 361 && (flags & SizingFlags.ALLOW_TOOLBAR_HIDE) == 0 |
| 362 && (flags & SizingFlags.ALLOW_TOOLBAR_ANIMATE) == 0) { |
| 363 useTexture = false; |
| 364 } |
| 365 |
| 366 nativeUpdateToolbarLayer( |
| 367 mNativeCompositorView, R.id.control_container, R.id.progress, of
fset, useTexture); |
| 368 } |
| 369 |
| 370 /** |
| 371 * Converts the layout into compositor layers. This is to be called on every
frame the layout |
| 372 * is changing. |
| 373 * @param provider Provides the layout to be rendered. |
| 374 * @param forRotation Whether or not this is a special draw durin
g a rotation. |
| 375 */ |
| 376 public void finalizeLayers(final LayoutProvider provider, boolean forRotatio
n) { |
| 377 TraceEvent.begin("CompositorView:finalizeLayers"); |
| 378 Layout layout = provider.getActiveLayout(); |
| 379 if (layout == null || mNativeCompositorView == 0) { |
| 380 TraceEvent.end("CompositorView:finalizeLayers"); |
| 381 return; |
| 382 } |
| 383 |
| 384 if (!mPreloadedResources) { |
| 385 // Attempt to prefetch any necessary resources |
| 386 mResourceManager.preloadResources(AndroidResourceType.STATIC, |
| 387 StaticResourcePreloads.getSynchronousResources(getContext())
, |
| 388 StaticResourcePreloads.getAsynchronousResources()); |
| 389 mPreloadedResources = true; |
| 390 } |
| 391 |
| 392 // IMPORTANT: Do not do anything that impacts the compositor layer tree
before this line. |
| 393 // If you do, you could inadvertently trigger follow up renders. For fu
rther information |
| 394 // see dtrainor@, tedchoc@, or klobag@. |
| 395 |
| 396 // TODO(jscholler): change 1.0f to dpToPx once the native part is fully
supporting dp. |
| 397 mRenderHost.getVisibleViewport(mCacheVisibleViewport); |
| 398 provider.getViewportPixel(mCacheViewport); |
| 399 nativeSetLayoutViewport(mNativeCompositorView, mCacheViewport.left, mCac
heViewport.top, |
| 400 mCacheViewport.width(), mCacheViewport.height(), mCacheVisibleVi
ewport.left, |
| 401 mCacheVisibleViewport.top, mRenderHost.getCurrentOverdrawBottomH
eight(), 1.0f); |
| 402 |
| 403 mCacheVisibleViewport.right = mCacheVisibleViewport.left + mSurfaceWidth
; |
| 404 mCacheVisibleViewport.bottom = mCacheVisibleViewport.top |
| 405 + Math.max(mSurfaceHeight - mRenderHost.getCurrentOverdrawBottom
Height(), 0); |
| 406 |
| 407 // TODO(changwan): move to treeprovider. |
| 408 updateToolbarLayer(provider, forRotation); |
| 409 |
| 410 SceneLayer sceneLayer = |
| 411 layout.getUpdatedSceneLayer(mCacheViewport, mCacheVisibleViewpor
t, mLayerTitleCache, |
| 412 mTabContentManager, mResourceManager, provider.getFullsc
reenManager()); |
| 413 |
| 414 nativeSetSceneLayer(mNativeCompositorView, sceneLayer); |
| 415 |
| 416 final LayoutTab[] tabs = layout.getLayoutTabsToRender(); |
| 417 final int tabsCount = tabs != null ? tabs.length : 0; |
| 418 mLastLayerCount = tabsCount; |
| 419 TabModelBase.flushActualTabSwitchLatencyMetric(); |
| 420 nativeFinalizeLayers(mNativeCompositorView); |
| 421 TraceEvent.end("CompositorView:finalizeLayers"); |
| 422 } |
| 423 |
| 424 /** |
| 425 * @return The number of layer put the last frame. |
| 426 */ |
| 427 @VisibleForTesting |
| 428 public int getLastLayerCount() { |
| 429 return mLastLayerCount; |
| 430 } |
| 431 |
| 432 @Override |
| 433 public int getOverlayTranslateY() { |
| 434 return mRenderHost.areTopControlsPermanentlyHidden() |
| 435 ? mRenderHost.getTopControlsHeightPixels() |
| 436 : mRenderHost.getVisibleViewport(mCacheVisibleViewport).top; |
| 437 } |
| 438 |
| 439 // Implemented in native |
| 440 private native long nativeInit(boolean lowMemDevice, int emptyColor, long na
tiveWindowAndroid, |
| 441 LayerTitleCache layerTitleCache, TabContentManager tabContentManager
); |
| 442 private native void nativeDestroy(long nativeCompositorView); |
| 443 private native ResourceManager nativeGetResourceManager(long nativeComposito
rView); |
| 444 private native void nativeSurfaceCreated(long nativeCompositorView); |
| 445 private native void nativeSurfaceDestroyed(long nativeCompositorView); |
| 446 private native void nativeSurfaceChanged( |
| 447 long nativeCompositorView, int format, int width, int height, Surfac
e surface); |
| 448 private native void nativeFinalizeLayers(long nativeCompositorView); |
| 449 private native void nativeSetNeedsComposite(long nativeCompositorView); |
| 450 private native void nativeSetLayoutViewport(long nativeCompositorView, float
x, float y, |
| 451 float width, float height, float visibleXOffset, float visibleYOffse
t, |
| 452 float overdrawBottomHeight, float dpToPixel); |
| 453 private native void nativeUpdateToolbarLayer(long nativeCompositorView, int
resourceId, |
| 454 int progressResourceId, float topOffset, boolean visible); |
| 455 private native void nativeSetOverlayVideoMode(long nativeCompositorView, boo
lean enabled); |
| 456 private native void nativeSetSceneLayer(long nativeCompositorView, SceneLaye
r sceneLayer); |
| 457 } |
OLD | NEW |