• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.view.cts.util.ASurfaceControlTestUtils.getSolidBuffer;
22 
23 import static org.junit.Assert.assertEquals;
24 import static org.junit.Assert.assertFalse;
25 import static org.junit.Assert.assertNotNull;
26 import static org.junit.Assert.assertTrue;
27 
28 import android.app.Activity;
29 import android.graphics.Color;
30 import android.graphics.Rect;
31 import android.hardware.HardwareBuffer;
32 import android.os.Bundle;
33 import android.platform.test.annotations.Presubmit;
34 import android.server.wm.CtsWindowInfoUtils;
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         if (!mReceivedResults) {
427             CtsWindowInfoUtils.dumpWindowsOnScreen(TAG, mName);
428         }
429         // Make sure we received the results and not just timed out
430         assertTrue("Timed out waiting for results", mReceivedResults);
431         assertEquals(result, mResult);
432     }
433 
434     public static class TestActivity extends Activity implements SurfaceHolder.Callback {
435         private final Executor mExecutor = Runnable::run;
436 
437         private final CountDownLatch mCountDownLatch = new CountDownLatch(3);
438 
439         private SurfaceView mSurfaceView;
440 
441         private final ArraySet<SurfaceControl> mSurfaceControls = new ArraySet<>();
442         private final ArraySet<HardwareBuffer> mBuffers = new ArraySet<>();
443 
444         private Size mSvSize;
445 
446         @Override
onCreate(@ullable Bundle savedInstanceState)447         protected void onCreate(@Nullable Bundle savedInstanceState) {
448             super.onCreate(savedInstanceState);
449             WindowManager wm = getSystemService(WindowManager.class);
450             Rect bounds = wm.getCurrentWindowMetrics().getBounds();
451             // Make sure the content rendering is smaller than the display and not getting cut off
452             // by the edges.
453             mSvSize = new Size(bounds.width() / 2, bounds.height() / 2);
454 
455             FrameLayout content = new FrameLayout(this);
456             mSurfaceView = new SurfaceView(this);
457             content.addView(mSurfaceView,
458                     new FrameLayout.LayoutParams(mSvSize.getWidth(), mSvSize.getHeight(),
459                             Gravity.CENTER));
460             setContentView(content);
461 
462             mSurfaceView.setZOrderOnTop(true);
463             mSurfaceView.getHolder().addCallback(this);
464 
465         }
466 
467         @Override
onEnterAnimationComplete()468         public void onEnterAnimationComplete() {
469             mCountDownLatch.countDown();
470         }
471 
472         @Override
surfaceCreated(@onNull SurfaceHolder holder)473         public void surfaceCreated(@NonNull SurfaceHolder holder) {
474             mCountDownLatch.countDown();
475         }
476 
477 
478         @Override
onAttachedToWindow()479         public void onAttachedToWindow() {
480             SurfaceControl.Transaction t = new SurfaceControl.Transaction();
481             t.addTransactionCommittedListener(mExecutor, mCountDownLatch::countDown);
482             getWindow().getRootSurfaceControl().applyTransactionOnDraw(t);
483         }
484 
485         @Override
surfaceChanged(@onNull SurfaceHolder holder, int format, int width, int height)486         public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
487                 int height) {
488 
489         }
490 
491         @Override
surfaceDestroyed(@onNull SurfaceHolder holder)492         public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
493             SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
494             for (SurfaceControl surfaceControl : mSurfaceControls) {
495                 transaction.reparent(surfaceControl, null);
496             }
497             transaction.apply();
498             mSurfaceControls.clear();
499 
500             for (HardwareBuffer buffer : mBuffers) {
501                 buffer.close();
502             }
503             mBuffers.clear();
504         }
505 
getSurfaceControl()506         public SurfaceControl getSurfaceControl() throws InterruptedException {
507             assertTrue(mCountDownLatch.await(5, TimeUnit.SECONDS));
508             return mSurfaceView.getSurfaceControl();
509         }
510     }
511 }
512