Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(219)

Side by Side Diff: chrome/android/junit/src/org/chromium/chrome/browser/compositor/CompositorSurfaceManagerTest.java

Issue 2201483002: Improve transition between opaque and translucent compositor views. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: unit tests Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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.app.Activity;
8 import android.graphics.PixelFormat;
9 import android.view.Surface;
10 import android.view.SurfaceHolder;
11 import android.view.SurfaceView;
12 import android.widget.FrameLayout;
13
14 import static org.junit.Assert.assertEquals;
15 import static org.junit.Assert.assertTrue;
16 import static org.mockito.ArgumentMatchers.anyInt;
17 import static org.mockito.ArgumentMatchers.eq;
18 import static org.mockito.Mockito.times;
19 import static org.mockito.Mockito.verify;
20
21 import org.junit.Before;
22 import org.junit.Test;
23 import org.junit.runner.RunWith;
24 import org.mockito.ArgumentMatchers;
25 import org.mockito.Mock;
26 import org.mockito.MockitoAnnotations;
27 import org.robolectric.Robolectric;
28 import org.robolectric.Shadows;
29 import org.robolectric.annotation.Config;
30 import org.robolectric.annotation.Implementation;
31 import org.robolectric.annotation.Implements;
32 import org.robolectric.annotation.RealObject;
33 import org.robolectric.shadows.ShadowLooper;
34 import org.robolectric.shadows.ShadowSurfaceView;
35
36 import org.chromium.base.test.util.Feature;
37 import org.chromium.testing.local.LocalRobolectricTestRunner;
38
39 import java.util.Set;
40
41 /**
42 * Unit tests for the CompositorSurfaceManager.
43 */
44 @RunWith(LocalRobolectricTestRunner.class)
45 @Config(manifest = Config.NONE)
46 public class CompositorSurfaceManagerTest {
47 @Mock
48 private SurfaceHolder.Callback mCallback;
49
50 private CompositorSurfaceManager mManager;
51
52 private FrameLayout mLayout;
53
54 /**
55 * Implementation of a SurfaceView shadow that provides additional functiona lity for controlling
56 * the state of the underlying (fake) Surface.
57 */
58 @Implements(SurfaceView.class)
59 public static class MyShadowSurfaceView extends ShadowSurfaceView {
60 @RealObject
61 private SurfaceView mRealSurfaceView;
Ted C 2017/01/31 23:17:51 Not familiar with RealObject, but this doesn't see
liberato (no reviews please) 2017/02/01 19:19:48 docs made me think that it was needed, but seems n
62
63 private final MyFakeSurfaceHolder mHolder = new MyFakeSurfaceHolder();
64
65 /**
66 * Robolectric's FakeSurfaceHolder doesn't keep track of the format, etc .
67 */
68 public static class MyFakeSurfaceHolder extends ShadowSurfaceView.FakeSu rfaceHolder {
69 /**
70 * Fake surface that lets us control whether it's valid or not.
71 */
72 public static class MyFakeSurface extends Surface {
73 public boolean valid = false;
74
75 @Override
76 public boolean isValid() {
77 return valid;
78 }
79 }
80
81 private int mFormat = PixelFormat.UNKNOWN;
82 private final MyFakeSurface mSurface = new MyFakeSurface();
83
84 @Implementation
85 public void setFormat(int format) {
86 mFormat = format;
87 }
88
89 public int getFormat() {
90 return mFormat;
91 }
92
93 // Return a surface that we can control if it's valid or not.
94 @Override
95 public Surface getSurface() {
96 return getFakeSurface();
97 }
98
99 public MyFakeSurface getFakeSurface() {
100 return mSurface;
101 }
102 }
103
104 public MyShadowSurfaceView() {}
105
106 @Implementation
107 public SurfaceHolder getHolder() {
108 return getMyFakeSurfaceHolder();
109 }
110
111 @Override
112 public FakeSurfaceHolder getFakeSurfaceHolder() {
113 return getMyFakeSurfaceHolder();
114 }
115
116 public MyFakeSurfaceHolder getMyFakeSurfaceHolder() {
117 return mHolder;
118 }
119 }
120
121 @Before
122 public void beforeTest() {
123 MockitoAnnotations.initMocks(this);
124 Activity activity = Robolectric.buildActivity(Activity.class).setup().ge t();
125 mLayout = new FrameLayout(activity);
126 mManager = new CompositorSurfaceManager(mLayout, mCallback);
127 }
128
129 private void runDelayedTasks() {
130 ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
131 }
132
133 /**
134 * Return the callback for |view|, or null. Will get mad if there's more th an one.
135 */
136 private SurfaceHolder.Callback callbackFor(SurfaceView view) {
137 MyShadowSurfaceView viewShadow = (MyShadowSurfaceView) Shadows.shadowOf( view);
138 ShadowSurfaceView.FakeSurfaceHolder viewHolder = viewShadow.getFakeSurfa ceHolder();
139 Set<SurfaceHolder.Callback> callbacks = viewHolder.getCallbacks();
140 // Zero or one is okay.
141 assertTrue(callbacks.size() < 2);
Ted C 2017/01/31 23:17:51 I don't know if we have access to it, but it would
liberato (no reviews please) 2017/02/01 19:19:48 Done.
142
143 if (callbacks.size() == 1) return callbacks.iterator().next();
144
145 return null;
146 }
147
148 private MyShadowSurfaceView.MyFakeSurfaceHolder fakeHolderFor(SurfaceView vi ew) {
149 MyShadowSurfaceView viewShadow = (MyShadowSurfaceView) Shadows.shadowOf( view);
150 return viewShadow.getMyFakeSurfaceHolder();
151 }
152
153 private void setSurfaceValid(SurfaceView view, boolean valid) {
154 fakeHolderFor(view).getFakeSurface().valid = valid;
155 }
156
157 /**
158 * Find and return the SurfaceView with format |format|.
159 */
160 private SurfaceView findSurface(int format) {
161 final int childCount = mLayout.getChildCount();
162 for (int i = 0; i < childCount; i++) {
163 final SurfaceView child = (SurfaceView) mLayout.getChildAt(i);
164 if (fakeHolderFor(child).getFormat() == format) return child;
165 }
166
167 return null;
168 }
169
170 /**
171 * Request the pixel format |format|, and return the SurfaceView for it if i t's attached.
172 */
173 private SurfaceView requestSurface(int format) {
174 mManager.requestSurface(format);
175 runDelayedTasks();
176
177 return findSurface(format);
178 }
179
180 /**
181 * Request format |format|, and send create / changed for it.
182 */
183 private SurfaceView requestAndGet(int format) {
Ted C 2017/01/31 23:17:51 Hmm...it would be hard to understand the differenc
liberato (no reviews please) 2017/02/01 19:19:48 i switched this to requestThenCreateSurface, and u
184 SurfaceView view = requestSurface(format);
185 setSurfaceValid(view, true);
186 callbackFor(view).surfaceCreated(view.getHolder());
187 final int actualFormat =
188 (format == PixelFormat.OPAQUE) ? PixelFormat.RGB_565 : PixelForm at.RGBA_8888;
189 final int width = 320;
190 final int height = 240;
191 callbackFor(view).surfaceChanged(view.getHolder(), actualFormat, width, height);
192
193 return view;
194 }
195
196 @Test
197 @Feature("Compositor")
198 @Config(shadows = {MyShadowSurfaceView.class})
199 public void testRequestOpaqueSurface() {
200 // Request a SurfaceView, and test in detail that it worked.
201 SurfaceView opaque = requestSurface(PixelFormat.OPAQUE);
202 verify(mCallback, times(0)).surfaceCreated(ArgumentMatchers.<SurfaceHold er>any());
203 verify(mCallback, times(0))
204 .surfaceChanged(
205 ArgumentMatchers.<SurfaceHolder>any(), anyInt(), anyInt( ), anyInt());
206 verify(mCallback, times(0)).surfaceDestroyed(ArgumentMatchers.<SurfaceHo lder>any());
207
208 // Check that there's an opaque SurfaceView .
209 assertEquals(1, mLayout.getChildCount());
210 assertTrue(fakeHolderFor(opaque).getFormat() == PixelFormat.OPAQUE);
211
212 // Verify that we are notified when the surface is created.
213 callbackFor(opaque).surfaceCreated(opaque.getHolder());
214 verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
215 verify(mCallback, times(0)).surfaceDestroyed(ArgumentMatchers.<SurfaceHo lder>any());
216
217 // Verify that we are notified when the surface is changed.
218 final int format = PixelFormat.RGB_565;
219 final int width = 320;
220 final int height = 240;
221 callbackFor(opaque).surfaceChanged(opaque.getHolder(), format, width, he ight);
222 verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
223 verify(mCallback, times(1)).surfaceChanged(opaque.getHolder(), format, w idth, height);
224 verify(mCallback, times(0)).surfaceDestroyed(ArgumentMatchers.<SurfaceHo lder>any());
225
226 // Verify that we are notified when the surface is destroyed.
227 callbackFor(opaque).surfaceDestroyed(opaque.getHolder());
228 verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
229 verify(mCallback, times(1)).surfaceChanged(opaque.getHolder(), format, w idth, height);
230 verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder());
231 }
232
233 @Test
234 @Feature("Compositor")
235 @Config(shadows = {MyShadowSurfaceView.class})
236 public void testRequestOpaqueThenTranslucentSurface() {
237 // Request opaque then translucent.
238 SurfaceView opaque = requestAndGet(PixelFormat.OPAQUE);
239 SurfaceView translucent = requestAndGet(PixelFormat.TRANSLUCENT);
240
241 // Verify that we received a destroy for |opaque| and created / changed for |translucent|.
242 verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder());
243 verify(mCallback, times(1)).surfaceCreated(translucent.getHolder());
244 verify(mCallback, times(1))
245 .surfaceChanged(eq(translucent.getHolder()), anyInt(), anyInt(), anyInt());
246
247 // Both views should be present.
248 assertEquals(2, mLayout.getChildCount());
249
250 // Only the translucent surface should be left. Note that the old view is still valid.
251 mManager.doneWithUnownedSurface();
252 runDelayedTasks();
253 assertEquals(1, mLayout.getChildCount());
254 assertTrue(findSurface(PixelFormat.TRANSLUCENT) != null);
Ted C 2017/01/31 23:17:51 would assertNotNull work here?
liberato (no reviews please) 2017/02/01 19:19:48 done, and elsewhere.
255 }
256
257 @Test
258 @Feature("Compositor")
259 @Config(shadows = {MyShadowSurfaceView.class})
260 public void testRequestSameSurface() {
261 // Request an opaque surface, get it, then request it again. Verify tha t we get synthetic
262 // create / destroy callbacks.
263 SurfaceView opaque = requestAndGet(PixelFormat.OPAQUE);
264 verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
265 verify(mCallback, times(1))
266 .surfaceChanged(eq(opaque.getHolder()), anyInt(), anyInt(), anyI nt());
267 verify(mCallback, times(0)).surfaceDestroyed(opaque.getHolder());
268
269 // Surface is curerntly valid. Request again. We should get back a des troy and create.
270 assertEquals(opaque, requestSurface(PixelFormat.OPAQUE));
271 verify(mCallback, times(2)).surfaceCreated(opaque.getHolder());
272 verify(mCallback, times(2))
273 .surfaceChanged(eq(opaque.getHolder()), anyInt(), anyInt(), anyI nt());
274 verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder());
275 assertEquals(1, mLayout.getChildCount());
276 }
277
278 @Test
279 @Feature("Compositor")
280 @Config(shadows = {MyShadowSurfaceView.class})
281 public void testRequestSameSurfaceBeforeReady() {
282 // Request an opaque surface, then request it again before the first one shows up.
283 SurfaceView opaque = requestSurface(PixelFormat.OPAQUE);
284 verify(mCallback, times(0)).surfaceCreated(opaque.getHolder());
285 verify(mCallback, times(0))
286 .surfaceChanged(eq(opaque.getHolder()), anyInt(), anyInt(), anyI nt());
287 verify(mCallback, times(0)).surfaceDestroyed(opaque.getHolder());
288
289 // Request again. We shouldn't get any callbacks, since the surface is still pending.
290 assertEquals(opaque, requestSurface(PixelFormat.OPAQUE));
291 verify(mCallback, times(0)).surfaceCreated(opaque.getHolder());
292 verify(mCallback, times(0))
293 .surfaceChanged(eq(opaque.getHolder()), anyInt(), anyInt(), anyI nt());
294 verify(mCallback, times(0)).surfaceDestroyed(opaque.getHolder());
295
296 // Only the opaque view should be attached.
297 assertEquals(1, mLayout.getChildCount());
298
299 // When the surface is created, we should get notified created / changed , but not destroyed.
300 callbackFor(opaque).surfaceCreated(opaque.getHolder());
301 verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
302
303 callbackFor(opaque).surfaceChanged(opaque.getHolder(), PixelFormat.RGB_5 65, 320, 240);
304 verify(mCallback, times(1))
305 .surfaceChanged(eq(opaque.getHolder()), anyInt(), anyInt(), anyI nt());
306 verify(mCallback, times(0)).surfaceDestroyed(opaque.getHolder());
307 }
308
309 @Test
310 @Feature("Compositor")
311 @Config(shadows = {MyShadowSurfaceView.class})
312 public void testRequestDifferentSurfacesBeforeReady() {
313 // Request an opaque surface, then request the translucent one before th e it one shows up.
314 SurfaceView opaque = requestSurface(PixelFormat.OPAQUE);
315 verify(mCallback, times(0)).surfaceCreated(opaque.getHolder());
316 verify(mCallback, times(0))
317 .surfaceChanged(eq(opaque.getHolder()), anyInt(), anyInt(), anyI nt());
318 verify(mCallback, times(0)).surfaceDestroyed(opaque.getHolder());
319
320 // Request translucent. We should get no callbacks, but both views shou ld be attached.
321 SurfaceView translucent = requestSurface(PixelFormat.TRANSLUCENT);
322 verify(mCallback, times(0)).surfaceCreated(opaque.getHolder());
323 verify(mCallback, times(0)).surfaceCreated(translucent.getHolder());
324 assertEquals(2, mLayout.getChildCount());
325
326 // If the opaque surface arrives, we shouldn't hear about it. It should be detached, since
327 // we've requested the other one.
328 callbackFor(opaque).surfaceCreated(opaque.getHolder());
329 runDelayedTasks();
330 assertEquals(1, mLayout.getChildCount());
331 verify(mCallback, times(0)).surfaceCreated(opaque.getHolder());
332 verify(mCallback, times(0)).surfaceCreated(translucent.getHolder());
333 verify(mCallback, times(0)).surfaceDestroyed(opaque.getHolder());
334
335 // When we create the translucent surface, we should be notified.
336 callbackFor(translucent).surfaceCreated(translucent.getHolder());
337 verify(mCallback, times(0)).surfaceCreated(opaque.getHolder());
338 verify(mCallback, times(1)).surfaceCreated(translucent.getHolder());
339 }
340
341 @Test
342 @Feature("Compositor")
343 @Config(shadows = {MyShadowSurfaceView.class})
344 public void testPendingSurfaceChangedCallback() {
345 // Request an opaque surface, and request it again between 'created' and 'changed'. We
346 // should get a synthetic 'created', but a real 'changed' callback.
347 SurfaceView opaque = requestSurface(PixelFormat.OPAQUE);
348 callbackFor(opaque).surfaceCreated(opaque.getHolder());
349 runDelayedTasks();
350
351 // Sanity check.
352 verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
353 verify(mCallback, times(0))
354 .surfaceChanged(eq(opaque.getHolder()), anyInt(), anyInt(), anyI nt());
355
356 // Re-request while 'changed' is still pending. We should get a synthet ic 'created'.
357 assertEquals(opaque, requestSurface(PixelFormat.OPAQUE));
358 verify(mCallback, times(2)).surfaceCreated(opaque.getHolder());
Ted C 2017/01/31 23:17:51 For the normal SurfaceView lifetime, should you al
liberato (no reviews please) 2017/02/01 19:19:48 we should get a synthetic destroyed too. i've add
359 verify(mCallback, times(0))
360 .surfaceChanged(eq(opaque.getHolder()), anyInt(), anyInt(), anyI nt());
361
362 // Send 'changed', and expect that we'll receive it.
363 callbackFor(opaque).surfaceChanged(opaque.getHolder(), PixelFormat.RGB_5 65, 320, 240);
364 verify(mCallback, times(1))
365 .surfaceChanged(eq(opaque.getHolder()), anyInt(), anyInt(), anyI nt());
366 }
367
368 @Test
369 @Feature("Compositor")
370 @Config(shadows = {MyShadowSurfaceView.class})
371 public void testJellyBeanWorkaround() {
372 // See if recreateSurfaceForJellyBean destroys / re-creates the surface.
373 // should get a synthetic 'created', but a real 'changed' callback.
374 SurfaceView opaque = requestAndGet(PixelFormat.OPAQUE);
375 verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
376 assertEquals(1, mLayout.getChildCount());
377
378 // We should be notified that the surface was destroyed via synthetic ca llback, and the
379 // surface should be detached.
380 mManager.recreateSurfaceForJellyBean();
381 verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
382 verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder());
383 assertEquals(0, mLayout.getChildCount());
384
385 // When the surface really is destroyed, it should be re-attached. We s hould not be
386 // notified again, though.
387 callbackFor(opaque).surfaceDestroyed(opaque.getHolder());
388 verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
389 verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder());
390 assertEquals(1, mLayout.getChildCount());
391
392 // When the surface is re-created, we should be notified.
393 callbackFor(opaque).surfaceCreated(opaque.getHolder());
394 verify(mCallback, times(2)).surfaceCreated(opaque.getHolder());
395 verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder());
396 assertEquals(1, mLayout.getChildCount());
397 }
398
399 @Test
400 @Feature("Compositor")
401 @Config(shadows = {MyShadowSurfaceView.class})
402 public void testRequestSurfaceDuringDestruction() {
403 // If we re-request a surface while we're tearing it down, it should be re-attached and
404 // given back to us once the destruction completes.
405 SurfaceView opaque = requestAndGet(PixelFormat.OPAQUE);
406 SurfaceView translucent = requestAndGet(PixelFormat.TRANSLUCENT);
407 mManager.doneWithUnownedSurface();
408
409 // The transparent surface should be attached, and the opaque one detach ed.
410 assertEquals(1, mLayout.getChildCount());
411 assertTrue(findSurface(PixelFormat.TRANSLUCENT) != null);
412
413 // Re-request the opaque surface. Nothing should happen until it's dest royed. It should
414 // not be re-attached, since that is also deferred until destruction.
415 assertEquals(null, requestSurface(PixelFormat.OPAQUE));
416 assertEquals(1, mLayout.getChildCount());
417 assertTrue(findSurface(PixelFormat.TRANSLUCENT) != null);
418
419 // When the opaque surface is destroyed, then it should be re-attached. No callbacks shoud
420 // have arrived yet, except for initial creation and (synthetic) destroy ed when we got the
421 // translucent surface.
422 callbackFor(opaque).surfaceDestroyed(opaque.getHolder());
423 assertEquals(2, mLayout.getChildCount());
424 verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
425 verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder());
426 verify(mCallback, times(0)).surfaceDestroyed(translucent.getHolder());
427
428 // When the opaque surface becomes available, we'll get the synthetic de stroy for the
429 // translucent one that we lost ownership of, and the real create for th e opaque one.
430 callbackFor(opaque).surfaceCreated(opaque.getHolder());
431 assertEquals(2, mLayout.getChildCount());
432 verify(mCallback, times(2)).surfaceCreated(opaque.getHolder());
433 verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder());
434 verify(mCallback, times(1)).surfaceDestroyed(translucent.getHolder());
435 }
436 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698