Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / content / public / android / java / src / org / chromium / content / browser / ContentViewRenderView.java
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.
4
5 package org.chromium.content.browser;
6
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;
18
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;
23
24 /***
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.
28  */
29 @JNINamespace("content")
30 public class ContentViewRenderView extends FrameLayout {
31     private static final int MAX_SWAP_BUFFER_COUNT = 2;
32
33     // The native side of this object.
34     private long mNativeContentViewRenderView;
35     private final SurfaceHolder.Callback mSurfaceCallback;
36
37     private final SurfaceView mSurfaceView;
38     private final VSyncAdapter mVSyncAdapter;
39
40     private int mPendingRenders;
41     private int mPendingSwapBuffers;
42     private boolean mNeedToRender;
43
44     private ContentView mCurrentContentView;
45
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;
53
54     private final Runnable mRenderRunnable = new Runnable() {
55         @Override
56         public void run() {
57             render();
58         }
59     };
60
61     public interface FirstRenderedFrameListener{
62         public void onFirstFrameReceived();
63     }
64
65     /**
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.
69      */
70     public ContentViewRenderView(Context context, WindowAndroid rootWindow) {
71         super(context);
72         assert rootWindow != null;
73         mNativeContentViewRenderView = nativeInit(rootWindow.getNativePointer());
74         assert mNativeContentViewRenderView != 0;
75
76         mSurfaceView = createSurfaceView(getContext());
77         mSurfaceView.setZOrderMediaOverlay(true);
78         mSurfaceCallback = new SurfaceHolder.Callback() {
79             @Override
80             public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
81                 assert mNativeContentViewRenderView != 0;
82                 nativeSurfaceChanged(mNativeContentViewRenderView,
83                         format, width, height, holder.getSurface());
84                 if (mCurrentContentView != null) {
85                     mCurrentContentView.getContentViewCore().onPhysicalBackingSizeChanged(
86                             width, height);
87                 }
88             }
89
90             @Override
91             public void surfaceCreated(SurfaceHolder holder) {
92                 assert mNativeContentViewRenderView != 0;
93                 nativeSurfaceCreated(mNativeContentViewRenderView);
94
95                 mPendingSwapBuffers = 0;
96                 mPendingRenders = 0;
97
98                 onReadyToRender();
99             }
100
101             @Override
102             public void surfaceDestroyed(SurfaceHolder holder) {
103                 assert mNativeContentViewRenderView != 0;
104                 nativeSurfaceDestroyed(mNativeContentViewRenderView);
105             }
106         };
107         mSurfaceView.getHolder().addCallback(mSurfaceCallback);
108         setSurfaceViewBackgroundColor(Color.WHITE);
109
110         mVSyncAdapter = new VSyncAdapter(getContext());
111         addView(mSurfaceView,
112                 new FrameLayout.LayoutParams(
113                         FrameLayout.LayoutParams.MATCH_PARENT,
114                         FrameLayout.LayoutParams.MATCH_PARENT));
115     }
116
117     private class VSyncAdapter implements VSyncManager.Provider, VSyncMonitor.Listener {
118         private final VSyncMonitor mVSyncMonitor;
119         private boolean mVSyncNotificationEnabled;
120         private VSyncManager.Listener mVSyncListener;
121
122         // The VSyncMonitor gives the timebase for the actual vsync, but we don't want render until
123         // we have had a chance for input events to propagate to the compositor thread. This takes
124         // 3 ms typically, so we adjust the vsync timestamps forward by a bit to give input events a
125         // chance to arrive.
126         private static final long INPUT_EVENT_LAG_FROM_VSYNC_MICROSECONDS = 3200;
127
128         VSyncAdapter(Context context) {
129             mVSyncMonitor = new VSyncMonitor(context, this);
130         }
131
132         @Override
133         public void onVSync(VSyncMonitor monitor, long vsyncTimeMicros) {
134             if (mNeedToRender) {
135                 if (mPendingSwapBuffers + mPendingRenders <= MAX_SWAP_BUFFER_COUNT) {
136                     mNeedToRender = false;
137                     mPendingRenders++;
138                     render();
139                 } else {
140                     TraceEvent.instant("ContentViewRenderView:bail");
141                 }
142             }
143
144             if (mVSyncListener != null) {
145                 if (mVSyncNotificationEnabled) {
146                     mVSyncListener.onVSync(vsyncTimeMicros);
147                     mVSyncMonitor.requestUpdate();
148                 } else {
149                     // Compensate for input event lag. Input events are delivered immediately on
150                     // pre-JB releases, so this adjustment is only done for later versions.
151                     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
152                         vsyncTimeMicros += INPUT_EVENT_LAG_FROM_VSYNC_MICROSECONDS;
153                     }
154                     mVSyncListener.updateVSync(vsyncTimeMicros,
155                             mVSyncMonitor.getVSyncPeriodInMicroseconds());
156                 }
157             }
158         }
159
160         @Override
161         public void registerVSyncListener(VSyncManager.Listener listener) {
162             if (!mVSyncNotificationEnabled) mVSyncMonitor.requestUpdate();
163             mVSyncNotificationEnabled = true;
164         }
165
166         @Override
167         public void unregisterVSyncListener(VSyncManager.Listener listener) {
168             mVSyncNotificationEnabled = false;
169         }
170
171         void setVSyncListener(VSyncManager.Listener listener) {
172             mVSyncListener = listener;
173             if (mVSyncListener != null) mVSyncMonitor.requestUpdate();
174         }
175
176         void requestUpdate() {
177             mVSyncMonitor.requestUpdate();
178         }
179     }
180
181     /**
182      * Sets the background color of the surface view.  This method is necessary because the
183      * background color of ContentViewRenderView itself is covered by the background of
184      * SurfaceView.
185      * @param color The color of the background.
186      */
187     public void setSurfaceViewBackgroundColor(int color) {
188         if (mSurfaceView != null) {
189             mSurfaceView.setBackgroundColor(color);
190         }
191     }
192
193     /**
194      * Should be called when the ContentViewRenderView is not needed anymore so its associated
195      * native resource can be freed.
196      */
197     public void destroy() {
198         mSurfaceView.getHolder().removeCallback(mSurfaceCallback);
199         nativeDestroy(mNativeContentViewRenderView);
200         mNativeContentViewRenderView = 0;
201     }
202
203     /**
204      * Makes the passed ContentView the one displayed by this ContentViewRenderView.
205      */
206     public void setCurrentContentView(ContentView contentView) {
207         assert mNativeContentViewRenderView != 0;
208         mCurrentContentView = contentView;
209
210         ContentViewCore contentViewCore =
211                 contentView != null ? contentView.getContentViewCore() : null;
212
213         nativeSetCurrentContentView(mNativeContentViewRenderView,
214                 contentViewCore != null ? contentViewCore.getNativeContentViewCore() : 0);
215
216         if (contentViewCore != null) {
217             contentViewCore.onPhysicalBackingSizeChanged(getWidth(), getHeight());
218             mVSyncAdapter.setVSyncListener(contentViewCore.getVSyncListener(mVSyncAdapter));
219         }
220     }
221
222     public void registerFirstRenderedFrameListener(FirstRenderedFrameListener listener) {
223         mFirstRenderedFrameListener = listener;
224         if (mFirstFrameReceived && mFirstRenderedFrameListener != null) {
225             mFirstRenderedFrameListener.onFirstFrameReceived();
226         }
227     }
228
229     /**
230      * This method should be subclassed to provide actions to be performed once the view is ready to
231      * render.
232      */
233     protected void onReadyToRender() {
234     }
235
236     /**
237      * This method could be subclassed optionally to provide a custom SurfaceView object to
238      * this ContentViewRenderView.
239      * @param context The context used to create the SurfaceView object.
240      * @return The created SurfaceView object.
241      */
242     protected SurfaceView createSurfaceView(Context context) {
243         return new SurfaceView(context) {
244             @Override
245             public void onDraw(Canvas canvas) {
246                 // We only need to draw to software canvases, which are used for taking screenshots.
247                 if (canvas.isHardwareAccelerated()) return;
248                 Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(),
249                         Bitmap.Config.ARGB_8888);
250                 if (nativeCompositeToBitmap(mNativeContentViewRenderView, bitmap)) {
251                     canvas.drawBitmap(bitmap, 0, 0, null);
252                 }
253             }
254         };
255     }
256
257     /**
258      * @return whether the surface view is initialized and ready to render.
259      */
260     public boolean isInitialized() {
261         return mSurfaceView.getHolder().getSurface() != null;
262     }
263
264     /**
265      * Enter or leave overlay video mode.
266      * @param enabled Whether overlay mode is enabled.
267      */
268     public void setOverlayVideoMode(boolean enabled) {
269         int format = enabled ? PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
270         mSurfaceView.getHolder().setFormat(format);
271         nativeSetOverlayVideoMode(mNativeContentViewRenderView, enabled);
272     }
273
274     @CalledByNative
275     private void requestRender() {
276         ContentViewCore contentViewCore = mCurrentContentView != null ?
277                 mCurrentContentView.getContentViewCore() : null;
278
279         boolean rendererHasFrame =
280                 contentViewCore != null && contentViewCore.consumePendingRendererFrame();
281
282         if (rendererHasFrame && mPendingSwapBuffers + mPendingRenders < MAX_SWAP_BUFFER_COUNT) {
283             TraceEvent.instant("requestRender:now");
284             mNeedToRender = false;
285             mPendingRenders++;
286
287             // The handler can be null if we are detached from the window.  Calling
288             // {@link View#post(Runnable)} properly handles this case, but we lose the front of
289             // queue behavior.  That is okay for this edge case.
290             Handler handler = getHandler();
291             if (handler != null) {
292                 handler.postAtFrontOfQueue(mRenderRunnable);
293             } else {
294                 post(mRenderRunnable);
295             }
296             mVSyncAdapter.requestUpdate();
297         } else if (mPendingRenders <= 0) {
298             assert mPendingRenders == 0;
299             TraceEvent.instant("requestRender:later");
300             mNeedToRender = true;
301             mVSyncAdapter.requestUpdate();
302         }
303     }
304
305     @CalledByNative
306     private void onSwapBuffersCompleted() {
307         TraceEvent.instant("onSwapBuffersCompleted");
308
309         if (!mFirstFrameReceived && mCurrentContentView != null &&
310                 mCurrentContentView.isReadyForSnapshot()) {
311             mFirstFrameReceived = true;
312             if (mFirstRenderedFrameListener != null) {
313                 mFirstRenderedFrameListener.onFirstFrameReceived();
314             }
315         }
316
317         if (mPendingSwapBuffers == MAX_SWAP_BUFFER_COUNT && mNeedToRender) requestRender();
318         if (mPendingSwapBuffers > 0) mPendingSwapBuffers--;
319     }
320
321     private void render() {
322         if (mPendingRenders > 0) mPendingRenders--;
323
324         // Waiting for the content view contents to be ready avoids compositing
325         // when the surface texture is still empty.
326         if (mCurrentContentView == null) return;
327         ContentViewCore contentViewCore = mCurrentContentView.getContentViewCore();
328         if (contentViewCore == null || !contentViewCore.isReady()) {
329             return;
330         }
331
332         boolean didDraw = nativeComposite(mNativeContentViewRenderView);
333         if (didDraw) {
334             mPendingSwapBuffers++;
335             if (mSurfaceView.getBackground() != null) {
336                 post(new Runnable() {
337                     @Override
338                     public void run() {
339                         mSurfaceView.setBackgroundResource(0);
340                     }
341                 });
342             }
343         }
344     }
345
346     private native long nativeInit(long rootWindowNativePointer);
347     private native void nativeDestroy(long nativeContentViewRenderView);
348     private native void nativeSetCurrentContentView(long nativeContentViewRenderView,
349             long nativeContentView);
350     private native void nativeSurfaceCreated(long nativeContentViewRenderView);
351     private native void nativeSurfaceDestroyed(long nativeContentViewRenderView);
352     private native void nativeSurfaceChanged(long nativeContentViewRenderView,
353             int format, int width, int height, Surface surface);
354     private native boolean nativeComposite(long nativeContentViewRenderView);
355     private native boolean nativeCompositeToBitmap(long nativeContentViewRenderView, Bitmap bitmap);
356     private native void nativeSetOverlayVideoMode(long nativeContentViewRenderView,
357             boolean enabled);
358 }