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