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