Upstream version 7.35.139.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.graphics.SurfaceTexture;
13 import android.os.Build;
14 import android.os.Handler;
15 import android.view.Surface;
16 import android.view.SurfaceHolder;
17 import android.view.SurfaceView;
18 import android.view.TextureView;
19 import android.view.TextureView.SurfaceTextureListener;
20 import android.widget.FrameLayout;
21
22 import org.chromium.base.CalledByNative;
23 import org.chromium.base.JNINamespace;
24 import org.chromium.base.ObserverList;
25 import org.chromium.base.ObserverList.RewindableIterator;
26 import org.chromium.base.TraceEvent;
27 import org.chromium.ui.base.WindowAndroid;
28
29 /***
30  * This view is used by a ContentView to render its content.
31  * Call {@link #setCurrentContentView(ContentView)} with the contentView that should be displayed.
32  * Note that only one ContentView can be shown at a time.
33  */
34 @JNINamespace("content")
35 public class ContentViewRenderView extends FrameLayout {
36     private static final int MAX_SWAP_BUFFER_COUNT = 2;
37
38     // The native side of this object.
39     private long mNativeContentViewRenderView;
40     private final SurfaceHolder.Callback mSurfaceCallback;
41
42     private final SurfaceView mSurfaceView;
43     private final VSyncAdapter mVSyncAdapter;
44
45     // Enum for the type of compositing surface:
46     //   SURFACE_VIEW - Use SurfaceView as compositing surface which
47     //                  has a bit performance advantage
48     //   TEXTURE_VIEW - Use TextureView as compositing surface which
49     //                  supports animation on the View
50     public enum CompositingSurfaceType { SURFACE_VIEW, TEXTURE_VIEW };
51
52     // The stuff for TextureView usage. It is not a good practice to mix 2 different
53     // implementations into one single class. However, for the sake of reducing the
54     // effort of rebasing maintanence in future, here we avoid heavily changes in
55     // this class.
56     private TextureView mTextureView;
57     private Surface mSurface;
58     private CompositingSurfaceType mCompositingSurfaceType;
59
60     private int mPendingRenders;
61     private int mPendingSwapBuffers;
62     private boolean mNeedToRender;
63
64     private ContentView mCurrentContentView;
65
66     // The listener which will be triggered when below two conditions become valid.
67     // 1. The view has been initialized and ready to draw content to the screen.
68     // 2. The compositor finished compositing and the OpenGL buffers has been swapped.
69     //    Which means the view has been updated with visually non-empty content.
70     // This listener will be triggered only once after registered.
71     private FirstRenderedFrameListener mFirstRenderedFrameListener;
72     private boolean mFirstFrameReceived;
73
74     private final Runnable mRenderRunnable = new Runnable() {
75         @Override
76         public void run() {
77             render();
78         }
79     };
80
81     public interface FirstRenderedFrameListener{
82         public void onFirstFrameReceived();
83     }
84
85     // Initialize the TextureView for rendering ContentView and configure the callback
86     // listeners.
87     private void initTextureView(Context context) {
88         mTextureView = new TextureView(context);
89         mTextureView.setBackgroundColor(Color.WHITE);
90
91         mTextureView.setSurfaceTextureListener(new SurfaceTextureListener() {
92             @Override
93             public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
94                     int width, int height) {
95                 assert mNativeContentViewRenderView != 0;
96
97                 mSurface = new Surface(surfaceTexture);
98                 nativeSurfaceCreated(mNativeContentViewRenderView);
99                 // Force to trigger the compositor to start working.
100                 onSurfaceTextureSizeChanged(surfaceTexture, width, height);
101
102                 mPendingSwapBuffers = 0;
103                 mPendingRenders = 0;
104                 onReadyToRender();
105             }
106
107             @Override
108             public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture,
109                     int width, int height) {
110                 assert mNativeContentViewRenderView != 0 && mSurface != null;
111                 assert surfaceTexture == mTextureView.getSurfaceTexture();
112                 assert mSurface != null;
113
114                 // Here we hard-code the pixel format since the native part requires
115                 // the format parameter to decide if the compositing surface should be
116                 // replaced with a new one when the format is changed.
117                 //
118                 // If TextureView is used, the surface won't be possible to changed,
119                 // so that the format is also not changed. There is no special reason
120                 // to use RGBA_8888 value since the native part won't use its real
121                 // value to do something for drawing.
122                 //
123                 // TODO(hmin): Figure out how to get pixel format from SurfaceTexture.
124                 int format = PixelFormat.RGBA_8888;
125                 nativeSurfaceChanged(mNativeContentViewRenderView,
126                         format, width, height, mSurface);
127                 if (mCurrentContentView != null) {
128                     mCurrentContentView.getContentViewCore().onPhysicalBackingSizeChanged(
129                             width, height);
130                 }
131             }
132
133             @Override
134             public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
135                 assert mNativeContentViewRenderView != 0;
136                 nativeSurfaceDestroyed(mNativeContentViewRenderView);
137
138                 // Release the underlying surface to make it invalid.
139                 mSurface.release();
140                 mSurface = null;
141                 return true;
142             }
143
144             @Override
145             public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
146                 // Do nothing since the SurfaceTexture won't be updated via updateTexImage().
147             }
148         });
149     }
150
151     public ContentViewRenderView(Context context, WindowAndroid rootWindow) {
152         this(context, rootWindow, CompositingSurfaceType.SURFACE_VIEW);
153     }
154
155     /**
156      * Constructs a new ContentViewRenderView that should be can to a view hierarchy.
157      * Native code should add/remove the layers to be rendered through the ContentViewLayerRenderer.
158      * @param context The context used to create this.
159      * @param useTextureView True if TextureView is used as compositing target surface,
160      *                       otherwise SurfaceView is used.
161      */
162     public ContentViewRenderView(Context context, WindowAndroid rootWindow,
163                 CompositingSurfaceType surfaceType) {
164         super(context);
165         assert rootWindow != null;
166         mNativeContentViewRenderView = nativeInit(rootWindow.getNativePointer());
167         assert mNativeContentViewRenderView != 0;
168
169         mCompositingSurfaceType = surfaceType;
170         if (surfaceType == CompositingSurfaceType.TEXTURE_VIEW) {
171             initTextureView(context);
172
173             mVSyncAdapter = new VSyncAdapter(getContext());
174             addView(mTextureView,
175                     new FrameLayout.LayoutParams(
176                             FrameLayout.LayoutParams.MATCH_PARENT,
177                             FrameLayout.LayoutParams.MATCH_PARENT));
178
179             // Avoid compiler warning.
180             mSurfaceView = null;
181             mSurfaceCallback = null;
182             return;
183         }
184
185         mSurfaceView = createSurfaceView(getContext());
186         mSurfaceView.setZOrderMediaOverlay(true);
187         mSurfaceCallback = new SurfaceHolder.Callback() {
188             @Override
189             public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
190                 assert mNativeContentViewRenderView != 0;
191                 nativeSurfaceChanged(mNativeContentViewRenderView,
192                         format, width, height, holder.getSurface());
193                 if (mCurrentContentView != null) {
194                     mCurrentContentView.getContentViewCore().onPhysicalBackingSizeChanged(
195                             width, height);
196                 }
197             }
198
199             @Override
200             public void surfaceCreated(SurfaceHolder holder) {
201                 setSurfaceViewBackgroundColor(Color.WHITE);
202
203                 assert mNativeContentViewRenderView != 0;
204                 nativeSurfaceCreated(mNativeContentViewRenderView);
205
206                 mPendingSwapBuffers = 0;
207                 mPendingRenders = 0;
208
209                 onReadyToRender();
210             }
211
212             @Override
213             public void surfaceDestroyed(SurfaceHolder holder) {
214                 assert mNativeContentViewRenderView != 0;
215                 nativeSurfaceDestroyed(mNativeContentViewRenderView);
216             }
217         };
218         mSurfaceView.getHolder().addCallback(mSurfaceCallback);
219
220         mVSyncAdapter = new VSyncAdapter(getContext());
221         addView(mSurfaceView,
222                 new FrameLayout.LayoutParams(
223                         FrameLayout.LayoutParams.MATCH_PARENT,
224                         FrameLayout.LayoutParams.MATCH_PARENT));
225     }
226
227     private class VSyncAdapter implements VSyncManager.Provider, VSyncMonitor.Listener {
228         private final VSyncMonitor mVSyncMonitor;
229         private boolean mVSyncNotificationEnabled;
230         private VSyncManager.Listener mVSyncListener;
231         private final ObserverList<VSyncManager.Listener> mCurrentVSyncListeners;
232         private final RewindableIterator<VSyncManager.Listener> mCurrentVSyncListenersIterator;
233
234         // The VSyncMonitor gives the timebase for the actual vsync, but we don't want render until
235         // we have had a chance for input events to propagate to the compositor thread. This takes
236         // 3 ms typically, so we adjust the vsync timestamps forward by a bit to give input events a
237         // chance to arrive.
238         private static final long INPUT_EVENT_LAG_FROM_VSYNC_MICROSECONDS = 3200;
239
240         VSyncAdapter(Context context) {
241             mVSyncMonitor = new VSyncMonitor(context, this);
242             mCurrentVSyncListeners = new ObserverList<VSyncManager.Listener>();
243             mCurrentVSyncListenersIterator = mCurrentVSyncListeners.rewindableIterator();
244         }
245
246         @Override
247         public void onVSync(VSyncMonitor monitor, long vsyncTimeMicros) {
248             if (mNeedToRender) {
249                 if (mPendingSwapBuffers + mPendingRenders <= MAX_SWAP_BUFFER_COUNT) {
250                     mNeedToRender = false;
251                     mPendingRenders++;
252                     render();
253                 } else {
254                     TraceEvent.instant("ContentViewRenderView:bail");
255                 }
256             }
257
258             if (mVSyncListener != null) {
259                 if (mVSyncNotificationEnabled) {
260                     for (mCurrentVSyncListenersIterator.rewind();
261                             mCurrentVSyncListenersIterator.hasNext();) {
262                         mCurrentVSyncListenersIterator.next().onVSync(vsyncTimeMicros);
263                     }
264                     mVSyncMonitor.requestUpdate();
265                 } else {
266                     // Compensate for input event lag. Input events are delivered immediately on
267                     // pre-JB releases, so this adjustment is only done for later versions.
268                     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
269                         vsyncTimeMicros += INPUT_EVENT_LAG_FROM_VSYNC_MICROSECONDS;
270                     }
271                     mVSyncListener.updateVSync(vsyncTimeMicros,
272                             mVSyncMonitor.getVSyncPeriodInMicroseconds());
273                 }
274             }
275         }
276
277         @Override
278         public void registerVSyncListener(VSyncManager.Listener listener) {
279             if (!mVSyncNotificationEnabled) mVSyncMonitor.requestUpdate();
280             mCurrentVSyncListeners.addObserver(listener);
281             mVSyncNotificationEnabled = true;
282         }
283
284         @Override
285         public void unregisterVSyncListener(VSyncManager.Listener listener) {
286             mCurrentVSyncListeners.removeObserver(listener);
287             if (mCurrentVSyncListeners.isEmpty()) {
288                 mVSyncNotificationEnabled = false;
289             }
290         }
291
292         void setVSyncListener(VSyncManager.Listener listener) {
293             mVSyncListener = listener;
294             if (mVSyncListener != null) mVSyncMonitor.requestUpdate();
295         }
296
297         void requestUpdate() {
298             mVSyncMonitor.requestUpdate();
299         }
300     }
301
302     /**
303      * Sets the background color of the surface view.  This method is necessary because the
304      * background color of ContentViewRenderView itself is covered by the background of
305      * SurfaceView.
306      * @param color The color of the background.
307      */
308     public void setSurfaceViewBackgroundColor(int color) {
309         if (mSurfaceView != null) {
310             mSurfaceView.setBackgroundColor(color);
311         }
312     }
313
314     /**
315      * Should be called when the ContentViewRenderView is not needed anymore so its associated
316      * native resource can be freed.
317      */
318     public void destroy() {
319         if (mCompositingSurfaceType == CompositingSurfaceType.TEXTURE_VIEW) {
320             mTextureView.setSurfaceTextureListener(null);
321             if (mSurface != null) {
322                 mSurface.release();
323                 mSurface = null;
324             }
325         } else {
326             mSurfaceView.getHolder().removeCallback(mSurfaceCallback);
327         }
328
329         nativeDestroy(mNativeContentViewRenderView);
330         mNativeContentViewRenderView = 0;
331     }
332
333     /**
334      * Makes the passed ContentView the one displayed by this ContentViewRenderView.
335      */
336     public void setCurrentContentView(ContentView contentView) {
337         assert mNativeContentViewRenderView != 0;
338         mCurrentContentView = contentView;
339
340         ContentViewCore contentViewCore =
341                 contentView != null ? contentView.getContentViewCore() : null;
342
343         nativeSetCurrentContentView(mNativeContentViewRenderView,
344                 contentViewCore != null ? contentViewCore.getNativeContentViewCore() : 0);
345
346         if (contentViewCore != null) {
347             contentViewCore.onPhysicalBackingSizeChanged(getWidth(), getHeight());
348             mVSyncAdapter.setVSyncListener(contentViewCore.getVSyncListener(mVSyncAdapter));
349         }
350     }
351
352     public void registerFirstRenderedFrameListener(FirstRenderedFrameListener listener) {
353         mFirstRenderedFrameListener = listener;
354         if (mFirstFrameReceived && mFirstRenderedFrameListener != null) {
355             mFirstRenderedFrameListener.onFirstFrameReceived();
356         }
357     }
358
359     /**
360      * This method should be subclassed to provide actions to be performed once the view is ready to
361      * render.
362      */
363     protected void onReadyToRender() {
364     }
365
366     /**
367      * This method could be subclassed optionally to provide a custom SurfaceView object to
368      * this ContentViewRenderView.
369      * @param context The context used to create the SurfaceView object.
370      * @return The created SurfaceView object.
371      */
372     protected SurfaceView createSurfaceView(Context context) {
373         return new SurfaceView(context) {
374             @Override
375             public void onDraw(Canvas canvas) {
376                 // We only need to draw to software canvases, which are used for taking screenshots.
377                 if (canvas.isHardwareAccelerated()) return;
378                 Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(),
379                         Bitmap.Config.ARGB_8888);
380                 if (nativeCompositeToBitmap(mNativeContentViewRenderView, bitmap)) {
381                     canvas.drawBitmap(bitmap, 0, 0, null);
382                 }
383             }
384         };
385     }
386
387     /**
388      * @return whether the surface view is initialized and ready to render.
389      */
390     public boolean isInitialized() {
391         return mSurfaceView.getHolder().getSurface() != null || mSurface != null;
392     }
393
394     /**
395      * Enter or leave overlay video mode.
396      * @param enabled Whether overlay mode is enabled.
397      */
398     public void setOverlayVideoMode(boolean enabled) {
399         if (mCompositingSurfaceType == CompositingSurfaceType.TEXTURE_VIEW) {
400             nativeSetOverlayVideoMode(mNativeContentViewRenderView, enabled);
401             return;
402         }
403
404         int format = enabled ? PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
405         mSurfaceView.getHolder().setFormat(format);
406         nativeSetOverlayVideoMode(mNativeContentViewRenderView, enabled);
407     }
408
409     @CalledByNative
410     private void requestRender() {
411         ContentViewCore contentViewCore = mCurrentContentView != null ?
412                 mCurrentContentView.getContentViewCore() : null;
413
414         boolean rendererHasFrame =
415                 contentViewCore != null && contentViewCore.consumePendingRendererFrame();
416
417         if (rendererHasFrame && mPendingSwapBuffers + mPendingRenders < MAX_SWAP_BUFFER_COUNT) {
418             TraceEvent.instant("requestRender:now");
419             mNeedToRender = false;
420             mPendingRenders++;
421
422             // The handler can be null if we are detached from the window.  Calling
423             // {@link View#post(Runnable)} properly handles this case, but we lose the front of
424             // queue behavior.  That is okay for this edge case.
425             Handler handler = getHandler();
426             if (handler != null) {
427                 handler.postAtFrontOfQueue(mRenderRunnable);
428             } else {
429                 post(mRenderRunnable);
430             }
431             mVSyncAdapter.requestUpdate();
432         } else if (mPendingRenders <= 0) {
433             assert mPendingRenders == 0;
434             TraceEvent.instant("requestRender:later");
435             mNeedToRender = true;
436             mVSyncAdapter.requestUpdate();
437         }
438     }
439
440     @CalledByNative
441     private void onSwapBuffersCompleted() {
442         TraceEvent.instant("onSwapBuffersCompleted");
443
444         if (!mFirstFrameReceived && mCurrentContentView != null &&
445                 mCurrentContentView.getContentViewCore().isReady()) {
446             mFirstFrameReceived = true;
447             if (mFirstRenderedFrameListener != null) {
448                 mFirstRenderedFrameListener.onFirstFrameReceived();
449             }
450         }
451
452         if (mPendingSwapBuffers == MAX_SWAP_BUFFER_COUNT && mNeedToRender) requestRender();
453         if (mPendingSwapBuffers > 0) mPendingSwapBuffers--;
454     }
455
456     private void render() {
457         if (mPendingRenders > 0) mPendingRenders--;
458
459         // Waiting for the content view contents to be ready avoids compositing
460         // when the surface texture is still empty.
461         if (mCurrentContentView == null) return;
462         ContentViewCore contentViewCore = mCurrentContentView.getContentViewCore();
463         if (contentViewCore == null || !contentViewCore.isReady()) {
464             return;
465         }
466
467         boolean didDraw = nativeComposite(mNativeContentViewRenderView);
468         if (didDraw) {
469             mPendingSwapBuffers++;
470             // Ignore if TextureView is used.
471             if (mCompositingSurfaceType == CompositingSurfaceType.TEXTURE_VIEW) return;
472             if (mSurfaceView.getBackground() != null) {
473                 post(new Runnable() {
474                     @Override
475                     public void run() {
476                         mSurfaceView.setBackgroundResource(0);
477                     }
478                 });
479             }
480         }
481     }
482
483     private native long nativeInit(long rootWindowNativePointer);
484     private native void nativeDestroy(long nativeContentViewRenderView);
485     private native void nativeSetCurrentContentView(long nativeContentViewRenderView,
486             long nativeContentView);
487     private native void nativeSurfaceCreated(long nativeContentViewRenderView);
488     private native void nativeSurfaceDestroyed(long nativeContentViewRenderView);
489     private native void nativeSurfaceChanged(long nativeContentViewRenderView,
490             int format, int width, int height, Surface surface);
491     private native boolean nativeComposite(long nativeContentViewRenderView);
492     private native boolean nativeCompositeToBitmap(long nativeContentViewRenderView, Bitmap bitmap);
493     private native void nativeSetOverlayVideoMode(long nativeContentViewRenderView,
494             boolean enabled);
495 }