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.view.SurfaceHolder; |
| 11 import android.view.SurfaceView; |
| 12 import android.view.View; |
| 13 import android.view.ViewGroup; |
| 14 import android.widget.FrameLayout; |
| 15 |
| 16 /** |
| 17 * Manage multiple SurfaceViews for the compositor, so that transitions between |
| 18 * surfaces with and without an alpha channel can be visually smooth. |
| 19 * |
| 20 * This class allows a client to request a 'translucent' or 'opaque' surface, an
d we will signal via |
| 21 * SurfaceHolder.Callback when it's ready. We guarantee that the client will re
ceive surfaceCreated |
| 22 * / surfaceDestroyed only for a surface that represents the most recently reque
sted PixelFormat. |
| 23 * |
| 24 * Internally, we maintain two SurfaceViews, since calling setFormat() to change
the PixelFormat |
| 25 * results in a visual glitch as the surface is torn down. crbug.com/679902 |
| 26 * |
| 27 * The client has the responsibility to call doneWithUnownedSurface() at some po
int between when we |
| 28 * call back its surfaceCreated, when it is safe for us to hide the SurfaceView
with the wrong |
| 29 * format. It is okay if it requests multiple surfaces without calling doneWith
UnownedSurface. |
| 30 * |
| 31 * If the client requests the same format more than once in a row, it will still
receive destroyed / |
| 32 * created / changed messages for it, even though we won't tear it down. |
| 33 * |
| 34 * The full design doc is at https://goo.gl/aAmQzR . |
| 35 */ |
| 36 class CompositorSurfaceManager implements SurfaceHolder.Callback { |
| 37 private static class SurfaceState { |
| 38 public SurfaceView surfaceView; |
| 39 |
| 40 // Do we expect a surfaceCreated? |
| 41 public boolean createPending; |
| 42 |
| 43 // Have we started destroying |surfaceView|, but haven't received surfac
eDestroyed yet? |
| 44 public boolean destroyPending; |
| 45 |
| 46 // Last PixelFormat that we received, or UNKNOWN if we don't know / don'
t want to cache it. |
| 47 public int format; |
| 48 |
| 49 // Last known width, height for thsi surface. |
| 50 public int width; |
| 51 public int height; |
| 52 |
| 53 // Parent ViewGroup, or null. |
| 54 private ViewGroup mParent; |
| 55 |
| 56 public SurfaceState(Context context, int format, SurfaceHolder.Callback
callback) { |
| 57 surfaceView = new SurfaceView(context); |
| 58 surfaceView.setZOrderMediaOverlay(true); |
| 59 surfaceView.setVisibility(View.VISIBLE); |
| 60 surfaceHolder().setFormat(format); |
| 61 surfaceHolder().addCallback(callback); |
| 62 |
| 63 // Set this to UNKNOWN until we get a format back. |
| 64 this.format = PixelFormat.UNKNOWN; |
| 65 } |
| 66 |
| 67 public SurfaceHolder surfaceHolder() { |
| 68 return surfaceView.getHolder(); |
| 69 } |
| 70 |
| 71 public boolean isValid() { |
| 72 return surfaceHolder().getSurface().isValid(); |
| 73 } |
| 74 |
| 75 // Attach to |parent|, such that isAttached() will be correct immediatel
y. Otherwise, |
| 76 // attaching and detaching can cause surfaceCreated / surfaceDestroyed c
allbacks without |
| 77 // View.hasParent being up to date. |
| 78 public void attachTo(ViewGroup parent, FrameLayout.LayoutParams lp) { |
| 79 mParent = parent; |
| 80 mParent.addView(surfaceView, lp); |
| 81 } |
| 82 |
| 83 public void detachFromParent() { |
| 84 final ViewGroup parent = mParent; |
| 85 // Since removeView can call surfaceDestroyed before returning, be s
ure that isAttached |
| 86 // will return false. |
| 87 mParent = null; |
| 88 parent.removeView(surfaceView); |
| 89 } |
| 90 |
| 91 public boolean isAttached() { |
| 92 return mParent != null; |
| 93 } |
| 94 } |
| 95 |
| 96 // SurfaceView with a translucent PixelFormat. |
| 97 private final SurfaceState mTranslucent; |
| 98 |
| 99 // SurfaceView with an opaque PixelFormat. |
| 100 private final SurfaceState mOpaque; |
| 101 |
| 102 // Surface that we last gave to the client with surfaceCreated. Cleared whe
n we call |
| 103 // surfaceDestroyed on |mClient|. Note that it's not necessary that Android
has notified us |
| 104 // the surface has been destroyed; we deliberately keep it around until the
client tells us that |
| 105 // it's okay to get rid of it. |
| 106 private SurfaceState mOwnedByClient; |
| 107 |
| 108 // Surface that was most recently requested by the client. |
| 109 private SurfaceState mRequestedByClient; |
| 110 |
| 111 // Client that we notify about surface change events. |
| 112 private SurfaceHolder.Callback mClient; |
| 113 |
| 114 // View to which we'll attach the SurfaceView. |
| 115 private final ViewGroup mParentView; |
| 116 |
| 117 public CompositorSurfaceManager(ViewGroup parentView, SurfaceHolder.Callback
client) { |
| 118 mParentView = parentView; |
| 119 mClient = client; |
| 120 |
| 121 mTranslucent = new SurfaceState(parentView.getContext(), PixelFormat.TRA
NSLUCENT, this); |
| 122 mOpaque = new SurfaceState(mParentView.getContext(), PixelFormat.OPAQUE,
this); |
| 123 } |
| 124 |
| 125 /** |
| 126 * Turn off everything. |
| 127 */ |
| 128 public void shutDown() { |
| 129 mTranslucent.surfaceHolder().removeCallback(this); |
| 130 mOpaque.surfaceHolder().removeCallback(this); |
| 131 } |
| 132 |
| 133 /** |
| 134 * Called by the client to request a surface. Once called, we guarantee tha
t the next call to |
| 135 * surfaceCreated will match the most recent value of |format|. If the surf
ace is already |
| 136 * available for use, then we'll send synthetic callbacks as though it were
destroyed and |
| 137 * recreated. Note that |format| must be either OPAQUE or TRANSLUCENT. |
| 138 */ |
| 139 public void requestSurface(int format) { |
| 140 mRequestedByClient = (format == PixelFormat.TRANSLUCENT) ? mTranslucent
: mOpaque; |
| 141 |
| 142 // If destruction is pending, then we must wait for it to complete. Whe
n we're notified |
| 143 // that it is destroyed, we'll re-start construction if the client still
wants this surface. |
| 144 // Note that we could send a surfaceDestroyed for the owned surface, if
there is one, but we |
| 145 // defer it until later so that the client can still use it until the ne
w one is ready. |
| 146 if (mRequestedByClient.destroyPending) return; |
| 147 |
| 148 // The requested surface isn't being torn down. |
| 149 |
| 150 // If the surface isn't attached yet, then attach it. Otherwise, we're
still waiting for |
| 151 // the surface to be created, or we've already received surfaceCreated f
or it. |
| 152 if (!mRequestedByClient.isAttached()) { |
| 153 attachSurfaceNow(mRequestedByClient); |
| 154 assert mRequestedByClient.isAttached(); |
| 155 return; |
| 156 } |
| 157 |
| 158 // Surface is not pending destroy, and is attached. See if we need to s
end any synthetic |
| 159 // callbacks to the client. If we're expecting a callback from Android,
then we'll handle |
| 160 // it when it arrives instead. |
| 161 if (mRequestedByClient.createPending) return; |
| 162 |
| 163 // Surface is attached and no create is pending. Send a synthetic creat
e. Note that, if |
| 164 // Android destroyed the surface itself, then we'd have set |createPendi
ng| at that point. |
| 165 // We don't check |isValid| here, since, technically, there could be a d
estroy in flight |
| 166 // from Android. It's okay; we'll just notify the client at that point.
Either way, we |
| 167 // must tell the client that it now owns the surface. |
| 168 |
| 169 // Send a notification about any owned surface. Note that this can be |
mRequestedByClient| |
| 170 // which is fine. We'll send destroy / create for it. Also note that w
e don't actually |
| 171 // start tear-down of the owned surface; the client notifies us via done
WithUnownedSurface |
| 172 // when it is safe to do that. |
| 173 disownClientSurface(mOwnedByClient); |
| 174 |
| 175 // The client now owns |mRequestedByClient|. Notify it that it's ready. |
| 176 mOwnedByClient = mRequestedByClient; |
| 177 mClient.surfaceCreated(mOwnedByClient.surfaceHolder()); |
| 178 |
| 179 // See if we're expecting a surfaceChanged. If not, then send a synthet
ic one. |
| 180 if (mOwnedByClient.format != PixelFormat.UNKNOWN) { |
| 181 mClient.surfaceChanged(mOwnedByClient.surfaceHolder(), mOwnedByClien
t.width, |
| 182 mOwnedByClient.height, mOwnedByClient.format); |
| 183 } |
| 184 } |
| 185 |
| 186 /** |
| 187 * Called to notify us that the client no longer needs the surface that it d
oesn't own. This |
| 188 * tells us that we may destroy it. Note that it's okay if it never had an
unowned surface. |
| 189 */ |
| 190 public void doneWithUnownedSurface() { |
| 191 if (mOwnedByClient == null) return; |
| 192 |
| 193 SurfaceState unowned = (mOwnedByClient == mTranslucent) ? mOpaque : mTra
nslucent; |
| 194 |
| 195 if (mRequestedByClient == unowned) { |
| 196 // Client is giving us back a surface that it's since requested but
hasn't gotten yet. |
| 197 // Do nothing. It will be notified when the new surface is ready, a
nd it can call us |
| 198 // again for the other surface, if it wants. |
| 199 return; |
| 200 } |
| 201 |
| 202 // Start destruction of this surface. To prevent recursive call-backs t
o the client, we |
| 203 // post this for later. |
| 204 detachSurfaceLater(unowned); |
| 205 } |
| 206 |
| 207 /** |
| 208 * Return the currently owned SurfaceHolder, if any. |
| 209 */ |
| 210 public SurfaceHolder getHolder() { |
| 211 return mOwnedByClient != null ? mOwnedByClient.surfaceHolder() : null; |
| 212 } |
| 213 |
| 214 /** |
| 215 * Destroy and re-create the surface. Useful for a JB workaround needed by
CompositorView. |
| 216 */ |
| 217 public void recreateSurfaceForJellyBean() { |
| 218 // If they don't have a surface, then they'll get a new one anyway. |
| 219 if (mOwnedByClient == null) return; |
| 220 |
| 221 // Notify the client that it no longer owns this surface, then destroy i
t. When destruction |
| 222 // completes, we will recreate it automatically, since it will look like
the client since |
| 223 // re-requested it. That's why we send surfaceDestroyed here rather tha
n letting our |
| 224 // surfaceDestroyed do it when destruction completes. If we just starte
d destruction while |
| 225 // the client still owns the surface, then our surfaceDestroyed would as
sume that Android |
| 226 // initiated the destruction, and wait for Android to recreate it. |
| 227 |
| 228 mParentView.post(new Runnable() { |
| 229 @Override |
| 230 public void run() { |
| 231 if (mOwnedByClient == null) return; |
| 232 SurfaceState owned = mOwnedByClient; |
| 233 mClient.surfaceDestroyed(mOwnedByClient.surfaceHolder()); |
| 234 mOwnedByClient = null; |
| 235 detachSurfaceNow(owned); |
| 236 } |
| 237 }); |
| 238 } |
| 239 |
| 240 @Override |
| 241 public void surfaceChanged(SurfaceHolder holder, int format, int width, int
height) { |
| 242 SurfaceState state = getStateForHolder(holder); |
| 243 assert state != null; |
| 244 |
| 245 // If this is the surface that the client currently cares about, then no
tify the client. |
| 246 // Note that surfaceChanged is guaranteed to come only after surfaceCrea
ted. Also, if the |
| 247 // client has requested a different surface but hasn't gotten it yet, th
en skip this. |
| 248 if (state == mOwnedByClient && state == mRequestedByClient) { |
| 249 state.width = width; |
| 250 state.height = height; |
| 251 state.format = format; |
| 252 mClient.surfaceChanged(holder, format, width, height); |
| 253 } |
| 254 } |
| 255 |
| 256 @Override |
| 257 public void surfaceCreated(SurfaceHolder holder) { |
| 258 SurfaceState state = getStateForHolder(holder); |
| 259 assert state != null; |
| 260 // Note that |createPending| might not be set, if Android destroyed and
recreated this |
| 261 // surface on its own. |
| 262 |
| 263 if (state != mRequestedByClient) { |
| 264 // Surface is created, but it's not the one that's been requested mo
st recently. Just |
| 265 // destroy it again. |
| 266 detachSurfaceLater(state); |
| 267 return; |
| 268 } |
| 269 |
| 270 // No create is pending. |
| 271 state.createPending = false; |
| 272 |
| 273 // A surfaceChanged should arrive. |
| 274 state.format = PixelFormat.UNKNOWN; |
| 275 |
| 276 // The client requested a surface, and it's now available. If the clien
t owns a surface, |
| 277 // then notify it that it doesn't. Note that the client can't own |stat
e| at this point, |
| 278 // since we would have removed ownership when we got surfaceDestroyed.
It's okay if the |
| 279 // client doesn't own either surface. |
| 280 assert mOwnedByClient != state; |
| 281 disownClientSurface(mOwnedByClient); |
| 282 |
| 283 // The client now owns this surface, so notify it. |
| 284 mOwnedByClient = mRequestedByClient; |
| 285 mClient.surfaceCreated(mOwnedByClient.surfaceHolder()); |
| 286 } |
| 287 |
| 288 @Override |
| 289 public void surfaceDestroyed(SurfaceHolder holder) { |
| 290 SurfaceState state = getStateForHolder(holder); |
| 291 assert state != null; |
| 292 |
| 293 // If no destroy is pending, then Android chose to destroy this surface
and will, hopefully, |
| 294 // re-create it at some point. Otherwise, a destroy is either posted or
has already |
| 295 // detached this SurfaceView. If it's already detached, then the destru
ction is complete |
| 296 // and we can clear |destroyPending|. Otherwise, Android has destroyed
this surface while |
| 297 // our destroy was posted, and might even return it before it runs. Whe
n the post runs, it |
| 298 // can sort that out based on whether the surface is valid or not. |
| 299 if (!state.destroyPending) { |
| 300 state.createPending = true; |
| 301 } else if (!state.isAttached()) { |
| 302 state.destroyPending = false; |
| 303 } |
| 304 |
| 305 state.format = PixelFormat.UNKNOWN; |
| 306 |
| 307 // If the client owns this surface, then notify it synchronously that it
no longer does. |
| 308 // This can happen if Android destroys the surface on its own. It's als
o possible that |
| 309 // we've detached it, if a destroy was pending. Either way, notify the
client. |
| 310 if (state == mOwnedByClient) { |
| 311 disownClientSurface(mOwnedByClient); |
| 312 |
| 313 // Do not re-request the surface here. If android gives the surface
back, then we'll |
| 314 // re-signal the client about construction. |
| 315 return; |
| 316 } |
| 317 |
| 318 // The client doesn't own this surface, but might want it. |
| 319 // If the client has requested this surface, then start construction on
it. The client will |
| 320 // be notified when it completes. This can happen if the client re-requ
ests a surface after |
| 321 // we start destruction on it from a previous request, for example. We
post this for later, |
| 322 // since we might be called while removing |state| from the view tree. I
n general, posting |
| 323 // from here is good. |
| 324 if (state == mRequestedByClient && !state.isAttached()) { |
| 325 attachSurfaceLater(state); |
| 326 } else if (state != mRequestedByClient && state.isAttached()) { |
| 327 // This isn't the requested surface. If android destroyed it, then
also unhook it so |
| 328 // that it isn't recreated later. |
| 329 detachSurfaceLater(state); |
| 330 } |
| 331 } |
| 332 |
| 333 /** |
| 334 * Update the background drawable on all surfaces. |
| 335 */ |
| 336 public void setBackgroundDrawable(Drawable background) { |
| 337 mTranslucent.surfaceView.setBackgroundDrawable(background); |
| 338 mOpaque.surfaceView.setBackgroundDrawable(background); |
| 339 } |
| 340 |
| 341 /** |
| 342 * Set |willNotDraw| on all surfaces. |
| 343 */ |
| 344 public void setWillNotDraw(boolean willNotDraw) { |
| 345 mTranslucent.surfaceView.setWillNotDraw(willNotDraw); |
| 346 mOpaque.surfaceView.setWillNotDraw(willNotDraw); |
| 347 } |
| 348 |
| 349 public void setVisibility(int visibility) { |
| 350 mTranslucent.surfaceView.setVisibility(visibility); |
| 351 mOpaque.surfaceView.setVisibility(visibility); |
| 352 } |
| 353 |
| 354 /** |
| 355 * Return the SurfaceState for |holder|, or null if it isn't either. |
| 356 */ |
| 357 private SurfaceState getStateForHolder(SurfaceHolder holder) { |
| 358 if (mTranslucent.surfaceHolder() == holder) return mTranslucent; |
| 359 |
| 360 if (mOpaque.surfaceHolder() == holder) return mOpaque; |
| 361 |
| 362 return null; |
| 363 } |
| 364 |
| 365 /** |
| 366 * Attach |state| to |mParentView| immedaitely. |
| 367 */ |
| 368 private void attachSurfaceNow(SurfaceState state) { |
| 369 if (state.isAttached()) return; |
| 370 |
| 371 // If there is a destroy in-flight for this surface, then do nothing. |
| 372 if (state.destroyPending) return; |
| 373 |
| 374 state.createPending = true; |
| 375 FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( |
| 376 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATC
H_PARENT); |
| 377 state.attachTo(mParentView, lp); |
| 378 mParentView.bringChildToFront(state.surfaceView); |
| 379 mParentView.postInvalidateOnAnimation(); |
| 380 } |
| 381 |
| 382 /** |
| 383 * Post a Runnable to attach |state|. This is helpful, since one cannot dir
ectly interact with |
| 384 * the View heirarchy during Surface callbacks. |
| 385 */ |
| 386 private void attachSurfaceLater(final SurfaceState state) { |
| 387 // We shouldn't try to post construction if there's an in-flight destroy
. |
| 388 assert !state.destroyPending; |
| 389 state.createPending = true; |
| 390 |
| 391 mParentView.post(new Runnable() { |
| 392 @Override |
| 393 public void run() { |
| 394 attachSurfaceNow(state); |
| 395 } |
| 396 }); |
| 397 } |
| 398 |
| 399 /** |
| 400 * Cause the client to disown |state| if it currently owns it. This involve
s notifying it that |
| 401 * the surface has been destroyed (recall that ownership involves getting cr
eated). It's okay |
| 402 * if |state| is null or isn't owned by the client. |
| 403 */ |
| 404 private void disownClientSurface(SurfaceState state) { |
| 405 if (mOwnedByClient != state || state == null) return; |
| 406 |
| 407 mClient.surfaceDestroyed(mOwnedByClient.surfaceHolder()); |
| 408 mOwnedByClient = null; |
| 409 } |
| 410 |
| 411 /** |
| 412 * Detach |state| from |mParentView| immediately. |
| 413 */ |
| 414 private void detachSurfaceNow(SurfaceState state) { |
| 415 // If we're called while we're not attached, then do nothing. This make
s it easier for the |
| 416 // client, since it doesn't have to keep track of whether the outgoing s
urface has been |
| 417 // destroyed or not. The client will be notified (or has already) when
the surface is |
| 418 // destroyed, if it currently owns it. |
| 419 if (state.isAttached()) { |
| 420 // We are attached. If the surface is not valid, then Android has d
estroyed it for some |
| 421 // other reason, and we should clean up. Otherwise, just wait for A
ndroid to finish. |
| 422 final boolean valid = state.isValid(); |
| 423 |
| 424 // If the surface is valid, then we expect a callback to surfaceDest
royed eventually. |
| 425 state.destroyPending = valid; |
| 426 |
| 427 // Note that this might call back surfaceDestroyed before returning! |
| 428 state.detachFromParent(); |
| 429 |
| 430 // If the surface was valid before, then we expect a surfaceDestroye
d callback, which |
| 431 // might have arrived during removeView. Either way, that callback
will finish cleanup |
| 432 // of |state|. |
| 433 if (valid) return; |
| 434 } |
| 435 |
| 436 // The surface isn't attached, or was attached but wasn't currently vali
d. Either way, |
| 437 // we're not going to get a destroy, so notify the client now if needed. |
| 438 disownClientSurface(state); |
| 439 |
| 440 // If the client has since re-requested the surface, then start construc
tion. |
| 441 if (state == mRequestedByClient) attachSurfaceNow(mRequestedByClient); |
| 442 } |
| 443 |
| 444 /** |
| 445 * Post detachment of |state|. This is safe during Surface callbacks. |
| 446 */ |
| 447 private void detachSurfaceLater(final SurfaceState state) { |
| 448 // If |state| is not attached, then do nothing. There might be a destro
y pending from |
| 449 // Android, but in any case leave it be. |
| 450 if (!state.isAttached()) return; |
| 451 |
| 452 state.destroyPending = true; |
| 453 mParentView.post(new Runnable() { |
| 454 @Override |
| 455 public void run() { |
| 456 detachSurfaceNow(state); |
| 457 } |
| 458 }); |
| 459 } |
| 460 } |
OLD | NEW |