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|m | |
Ted C
2017/01/31 23:17:51
there seems to be a trailing m after the final |
liberato (no reviews please)
2017/02/01 19:19:48
thanks. vim column bar @100 is the same color as
| |
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 donw WithUnownedSurface | |
Ted C
2017/01/31 23:17:50
s/donw/done
liberato (no reviews please)
2017/02/01 19:19:48
vim column bar @80 is... maybe i should change the
| |
172 // when it is safe to do that. | |
173 disownClientSurface(mOwnedByClient); | |
Ted C
2017/01/31 23:17:50
Would it be better to post this block of code to m
liberato (no reviews please)
2017/02/01 19:19:47
i think that there are other cases, though rare.
| |
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 /** | |
350 * Return the SurfaceState for |holder|, or null if it isn't either. | |
351 */ | |
352 private SurfaceState getStateForHolder(SurfaceHolder holder) { | |
353 if (mTranslucent.surfaceHolder() == holder) return mTranslucent; | |
354 | |
355 if (mOpaque.surfaceHolder() == holder) return mOpaque; | |
356 | |
357 return null; | |
358 } | |
359 | |
360 /** | |
361 * Attach |state| to |mParentView| immedaitely. | |
362 */ | |
363 private void attachSurfaceNow(SurfaceState state) { | |
364 if (state.isAttached()) return; | |
365 | |
366 // If there is a destroy in-flight for this surface, then do nothing. | |
367 if (state.destroyPending) return; | |
368 | |
369 state.createPending = true; | |
370 FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( | |
371 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATC H_PARENT); | |
372 state.attachTo(mParentView, lp); | |
373 mParentView.bringChildToFront(state.surfaceView); | |
374 mParentView.postInvalidateOnAnimation(); | |
375 } | |
376 | |
377 /** | |
378 * Post a Runnable to attach |state|. This is helpful, since one cannot dir ectly interact with | |
379 * the View heirarchy during Surface callbacks. | |
380 */ | |
381 private void attachSurfaceLater(final SurfaceState state) { | |
382 // We shouldn't try to post construction if there's an in-flight destroy . | |
383 assert !state.destroyPending; | |
384 state.createPending = true; | |
385 | |
386 mParentView.post(new Runnable() { | |
387 @Override | |
388 public void run() { | |
389 attachSurfaceNow(state); | |
390 } | |
391 }); | |
392 } | |
393 | |
394 /** | |
395 * Cause the client to disown |state| if it currently owns it. This involve s notifying it that | |
396 * the surface has been destroyed (recall that ownership involves getting cr eated). It's okay | |
397 * if |state| is null or isn't owned by the client. | |
398 */ | |
399 private void disownClientSurface(SurfaceState state) { | |
400 if (mOwnedByClient != state || state == null) return; | |
401 | |
402 mClient.surfaceDestroyed(mOwnedByClient.surfaceHolder()); | |
403 mOwnedByClient = null; | |
404 } | |
405 | |
406 /** | |
407 * Detach |state| from |mParentView| immediately. | |
408 */ | |
409 private void detachSurfaceNow(SurfaceState state) { | |
410 // If we're called while we're not attached, then do nothing. This make s it easier for the | |
411 // client, since it doesn't have to keep track of whether the outgoing s urface has been | |
412 // destroyed or not. The client will be notified (or has already) when the surface is | |
413 // destroyed, if it currently owns it. | |
414 if (state.isAttached()) { | |
415 // We are attached. If the surface is not valid, then Android has d estroyed it for some | |
416 // other reason, and we should clean up. Otherwise, just wait for A ndroid to finish. | |
417 | |
Ted C
2017/01/31 23:17:50
remove blank line
liberato (no reviews please)
2017/02/01 19:19:48
Done.
| |
418 final boolean valid = state.isValid(); | |
419 | |
420 // If the surface is valid, then we expect a callback to surfaceDest royed eventually. | |
421 state.destroyPending = valid; | |
422 | |
423 // Note that this might call back surfaceDestroyed before returning! | |
424 state.detachFromParent(); | |
425 | |
426 // If the surface was valid before, then we expect a surfaceDestroye d callback, which | |
427 // might have arrived during removeView. Either way, that callback will finish cleanup | |
428 // of |state|. | |
429 if (valid) return; | |
430 } | |
431 | |
432 // The surface isn't attached, or was attached but wasn't currently vali d. Either way, | |
433 // we're not going to get a destroy, so notify the client now if needed. | |
434 disownClientSurface(state); | |
435 | |
436 // If the client has since re-requested the surface, then start construc tion. | |
437 if (state == mRequestedByClient) attachSurfaceNow(mRequestedByClient); | |
438 } | |
439 | |
440 /** | |
441 * Post detachment of |state|. This is safe during Surface callbacks. | |
442 */ | |
443 private void detachSurfaceLater(final SurfaceState state) { | |
444 // If |state| is not attached, then do nothing. There might be a destro y pending from | |
445 // Android, but in any case leave it be. | |
446 if (!state.isAttached()) return; | |
447 | |
448 state.destroyPending = true; | |
449 mParentView.post(new Runnable() { | |
450 @Override | |
451 public void run() { | |
452 detachSurfaceNow(state); | |
453 } | |
454 }); | |
455 } | |
456 } | |
OLD | NEW |