Upstream version 5.34.92.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                 nativeSurfaceSetSize(mNativeContentViewRenderView, width, height);
83                 if (mCurrentContentView != null) {
84                     mCurrentContentView.getContentViewCore().onPhysicalBackingSizeChanged(
85                             width, height);
86                 }
87             }
88
89             @Override
90             public void surfaceCreated(SurfaceHolder holder) {
91                 assert mNativeContentViewRenderView != 0;
92                 nativeSurfaceCreated(mNativeContentViewRenderView, holder.getSurface());
93                 onReadyToRender();
94             }
95
96             @Override
97             public void surfaceDestroyed(SurfaceHolder holder) {
98                 assert mNativeContentViewRenderView != 0;
99                 nativeSurfaceDestroyed(mNativeContentViewRenderView);
100             }
101         };
102         mSurfaceView.getHolder().addCallback(mSurfaceCallback);
103         setSurfaceViewBackgroundColor(Color.WHITE);
104
105         mVSyncAdapter = new VSyncAdapter(getContext());
106         addView(mSurfaceView,
107                 new FrameLayout.LayoutParams(
108                         FrameLayout.LayoutParams.MATCH_PARENT,
109                         FrameLayout.LayoutParams.MATCH_PARENT));
110     }
111
112     private class VSyncAdapter implements VSyncManager.Provider, VSyncMonitor.Listener {
113         private final VSyncMonitor mVSyncMonitor;
114         private boolean mVSyncNotificationEnabled;
115         private VSyncManager.Listener mVSyncListener;
116
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
120         // chance to arrive.
121         private static final long INPUT_EVENT_LAG_FROM_VSYNC_MICROSECONDS = 3200;
122
123         VSyncAdapter(Context context) {
124             mVSyncMonitor = new VSyncMonitor(context, this);
125         }
126
127         @Override
128         public void onVSync(VSyncMonitor monitor, long vsyncTimeMicros) {
129             if (mNeedToRender) {
130                 if (mPendingSwapBuffers + mPendingRenders <= MAX_SWAP_BUFFER_COUNT) {
131                     mNeedToRender = false;
132                     mPendingRenders++;
133                     render();
134                 } else {
135                     TraceEvent.instant("ContentViewRenderView:bail");
136                 }
137             }
138
139             if (mVSyncListener != null) {
140                 if (mVSyncNotificationEnabled) {
141                     mVSyncListener.onVSync(vsyncTimeMicros);
142                     mVSyncMonitor.requestUpdate();
143                 } else {
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;
148                     }
149                     mVSyncListener.updateVSync(vsyncTimeMicros,
150                             mVSyncMonitor.getVSyncPeriodInMicroseconds());
151                 }
152             }
153         }
154
155         @Override
156         public void registerVSyncListener(VSyncManager.Listener listener) {
157             if (!mVSyncNotificationEnabled) mVSyncMonitor.requestUpdate();
158             mVSyncNotificationEnabled = true;
159         }
160
161         @Override
162         public void unregisterVSyncListener(VSyncManager.Listener listener) {
163             mVSyncNotificationEnabled = false;
164         }
165
166         void setVSyncListener(VSyncManager.Listener listener) {
167             mVSyncListener = listener;
168             if (mVSyncListener != null) mVSyncMonitor.requestUpdate();
169         }
170
171         void requestUpdate() {
172             mVSyncMonitor.requestUpdate();
173         }
174     }
175
176     /**
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
179      * SurfaceView.
180      * @param color The color of the background.
181      */
182     public void setSurfaceViewBackgroundColor(int color) {
183         if (mSurfaceView != null) {
184             mSurfaceView.setBackgroundColor(color);
185         }
186     }
187
188     /**
189      * Should be called when the ContentViewRenderView is not needed anymore so its associated
190      * native resource can be freed.
191      */
192     public void destroy() {
193         mSurfaceView.getHolder().removeCallback(mSurfaceCallback);
194         nativeDestroy(mNativeContentViewRenderView);
195         mNativeContentViewRenderView = 0;
196     }
197
198     /**
199      * Makes the passed ContentView the one displayed by this ContentViewRenderView.
200      */
201     public void setCurrentContentView(ContentView contentView) {
202         assert mNativeContentViewRenderView != 0;
203         mCurrentContentView = contentView;
204
205         ContentViewCore contentViewCore =
206                 contentView != null ? contentView.getContentViewCore() : null;
207
208         nativeSetCurrentContentView(mNativeContentViewRenderView,
209                 contentViewCore != null ? contentViewCore.getNativeContentViewCore() : 0);
210
211         if (contentViewCore != null) {
212             contentViewCore.onPhysicalBackingSizeChanged(getWidth(), getHeight());
213             mVSyncAdapter.setVSyncListener(contentViewCore.getVSyncListener(mVSyncAdapter));
214         }
215     }
216
217     public void registerFirstRenderedFrameListener(FirstRenderedFrameListener listener) {
218         mFirstRenderedFrameListener = listener;
219         if (mFirstFrameReceived && mFirstRenderedFrameListener != null) {
220             mFirstRenderedFrameListener.onFirstFrameReceived();
221         }
222     }
223
224     /**
225      * This method should be subclassed to provide actions to be performed once the view is ready to
226      * render.
227      */
228     protected void onReadyToRender() {
229         mPendingSwapBuffers = 0;
230         mPendingRenders = 0;
231     }
232
233     /**
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.
238      */
239     protected SurfaceView createSurfaceView(Context context) {
240         return new SurfaceView(context) {
241             @Override
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);
249                 }
250             }
251         };
252     }
253
254     /**
255      * @return whether the surface view is initialized and ready to render.
256      */
257     public boolean isInitialized() {
258         return mSurfaceView.getHolder().getSurface() != null;
259     }
260
261     /**
262      * Enter or leave overlay video mode.
263      * @param enabled Whether overlay mode is enabled.
264      */
265     public void setOverlayVideoMode(boolean enabled) {
266         int format = enabled ? PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
267         mSurfaceView.getHolder().setFormat(format);
268         nativeSetOverlayVideoMode(mNativeContentViewRenderView, enabled);
269     }
270
271     @CalledByNative
272     private void requestRender() {
273         ContentViewCore contentViewCore = mCurrentContentView != null ?
274                 mCurrentContentView.getContentViewCore() : null;
275
276         boolean rendererHasFrame =
277                 contentViewCore != null && contentViewCore.consumePendingRendererFrame();
278
279         if (rendererHasFrame && mPendingSwapBuffers + mPendingRenders < MAX_SWAP_BUFFER_COUNT) {
280             TraceEvent.instant("requestRender:now");
281             mNeedToRender = false;
282             mPendingRenders++;
283
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);
290             } else {
291                 post(mRenderRunnable);
292             }
293             mVSyncAdapter.requestUpdate();
294         } else if (mPendingRenders <= 0) {
295             assert mPendingRenders == 0;
296             TraceEvent.instant("requestRender:later");
297             mNeedToRender = true;
298             mVSyncAdapter.requestUpdate();
299         }
300     }
301
302     @CalledByNative
303     private void onSwapBuffersCompleted() {
304         TraceEvent.instant("onSwapBuffersCompleted");
305
306         if (!mFirstFrameReceived && mCurrentContentView != null &&
307                 mCurrentContentView.isReadyForSnapshot()) {
308             mFirstFrameReceived = true;
309             if (mFirstRenderedFrameListener != null) {
310                 mFirstRenderedFrameListener.onFirstFrameReceived();
311             }
312         }
313
314         if (mPendingSwapBuffers == MAX_SWAP_BUFFER_COUNT && mNeedToRender) requestRender();
315         if (mPendingSwapBuffers > 0) mPendingSwapBuffers--;
316     }
317
318     private void render() {
319         if (mPendingRenders > 0) mPendingRenders--;
320
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()) {
326             return;
327         }
328
329         boolean didDraw = nativeComposite(mNativeContentViewRenderView);
330         if (didDraw) {
331             mPendingSwapBuffers++;
332             if (mSurfaceView.getBackground() != null) {
333                 post(new Runnable() {
334                     @Override
335                     public void run() {
336                         mSurfaceView.setBackgroundResource(0);
337                     }
338                 });
339             }
340         }
341     }
342
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,
354             boolean enabled);
355 }