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