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