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 22 import static org.junit.Assert.assertEquals; 23 import static org.junit.Assert.assertFalse; 24 import static org.junit.Assert.assertNotNull; 25 import static org.junit.Assert.fail; 26 27 import android.app.Activity; 28 import android.graphics.Color; 29 import android.os.Binder; 30 import android.os.SystemClock; 31 import android.platform.test.annotations.Presubmit; 32 import android.platform.test.annotations.RequiresFlagsEnabled; 33 import android.platform.test.flag.junit.CheckFlagsRule; 34 import android.platform.test.flag.junit.DeviceFlagsValueProvider; 35 import android.server.wm.CtsWindowInfoUtils; 36 import android.util.Log; 37 import android.view.SurfaceControl; 38 import android.view.SurfaceControlViewHost; 39 import android.view.SurfaceView; 40 import android.view.View; 41 import android.view.WindowManager; 42 import android.window.TrustedPresentationThresholds; 43 44 import androidx.annotation.NonNull; 45 import androidx.test.ext.junit.rules.ActivityScenarioRule; 46 47 import com.android.window.flags.Flags; 48 49 import junit.framework.Assert; 50 51 import org.junit.After; 52 import org.junit.Before; 53 import org.junit.Rule; 54 import org.junit.Test; 55 import org.junit.rules.TestName; 56 57 import java.util.ArrayList; 58 import java.util.Collections; 59 import java.util.List; 60 import java.util.concurrent.CountDownLatch; 61 import java.util.concurrent.TimeUnit; 62 import java.util.function.Consumer; 63 64 @Presubmit 65 public class TrustedPresentationListenerTest { 66 private static final String TAG = "TrustedPresentationListenerTest"; 67 private static final int STABILITY_REQUIREMENT_MS = 500; 68 private static final long WAIT_TIME_MS = HW_TIMEOUT_MULTIPLIER * 4000L; 69 70 private static final float FRACTION_VISIBLE = 0.1f; 71 72 73 private TrustedPresentationThresholds mThresholds = new TrustedPresentationThresholds( 74 1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS); 75 76 @Rule 77 public TestName mName = new TestName(); 78 79 @Rule 80 public ActivityScenarioRule<TestActivity> mActivityRule = createFullscreenActivityScenarioRule( 81 TestActivity.class); 82 83 @Rule 84 public final CheckFlagsRule mCheckFlagsRule = 85 DeviceFlagsValueProvider.createCheckFlagsRule(); 86 87 private TestActivity mActivity; 88 89 private SurfaceControlViewHost.SurfacePackage mSurfacePackage = null; 90 91 @Before setup()92 public void setup() { 93 mActivityRule.getScenario().onActivity(activity -> mActivity = activity); 94 mDefaultListener = new Listener(1); 95 } 96 97 @After tearDown()98 public void tearDown() { 99 if (mSurfacePackage != null) { 100 new SurfaceControl.Transaction() 101 .reparent(mSurfacePackage.getSurfaceControl(), null).apply(); 102 mSurfacePackage.release(); 103 } 104 } 105 106 private class Listener implements Consumer<Boolean> { 107 CountDownLatch mReceivedResultsLatch; 108 final List<Boolean> mResults = Collections.synchronizedList(new ArrayList<>()); 109 Listener(int numExpectedResults)110 Listener(int numExpectedResults) { 111 mReceivedResultsLatch = new CountDownLatch(numExpectedResults); 112 } 113 114 @Override accept(Boolean inTrustedPresentationState)115 public void accept(Boolean inTrustedPresentationState) { 116 Log.d(TAG, "onTrustedPresentationChanged " + inTrustedPresentationState); 117 mResults.add(inTrustedPresentationState); 118 mReceivedResultsLatch.countDown(); 119 } 120 waitForResults()121 void waitForResults() { 122 if (!TrustedPresentationListenerTest.wait(mReceivedResultsLatch, WAIT_TIME_MS)) { 123 try { 124 CtsWindowInfoUtils.dumpWindowsOnScreen(TAG, "test " + mName.getMethodName()); 125 } catch (InterruptedException e) { 126 Log.d(TAG, "Couldn't dump windows", e); 127 } 128 Assert.fail( 129 "Timed out waiting for results mReceivedResultsLatch.count=" 130 + mReceivedResultsLatch.getCount() 131 + " mResults=" 132 + mResults); 133 } 134 } 135 } 136 137 private Listener mDefaultListener; 138 139 @Test 140 @RequiresFlagsEnabled(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) testAddTrustedPresentationListenerOnWindow()141 public void testAddTrustedPresentationListenerOnWindow() { 142 WindowManager windowManager = mActivity.getSystemService(WindowManager.class); 143 windowManager.registerTrustedPresentationListener( 144 mActivity.getWindow().getDecorView().getWindowToken(), mThresholds, Runnable::run, 145 mDefaultListener); 146 assertResults(mDefaultListener, List.of(true)); 147 } 148 149 @Test 150 @RequiresFlagsEnabled(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) testRemoveTrustedPresentationListenerOnWindow()151 public void testRemoveTrustedPresentationListenerOnWindow() throws InterruptedException { 152 WindowManager windowManager = mActivity.getSystemService(WindowManager.class); 153 windowManager.registerTrustedPresentationListener( 154 mActivity.getWindow().getDecorView().getWindowToken(), mThresholds, Runnable::run, 155 mDefaultListener); 156 assertResults(mDefaultListener, List.of(true)); 157 // reset the latch 158 mDefaultListener.mReceivedResultsLatch = new CountDownLatch(1); 159 160 windowManager.unregisterTrustedPresentationListener(mDefaultListener); 161 // Ensure we waited the full time and never received a notify on the result from the 162 // callback. 163 assertFalse( 164 "Should never have received a callback", 165 wait(mDefaultListener.mReceivedResultsLatch, WAIT_TIME_MS)); 166 // Ensure we waited the full time and never received a notify on the result from the 167 // callback. 168 // results shouldn't have changed. 169 assertEquals(mDefaultListener.mResults, List.of(true)); 170 } 171 172 @Test 173 @RequiresFlagsEnabled(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) testRemovingUnknownListenerIsANoop()174 public void testRemovingUnknownListenerIsANoop() { 175 WindowManager windowManager = mActivity.getSystemService(WindowManager.class); 176 assertNotNull(windowManager); 177 windowManager.unregisterTrustedPresentationListener(mDefaultListener); 178 } 179 180 @Test 181 @RequiresFlagsEnabled(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) testAddDuplicateListenerUpdatesThresholds()182 public void testAddDuplicateListenerUpdatesThresholds() throws InterruptedException { 183 Binder nonExistentWindow = new Binder(); 184 WindowManager windowManager = mActivity.getSystemService(WindowManager.class); 185 windowManager.registerTrustedPresentationListener( 186 nonExistentWindow, mThresholds, 187 Runnable::run, mDefaultListener); 188 189 // Ensure we waited the full time and never received a notify on the result from the 190 // callback. 191 assertFalse( 192 "Should never have received a callback", 193 wait(mDefaultListener.mReceivedResultsLatch, WAIT_TIME_MS)); 194 195 windowManager.registerTrustedPresentationListener( 196 mActivity.getWindow().getDecorView().getWindowToken(), mThresholds, 197 Runnable::run, mDefaultListener); 198 assertResults(mDefaultListener, List.of(true)); 199 } 200 201 @Test 202 @RequiresFlagsEnabled(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) testAddDuplicateThresholds()203 public void testAddDuplicateThresholds() { 204 var listener1 = new Listener(1 /*numExpectedResults*/); 205 WindowManager windowManager = mActivity.getSystemService(WindowManager.class); 206 windowManager.registerTrustedPresentationListener( 207 mActivity.getWindow().getDecorView().getWindowToken(), 208 mThresholds, 209 Runnable::run, 210 listener1); 211 212 var listener2 = new Listener(1 /*numExpectedResults*/); 213 windowManager.registerTrustedPresentationListener( 214 mActivity.getWindow().getDecorView().getWindowToken(), 215 mThresholds, 216 Runnable::run, 217 listener2); 218 219 assertResults(listener1, List.of(true)); 220 assertResults(listener2, List.of(true)); 221 } 222 waitForViewAttach(View view)223 private void waitForViewAttach(View view) { 224 final CountDownLatch viewAttached = new CountDownLatch(1); 225 view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { 226 @Override 227 public void onViewAttachedToWindow(@NonNull View v) { 228 viewAttached.countDown(); 229 } 230 231 @Override 232 public void onViewDetachedFromWindow(@NonNull View v) { 233 234 } 235 }); 236 try { 237 viewAttached.await(2000, TimeUnit.MILLISECONDS); 238 } catch (InterruptedException e) { 239 throw new RuntimeException(e); 240 } 241 if (!wait(viewAttached, 2000 /* waitTimeMs */)) { 242 fail("Couldn't attach view=" + view); 243 } 244 } 245 246 @Test 247 @RequiresFlagsEnabled(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) testAddListenerToScvh()248 public void testAddListenerToScvh() { 249 WindowManager windowManager = mActivity.getSystemService(WindowManager.class); 250 var hostSurfaceView = new SurfaceView(mActivity); 251 hostSurfaceView.setZOrderOnTop(true); 252 var embeddedView = new View(mActivity); 253 embeddedView.setBackgroundColor(Color.GREEN); 254 mActivityRule.getScenario().onActivity(activity -> { 255 activity.setContentView(hostSurfaceView); 256 var scvh = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(), 257 hostSurfaceView.getHostToken()); 258 mSurfacePackage = scvh.getSurfacePackage(); 259 scvh.setView(embeddedView, mActivity.getWindow().getDecorView().getWidth(), 260 mActivity.getWindow().getDecorView().getHeight()); 261 hostSurfaceView.setChildSurfacePackage(mSurfacePackage); 262 }); 263 264 waitForViewAttach(embeddedView); 265 windowManager.registerTrustedPresentationListener( 266 embeddedView.getWindowToken(), mThresholds, Runnable::run, mDefaultListener); 267 268 assertResults(mDefaultListener, List.of(true)); 269 } 270 271 @Test 272 @RequiresFlagsEnabled(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) testTrustedPresentationThresholdGetters()273 public void testTrustedPresentationThresholdGetters() { 274 float alpha = 0.5f; 275 float fractionRendered = 0.9f; 276 int stabilityRequirementMs = 20; 277 TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds(alpha, 278 fractionRendered, stabilityRequirementMs); 279 Assert.assertEquals(alpha, thresholds.getMinAlpha()); 280 Assert.assertEquals(fractionRendered, thresholds.getMinFractionRendered()); 281 Assert.assertEquals(stabilityRequirementMs, thresholds.getStabilityRequirementMillis()); 282 } 283 284 @Test 285 @RequiresFlagsEnabled(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) testEquals()286 public void testEquals() { 287 float alpha = 0.5f; 288 float fractionRendered = 0.9f; 289 int stabilityRequirementMs = 20; 290 TrustedPresentationThresholds thresholdsA = new TrustedPresentationThresholds(alpha, 291 fractionRendered, stabilityRequirementMs); 292 TrustedPresentationThresholds thresholdsB = 293 new TrustedPresentationThresholds(alpha, fractionRendered, stabilityRequirementMs); 294 Assert.assertEquals(thresholdsA, thresholdsB); 295 } 296 297 @Test 298 @RequiresFlagsEnabled(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) testInvisibleWindowsDoesNotOcclude()299 public void testInvisibleWindowsDoesNotOcclude() { 300 WindowManager windowManager = mActivity.getSystemService(WindowManager.class); 301 var hostSurfaceView = new SurfaceView(mActivity); 302 hostSurfaceView.setZOrderOnTop(true); 303 var embeddedView = new View(mActivity); 304 embeddedView.setBackgroundColor(Color.GREEN); 305 mActivityRule 306 .getScenario() 307 .onActivity( 308 activity -> { 309 activity.setContentView(hostSurfaceView); 310 var scvh = 311 new SurfaceControlViewHost( 312 mActivity, 313 mActivity.getDisplay(), 314 hostSurfaceView.getHostToken()); 315 mSurfacePackage = scvh.getSurfacePackage(); 316 scvh.setView( 317 embeddedView, 318 mActivity.getWindow().getDecorView().getWidth(), 319 mActivity.getWindow().getDecorView().getHeight()); 320 hostSurfaceView.setChildSurfacePackage(mSurfacePackage); 321 }); 322 323 waitForViewAttach(embeddedView); 324 Log.d(TAG, "Embedded window added"); 325 326 // at this point the main window should be occluded. 327 328 // make the occluding surface invisible 329 new SurfaceControl.Transaction().setAlpha(mSurfacePackage.getSurfaceControl(), 0f).apply(); 330 331 var listener = new Listener(1 /*numExpectedResults*/); 332 windowManager.registerTrustedPresentationListener( 333 mActivity.getWindow().getDecorView().getWindowToken(), 334 mThresholds, 335 Runnable::run, 336 listener); 337 assertResults(listener, List.of(true)); 338 } 339 wait(CountDownLatch latch, long waitTimeMs)340 static boolean wait(CountDownLatch latch, long waitTimeMs) { 341 while (true) { 342 long now = SystemClock.uptimeMillis(); 343 try { 344 return latch.await(waitTimeMs, TimeUnit.MILLISECONDS); 345 } catch (InterruptedException e) { 346 long elapsedTime = SystemClock.uptimeMillis() - now; 347 waitTimeMs = Math.max(0, waitTimeMs - elapsedTime); 348 } 349 } 350 351 } 352 assertResults(Listener listener, List<Boolean> expectedResults)353 private void assertResults(Listener listener, List<Boolean> expectedResults) { 354 listener.waitForResults(); 355 assertEquals(expectedResults.toArray(), listener.mResults.toArray()); 356 } 357 358 public static class TestActivity extends Activity { 359 } 360 } 361