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;
18 
19 import static android.Manifest.permission.READ_FRAME_BUFFER;
20 import static android.content.pm.PackageManager.PERMISSION_DENIED;
21 
22 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
23 
24 import static org.junit.Assert.assertEquals;
25 import static org.junit.Assert.assertNotEquals;
26 import static org.junit.Assert.assertFalse;
27 import static org.junit.Assert.assertTrue;
28 import static org.junit.Assert.fail;
29 import static org.mockito.ArgumentMatchers.eq;
30 import static org.mockito.Mockito.doReturn;
31 import static org.mockito.Mockito.mock;
32 
33 import android.content.Context;
34 import android.platform.test.annotations.Presubmit;
35 
36 import androidx.test.runner.AndroidJUnit4;
37 
38 import org.junit.AfterClass;
39 import org.junit.BeforeClass;
40 import org.junit.Test;
41 import org.junit.runner.RunWith;
42 
43 import java.io.PrintWriter;
44 import java.util.WeakHashMap;
45 
46 /**
47  * Class for testing {@link SurfaceControlRegistry}.
48  *
49  * Build/Install/Run:
50  *  atest FrameworksCoreTests:android.view.SurfaceControlRegistryTests
51  */
52 @Presubmit
53 @RunWith(AndroidJUnit4.class)
54 public class SurfaceControlRegistryTests {
55 
56     @BeforeClass
setUpOnce()57     public static void setUpOnce() {
58         SurfaceControlRegistry.createProcessInstance(getInstrumentation().getTargetContext());
59     }
60 
61     @AfterClass
tearDownOnce()62     public static void tearDownOnce() {
63         SurfaceControlRegistry.destroyProcessInstance();
64     }
65 
66     @Test
testRequiresPermissionToCreateProcessInstance()67     public void testRequiresPermissionToCreateProcessInstance() {
68         try {
69             Context ctx = mock(Context.class);
70             doReturn(PERMISSION_DENIED).when(ctx).checkSelfPermission(eq(READ_FRAME_BUFFER));
71             SurfaceControlRegistry.createProcessInstance(ctx);
72             fail("Expected SecurityException due to missing permission");
73         } catch (SecurityException se) {
74             // Expected failure
75         } catch (Exception e) {
76             fail("Unexpected exception: " + e);
77         }
78     }
79 
80     @Test
testCreateReleaseSurfaceControl()81     public void testCreateReleaseSurfaceControl() {
82         int hash0 = SurfaceControlRegistry.getProcessInstance().hashCode();
83         SurfaceControl sc = buildTestSurface();
84         assertNotEquals(hash0, SurfaceControlRegistry.getProcessInstance().hashCode());
85         sc.release();
86         assertEquals(hash0, SurfaceControlRegistry.getProcessInstance().hashCode());
87     }
88 
89     @Test
testCreateReleaseMultipleSurfaceControls()90     public void testCreateReleaseMultipleSurfaceControls() {
91         int hash0 = SurfaceControlRegistry.getProcessInstance().hashCode();
92         SurfaceControl sc1 = buildTestSurface();
93         int hash1 = SurfaceControlRegistry.getProcessInstance().hashCode();
94         assertNotEquals(hash0, hash1);
95         SurfaceControl sc2 = buildTestSurface();
96         int hash1_2 = SurfaceControlRegistry.getProcessInstance().hashCode();
97         assertNotEquals(hash0, hash1_2);
98         assertNotEquals(hash1, hash1_2);
99         // Release in inverse order to verify hashes still differ
100         sc1.release();
101         int hash2 = SurfaceControlRegistry.getProcessInstance().hashCode();
102         assertNotEquals(hash0, hash2);
103         sc2.release();
104         assertEquals(hash0, SurfaceControlRegistry.getProcessInstance().hashCode());
105     }
106 
107     @Test
testInvalidSurfaceControlNotAddedToRegistry()108     public void testInvalidSurfaceControlNotAddedToRegistry() {
109         int hash0 = SurfaceControlRegistry.getProcessInstance().hashCode();
110         // Verify no changes to the registry when dealing with invalid surface controls
111         SurfaceControl sc0 = new SurfaceControl();
112         SurfaceControl sc1 = new SurfaceControl(sc0, "test");
113         assertEquals(hash0, SurfaceControlRegistry.getProcessInstance().hashCode());
114         sc0.release();
115         sc1.release();
116         assertEquals(hash0, SurfaceControlRegistry.getProcessInstance().hashCode());
117     }
118 
119     @Test
testThresholds()120     public void testThresholds() {
121         SurfaceControlRegistry registry = SurfaceControlRegistry.getProcessInstance();
122         TestReporter reporter = new TestReporter();
123         registry.setReportingThresholds(4 /* max */, 2 /* reset */, reporter);
124 
125         // Exceed the threshold ensure the callback is made
126         SurfaceControl sc1 = buildTestSurface();
127         SurfaceControl sc2 = buildTestSurface();
128         SurfaceControl sc3 = buildTestSurface();
129         SurfaceControl sc4 = buildTestSurface();
130         reporter.assertMaxThresholdExceededCallCount(1);
131         reporter.assertLastReportedSetEquals(sc1, sc2, sc3, sc4);
132 
133         // Create a few more, ensure we don't report again for the time being
134         SurfaceControl sc5 = buildTestSurface();
135         SurfaceControl sc6 = buildTestSurface();
136         reporter.assertMaxThresholdExceededCallCount(1);
137         reporter.assertLastReportedSetEquals(sc1, sc2, sc3, sc4);
138 
139         // Release down to the reset threshold
140         sc1.release();
141         sc2.release();
142         sc3.release();
143         sc4.release();
144 
145         // Create a few more to hit the max threshold again
146         SurfaceControl sc7 = buildTestSurface();
147         SurfaceControl sc8 = buildTestSurface();
148         reporter.assertMaxThresholdExceededCallCount(2);
149         reporter.assertLastReportedSetEquals(sc5, sc6, sc7, sc8);
150     }
151 
152     @Test
testCallStackDebugging_matchesFilters()153     public void testCallStackDebugging_matchesFilters() {
154         SurfaceControlRegistry registry = SurfaceControlRegistry.getProcessInstance();
155 
156         // Specific name, any call
157         registry.setCallStackDebuggingParams("com.android.app1", "");
158         assertFalse(registry.matchesForCallStackDebugging("com.android.noMatchApp", "setAlpha"));
159         assertTrue(registry.matchesForCallStackDebugging("com.android.app1", "setAlpha"));
160 
161         // Any name, specific call
162         registry.setCallStackDebuggingParams("", "setAlpha");
163         assertFalse(registry.matchesForCallStackDebugging("com.android.app1", "setLayer"));
164         assertTrue(registry.matchesForCallStackDebugging("com.android.app1", "setAlpha"));
165         assertTrue(registry.matchesForCallStackDebugging("com.android.app2", "setAlpha"));
166 
167         // Specific name, specific call
168         registry.setCallStackDebuggingParams("com.android.app1", "setAlpha");
169         assertFalse(registry.matchesForCallStackDebugging("com.android.app1", "setLayer"));
170         assertFalse(registry.matchesForCallStackDebugging("com.android.app2", "setAlpha"));
171         assertTrue(registry.matchesForCallStackDebugging("com.android.app1", "setAlpha"));
172     }
173 
buildTestSurface()174     private SurfaceControl buildTestSurface() {
175         return new SurfaceControl.Builder()
176                 .setContainerLayer()
177                 .setName("SurfaceControlRegistryTests")
178                 .setCallsite("SurfaceControlRegistryTests")
179                 .build();
180     }
181 
182     private static class TestReporter implements SurfaceControlRegistry.Reporter {
183         WeakHashMap<SurfaceControl, Long> lastSurfaceControls = new WeakHashMap<>();
184         int callCount = 0;
185 
186         @Override
onMaxLayersExceeded(WeakHashMap<SurfaceControl, Long> surfaceControls, int limit, PrintWriter pw)187         public void onMaxLayersExceeded(WeakHashMap<SurfaceControl, Long> surfaceControls,
188                 int limit, PrintWriter pw) {
189             lastSurfaceControls.clear();
190             lastSurfaceControls.putAll(surfaceControls);
191             callCount++;
192         }
193 
assertMaxThresholdExceededCallCount(int count)194         public void assertMaxThresholdExceededCallCount(int count) {
195             assertTrue("Expected " + count + " got " + callCount, count == callCount);
196         }
197 
assertLastReportedSetEquals(SurfaceControl... surfaces)198         public void assertLastReportedSetEquals(SurfaceControl... surfaces) {
199             WeakHashMap<SurfaceControl, Long> last = new WeakHashMap<>(lastSurfaceControls);
200             for (int i = 0; i < surfaces.length; i++) {
201                 last.remove(surfaces[i]);
202             }
203             if (!last.isEmpty()) {
204                 fail("Sets differ");
205             }
206         }
207     }
208 }
209