1 /*
2  * Copyright (C) 2023 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 android.view.surfacecontrol.cts;
18 
19 import static android.server.wm.ActivityManagerTestBase.createFullscreenActivityScenarioRule;
20 import static android.server.wm.BuildUtils.HW_TIMEOUT_MULTIPLIER;
21 import static android.server.wm.CtsWindowInfoUtils.assertAndDumpWindowState;
22 import static android.view.cts.util.ASurfaceControlTestUtils.getSolidBuffer;
23 
24 import static org.junit.Assert.assertEquals;
25 import static org.junit.Assert.assertFalse;
26 import static org.junit.Assert.assertNotNull;
27 import static org.junit.Assert.assertTrue;
28 
29 import android.app.Activity;
30 import android.graphics.Color;
31 import android.graphics.Rect;
32 import android.hardware.HardwareBuffer;
33 import android.os.Bundle;
34 import android.platform.test.annotations.Presubmit;
35 import android.util.ArraySet;
36 import android.util.Size;
37 import android.view.Gravity;
38 import android.view.SurfaceControl;
39 import android.view.SurfaceControl.TrustedPresentationThresholds;
40 import android.view.SurfaceHolder;
41 import android.view.SurfaceView;
42 import android.view.View;
43 import android.view.WindowManager;
44 import android.widget.FrameLayout;
45 
46 import androidx.annotation.GuardedBy;
47 import androidx.annotation.NonNull;
48 import androidx.annotation.Nullable;
49 import androidx.test.ext.junit.rules.ActivityScenarioRule;
50 
51 import org.junit.Before;
52 import org.junit.Rule;
53 import org.junit.Test;
54 import org.junit.rules.TestName;
55 
56 import java.util.concurrent.CountDownLatch;
57 import java.util.concurrent.Executor;
58 import java.util.concurrent.TimeUnit;
59 
60 @Presubmit
61 public class TrustedPresentationCallbackTest {
62     static {
63         System.loadLibrary("ctssurfacecontrol_jni");
64     }
65 
66     private static final String TAG = "TrustedPresentationCallbackTest";
67     private static final int STABILITY_REQUIREMENT_MS = 500;
68     private static final long WAIT_TIME_MS = HW_TIMEOUT_MULTIPLIER * 2000L;
69 
70     private static final float FRACTION_VISIBLE = 0.1f;
71 
72     private static final float FRACTION_SMALLER_THAN_VISIBLE = FRACTION_VISIBLE - .01f;
73 
74     @Rule
75     public TestName mName = new TestName();
76 
77     @Rule
78     public ActivityScenarioRule<TestActivity> mActivityRule =
79             createFullscreenActivityScenarioRule(TestActivity.class);
80 
81     private TestActivity mActivity;
82     private final Object mResultsLock = new Object();
83     @GuardedBy("mResultsLock")
84     private boolean mResult;
85     @GuardedBy("mResultsLock")
86     private boolean mReceivedResults;
87 
88     @Before
setup()89     public void setup() {
90         mActivityRule.getScenario().onActivity(activity -> mActivity = activity);
91         mResult = false;
92         mReceivedResults = false;
93     }
94 
registerTrustedPresentationCallback(SurfaceControl sc)95     private void registerTrustedPresentationCallback(SurfaceControl sc) {
96         TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds(
97                 1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS);
98         SurfaceControl.Transaction t = new SurfaceControl.Transaction();
99         t.setTrustedPresentationCallback(sc, thresholds, mActivity.mExecutor,
100                 inTrustedPresentationState -> {
101                     synchronized (mResultsLock) {
102                         mResult = inTrustedPresentationState;
103                         mReceivedResults = true;
104                         mResultsLock.notify();
105                     }
106                 }).apply();
107     }
108 
createChildSc(SurfaceControl parent)109     private SurfaceControl createChildSc(SurfaceControl parent) {
110         SurfaceControl surfaceControl = new SurfaceControl.Builder()
111                 .setParent(parent)
112                 .setName("ChildSc")
113                 .setHidden(false)
114                 .build();
115 
116         return surfaceControl;
117     }
118 
setBuffer(SurfaceControl surfaceControl, int width, int height)119     private void setBuffer(SurfaceControl surfaceControl, int width, int height) {
120         HardwareBuffer buffer = getSolidBuffer(width, height, Color.RED);
121         assertNotNull("failed to make solid buffer", buffer);
122         new SurfaceControl.Transaction()
123                 .setBuffer(surfaceControl, buffer)
124                 .apply();
125 
126         mActivity.mSurfaceControls.add(surfaceControl);
127         mActivity.mBuffers.add(buffer);
128     }
129 
130     @Test
testTrustedPresentationListener()131     public void testTrustedPresentationListener() throws InterruptedException {
132         SurfaceControl rootSc = mActivity.getSurfaceControl();
133         SurfaceControl sc = createChildSc(rootSc);
134         registerTrustedPresentationCallback(sc);
135 
136         synchronized (mResultsLock) {
137             setBuffer(sc, mActivity.mSvSize.getWidth(), mActivity.mSvSize.getHeight());
138             assertResults(true);
139         }
140 
141         synchronized (mResultsLock) {
142             mReceivedResults = false;
143             new SurfaceControl.Transaction().setVisibility(sc, false).apply();
144             assertResults(false);
145         }
146     }
147 
148 
149     @Test
testTrustedPresentationListener_parentVisibilityChanges()150     public void testTrustedPresentationListener_parentVisibilityChanges()
151             throws InterruptedException {
152         SurfaceControl rootSc = mActivity.getSurfaceControl();
153         SurfaceControl sc = createChildSc(rootSc);
154         registerTrustedPresentationCallback(sc);
155 
156         synchronized (mResultsLock) {
157             setBuffer(sc, mActivity.mSvSize.getWidth(), mActivity.mSvSize.getHeight());
158             assertResults(true);
159         }
160 
161         synchronized (mResultsLock) {
162             mReceivedResults = false;
163             CountDownLatch countDownLatch = new CountDownLatch(1);
164             mActivity.runOnUiThread(() -> {
165                 mActivity.mSurfaceView.setVisibility(View.INVISIBLE);
166                 countDownLatch.countDown();
167             });
168             countDownLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
169             assertResults(false);
170         }
171     }
172 
173     @Test
testTrustedPresentationListener_alphaBelow()174     public void testTrustedPresentationListener_alphaBelow() throws InterruptedException {
175         SurfaceControl rootSc = mActivity.getSurfaceControl();
176         SurfaceControl sc = createChildSc(rootSc);
177         registerTrustedPresentationCallback(sc);
178 
179         synchronized (mResultsLock) {
180             setBuffer(sc, mActivity.mSvSize.getWidth(), mActivity.mSvSize.getHeight());
181             assertResults(true);
182         }
183 
184         synchronized (mResultsLock) {
185             mReceivedResults = false;
186             new SurfaceControl.Transaction().setAlpha(sc, .5f).apply();
187             assertResults(false);
188         }
189     }
190 
191     @Test
testTrustedPresentationListener_minFractionDueToCrop()192     public void testTrustedPresentationListener_minFractionDueToCrop() throws InterruptedException {
193         SurfaceControl rootSc = mActivity.getSurfaceControl();
194         SurfaceControl sc = createChildSc(rootSc);
195         registerTrustedPresentationCallback(sc);
196 
197         synchronized (mResultsLock) {
198             setBuffer(sc, mActivity.mSvSize.getWidth(), mActivity.mSvSize.getHeight());
199             assertResults(true);
200         }
201 
202         synchronized (mResultsLock) {
203             mReceivedResults = false;
204             // threshold is 10% so make sure to crop even smaller than that
205             new SurfaceControl.Transaction().setCrop(sc,
206                     new Rect(0, 0,
207                             (int) (mActivity.mSvSize.getWidth() * FRACTION_SMALLER_THAN_VISIBLE),
208                             mActivity.mSvSize.getHeight())).apply();
209             assertResults(false);
210         }
211     }
212 
213     @Test
testTrustedPresentationListener_minFractionDueToCovered()214     public void testTrustedPresentationListener_minFractionDueToCovered()
215             throws InterruptedException {
216         SurfaceControl rootSc = mActivity.getSurfaceControl();
217         SurfaceControl sc1 = createChildSc(rootSc);
218         SurfaceControl sc2 = createChildSc(rootSc);
219         registerTrustedPresentationCallback(sc1);
220 
221         synchronized (mResultsLock) {
222             setBuffer(sc1, mActivity.mSvSize.getWidth(), mActivity.mSvSize.getHeight());
223             assertResults(true);
224         }
225 
226         // Make Second SC visible
227         synchronized (mResultsLock) {
228             mReceivedResults = false;
229             // Cover slightly more than what the threshold is expecting to ensure the listener
230             // reports it's no longer at the threshold.
231             setBuffer(sc2,
232                     (int) (mActivity.mSvSize.getWidth() * (1 - FRACTION_SMALLER_THAN_VISIBLE)),
233                     mActivity.mSvSize.getHeight());
234             assertResults(false);
235         }
236     }
237 
238     @Test
testTrustedPresentationListener_enteredExitEntered()239     public void testTrustedPresentationListener_enteredExitEntered() throws InterruptedException {
240         SurfaceControl rootSc = mActivity.getSurfaceControl();
241         SurfaceControl sc = createChildSc(rootSc);
242         registerTrustedPresentationCallback(sc);
243 
244         synchronized (mResultsLock) {
245             setBuffer(sc, mActivity.mSvSize.getWidth(), mActivity.mSvSize.getHeight());
246             assertResults(true);
247         }
248 
249         synchronized (mResultsLock) {
250             mReceivedResults = false;
251             new SurfaceControl.Transaction().setCrop(sc,
252                     new Rect(0, 0,
253                             (int) (mActivity.mSvSize.getWidth() * FRACTION_SMALLER_THAN_VISIBLE),
254                             mActivity.mSvSize.getHeight())).apply();
255             assertResults(false);
256         }
257 
258         synchronized (mResultsLock) {
259             mReceivedResults = false;
260             new SurfaceControl.Transaction().setCrop(sc, null).apply();
261             assertResults(true);
262         }
263     }
264 
265     @Test
testTrustedPresentationListener_invalidThreshold()266     public void testTrustedPresentationListener_invalidThreshold() {
267         boolean threwException = false;
268         try {
269             new TrustedPresentationThresholds(-1 /* minAlpha */, 1 /* minFractionRendered */,
270                     1000 /* stabilityRequirementMs */);
271         } catch (IllegalArgumentException e) {
272             threwException = true;
273         }
274         assertTrue(threwException);
275 
276         threwException = false;
277         try {
278             new TrustedPresentationThresholds(1 /* minAlpha */, -1 /* minFractionRendered */,
279                     1000 /* stabilityRequirementMs */);
280         } catch (IllegalArgumentException e) {
281             threwException = true;
282         }
283         assertTrue(threwException);
284 
285         threwException = false;
286         try {
287             new TrustedPresentationThresholds(1 /* minAlpha */, 1 /* minFractionRendered */,
288                     0 /* stabilityRequirementMs */);
289         } catch (IllegalArgumentException e) {
290             threwException = true;
291         }
292 
293         assertTrue(threwException);
294     }
295 
296     @Test
testTrustedPresentationListener_clearCallback()297     public void testTrustedPresentationListener_clearCallback()
298             throws InterruptedException {
299         SurfaceControl rootSc = mActivity.getSurfaceControl();
300         SurfaceControl sc = createChildSc(rootSc);
301         registerTrustedPresentationCallback(sc);
302 
303         synchronized (mResultsLock) {
304             setBuffer(sc, mActivity.mSvSize.getWidth(), mActivity.mSvSize.getHeight());
305             assertResults(true);
306         }
307 
308         synchronized (mResultsLock) {
309             mReceivedResults = false;
310             new SurfaceControl.Transaction().clearTrustedPresentationCallback(sc)
311                     .setVisibility(sc, false).apply();
312             mResultsLock.wait(WAIT_TIME_MS);
313             // Ensure we waited the full time and never received a notify on the result from the
314             // callback.
315             assertFalse("Should never have received a callback", mReceivedResults);
316             // results shouldn't have changed.
317             assertTrue(mResult);
318         }
319     }
320 
321     @Test
testTrustedPresentationListener_multipleSetCallbacks()322     public void testTrustedPresentationListener_multipleSetCallbacks()
323             throws InterruptedException {
324         SurfaceControl rootSc = mActivity.getSurfaceControl();
325         SurfaceControl sc = createChildSc(rootSc);
326 
327         TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds(
328                 1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS);
329 
330         CountDownLatch latch1 = new CountDownLatch(1);
331         SurfaceControl.Transaction t = new SurfaceControl.Transaction();
332         t.setTrustedPresentationCallback(sc, thresholds, mActivity.mExecutor,
333                 inTrustedPresentationState -> latch1.countDown()).apply();
334 
335         CountDownLatch latch2 = new CountDownLatch(1);
336         t.setTrustedPresentationCallback(sc, thresholds, mActivity.mExecutor,
337                 inTrustedPresentationState -> {
338                     latch2.countDown();
339                     mResult = inTrustedPresentationState;
340                 }).apply();
341 
342         setBuffer(sc, mActivity.mSvSize.getWidth(), mActivity.mSvSize.getHeight());
343 
344         // The first callback should never get callback for first presentation callback since we
345         // overwrote it.
346         assertFalse(latch1.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
347         assertTrue(latch2.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
348 
349         assertTrue(mResult);
350     }
351 
352     @Test
testSetTrustedPresentationListenerAfterThreshold()353     public void testSetTrustedPresentationListenerAfterThreshold() throws InterruptedException {
354         SurfaceControl rootSc = mActivity.getSurfaceControl();
355         SurfaceControl sc = createChildSc(rootSc);
356         registerTrustedPresentationCallback(sc);
357 
358         // Ensure received tpc callback so we know it's ready.
359         synchronized (mResultsLock) {
360             setBuffer(sc, mActivity.mSvSize.getWidth(), mActivity.mSvSize.getHeight());
361             assertResults(true);
362         }
363 
364         // Register a new trusted presentation listener to make sure we get a callback if we
365         // registered after already in the trusted presented state. We'll need to wait the full
366         // time again.
367         synchronized (mResultsLock) {
368             mReceivedResults = false;
369             mResult = false;
370             registerTrustedPresentationCallback(sc);
371             long startTime = System.currentTimeMillis();
372             assertResults(true);
373             assertTrue(System.currentTimeMillis() - startTime >= STABILITY_REQUIREMENT_MS);
374         }
375     }
376 
377     @Test
testSetTrustedPresentationListenerAfterClearing()378     public void testSetTrustedPresentationListenerAfterClearing() throws InterruptedException {
379         SurfaceControl rootSc = mActivity.getSurfaceControl();
380         SurfaceControl sc = createChildSc(rootSc);
381         registerTrustedPresentationCallback(sc);
382 
383         // Ensure received tpc callback so we know it's ready.
384         synchronized (mResultsLock) {
385             setBuffer(sc, mActivity.mSvSize.getWidth(), mActivity.mSvSize.getHeight());
386             assertResults(true);
387         }
388 
389 
390         // Register a new trusted presentation listener to make sure we get a callback if we
391         // registered after already in the trusted presented state. We'll need to wait the full
392         // time again.
393         synchronized (mResultsLock) {
394             mReceivedResults = false;
395             mResult = false;
396             // Use a long stability requirement so we don't accidentally trigger it too early.
397             TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds(
398                     1 /* minAlpha */, FRACTION_VISIBLE, HW_TIMEOUT_MULTIPLIER * 20000);
399             SurfaceControl.Transaction t = new SurfaceControl.Transaction();
400             t.setTrustedPresentationCallback(sc, thresholds, mActivity.mExecutor,
401                     inTrustedPresentationState -> {
402                         synchronized (mResultsLock) {
403                             mResult = inTrustedPresentationState;
404                             mReceivedResults = true;
405                             mResultsLock.notify();
406                         }
407                     }).apply();
408         }
409 
410 
411         new SurfaceControl.Transaction().clearTrustedPresentationCallback(sc).apply();
412         synchronized (mResultsLock) {
413             assertFalse(mResult);
414             assertFalse(mReceivedResults);
415             registerTrustedPresentationCallback(sc);
416             long startTime = System.currentTimeMillis();
417             assertResults(true);
418             assertTrue(System.currentTimeMillis() - startTime >= STABILITY_REQUIREMENT_MS);
419         }
420     }
421 
422     @GuardedBy("mResultsLock")
assertResults(boolean result)423     private void assertResults(boolean result) throws InterruptedException {
424         mResultsLock.wait(WAIT_TIME_MS);
425 
426         assertAndDumpWindowState(TAG, "Timed out waiting for results",
427                 mReceivedResults);
428         assertEquals(result, mResult);
429     }
430 
431     public static class TestActivity extends Activity implements SurfaceHolder.Callback {
432         private final Executor mExecutor = Runnable::run;
433 
434         private final CountDownLatch mCountDownLatch = new CountDownLatch(3);
435 
436         private SurfaceView mSurfaceView;
437 
438         private final ArraySet<SurfaceControl> mSurfaceControls = new ArraySet<>();
439         private final ArraySet<HardwareBuffer> mBuffers = new ArraySet<>();
440 
441         private Size mSvSize;
442 
443         @Override
onCreate(@ullable Bundle savedInstanceState)444         protected void onCreate(@Nullable Bundle savedInstanceState) {
445             super.onCreate(savedInstanceState);
446             WindowManager wm = getSystemService(WindowManager.class);
447             Rect bounds = wm.getCurrentWindowMetrics().getBounds();
448             // Make sure the content rendering is smaller than the display and not getting cut off
449             // by the edges.
450             mSvSize = new Size(bounds.width() / 2, bounds.height() / 2);
451 
452             FrameLayout content = new FrameLayout(this);
453             mSurfaceView = new SurfaceView(this);
454             content.addView(mSurfaceView,
455                     new FrameLayout.LayoutParams(mSvSize.getWidth(), mSvSize.getHeight(),
456                             Gravity.CENTER));
457             setContentView(content);
458 
459             mSurfaceView.setZOrderOnTop(true);
460             mSurfaceView.getHolder().addCallback(this);
461 
462         }
463 
464         @Override
onEnterAnimationComplete()465         public void onEnterAnimationComplete() {
466             mCountDownLatch.countDown();
467         }
468 
469         @Override
surfaceCreated(@onNull SurfaceHolder holder)470         public void surfaceCreated(@NonNull SurfaceHolder holder) {
471             mCountDownLatch.countDown();
472         }
473 
474 
475         @Override
onAttachedToWindow()476         public void onAttachedToWindow() {
477             SurfaceControl.Transaction t = new SurfaceControl.Transaction();
478             t.addTransactionCommittedListener(mExecutor, mCountDownLatch::countDown);
479             getWindow().getRootSurfaceControl().applyTransactionOnDraw(t);
480         }
481 
482         @Override
surfaceChanged(@onNull SurfaceHolder holder, int format, int width, int height)483         public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
484                 int height) {
485 
486         }
487 
488         @Override
surfaceDestroyed(@onNull SurfaceHolder holder)489         public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
490             SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
491             for (SurfaceControl surfaceControl : mSurfaceControls) {
492                 transaction.reparent(surfaceControl, null);
493             }
494             transaction.apply();
495             mSurfaceControls.clear();
496 
497             for (HardwareBuffer buffer : mBuffers) {
498                 buffer.close();
499             }
500             mBuffers.clear();
501         }
502 
getSurfaceControl()503         public SurfaceControl getSurfaceControl() throws InterruptedException {
504             assertAndDumpWindowState(TAG, "No SurfaceView found",
505                     mCountDownLatch.await(5, TimeUnit.SECONDS));
506             return mSurfaceView.getSurfaceControl();
507         }
508     }
509 }
510