1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wm;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertNotEquals;
22 import static org.junit.Assert.assertTrue;
23 import static org.mockito.Mockito.never;
24 import static org.mockito.Mockito.spy;
25 import static org.mockito.Mockito.verify;
26 
27 import android.platform.test.annotations.Presubmit;
28 import android.view.SurfaceControl;
29 import android.window.SurfaceSyncGroup;
30 
31 import androidx.test.filters.SmallTest;
32 
33 import com.android.server.testutils.StubTransaction;
34 
35 import org.junit.After;
36 import org.junit.Before;
37 import org.junit.Test;
38 
39 import java.util.concurrent.CountDownLatch;
40 import java.util.concurrent.Executor;
41 import java.util.concurrent.TimeUnit;
42 
43 @SmallTest
44 @Presubmit
45 public class SurfaceSyncGroupTest {
46     private static final String TAG = "SurfaceSyncGroupTest";
47     private static final int TIMEOUT_MS = 100;
48 
49     private final Executor mExecutor = Runnable::run;
50 
51     @Before
setup()52     public void setup() {
53         SurfaceSyncGroup.setTransactionFactory(StubTransaction::new);
54     }
55 
56     @After
tearDown()57     public void tearDown() {
58         SurfaceSyncGroup.setTransactionFactory(SurfaceControl.Transaction::new);
59     }
60 
61     @Test
testSyncOne()62     public void testSyncOne() throws InterruptedException {
63         final CountDownLatch finishedLatch = new CountDownLatch(1);
64         SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG);
65         syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
66         SurfaceSyncGroup syncTarget = new SurfaceSyncGroup("FakeSyncTarget");
67         syncGroup.add(syncTarget, null /* runnable */);
68         syncGroup.markSyncReady();
69 
70         syncTarget.markSyncReady();
71 
72         finishedLatch.await(5, TimeUnit.SECONDS);
73         assertEquals(0, finishedLatch.getCount());
74     }
75 
76     @Test
testSyncMultiple()77     public void testSyncMultiple() throws InterruptedException {
78         final CountDownLatch finishedLatch = new CountDownLatch(1);
79         SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG);
80         syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
81         SurfaceSyncGroup syncTarget1 = new SurfaceSyncGroup("FakeSyncTarget1");
82         SurfaceSyncGroup syncTarget2 = new SurfaceSyncGroup("FakeSyncTarget2");
83         SurfaceSyncGroup syncTarget3 = new SurfaceSyncGroup("FakeSyncTarget3");
84 
85         syncGroup.add(syncTarget1, null /* runnable */);
86         syncGroup.add(syncTarget2, null /* runnable */);
87         syncGroup.add(syncTarget3, null /* runnable */);
88         syncGroup.markSyncReady();
89 
90         syncTarget1.markSyncReady();
91         assertNotEquals(0, finishedLatch.getCount());
92 
93         syncTarget3.markSyncReady();
94         assertNotEquals(0, finishedLatch.getCount());
95 
96         syncTarget2.markSyncReady();
97 
98         finishedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
99         assertEquals(0, finishedLatch.getCount());
100     }
101 
102     @Test
testAddSyncWhenSyncComplete()103     public void testAddSyncWhenSyncComplete() {
104         SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG);
105 
106         SurfaceSyncGroup syncTarget1 = new SurfaceSyncGroup("FakeSyncTarget1");
107         SurfaceSyncGroup syncTarget2 = new SurfaceSyncGroup("FakeSyncTarget2");
108 
109         assertTrue(syncGroup.add(syncTarget1, null /* runnable */));
110         syncGroup.markSyncReady();
111         // Adding to a sync that has been completed is also invalid since the sync id has been
112         // cleared.
113         assertFalse(syncGroup.add(syncTarget2, null /* runnable */));
114     }
115 
116     @Test
testMultipleSyncGroups()117     public void testMultipleSyncGroups() throws InterruptedException {
118         final CountDownLatch finishedLatch1 = new CountDownLatch(1);
119         final CountDownLatch finishedLatch2 = new CountDownLatch(1);
120         SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup(TAG);
121         SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup(TAG);
122 
123         syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
124         syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
125 
126         SurfaceSyncGroup syncTarget1 = new SurfaceSyncGroup("FakeSyncTarget1");
127         SurfaceSyncGroup syncTarget2 = new SurfaceSyncGroup("FakeSyncTarget2");
128 
129         assertTrue(syncGroup1.add(syncTarget1, null /* runnable */));
130         assertTrue(syncGroup2.add(syncTarget2, null /* runnable */));
131         syncGroup1.markSyncReady();
132         syncGroup2.markSyncReady();
133 
134         syncTarget1.markSyncReady();
135 
136         finishedLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
137         assertEquals(0, finishedLatch1.getCount());
138         assertNotEquals(0, finishedLatch2.getCount());
139 
140         syncTarget2.markSyncReady();
141 
142         finishedLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
143         assertEquals(0, finishedLatch2.getCount());
144     }
145 
146     @Test
testAddSyncGroup()147     public void testAddSyncGroup() throws InterruptedException {
148         final CountDownLatch finishedLatch1 = new CountDownLatch(1);
149         final CountDownLatch finishedLatch2 = new CountDownLatch(1);
150         SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup(TAG);
151         SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup(TAG);
152 
153         syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
154         syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
155 
156         SurfaceSyncGroup syncTarget1 = new SurfaceSyncGroup("FakeSyncTarget1");
157         SurfaceSyncGroup syncTarget2 = new SurfaceSyncGroup("FakeSyncTarget2");
158 
159         assertTrue(syncGroup1.add(syncTarget1, null /* runnable */));
160         assertTrue(syncGroup2.add(syncTarget2, null /* runnable */));
161         syncGroup1.markSyncReady();
162         syncGroup2.add(syncGroup1, null /* runnable */);
163         syncGroup2.markSyncReady();
164 
165         // Finish syncTarget2 first to test that the syncGroup is not complete until the merged sync
166         // is also done.
167         syncTarget2.markSyncReady();
168         finishedLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
169         // Sync did not complete yet
170         assertNotEquals(0, finishedLatch2.getCount());
171 
172         syncTarget1.markSyncReady();
173 
174         // The first sync will still get a callback when it's sync requirements are done.
175         finishedLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
176         assertEquals(0, finishedLatch1.getCount());
177 
178         finishedLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
179         assertEquals(0, finishedLatch2.getCount());
180     }
181 
182     @Test
testAddSyncAlreadyComplete()183     public void testAddSyncAlreadyComplete() throws InterruptedException {
184         final CountDownLatch finishedLatch1 = new CountDownLatch(1);
185         final CountDownLatch finishedLatch2 = new CountDownLatch(1);
186         SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup(TAG);
187         SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup(TAG);
188 
189         syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
190         syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
191 
192         SurfaceSyncGroup syncTarget1 = new SurfaceSyncGroup("FakeSyncTarget1");
193         SurfaceSyncGroup syncTarget2 = new SurfaceSyncGroup("FakeSyncTarget2");
194 
195         assertTrue(syncGroup1.add(syncTarget1, null /* runnable */));
196         assertTrue(syncGroup2.add(syncTarget2, null /* runnable */));
197         syncGroup1.markSyncReady();
198         syncTarget1.markSyncReady();
199 
200         // The first sync will still get a callback when it's sync requirements are done.
201         finishedLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
202         assertEquals(0, finishedLatch1.getCount());
203 
204         syncGroup2.add(syncGroup1, null /* runnable */);
205         syncGroup2.markSyncReady();
206         syncTarget2.markSyncReady();
207 
208         // Verify that the second sync will receive complete since the merged sync was already
209         // completed before the merge.
210         finishedLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
211         assertEquals(0, finishedLatch2.getCount());
212     }
213 
214     @Test
testAddSyncAlreadyInASync_NewSyncReadyFirst()215     public void testAddSyncAlreadyInASync_NewSyncReadyFirst() throws InterruptedException {
216         final CountDownLatch finishedLatch1 = new CountDownLatch(1);
217         final CountDownLatch finishedLatch2 = new CountDownLatch(1);
218         SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup(TAG);
219         SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup(TAG);
220 
221         syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
222         syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
223 
224         SurfaceSyncGroup syncTarget1 = new SurfaceSyncGroup("FakeSyncTarget1");
225         SurfaceSyncGroup syncTarget2 = new SurfaceSyncGroup("FakeSyncTarget2");
226         SurfaceSyncGroup syncTarget3 = new SurfaceSyncGroup("FakeSyncTarget3");
227 
228         assertTrue(syncGroup1.add(syncTarget1, null /* runnable */));
229         assertTrue(syncGroup1.add(syncTarget2, null /* runnable */));
230 
231         // Add syncTarget1 to syncGroup2 so it forces syncGroup1 into syncGroup2
232         assertTrue(syncGroup2.add(syncTarget1, null /* runnable */));
233         assertTrue(syncGroup2.add(syncTarget3, null /* runnable */));
234 
235         syncGroup1.markSyncReady();
236         syncGroup2.markSyncReady();
237 
238         // Make target1 and target3 ready, but not target2. SyncGroup2 should not be ready since
239         // SyncGroup2 also waits for all of SyncGroup1 to finish, which includes target2
240         syncTarget1.markSyncReady();
241         syncTarget3.markSyncReady();
242 
243         // Neither SyncGroup will be ready.
244         finishedLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
245         finishedLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
246 
247         assertEquals(1, finishedLatch1.getCount());
248         assertEquals(1, finishedLatch2.getCount());
249 
250         syncTarget2.markSyncReady();
251 
252         // Both sync groups should be ready after target2 completed.
253         finishedLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
254         finishedLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
255         assertEquals(0, finishedLatch1.getCount());
256         assertEquals(0, finishedLatch2.getCount());
257     }
258 
259     @Test
testAddSyncAlreadyInASync_OldSyncFinishesFirst()260     public void testAddSyncAlreadyInASync_OldSyncFinishesFirst() throws InterruptedException {
261         final CountDownLatch finishedLatch1 = new CountDownLatch(1);
262         final CountDownLatch finishedLatch2 = new CountDownLatch(1);
263         SurfaceSyncGroup syncGroup1 = new SurfaceSyncGroup(TAG);
264         SurfaceSyncGroup syncGroup2 = new SurfaceSyncGroup(TAG);
265 
266         syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
267         syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
268 
269         SurfaceSyncGroup syncTarget1 = new SurfaceSyncGroup("FakeSyncTarget1");
270         SurfaceSyncGroup syncTarget2 = new SurfaceSyncGroup("FakeSyncTarget2");
271         SurfaceSyncGroup syncTarget3 = new SurfaceSyncGroup("FakeSyncTarget3");
272 
273         assertTrue(syncGroup1.add(syncTarget1, null /* runnable */));
274         assertTrue(syncGroup1.add(syncTarget2, null /* runnable */));
275         syncTarget2.markSyncReady();
276 
277         // Add syncTarget1 to syncGroup2 so it forces syncGroup1 into syncGroup2
278         assertTrue(syncGroup2.add(syncTarget1, null /* runnable */));
279         assertTrue(syncGroup2.add(syncTarget3, null /* runnable */));
280 
281         syncGroup1.markSyncReady();
282         syncGroup2.markSyncReady();
283 
284         syncTarget1.markSyncReady();
285 
286         // Only SyncGroup1 will be ready, but SyncGroup2 still needs its own targets to be ready.
287         finishedLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
288         finishedLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
289 
290         assertEquals(0, finishedLatch1.getCount());
291         assertEquals(1, finishedLatch2.getCount());
292 
293         syncTarget3.markSyncReady();
294 
295         // SyncGroup2 is finished after target3 completed.
296         finishedLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
297         assertEquals(0, finishedLatch2.getCount());
298     }
299 
300     @Test
testParentSyncGroupMerge_true()301     public void testParentSyncGroupMerge_true() {
302         // Temporarily set a new transaction factory so it will return the stub transaction for
303         // the sync group.
304         SurfaceControl.Transaction parentTransaction = spy(new StubTransaction());
305         SurfaceSyncGroup.setTransactionFactory(() -> parentTransaction);
306 
307         final CountDownLatch finishedLatch = new CountDownLatch(1);
308         SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG);
309         syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
310 
311         SurfaceControl.Transaction targetTransaction = spy(new StubTransaction());
312         SurfaceSyncGroup.setTransactionFactory(() -> targetTransaction);
313 
314         SurfaceSyncGroup syncTarget = new SurfaceSyncGroup("FakeSyncTarget");
315         assertTrue(
316                 syncGroup.add(syncTarget.mISurfaceSyncGroup, true /* parentSyncGroupMerge */,
317                         null /* runnable */));
318         syncTarget.markSyncReady();
319 
320         // When parentSyncGroupMerge is true, the transaction passed in merges the main SyncGroup
321         // transaction first because it knows the previous parentSyncGroup is older so it should
322         // be overwritten by anything newer.
323         verify(targetTransaction).merge(parentTransaction);
324         verify(parentTransaction).merge(targetTransaction);
325     }
326 
327     @Test
testParentSyncGroupMerge_false()328     public void testParentSyncGroupMerge_false() {
329         // Temporarily set a new transaction factory so it will return the stub transaction for
330         // the sync group.
331         SurfaceControl.Transaction parentTransaction = spy(new StubTransaction());
332         SurfaceSyncGroup.setTransactionFactory(() -> parentTransaction);
333 
334         final CountDownLatch finishedLatch = new CountDownLatch(1);
335         SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG);
336         syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
337 
338         SurfaceControl.Transaction targetTransaction = spy(new StubTransaction());
339         SurfaceSyncGroup.setTransactionFactory(() -> targetTransaction);
340 
341         SurfaceSyncGroup syncTarget = new SurfaceSyncGroup("FakeSyncTarget");
342         assertTrue(syncGroup.add(syncTarget, null /* runnable */));
343         syncTarget.markSyncReady();
344 
345         // When parentSyncGroupMerge is false, the transaction passed in should not merge
346         // the main SyncGroup since we don't need to change the transaction order
347         verify(targetTransaction, never()).merge(parentTransaction);
348         verify(parentTransaction).merge(targetTransaction);
349     }
350 
351     @Test
testAddToSameParentNoCrash()352     public void testAddToSameParentNoCrash() {
353         final CountDownLatch finishedLatch = new CountDownLatch(1);
354         SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG);
355         syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
356         SurfaceSyncGroup syncTarget = new SurfaceSyncGroup("FakeSyncTarget");
357         syncGroup.add(syncTarget, null /* runnable */);
358         // Add the syncTarget to the same syncGroup and ensure it doesn't crash.
359         syncGroup.add(syncTarget, null /* runnable */);
360         syncGroup.markSyncReady();
361 
362         syncTarget.markSyncReady();
363 
364         try {
365             finishedLatch.await(5, TimeUnit.SECONDS);
366         } catch (InterruptedException e) {
367             throw new RuntimeException(e);
368         }
369 
370         assertEquals(0, finishedLatch.getCount());
371     }
372 
testSurfaceSyncGroupTimeout()373     public void testSurfaceSyncGroupTimeout() throws InterruptedException {
374         final CountDownLatch finishedLatch = new CountDownLatch(1);
375         SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG);
376         syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
377         SurfaceSyncGroup syncTarget1 = new SurfaceSyncGroup("FakeSyncTarget1");
378         SurfaceSyncGroup syncTarget2 = new SurfaceSyncGroup("FakeSyncTarget2");
379 
380         syncGroup.add(syncTarget1, null /* runnable */);
381         syncGroup.add(syncTarget2, null /* runnable */);
382         syncGroup.markSyncReady();
383 
384         syncTarget1.markSyncReady();
385         assertNotEquals(0, finishedLatch.getCount());
386 
387         // Never finish syncTarget2 so it forces the timeout. Timeout is 1 second so wait a little
388         // over 1 second to make sure it completes.
389         finishedLatch.await(1100, TimeUnit.MILLISECONDS);
390         assertEquals(0, finishedLatch.getCount());
391     }
392 }
393