1 // Copyright 2012 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.
5 package org.chromium.content.browser;
7 import android.content.Context;
8 import android.graphics.Bitmap;
9 import android.graphics.Canvas;
10 import android.graphics.Color;
11 import android.graphics.PixelFormat;
12 import android.os.Build;
13 import android.os.Handler;
14 import android.view.Surface;
15 import android.view.SurfaceHolder;
16 import android.view.SurfaceView;
17 import android.widget.FrameLayout;
19 import org.chromium.base.CalledByNative;
20 import org.chromium.base.JNINamespace;
21 import org.chromium.base.TraceEvent;
22 import org.chromium.ui.base.WindowAndroid;
25 * This view is used by a ContentView to render its content.
26 * Call {@link #setCurrentContentView(ContentView)} with the contentView that should be displayed.
27 * Note that only one ContentView can be shown at a time.
29 @JNINamespace("content")
30 public class ContentViewRenderView extends FrameLayout {
31 private static final int MAX_SWAP_BUFFER_COUNT = 2;
33 // The native side of this object.
34 private long mNativeContentViewRenderView;
35 private final SurfaceHolder.Callback mSurfaceCallback;
37 private final SurfaceView mSurfaceView;
38 private final VSyncAdapter mVSyncAdapter;
40 private int mPendingRenders;
41 private int mPendingSwapBuffers;
42 private boolean mNeedToRender;
44 private ContentView mCurrentContentView;
46 // The listener which will be triggered when below two conditions become valid.
47 // 1. The view has been initialized and ready to draw content to the screen.
48 // 2. The compositor finished compositing and the OpenGL buffers has been swapped.
49 // Which means the view has been updated with visually non-empty content.
50 // This listener will be triggered only once after registered.
51 private FirstRenderedFrameListener mFirstRenderedFrameListener;
52 private boolean mFirstFrameReceived;
54 private final Runnable mRenderRunnable = new Runnable() {
61 public interface FirstRenderedFrameListener{
62 public void onFirstFrameReceived();
66 * Constructs a new ContentViewRenderView that should be can to a view hierarchy.
67 * Native code should add/remove the layers to be rendered through the ContentViewLayerRenderer.
68 * @param context The context used to create this.
70 public ContentViewRenderView(Context context, WindowAndroid rootWindow) {
72 assert rootWindow != null;
73 mNativeContentViewRenderView = nativeInit(rootWindow.getNativePointer());
74 assert mNativeContentViewRenderView != 0;
76 mSurfaceView = createSurfaceView(getContext());
77 mSurfaceView.setZOrderMediaOverlay(true);
78 mSurfaceCallback = new SurfaceHolder.Callback() {
80 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
81 assert mNativeContentViewRenderView != 0;
82 nativeSurfaceSetSize(mNativeContentViewRenderView, width, height);
83 if (mCurrentContentView != null) {
84 mCurrentContentView.getContentViewCore().onPhysicalBackingSizeChanged(
90 public void surfaceCreated(SurfaceHolder holder) {
91 assert mNativeContentViewRenderView != 0;
92 nativeSurfaceCreated(mNativeContentViewRenderView, holder.getSurface());
97 public void surfaceDestroyed(SurfaceHolder holder) {
98 assert mNativeContentViewRenderView != 0;
99 nativeSurfaceDestroyed(mNativeContentViewRenderView);
102 mSurfaceView.getHolder().addCallback(mSurfaceCallback);
103 setSurfaceViewBackgroundColor(Color.WHITE);
105 mVSyncAdapter = new VSyncAdapter(getContext());
106 addView(mSurfaceView,
107 new FrameLayout.LayoutParams(
108 FrameLayout.LayoutParams.MATCH_PARENT,
109 FrameLayout.LayoutParams.MATCH_PARENT));
112 private class VSyncAdapter implements VSyncManager.Provider, VSyncMonitor.Listener {
113 private final VSyncMonitor mVSyncMonitor;
114 private boolean mVSyncNotificationEnabled;
115 private VSyncManager.Listener mVSyncListener;
117 // The VSyncMonitor gives the timebase for the actual vsync, but we don't want render until
118 // we have had a chance for input events to propagate to the compositor thread. This takes
119 // 3 ms typically, so we adjust the vsync timestamps forward by a bit to give input events a
121 private static final long INPUT_EVENT_LAG_FROM_VSYNC_MICROSECONDS = 3200;
123 VSyncAdapter(Context context) {
124 mVSyncMonitor = new VSyncMonitor(context, this);
128 public void onVSync(VSyncMonitor monitor, long vsyncTimeMicros) {
130 if (mPendingSwapBuffers + mPendingRenders <= MAX_SWAP_BUFFER_COUNT) {
131 mNeedToRender = false;
135 TraceEvent.instant("ContentViewRenderView:bail");
139 if (mVSyncListener != null) {
140 if (mVSyncNotificationEnabled) {
141 mVSyncListener.onVSync(vsyncTimeMicros);
142 mVSyncMonitor.requestUpdate();
144 // Compensate for input event lag. Input events are delivered immediately on
145 // pre-JB releases, so this adjustment is only done for later versions.
146 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
147 vsyncTimeMicros += INPUT_EVENT_LAG_FROM_VSYNC_MICROSECONDS;
149 mVSyncListener.updateVSync(vsyncTimeMicros,
150 mVSyncMonitor.getVSyncPeriodInMicroseconds());
156 public void registerVSyncListener(VSyncManager.Listener listener) {
157 if (!mVSyncNotificationEnabled) mVSyncMonitor.requestUpdate();
158 mVSyncNotificationEnabled = true;
162 public void unregisterVSyncListener(VSyncManager.Listener listener) {
163 mVSyncNotificationEnabled = false;
166 void setVSyncListener(VSyncManager.Listener listener) {
167 mVSyncListener = listener;
168 if (mVSyncListener != null) mVSyncMonitor.requestUpdate();
171 void requestUpdate() {
172 mVSyncMonitor.requestUpdate();
177 * Sets the background color of the surface view. This method is necessary because the
178 * background color of ContentViewRenderView itself is covered by the background of
180 * @param color The color of the background.
182 public void setSurfaceViewBackgroundColor(int color) {
183 if (mSurfaceView != null) {
184 mSurfaceView.setBackgroundColor(color);
189 * Should be called when the ContentViewRenderView is not needed anymore so its associated
190 * native resource can be freed.
192 public void destroy() {
193 mSurfaceView.getHolder().removeCallback(mSurfaceCallback);
194 nativeDestroy(mNativeContentViewRenderView);
195 mNativeContentViewRenderView = 0;
199 * Makes the passed ContentView the one displayed by this ContentViewRenderView.
201 public void setCurrentContentView(ContentView contentView) {
202 assert mNativeContentViewRenderView != 0;
203 mCurrentContentView = contentView;
205 ContentViewCore contentViewCore =
206 contentView != null ? contentView.getContentViewCore() : null;
208 nativeSetCurrentContentView(mNativeContentViewRenderView,
209 contentViewCore != null ? contentViewCore.getNativeContentViewCore() : 0);
211 if (contentViewCore != null) {
212 contentViewCore.onPhysicalBackingSizeChanged(getWidth(), getHeight());
213 mVSyncAdapter.setVSyncListener(contentViewCore.getVSyncListener(mVSyncAdapter));
217 public void registerFirstRenderedFrameListener(FirstRenderedFrameListener listener) {
218 mFirstRenderedFrameListener = listener;
219 if (mFirstFrameReceived && mFirstRenderedFrameListener != null) {
220 mFirstRenderedFrameListener.onFirstFrameReceived();
225 * This method should be subclassed to provide actions to be performed once the view is ready to
228 protected void onReadyToRender() {
229 mPendingSwapBuffers = 0;
234 * This method could be subclassed optionally to provide a custom SurfaceView object to
235 * this ContentViewRenderView.
236 * @param context The context used to create the SurfaceView object.
237 * @return The created SurfaceView object.
239 protected SurfaceView createSurfaceView(Context context) {
240 return new SurfaceView(context) {
242 public void onDraw(Canvas canvas) {
243 // We only need to draw to software canvases, which are used for taking screenshots.
244 if (canvas.isHardwareAccelerated()) return;
245 Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(),
246 Bitmap.Config.ARGB_8888);
247 if (nativeCompositeToBitmap(mNativeContentViewRenderView, bitmap)) {
248 canvas.drawBitmap(bitmap, 0, 0, null);
255 * @return whether the surface view is initialized and ready to render.
257 public boolean isInitialized() {
258 return mSurfaceView.getHolder().getSurface() != null;
262 * Enter or leave overlay video mode.
263 * @param enabled Whether overlay mode is enabled.
265 public void setOverlayVideoMode(boolean enabled) {
266 int format = enabled ? PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
267 mSurfaceView.getHolder().setFormat(format);
268 nativeSetOverlayVideoMode(mNativeContentViewRenderView, enabled);
272 private void requestRender() {
273 ContentViewCore contentViewCore = mCurrentContentView != null ?
274 mCurrentContentView.getContentViewCore() : null;
276 boolean rendererHasFrame =
277 contentViewCore != null && contentViewCore.consumePendingRendererFrame();
279 if (rendererHasFrame && mPendingSwapBuffers + mPendingRenders < MAX_SWAP_BUFFER_COUNT) {
280 TraceEvent.instant("requestRender:now");
281 mNeedToRender = false;
284 // The handler can be null if we are detached from the window. Calling
285 // {@link View#post(Runnable)} properly handles this case, but we lose the front of
286 // queue behavior. That is okay for this edge case.
287 Handler handler = getHandler();
288 if (handler != null) {
289 handler.postAtFrontOfQueue(mRenderRunnable);
291 post(mRenderRunnable);
293 mVSyncAdapter.requestUpdate();
294 } else if (mPendingRenders <= 0) {
295 assert mPendingRenders == 0;
296 TraceEvent.instant("requestRender:later");
297 mNeedToRender = true;
298 mVSyncAdapter.requestUpdate();
303 private void onSwapBuffersCompleted() {
304 TraceEvent.instant("onSwapBuffersCompleted");
306 if (!mFirstFrameReceived && mCurrentContentView != null &&
307 mCurrentContentView.isReadyForSnapshot()) {
308 mFirstFrameReceived = true;
309 if (mFirstRenderedFrameListener != null) {
310 mFirstRenderedFrameListener.onFirstFrameReceived();
314 if (mPendingSwapBuffers == MAX_SWAP_BUFFER_COUNT && mNeedToRender) requestRender();
315 if (mPendingSwapBuffers > 0) mPendingSwapBuffers--;
318 private void render() {
319 if (mPendingRenders > 0) mPendingRenders--;
321 // Waiting for the content view contents to be ready avoids compositing
322 // when the surface texture is still empty.
323 if (mCurrentContentView == null) return;
324 ContentViewCore contentViewCore = mCurrentContentView.getContentViewCore();
325 if (contentViewCore == null || !contentViewCore.isReady()) {
329 boolean didDraw = nativeComposite(mNativeContentViewRenderView);
331 mPendingSwapBuffers++;
332 if (mSurfaceView.getBackground() != null) {
333 post(new Runnable() {
336 mSurfaceView.setBackgroundResource(0);
343 private native long nativeInit(long rootWindowNativePointer);
344 private native void nativeDestroy(long nativeContentViewRenderView);
345 private native void nativeSetCurrentContentView(long nativeContentViewRenderView,
346 long nativeContentView);
347 private native void nativeSurfaceCreated(long nativeContentViewRenderView, Surface surface);
348 private native void nativeSurfaceDestroyed(long nativeContentViewRenderView);
349 private native void nativeSurfaceSetSize(long nativeContentViewRenderView,
350 int width, int height);
351 private native boolean nativeComposite(long nativeContentViewRenderView);
352 private native boolean nativeCompositeToBitmap(long nativeContentViewRenderView, Bitmap bitmap);
353 private native void nativeSetOverlayVideoMode(long nativeContentViewRenderView,