Index: chrome/android/java_staging/src/org/chromium/chrome/browser/compositor/CompositorView.java |
diff --git a/chrome/android/java_staging/src/org/chromium/chrome/browser/compositor/CompositorView.java b/chrome/android/java_staging/src/org/chromium/chrome/browser/compositor/CompositorView.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..78c220cc62a0ad4bbb52ba89650471ecb677f196 |
--- /dev/null |
+++ b/chrome/android/java_staging/src/org/chromium/chrome/browser/compositor/CompositorView.java |
@@ -0,0 +1,457 @@ |
+// Copyright 2015 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.compositor; |
+ |
+import android.app.Activity; |
+import android.content.Context; |
+import android.graphics.Color; |
+import android.graphics.PixelFormat; |
+import android.graphics.Rect; |
+import android.view.MotionEvent; |
+import android.view.Surface; |
+import android.view.SurfaceHolder; |
+import android.view.SurfaceView; |
+import android.view.View; |
+ |
+import com.google.android.apps.chrome.R; |
+ |
+import org.chromium.base.CalledByNative; |
+import org.chromium.base.CommandLine; |
+import org.chromium.base.JNINamespace; |
+import org.chromium.base.Log; |
+import org.chromium.base.TraceEvent; |
+import org.chromium.base.VisibleForTesting; |
+import org.chromium.chrome.browser.ChromeSwitches; |
+import org.chromium.chrome.browser.ChromiumApplication; |
+import org.chromium.chrome.browser.compositor.layouts.Layout; |
+import org.chromium.chrome.browser.compositor.layouts.Layout.SizingFlags; |
+import org.chromium.chrome.browser.compositor.layouts.LayoutProvider; |
+import org.chromium.chrome.browser.compositor.layouts.LayoutRenderHost; |
+import org.chromium.chrome.browser.compositor.layouts.components.LayoutTab; |
+import org.chromium.chrome.browser.compositor.layouts.content.ContentOffsetProvider; |
+import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager; |
+import org.chromium.chrome.browser.compositor.resources.StaticResourcePreloads; |
+import org.chromium.chrome.browser.compositor.scene_layer.SceneLayer; |
+import org.chromium.chrome.browser.device.DeviceClassManager; |
+import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager; |
+import org.chromium.chrome.browser.tabmodel.TabModelBase; |
+import org.chromium.content.browser.ContentReadbackHandler; |
+import org.chromium.ui.base.DeviceFormFactor; |
+import org.chromium.ui.base.WindowAndroid; |
+import org.chromium.ui.resources.AndroidResourceType; |
+import org.chromium.ui.resources.ResourceManager; |
+ |
+/** |
+ * The is the {@link View} displaying the ui compositor results; including webpages and tabswitcher. |
+ */ |
+@JNINamespace("chrome::android") |
+public class CompositorView |
+ extends SurfaceView implements ContentOffsetProvider, SurfaceHolder.Callback2 { |
+ private static final String TAG = "CompositorView"; |
+ |
+ // Cache objects that should not be created every frame |
+ private final Rect mCacheViewport = new Rect(); |
+ private final Rect mCacheAppRect = new Rect(); |
+ private final Rect mCacheVisibleViewport = new Rect(); |
+ private final int[] mCacheViewPosition = new int[2]; |
+ |
+ private long mNativeCompositorView; |
+ private final LayoutRenderHost mRenderHost; |
+ private boolean mEnableTabletTabStack; |
+ private int mPreviousWindowTop = -1; |
+ |
+ private int mLastLayerCount; |
+ |
+ // Resource Management |
+ private ResourceManager mResourceManager; |
+ |
+ // Lazily populated as it is needed. |
+ private View mRootActivityView; |
+ private WindowAndroid mWindowAndroid; |
+ private LayerTitleCache mLayerTitleCache; |
+ private TabContentManager mTabContentManager; |
+ |
+ private View mRootView; |
+ private int mSurfaceWidth; |
+ private int mSurfaceHeight; |
+ private boolean mPreloadedResources; |
+ |
+ private ContentReadbackHandler mContentReadbackHandler; |
+ |
+ // The current SurfaceView pixel format. Defaults to OPAQUE. |
+ private int mCurrentPixelFormat = PixelFormat.OPAQUE; |
+ |
+ /** |
+ * Creates a {@link CompositorView}. This can be called only after the native library is |
+ * properly loaded. |
+ * @param c The Context to create this {@link CompositorView} in. |
+ * @param host The renderer host owning this view. |
+ */ |
+ public CompositorView(Context c, LayoutRenderHost host) { |
+ super(c); |
+ mRenderHost = host; |
+ resetFlags(); |
+ setVisibility(View.INVISIBLE); |
+ setZOrderMediaOverlay(true); |
+ mContentReadbackHandler = new ContentReadbackHandler() { |
+ @Override |
+ protected boolean readyForReadback() { |
+ return mNativeCompositorView != 0; |
+ } |
+ }; |
+ } |
+ |
+ /** |
+ * @param view The root view of the hierarchy. |
+ */ |
+ public void setRootView(View view) { |
+ mRootView = view; |
+ } |
+ |
+ /** |
+ * Reset the commandline flags. This gets called after we switch over to the |
+ * native command line. |
+ */ |
+ public void resetFlags() { |
+ CommandLine commandLine = CommandLine.getInstance(); |
+ mEnableTabletTabStack = commandLine.hasSwitch(ChromeSwitches.ENABLE_TABLET_TAB_STACK) |
+ && DeviceFormFactor.isTablet(getContext()); |
+ } |
+ |
+ @Override |
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
+ if (mRootView != null) { |
+ mRootView.getWindowVisibleDisplayFrame(mCacheAppRect); |
+ |
+ // Check whether the top position of the window has changed as we always must |
+ // resize in that case to the specified height spec. On certain versions of |
+ // Android when you change the top position (i.e. by leaving fullscreen) and |
+ // do not shrink the SurfaceView, it will appear to be pinned to the top of |
+ // the screen under the notification bar and all touch offsets will be wrong |
+ // as well as a gap will appear at the bottom of the screen. |
+ int windowTop = mCacheAppRect.top; |
+ boolean topChanged = windowTop != mPreviousWindowTop; |
+ mPreviousWindowTop = windowTop; |
+ |
+ Activity activity = mWindowAndroid != null ? mWindowAndroid.getActivity().get() : null; |
+ ChromiumApplication application = |
+ (ChromiumApplication) getContext().getApplicationContext(); |
+ boolean isMultiWindow = application.isMultiWindow(activity); |
+ |
+ // If the measured width is the same as the allowed width (i.e. the orientation has |
+ // not changed) and multi-window mode is off, use the largest measured height seen thus |
+ // far. This will prevent surface resizes as a result of showing the keyboard. |
+ if (!topChanged && !isMultiWindow |
+ && getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec) |
+ && getMeasuredHeight() > MeasureSpec.getSize(heightMeasureSpec)) { |
+ heightMeasureSpec = |
+ MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY); |
+ } |
+ } |
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec); |
+ } |
+ |
+ @Override |
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) { |
+ super.onLayout(changed, left, top, right, bottom); |
+ mRenderHost.onOverdrawBottomHeightChanged(getOverdrawBottomHeight()); |
+ } |
+ |
+ @Override |
+ protected void onDetachedFromWindow() { |
+ super.onDetachedFromWindow(); |
+ mPreviousWindowTop = -1; |
+ } |
+ |
+ /** |
+ * @return The content readback handler. |
+ */ |
+ public ContentReadbackHandler getContentReadbackHandler() { |
+ return mContentReadbackHandler; |
+ } |
+ |
+ /** |
+ * @return The ResourceManager. |
+ */ |
+ public ResourceManager getResourceManager() { |
+ return mResourceManager; |
+ } |
+ |
+ /** |
+ * @return The amount the surface view is overdrawing the window bounds. |
+ */ |
+ public int getOverdrawBottomHeight() { |
+ if (mRootActivityView == null) { |
+ mRootActivityView = mRootView.findViewById(android.R.id.content); |
+ } |
+ if (mRootActivityView != null) { |
+ int compositorHeight = getHeight(); |
+ int rootViewHeight = mRootActivityView.getHeight(); |
+ return Math.max(0, compositorHeight - rootViewHeight); |
+ } |
+ return 0; |
+ } |
+ |
+ /** |
+ * Should be called for cleanup when the CompositorView instance is no longer used. |
+ */ |
+ public void shutDown() { |
+ getHolder().removeCallback(this); |
+ mContentReadbackHandler.destroy(); |
+ mContentReadbackHandler = null; |
+ if (mNativeCompositorView != 0) nativeDestroy(mNativeCompositorView); |
+ mNativeCompositorView = 0; |
+ } |
+ |
+ /** |
+ * Initializes the {@link CompositorView}'s native parts (e.g. the rendering parts). |
+ * @param lowMemDevice If this is a low memory device. |
+ * @param windowAndroid A {@link WindowAndroid} instance. |
+ * @param layerTitleCache A {@link LayerTitleCache} instance. |
+ * @param tabContentManager A {@link TabContentManager} instance. |
+ */ |
+ public void initNativeCompositor(boolean lowMemDevice, WindowAndroid windowAndroid, |
+ LayerTitleCache layerTitleCache, TabContentManager tabContentManager) { |
+ mWindowAndroid = windowAndroid; |
+ mLayerTitleCache = layerTitleCache; |
+ mTabContentManager = tabContentManager; |
+ |
+ mNativeCompositorView = |
+ nativeInit(lowMemDevice, getResources().getColor(R.color.tab_switcher_background), |
+ windowAndroid.getNativePointer(), layerTitleCache, tabContentManager); |
+ |
+ assert !getHolder().getSurface().isValid() |
+ : "Surface created before native library loaded."; |
+ getHolder().addCallback(this); |
+ |
+ // Cover the black surface before it has valid content. |
+ setBackgroundColor(Color.WHITE); |
+ setVisibility(View.VISIBLE); |
+ |
+ // Grab the Resource Manager |
+ mResourceManager = nativeGetResourceManager(mNativeCompositorView); |
+ |
+ mContentReadbackHandler.initNativeContentReadbackHandler(); |
+ } |
+ |
+ @Override |
+ public boolean onTouchEvent(MotionEvent e) { |
+ return super.onTouchEvent(e); |
+ } |
+ |
+ /** |
+ * Enables/disables overlay video mode. Affects alpha blending on this view. |
+ * @param enabled Whether to enter or leave overlay video mode. |
+ */ |
+ public void setOverlayVideoMode(boolean enabled) { |
+ mCurrentPixelFormat = enabled ? PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; |
+ getHolder().setFormat(mCurrentPixelFormat); |
+ nativeSetOverlayVideoMode(mNativeCompositorView, enabled); |
+ } |
+ |
+ @Override |
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { |
+ if (mNativeCompositorView == 0) return; |
+ nativeSurfaceChanged(mNativeCompositorView, format, width, height, holder.getSurface()); |
+ mRenderHost.onPhysicalBackingSizeChanged(width, height); |
+ mSurfaceWidth = width; |
+ mSurfaceHeight = height; |
+ } |
+ |
+ @Override |
+ public void surfaceCreated(SurfaceHolder holder) { |
+ if (mNativeCompositorView == 0) return; |
+ nativeSurfaceCreated(mNativeCompositorView); |
+ mRenderHost.onSurfaceCreated(); |
+ } |
+ |
+ @Override |
+ public void surfaceDestroyed(SurfaceHolder holder) { |
+ if (mNativeCompositorView == 0) return; |
+ nativeSurfaceDestroyed(mNativeCompositorView); |
+ } |
+ |
+ @Override |
+ public void surfaceRedrawNeeded(SurfaceHolder holder) {} |
+ |
+ @CalledByNative |
+ private void onCompositorLayout() { |
+ mRenderHost.onCompositorLayout(); |
+ } |
+ |
+ /* |
+ * On JellyBean there is a known bug where a crashed producer process |
+ * (i.e. GPU process) does not properly disconnect from the BufferQueue, |
+ * which means we won't be able to reconnect to it ever again. |
+ * This workaround forces the creation of a new Surface. |
+ */ |
+ @CalledByNative |
+ private void onJellyBeanSurfaceDisconnectWorkaround(boolean inOverlayMode) { |
+ // There is a bug in JellyBean because of which we will not be able to |
+ // reconnect to the existing Surface after we launch a new GPU process. |
+ // We simply trick the JB Android code to allocate a new Surface. |
+ // It does a strict comparison between the current format and the requested |
+ // one, even if they are the same in practice. Furthermore, the format |
+ // does not matter here since the producer-side EGL config overwrites it |
+ // (but transparency might matter). |
+ switch (mCurrentPixelFormat) { |
+ case PixelFormat.OPAQUE: |
+ mCurrentPixelFormat = PixelFormat.RGBA_8888; |
+ break; |
+ case PixelFormat.RGBA_8888: |
+ mCurrentPixelFormat = inOverlayMode |
+ ? PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; |
+ break; |
+ case PixelFormat.TRANSLUCENT: |
+ mCurrentPixelFormat = PixelFormat.RGBA_8888; |
+ break; |
+ default: |
+ assert false; |
+ Log.e(TAG, "Unknown current pixel format."); |
+ } |
+ getHolder().setFormat(mCurrentPixelFormat); |
+ } |
+ |
+ /** |
+ * Request compositor view to render a frame. |
+ */ |
+ public void requestRender() { |
+ if (mNativeCompositorView != 0) nativeSetNeedsComposite(mNativeCompositorView); |
+ } |
+ |
+ @CalledByNative |
+ private void onSwapBuffersCompleted(int pendingSwapBuffersCount) { |
+ // Clear the color used to cover the uninitialized surface. |
+ if (getBackground() != null) { |
+ post(new Runnable() { |
+ @Override |
+ public void run() { |
+ setBackgroundResource(0); |
+ } |
+ }); |
+ } |
+ |
+ mRenderHost.onSwapBuffersCompleted(pendingSwapBuffersCount); |
+ } |
+ |
+ private void updateToolbarLayer(LayoutProvider provider, boolean forRotation) { |
+ if (forRotation || !DeviceClassManager.enableFullscreen()) return; |
+ |
+ ChromeFullscreenManager fullscreenManager = provider.getFullscreenManager(); |
+ if (fullscreenManager == null) return; |
+ |
+ float offset = fullscreenManager.getControlOffset(); |
+ boolean useTexture = fullscreenManager.drawControlsAsTexture() || offset == 0; |
+ |
+ float dpToPx = getContext().getResources().getDisplayMetrics().density; |
+ float layoutOffsetDp = provider.getActiveLayout().getTopControlsOffset(offset / dpToPx); |
+ boolean validLayoutOffset = !Float.isNaN(layoutOffsetDp); |
+ |
+ if (validLayoutOffset) { |
+ offset = layoutOffsetDp * dpToPx; |
+ useTexture = true; |
+ } |
+ |
+ fullscreenManager.setHideTopControlsAndroidView(validLayoutOffset && layoutOffsetDp != 0.f); |
+ |
+ int flags = provider.getActiveLayout().getSizingFlags(); |
+ if ((flags & SizingFlags.REQUIRE_FULLSCREEN_SIZE) != 0 |
+ && (flags & SizingFlags.ALLOW_TOOLBAR_HIDE) == 0 |
+ && (flags & SizingFlags.ALLOW_TOOLBAR_ANIMATE) == 0) { |
+ useTexture = false; |
+ } |
+ |
+ nativeUpdateToolbarLayer( |
+ mNativeCompositorView, R.id.control_container, R.id.progress, offset, useTexture); |
+ } |
+ |
+ /** |
+ * Converts the layout into compositor layers. This is to be called on every frame the layout |
+ * is changing. |
+ * @param provider Provides the layout to be rendered. |
+ * @param forRotation Whether or not this is a special draw during a rotation. |
+ */ |
+ public void finalizeLayers(final LayoutProvider provider, boolean forRotation) { |
+ TraceEvent.begin("CompositorView:finalizeLayers"); |
+ Layout layout = provider.getActiveLayout(); |
+ if (layout == null || mNativeCompositorView == 0) { |
+ TraceEvent.end("CompositorView:finalizeLayers"); |
+ return; |
+ } |
+ |
+ if (!mPreloadedResources) { |
+ // Attempt to prefetch any necessary resources |
+ mResourceManager.preloadResources(AndroidResourceType.STATIC, |
+ StaticResourcePreloads.getSynchronousResources(getContext()), |
+ StaticResourcePreloads.getAsynchronousResources()); |
+ mPreloadedResources = true; |
+ } |
+ |
+ // IMPORTANT: Do not do anything that impacts the compositor layer tree before this line. |
+ // If you do, you could inadvertently trigger follow up renders. For further information |
+ // see dtrainor@, tedchoc@, or klobag@. |
+ |
+ // TODO(jscholler): change 1.0f to dpToPx once the native part is fully supporting dp. |
+ mRenderHost.getVisibleViewport(mCacheVisibleViewport); |
+ provider.getViewportPixel(mCacheViewport); |
+ nativeSetLayoutViewport(mNativeCompositorView, mCacheViewport.left, mCacheViewport.top, |
+ mCacheViewport.width(), mCacheViewport.height(), mCacheVisibleViewport.left, |
+ mCacheVisibleViewport.top, mRenderHost.getCurrentOverdrawBottomHeight(), 1.0f); |
+ |
+ mCacheVisibleViewport.right = mCacheVisibleViewport.left + mSurfaceWidth; |
+ mCacheVisibleViewport.bottom = mCacheVisibleViewport.top |
+ + Math.max(mSurfaceHeight - mRenderHost.getCurrentOverdrawBottomHeight(), 0); |
+ |
+ // TODO(changwan): move to treeprovider. |
+ updateToolbarLayer(provider, forRotation); |
+ |
+ SceneLayer sceneLayer = |
+ layout.getUpdatedSceneLayer(mCacheViewport, mCacheVisibleViewport, mLayerTitleCache, |
+ mTabContentManager, mResourceManager, provider.getFullscreenManager()); |
+ |
+ nativeSetSceneLayer(mNativeCompositorView, sceneLayer); |
+ |
+ final LayoutTab[] tabs = layout.getLayoutTabsToRender(); |
+ final int tabsCount = tabs != null ? tabs.length : 0; |
+ mLastLayerCount = tabsCount; |
+ TabModelBase.flushActualTabSwitchLatencyMetric(); |
+ nativeFinalizeLayers(mNativeCompositorView); |
+ TraceEvent.end("CompositorView:finalizeLayers"); |
+ } |
+ |
+ /** |
+ * @return The number of layer put the last frame. |
+ */ |
+ @VisibleForTesting |
+ public int getLastLayerCount() { |
+ return mLastLayerCount; |
+ } |
+ |
+ @Override |
+ public int getOverlayTranslateY() { |
+ return mRenderHost.areTopControlsPermanentlyHidden() |
+ ? mRenderHost.getTopControlsHeightPixels() |
+ : mRenderHost.getVisibleViewport(mCacheVisibleViewport).top; |
+ } |
+ |
+ // Implemented in native |
+ private native long nativeInit(boolean lowMemDevice, int emptyColor, long nativeWindowAndroid, |
+ LayerTitleCache layerTitleCache, TabContentManager tabContentManager); |
+ private native void nativeDestroy(long nativeCompositorView); |
+ private native ResourceManager nativeGetResourceManager(long nativeCompositorView); |
+ private native void nativeSurfaceCreated(long nativeCompositorView); |
+ private native void nativeSurfaceDestroyed(long nativeCompositorView); |
+ private native void nativeSurfaceChanged( |
+ long nativeCompositorView, int format, int width, int height, Surface surface); |
+ private native void nativeFinalizeLayers(long nativeCompositorView); |
+ private native void nativeSetNeedsComposite(long nativeCompositorView); |
+ private native void nativeSetLayoutViewport(long nativeCompositorView, float x, float y, |
+ float width, float height, float visibleXOffset, float visibleYOffset, |
+ float overdrawBottomHeight, float dpToPixel); |
+ private native void nativeUpdateToolbarLayer(long nativeCompositorView, int resourceId, |
+ int progressResourceId, float topOffset, boolean visible); |
+ private native void nativeSetOverlayVideoMode(long nativeCompositorView, boolean enabled); |
+ private native void nativeSetSceneLayer(long nativeCompositorView, SceneLayer sceneLayer); |
+} |