blob: 6a22e52302a7318b1648bb34d3a1c2ac711068be [file] [log] [blame]
// 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);
}