| // 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 org.chromium.base.ApiCompatibilityUtils; |
| import org.chromium.base.CommandLine; |
| import org.chromium.base.Log; |
| import org.chromium.base.TraceEvent; |
| import org.chromium.base.VisibleForTesting; |
| import org.chromium.base.annotations.CalledByNative; |
| import org.chromium.base.annotations.JNINamespace; |
| import org.chromium.chrome.R; |
| import org.chromium.chrome.browser.ChromeSwitches; |
| 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.externalnav.IntentWithGesturesHandler; |
| import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager; |
| import org.chromium.chrome.browser.multiwindow.MultiWindowUtils; |
| import org.chromium.chrome.browser.tabmodel.TabModelImpl; |
| import org.chromium.chrome.browser.widget.ClipDrawableProgressBar.DrawingInfo; |
| 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; |
| |
| // 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); |
| } |
| |
| /** |
| * @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; |
| boolean isMultiWindow = MultiWindowUtils.getInstance().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); |
| } |
| |
| @Override |
| protected void onDetachedFromWindow() { |
| super.onDetachedFromWindow(); |
| mPreviousWindowTop = -1; |
| } |
| |
| /** |
| * @return The ResourceManager. |
| */ |
| public ResourceManager getResourceManager() { |
| return mResourceManager; |
| } |
| |
| /** |
| * Should be called for cleanup when the CompositorView instance is no longer used. |
| */ |
| public void shutDown() { |
| getHolder().removeCallback(this); |
| 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, |
| ApiCompatibilityUtils.getColor(getResources(), 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); |
| } |
| |
| @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) {} |
| |
| @Override |
| public void onWindowVisibilityChanged(int visibility) { |
| super.onWindowVisibilityChanged(visibility); |
| if (mWindowAndroid == null) return; |
| if (visibility == View.GONE) { |
| mWindowAndroid.onVisibilityChanged(false); |
| } else if (visibility == View.VISIBLE) { |
| mWindowAndroid.onVisibilityChanged(true); |
| } |
| IntentWithGesturesHandler.getInstance().clear(); |
| } |
| |
| @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, |
| final DrawingInfo progressBarDrawingInfo) { |
| if (forRotation || !DeviceClassManager.enableFullscreen()) return; |
| |
| ChromeFullscreenManager fullscreenManager = provider.getFullscreenManager(); |
| if (fullscreenManager == null) return; |
| |
| float offset = fullscreenManager.getControlOffset(); |
| boolean forceHideTopControlsAndroidView = |
| provider.getActiveLayout().forceHideTopControlsAndroidView(); |
| boolean useTexture = fullscreenManager.drawControlsAsTexture() || offset == 0 |
| || forceHideTopControlsAndroidView; |
| |
| 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(forceHideTopControlsAndroidView |
| || (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, |
| mRenderHost.getTopControlsBackgroundColor(), R.drawable.textbox, |
| mRenderHost.getTopControlsUrlBarAlpha(), offset, |
| provider.getActiveLayout().getToolbarBrightness(), |
| useTexture, forceHideTopControlsAndroidView); |
| |
| if (progressBarDrawingInfo == null) return; |
| nativeUpdateProgressBar(mNativeCompositorView, |
| progressBarDrawingInfo.progressBarRect.left, |
| progressBarDrawingInfo.progressBarRect.top, |
| progressBarDrawingInfo.progressBarRect.width(), |
| progressBarDrawingInfo.progressBarRect.height(), |
| progressBarDrawingInfo.progressBarColor, |
| progressBarDrawingInfo.progressBarBackgroundRect.left, |
| progressBarDrawingInfo.progressBarBackgroundRect.top, |
| progressBarDrawingInfo.progressBarBackgroundRect.width(), |
| progressBarDrawingInfo.progressBarBackgroundRect.height(), |
| progressBarDrawingInfo.progressBarBackgroundColor); |
| } |
| |
| /** |
| * 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, |
| final DrawingInfo progressBarDrawingInfo) { |
| 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(getContext())); |
| 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, 1.0f); |
| |
| // TODO(changwan): move to treeprovider. |
| updateToolbarLayer(provider, forRotation, progressBarDrawingInfo); |
| |
| SceneLayer sceneLayer = |
| provider.getUpdatedActiveSceneLayer(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; |
| TabModelImpl.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 dpToPixel); |
| private native void nativeUpdateToolbarLayer(long nativeCompositorView, int resourceId, |
| int toolbarBackgroundColor, int urlBarResourceId, float urlBarAlpha, float topOffset, |
| float brightness, boolean visible, boolean showShadow); |
| private native void nativeUpdateProgressBar( |
| long nativeCompositorView, |
| int progressBarX, |
| int progressBarY, |
| int progressBarWidth, |
| int progressBarHeight, |
| int progressBarColor, |
| int progressBarBackgroundX, |
| int progressBarBackgroundY, |
| int progressBarBackgroundWidth, |
| int progressBarBackgroundHeight, |
| int progressBarBackgroundColor); |
| private native void nativeSetOverlayVideoMode(long nativeCompositorView, boolean enabled); |
| private native void nativeSetSceneLayer(long nativeCompositorView, SceneLayer sceneLayer); |
| } |