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