Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2017 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.chrome.browser.compositor; | |
| 6 | |
| 7 import android.content.Context; | |
| 8 import android.graphics.PixelFormat; | |
| 9 import android.graphics.drawable.Drawable; | |
| 10 import android.os.Build; | |
| 11 import android.view.SurfaceHolder; | |
| 12 import android.view.SurfaceView; | |
| 13 import android.view.View; | |
| 14 import android.view.ViewGroup; | |
| 15 import android.widget.FrameLayout; | |
| 16 | |
| 17 /** | |
| 18 * Manage multiple SurfaceViews for the compositor, so that transitions between | |
| 19 * surfaces with and without an alpha channel can be visually smooth. | |
| 20 * | |
| 21 * This class allows a client to request a 'translucent' or 'opaque' surface, an d we will signal via | |
| 22 * SurfaceHolder.Callback when it's ready. We guarantee that the client will re ceive surfaceCreated | |
| 23 * / surfaceDestroyed only for a surface that represents the most recently reque sted PixelFormat. | |
| 24 * | |
| 25 * Internally, we maintain two SurfaceViews, since calling setFormat() to change the PixelFormat | |
| 26 * results in a visual glitch as the surface is torn down. crbug.com/679902 | |
| 27 * | |
| 28 * The client has the responsibility to call doneWithUnownedSurface() at some po int between when we | |
| 29 * call back its surfaceCreated, when it is safe for us to hide the SurfaceView with the wrong | |
| 30 * format. | |
| 31 * | |
| 32 * We do not guarantee that the surface will always actually have that format, t hough, if we're set | |
| 33 * up to always provide a translucent surface. This helps low-memory devices, b ut not requiring two | |
| 34 * SurfaceViews. However, it does require that the compositor use the EGL confi g to turn off alpha | |
| 35 * blending. For low memory devices, this already happens, since it selects bet ween 565 and 8888 | |
| 36 * EGL configs. In this case, we will still provide synthetic surfaceCreated / surfaceDestroyed | |
| 37 * messages to the client, even though the underlying surface isn't changing. | |
| 38 * | |
| 39 * The full design doc is at https://goo.gl/aAmQzR . | |
| 40 */ | |
| 41 class CompositorSurfaceManager implements SurfaceHolder.Callback { | |
| 42 private static class SurfaceState { | |
| 43 public SurfaceView surfaceView; | |
| 44 | |
| 45 // Have we started destroying |surfaceView|, but haven't been notified o f it yet? | |
| 46 public boolean destroyPending; | |
| 47 | |
| 48 public SurfaceState(Context context, int format, SurfaceHolder.Callback callback) { | |
| 49 surfaceView = new SurfaceView(context); | |
| 50 surfaceView.setZOrderMediaOverlay(true); | |
| 51 surfaceView.setVisibility(View.VISIBLE); | |
| 52 surfaceHolder().setFormat(format); | |
| 53 surfaceHolder().addCallback(callback); | |
| 54 } | |
| 55 | |
| 56 public SurfaceHolder surfaceHolder() { | |
| 57 return surfaceView.getHolder(); | |
| 58 } | |
| 59 | |
| 60 public boolean isValid() { | |
| 61 return surfaceHolder().getSurface().isValid(); | |
| 62 } | |
| 63 } | |
| 64 | |
| 65 // SurfaceView with a translucent PixelFormat. | |
| 66 private final SurfaceState mTranslucent; | |
| 67 | |
| 68 // SurfaceView with an opaque PixelFormat. | |
| 69 private final SurfaceState mOpaque; | |
| 70 | |
| 71 // Surface that we last gave to the client with surfaceCreated. Cleared whe n we call | |
| 72 // surfaceDestroyed. | |
| 73 private SurfaceState mOwnedByClient; | |
| 74 | |
| 75 // Surface that was most recently requested by the client. | |
| 76 private SurfaceState mRequestedByClient; | |
| 77 | |
| 78 // Client that we notify about surface change events. | |
| 79 private SurfaceHolder.Callback mClient; | |
| 80 | |
| 81 // View to which we'll attach the SurfaceView. | |
| 82 private final ViewGroup mParentView; | |
| 83 | |
| 84 public CompositorSurfaceManager(ViewGroup parentView, SurfaceHolder.Callback client) { | |
| 85 mParentView = parentView; | |
| 86 mClient = client; | |
| 87 | |
| 88 mTranslucent = new SurfaceState(parentView.getContext(), PixelFormat.TRA NSLUCENT, this); | |
| 89 mOpaque = new SurfaceState(mParentView.getContext(), PixelFormat.OPAQUE, this); | |
| 90 } | |
| 91 | |
| 92 /** | |
| 93 * Turn off everything. | |
| 94 */ | |
| 95 public void shutDown() { | |
| 96 mTranslucent.surfaceHolder().removeCallback(this); | |
| 97 mOpaque.surfaceHolder().removeCallback(this); | |
| 98 } | |
| 99 | |
| 100 /** | |
| 101 * Called by the client to request a surface. Once called, we guarantee tha t the next call to | |
| 102 * onSurfaceAvailable will match the most recent value of |format|. If the surface is already | |
| 103 * available for use, then we'll elide the created callback. Note that |for mat| must be either | |
| 104 * OPAQUE or TRANSLUCENT. | |
| 105 */ | |
| 106 public void requestSurface(int format) { | |
| 107 mRequestedByClient = (format == PixelFormat.TRANSLUCENT) ? mTranslucent : mOpaque; | |
| 108 | |
| 109 // Is the requested surface ready? "Ready" means that it's valid, and t hat we don't have a | |
| 110 // destroy in-flight for it. | |
| 111 if (mRequestedByClient.isValid() && !mRequestedByClient.destroyPending) { | |
| 112 // The client has requested a surface that's already valid and isn't being torn down. | |
| 113 // It might be requesting the same surface it already has, or it mig ht have toggled | |
| 114 // back to one it had before without notifying doneWithUnownedSurfac e. In either case, | |
| 115 // we elide the surface created / destroyed callbacks. We might wan t to return whether | |
| 116 // we've elided them, but CompositorView doesn't care. | |
| 117 mOwnedByClient = mRequestedByClient; | |
| 118 return; | |
| 119 } | |
| 120 | |
| 121 // Surface is not valid, or it'll be destroyed soon. If it's not valid now, then start | |
| 122 // construction. Once it's ready, we'll signal the client if it still w ants this surface. | |
| 123 // If destruction is pending, then we must wait for it to complete. Whe n we're notified | |
| 124 // that it is destroyed, we'll re-start construction if the client still wants this surface. | |
| 125 // Note that we could send a surfaceDestroyed for the owned surface, if there is one, but we | |
| 126 // defer it until later so that the compositor can still use it. | |
| 127 if (!mRequestedByClient.isValid()) startSurfaceConstruction(mRequestedBy Client); | |
| 128 } | |
| 129 | |
| 130 /** | |
| 131 * Called to notify us that the client no longer needs the surface that it d oesn't own. This | |
| 132 * tells us that we may destroy it. Note that it's okay if it never had an unowned surface. | |
| 133 */ | |
| 134 public void doneWithUnownedSurface() { | |
| 135 if (mOwnedByClient == null) return; | |
| 136 | |
| 137 SurfaceState unowned = (mOwnedByClient == mTranslucent) ? mOpaque : mTra nslucent; | |
| 138 | |
| 139 if (mRequestedByClient == unowned) { | |
| 140 // Client is giving us back a surface that it's since requested but hasn't gotten yet. | |
| 141 // Do nothing. It will be notified when the new surface is ready, a nd it can call us | |
| 142 // again. | |
| 143 return; | |
| 144 } | |
| 145 | |
| 146 // Start destruction of this surface. | |
| 147 postSurfaceDestruction(unowned); | |
| 148 } | |
| 149 | |
| 150 /** | |
| 151 * Return the currently owned SurfaceHolder, if any. | |
| 152 */ | |
| 153 public SurfaceHolder getHolder() { | |
| 154 return mOwnedByClient != null ? mOwnedByClient.surfaceHolder() : null; | |
| 155 } | |
| 156 | |
| 157 /** | |
| 158 * Destroy and re-create the surface. Useful for a JB workaround needed by CompositorView. | |
| 159 */ | |
| 160 public void recreateSurfaceForJellyBean() { | |
| 161 assert Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2; | |
| 162 | |
| 163 // If they don't have a surface, then they'll get a new one anyway. | |
| 164 if (mOwnedByClient == null) return; | |
| 165 | |
| 166 // Notify the client that it no longer owns this surface, then destroy i t. When destruction | |
| 167 // completes, we will recreate it automatically, since it will look like the client since | |
| 168 // re-requested it. That's why we send surfaceDestroyed here rather tha n letting our | |
| 169 // surfaceDestroyed do it when destruction completes. If we just starte d destruction while | |
| 170 // the client still owns the surface, then our surfaceDestroyed would as sume that android | |
| 171 // initiated the destruction since |mSurfaceDestroyed| would imply that the client didn't. | |
| 172 | |
| 173 mParentView.post(new Runnable() { | |
| 174 @Override | |
| 175 public void run() { | |
| 176 if (mOwnedByClient == null) return; | |
| 177 SurfaceState owned = mOwnedByClient; | |
| 178 mClient.surfaceDestroyed(mOwnedByClient.surfaceHolder()); | |
| 179 mOwnedByClient = null; | |
| 180 startSurfaceDestruction(owned); | |
| 181 } | |
| 182 }); | |
| 183 } | |
| 184 | |
| 185 @Override | |
| 186 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { | |
| 187 SurfaceState state = getStateForHolder(holder); | |
| 188 assert state != null; | |
| 189 | |
| 190 // If this is the surface that the client currently cares about, then no tify the client. | |
| 191 // Note that surfaceChanged is guaranteed to come only after surfaceCrea ted. Also, if the | |
| 192 // client has requested a different surface but hasn't gotten it yet, th en skip this. | |
| 193 if (state == mOwnedByClient && state == mRequestedByClient) { | |
| 194 mClient.surfaceChanged(holder, format, width, height); | |
| 195 } | |
| 196 } | |
| 197 | |
| 198 @Override | |
| 199 public void surfaceCreated(SurfaceHolder holder) { | |
| 200 SurfaceState state = getStateForHolder(holder); | |
| 201 assert state != null; | |
| 202 | |
| 203 if (state != mRequestedByClient) { | |
| 204 // Surface is created, but it's not the one that's been requested mo st recently. Just | |
| 205 // destroy it again. | |
| 206 postSurfaceDestruction(state); | |
| 207 return; | |
| 208 } | |
| 209 | |
| 210 // The client requested a surface, and it's now available. If the clien t owns a | |
| 211 // surface, then notify it that it doesn't. Note that the client can't own |state| at | |
| 212 // this point, since we would have removed ownership when we got surface Destroyed. | |
| 213 if (mOwnedByClient != null) mClient.surfaceDestroyed(mOwnedByClient.surf aceHolder()); | |
| 214 | |
| 215 mOwnedByClient = mRequestedByClient; | |
| 216 mClient.surfaceCreated(mOwnedByClient.surfaceHolder()); | |
| 217 } | |
| 218 | |
| 219 @Override | |
| 220 public void surfaceDestroyed(SurfaceHolder holder) { | |
| 221 SurfaceState state = getStateForHolder(holder); | |
| 222 assert state != null; | |
| 223 | |
| 224 // We might not have requested destruction, but clear the flag in case w e did. | |
| 225 state.destroyPending = false; | |
| 226 | |
| 227 // If the client owns this surface, then notify it synchronously that it no longer does. | |
| 228 // This can happen if Android destroys the surface on its own. | |
| 229 if (state == mOwnedByClient) { | |
| 230 mClient.surfaceDestroyed(holder); | |
| 231 mOwnedByClient = null; | |
| 232 | |
| 233 // Do not re-request the surface here. If android gives the surface back, then we'll | |
| 234 // re-signal the client about construction. | |
| 235 return; | |
| 236 } | |
| 237 | |
| 238 // The client doesn't own this surface, but might want it. | |
| 239 // If the client has requested this surface, then start construction on it. The client will | |
| 240 // be notified when it completes. This can happen if the client re-requ ests a surface after | |
| 241 // we start destruction on it from a previous request, for example. We post this for later, | |
| 242 // since we might be called while removing |state| from the view tree. I n general, posting | |
| 243 // from here is good. | |
| 244 if (state == mRequestedByClient) { | |
| 245 postSurfaceConstruction(state); | |
| 246 } else { | |
| 247 // This isn't the requested surface. If android destroyed it, then also unhook it so | |
| 248 // that it isn't recreated later. If we did, then it's already not attached, and this | |
| 249 // will do nothing. | |
| 250 postSurfaceDestruction(state); | |
| 251 } | |
| 252 } | |
| 253 | |
| 254 /** | |
| 255 * Update the background drawable on all surfaces. | |
| 256 */ | |
| 257 public void setBackgroundDrawable(Drawable background) { | |
| 258 mTranslucent.surfaceView.setBackgroundDrawable(background); | |
| 259 mOpaque.surfaceView.setBackgroundDrawable(background); | |
| 260 } | |
| 261 | |
| 262 /** | |
| 263 * Set |willNotDraw| on all surfaces. | |
| 264 */ | |
| 265 public void setWillNotDraw(boolean willNotDraw) { | |
| 266 mTranslucent.surfaceView.setWillNotDraw(willNotDraw); | |
| 267 mOpaque.surfaceView.setWillNotDraw(willNotDraw); | |
| 268 } | |
| 269 | |
| 270 /** | |
| 271 * Return the SurfaceState for |holder|, or null if it isn't either. | |
| 272 */ | |
| 273 private SurfaceState getStateForHolder(SurfaceHolder holder) { | |
| 274 if (mTranslucent.surfaceHolder() == holder) return mTranslucent; | |
| 275 | |
| 276 if (mOpaque.surfaceHolder() == holder) return mOpaque; | |
| 277 | |
| 278 return null; | |
| 279 } | |
| 280 | |
| 281 private void startSurfaceConstruction(SurfaceState state) { | |
| 282 if (state.surfaceView.getParent() != null) return; | |
| 283 | |
| 284 FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( | |
| 285 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATC H_PARENT); | |
| 286 mParentView.addView(state.surfaceView, lp); | |
| 287 mParentView.bringChildToFront(state.surfaceView); | |
| 288 mParentView.postInvalidateOnAnimation(); | |
| 289 } | |
| 290 | |
| 291 private void postSurfaceConstruction(final SurfaceState state) { | |
| 292 mParentView.post(new Runnable() { | |
| 293 @Override | |
| 294 public void run() { | |
| 295 startSurfaceConstruction(state); | |
| 296 } | |
| 297 }); | |
| 298 } | |
| 299 | |
| 300 private void startSurfaceDestruction(SurfaceState state) { | |
| 301 // If we're called while we're not attached, then do nothing. This make s it easier for the | |
| 302 // client, since it doesn't have to keep track of whether the outgoing s urface has been | |
| 303 // destroyed or not. | |
| 304 if (state.surfaceView.getParent() == null) return; | |
| 305 | |
| 306 state.destroyPending = true; | |
|
Ted C
2017/01/20 21:59:01
Should we set this in postSurfaceDestruction? At
liberato (no reviews please)
2017/01/24 23:25:49
i think that you're right that it should set destr
| |
| 307 mParentView.removeView(state.surfaceView); | |
| 308 } | |
| 309 | |
| 310 private void postSurfaceDestruction(final SurfaceState state) { | |
| 311 mParentView.post(new Runnable() { | |
| 312 @Override | |
| 313 public void run() { | |
| 314 startSurfaceDestruction(state); | |
| 315 } | |
| 316 }); | |
| 317 } | |
| 318 } | |
| OLD | NEW |