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.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 } | |
OLD | NEW |