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 private final List<Boolean> mResults = Collections.synchronizedList(new ArrayList<>()); 73 private CountDownLatch mReceivedResults = new CountDownLatch(1); 74 75 private TrustedPresentationThresholds mThresholds = new TrustedPresentationThresholds( 76 1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS); 77 78 @Rule 79 public TestName mName = new TestName(); 80 81 @Rule 82 public ActivityScenarioRule<TestActivity> mActivityRule = createFullscreenActivityScenarioRule( 83 TestActivity.class); 84 85 @Rule 86 public final CheckFlagsRule mCheckFlagsRule = 87 DeviceFlagsValueProvider.createCheckFlagsRule(); 88 89 private TestActivity mActivity; 90 91 private SurfaceControlViewHost.SurfacePackage mSurfacePackage = null; 92 93 @Before setup()94 public void setup() { 95 mActivityRule.getScenario().onActivity(activity -> mActivity = activity); 96 mDefaultListener = new Listener(mReceivedResults); 97 } 98 99 @After tearDown()100 public void tearDown() { 101 if (mSurfacePackage != null) { 102 new SurfaceControl.Transaction() 103 .reparent(mSurfacePackage.getSurfaceControl(), null).apply(); 104 mSurfacePackage.release(); 105 } 106 } 107 108 private class Listener implements Consumer<Boolean> { 109 final CountDownLatch mLatch; 110 Listener(CountDownLatch latch)111 Listener(CountDownLatch latch) { 112 mLatch = latch; 113 } 114 115 @Override accept(Boolean inTrustedPresentationState)116 public void accept(Boolean inTrustedPresentationState) { 117 Log.d(TAG, "onTrustedPresentationChanged " + inTrustedPresentationState); 118 mResults.add(inTrustedPresentationState); 119 mLatch.countDown(); 120 } 121 } 122 123 private Consumer<Boolean> mDefaultListener; 124 125 @Test 126 @RequiresFlagsEnabled(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) testAddTrustedPresentationListenerOnWindow()127 public void testAddTrustedPresentationListenerOnWindow() { 128 WindowManager windowManager = mActivity.getSystemService(WindowManager.class); 129 windowManager.registerTrustedPresentationListener( 130 mActivity.getWindow().getDecorView().getWindowToken(), mThresholds, Runnable::run, 131 mDefaultListener); 132 assertResults(List.of(true)); 133 } 134 135 @Test 136 @RequiresFlagsEnabled(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) testRemoveTrustedPresentationListenerOnWindow()137 public void testRemoveTrustedPresentationListenerOnWindow() throws InterruptedException { 138 WindowManager windowManager = mActivity.getSystemService(WindowManager.class); 139 windowManager.registerTrustedPresentationListener( 140 mActivity.getWindow().getDecorView().getWindowToken(), mThresholds, Runnable::run, 141 mDefaultListener); 142 assertResults(List.of(true)); 143 // reset the latch 144 mReceivedResults = new CountDownLatch(1); 145 146 windowManager.unregisterTrustedPresentationListener(mDefaultListener); 147 // Ensure we waited the full time and never received a notify on the result from the 148 // callback. 149 assertFalse("Should never have received a callback", wait(mReceivedResults, WAIT_TIME_MS)); 150 // Ensure we waited the full time and never received a notify on the result from the 151 // callback. 152 // results shouldn't have changed. 153 assertEquals(mResults, List.of(true)); 154 } 155 156 @Test 157 @RequiresFlagsEnabled(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) testRemovingUnknownListenerIsANoop()158 public void testRemovingUnknownListenerIsANoop() { 159 WindowManager windowManager = mActivity.getSystemService(WindowManager.class); 160 assertNotNull(windowManager); 161 windowManager.unregisterTrustedPresentationListener(mDefaultListener); 162 } 163 164 @Test 165 @RequiresFlagsEnabled(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) testAddDuplicateListenerUpdatesThresholds()166 public void testAddDuplicateListenerUpdatesThresholds() throws InterruptedException { 167 Binder nonExistentWindow = new Binder(); 168 WindowManager windowManager = mActivity.getSystemService(WindowManager.class); 169 windowManager.registerTrustedPresentationListener( 170 nonExistentWindow, mThresholds, 171 Runnable::run, mDefaultListener); 172 173 // Ensure we waited the full time and never received a notify on the result from the 174 // callback. 175 assertFalse("Should never have received a callback", wait(mReceivedResults, WAIT_TIME_MS)); 176 177 windowManager.registerTrustedPresentationListener( 178 mActivity.getWindow().getDecorView().getWindowToken(), mThresholds, 179 Runnable::run, mDefaultListener); 180 assertResults(List.of(true)); 181 } 182 183 @Test 184 @RequiresFlagsEnabled(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) testAddDuplicateThresholds()185 public void testAddDuplicateThresholds() { 186 mReceivedResults = new CountDownLatch(2); 187 mDefaultListener = new Listener(mReceivedResults); 188 WindowManager windowManager = mActivity.getSystemService(WindowManager.class); 189 windowManager.registerTrustedPresentationListener( 190 mActivity.getWindow().getDecorView().getWindowToken(), mThresholds, 191 Runnable::run, mDefaultListener); 192 193 Consumer<Boolean> mNewListener = new Listener(mReceivedResults); 194 195 windowManager.registerTrustedPresentationListener( 196 mActivity.getWindow().getDecorView().getWindowToken(), mThresholds, 197 Runnable::run, mNewListener); 198 assertResults(List.of(true, true)); 199 } 200 waitForViewAttach(View view)201 private void waitForViewAttach(View view) { 202 final CountDownLatch viewAttached = new CountDownLatch(1); 203 view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { 204 @Override 205 public void onViewAttachedToWindow(@NonNull View v) { 206 viewAttached.countDown(); 207 } 208 209 @Override 210 public void onViewDetachedFromWindow(@NonNull View v) { 211 212 } 213 }); 214 try { 215 viewAttached.await(2000, TimeUnit.MILLISECONDS); 216 } catch (InterruptedException e) { 217 throw new RuntimeException(e); 218 } 219 if (!wait(viewAttached, 2000 /* waitTimeMs */)) { 220 fail("Couldn't attach view=" + view); 221 } 222 } 223 224 @Test 225 @RequiresFlagsEnabled(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) testAddListenerToScvh()226 public void testAddListenerToScvh() { 227 WindowManager windowManager = mActivity.getSystemService(WindowManager.class); 228 var hostSurfaceView = new SurfaceView(mActivity); 229 hostSurfaceView.setZOrderOnTop(true); 230 var embeddedView = new View(mActivity); 231 embeddedView.setBackgroundColor(Color.GREEN); 232 mActivityRule.getScenario().onActivity(activity -> { 233 activity.setContentView(hostSurfaceView); 234 var scvh = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(), 235 hostSurfaceView.getHostToken()); 236 mSurfacePackage = scvh.getSurfacePackage(); 237 scvh.setView(embeddedView, mActivity.getWindow().getDecorView().getWidth(), 238 mActivity.getWindow().getDecorView().getHeight()); 239 hostSurfaceView.setChildSurfacePackage(mSurfacePackage); 240 }); 241 242 waitForViewAttach(embeddedView); 243 windowManager.registerTrustedPresentationListener(embeddedView.getWindowToken(), 244 mThresholds, 245 Runnable::run, mDefaultListener); 246 247 assertResults(List.of(true)); 248 } 249 250 @Test 251 @RequiresFlagsEnabled(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) testTrustedPresentationThresholdGetters()252 public void testTrustedPresentationThresholdGetters() { 253 float alpha = 0.5f; 254 float fractionRendered = 0.9f; 255 int stabilityRequirementMs = 20; 256 TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds(alpha, 257 fractionRendered, stabilityRequirementMs); 258 Assert.assertEquals(alpha, thresholds.getMinAlpha()); 259 Assert.assertEquals(fractionRendered, thresholds.getMinFractionRendered()); 260 Assert.assertEquals(stabilityRequirementMs, thresholds.getStabilityRequirementMillis()); 261 } 262 263 @Test 264 @RequiresFlagsEnabled(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) testEquals()265 public void testEquals() { 266 float alpha = 0.5f; 267 float fractionRendered = 0.9f; 268 int stabilityRequirementMs = 20; 269 TrustedPresentationThresholds thresholdsA = new TrustedPresentationThresholds(alpha, 270 fractionRendered, stabilityRequirementMs); 271 TrustedPresentationThresholds thresholdsB = new TrustedPresentationThresholds(alpha, 272 fractionRendered, stabilityRequirementMs); 273 Assert.assertEquals(thresholdsA, thresholdsB); 274 } 275 wait(CountDownLatch latch, long waitTimeMs)276 private boolean wait(CountDownLatch latch, long waitTimeMs) { 277 while (true) { 278 long now = SystemClock.uptimeMillis(); 279 try { 280 return latch.await(waitTimeMs, TimeUnit.MILLISECONDS); 281 } catch (InterruptedException e) { 282 long elapsedTime = SystemClock.uptimeMillis() - now; 283 waitTimeMs = Math.max(0, waitTimeMs - elapsedTime); 284 } 285 } 286 287 } 288 assertResults(List<Boolean> results)289 private void assertResults(List<Boolean> results) { 290 if (!wait(mReceivedResults, WAIT_TIME_MS)) { 291 try { 292 CtsWindowInfoUtils.dumpWindowsOnScreen(TAG, "test " + mName.getMethodName()); 293 } catch (InterruptedException e) { 294 Log.d(TAG, "Couldn't dump windows", e); 295 } 296 Assert.fail("Timed out waiting for results mReceivedResults.count=" 297 + mReceivedResults.getCount() + "mReceivedResults=" + mReceivedResults); 298 } 299 300 // Make sure we received the results 301 assertEquals(results.toArray(), mResults.toArray()); 302 } 303 304 public static class TestActivity extends Activity { 305 } 306 } 307