1 /*
2  * Copyright (C) 2020 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.server.wm;
18 
19 import static android.server.wm.WindowManagerTestBase.startActivity;
20 import static android.view.Display.DEFAULT_DISPLAY;
21 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
22 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
23 
24 import static com.google.common.truth.Truth.assertThat;
25 
26 import static org.junit.Assert.assertEquals;
27 import static org.junit.Assert.assertTrue;
28 
29 import android.content.ComponentCallbacks;
30 import android.content.ComponentName;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.ServiceConnection;
34 import android.content.res.Configuration;
35 import android.graphics.Rect;
36 import android.os.Binder;
37 import android.os.IBinder;
38 import android.platform.test.annotations.AppModeFull;
39 import android.platform.test.annotations.Presubmit;
40 import android.server.wm.WindowContextTests.TestWindowService.TestToken;
41 import android.view.View;
42 import android.view.WindowManager;
43 import android.window.WindowProviderService;
44 
45 import androidx.annotation.NonNull;
46 import androidx.annotation.Nullable;
47 import androidx.test.core.app.ApplicationProvider;
48 import androidx.test.rule.ServiceTestRule;
49 
50 import org.junit.Test;
51 
52 import java.util.concurrent.CountDownLatch;
53 import java.util.concurrent.TimeUnit;
54 
55 /**
56  * Tests that verify the behavior of window context
57  *
58  * Build/Install/Run:
59  *     atest CtsWindowManagerDeviceTestCases:WindowContextTests
60  */
61 @Presubmit
62 public class WindowContextTests extends WindowContextTestBase {
63     @Test
64     @AppModeFull
testWindowContextConfigChanges()65     public void testWindowContextConfigChanges() {
66         createAllowSystemAlertWindowAppOpSession();
67         final WindowManagerState.DisplayContent display = createManagedVirtualDisplaySession()
68                 .setSimulateDisplay(true).createDisplay();
69         final Context windowContext = createWindowContext(display.mId);
70         mInstrumentation.runOnMainSync(() -> {
71             final View view = new View(windowContext);
72             WindowManager wm = windowContext.getSystemService(WindowManager.class);
73             wm.addView(view, new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY));
74         });
75         final DisplayMetricsSession displayMetricsSession =
76                 createManagedDisplayMetricsSession(display.mId);
77 
78         mWmState.computeState();
79 
80         Rect bounds = windowContext.getSystemService(WindowManager.class).getCurrentWindowMetrics()
81                 .getBounds();
82         assertBoundsEquals(displayMetricsSession.getDisplayMetrics(), bounds);
83 
84         displayMetricsSession.changeDisplayMetrics(1.2 /* sizeRatio */, 1.1 /* densityRatio */);
85 
86         mWmState.computeState();
87 
88         bounds = windowContext.getSystemService(WindowManager.class).getCurrentWindowMetrics()
89                 .getBounds();
90         assertBoundsEquals(displayMetricsSession.getDisplayMetrics(), bounds);
91     }
92 
assertBoundsEquals(ReportedDisplayMetrics expectedMetrics, Rect bounds)93     private void assertBoundsEquals(ReportedDisplayMetrics expectedMetrics,
94             Rect bounds) {
95         assertEquals(expectedMetrics.getSize().getWidth(), bounds.width());
96         assertEquals(expectedMetrics.getSize().getHeight(), bounds.height());
97     }
98 
99     @Test
100     @AppModeFull
testWindowContextBindService()101     public void testWindowContextBindService() {
102         createAllowSystemAlertWindowAppOpSession();
103         final Context windowContext = createWindowContext(DEFAULT_DISPLAY);
104         final ServiceConnection serviceConnection = new ServiceConnection() {
105             @Override
106             public void onServiceConnected(ComponentName name, IBinder service) {}
107 
108             @Override
109             public void onServiceDisconnected(ComponentName name) {}
110         };
111         try {
112             assertTrue("WindowContext must bind service successfully.",
113                     windowContext.bindService(new Intent(windowContext, TestLogService.class),
114                             serviceConnection, Context.BIND_AUTO_CREATE));
115         } finally {
116             windowContext.unbindService(serviceConnection);
117         }
118     }
119 
120     /**
121      * Verify if the {@link ComponentCallbacks#onConfigurationChanged(Configuration)} callback
122      * is received when the window context configuration changes.
123      */
124     @Test
testWindowContextRegisterComponentCallbacks()125     public void testWindowContextRegisterComponentCallbacks() throws Exception {
126         final TestComponentCallbacks callbacks = new TestComponentCallbacks();
127         final WindowManagerState.DisplayContent display = createManagedVirtualDisplaySession()
128                 .setSimulateDisplay(true).createDisplay();
129         final Context windowContext = createWindowContext(display.mId);
130         final DisplayMetricsSession displayMetricsSession =
131                 createManagedDisplayMetricsSession(display.mId);
132 
133         windowContext.registerComponentCallbacks(callbacks);
134 
135         callbacks.mLatch = new CountDownLatch(1);
136 
137         displayMetricsSession.changeDisplayMetrics(1.2 /* sizeRatio */, 1.1 /* densityRatio */);
138 
139         // verify if there is a callback from the window context configuration change.
140         assertTrue(callbacks.mLatch.await(4, TimeUnit.SECONDS));
141         Rect bounds = callbacks.mConfiguration.windowConfiguration.getBounds();
142         assertBoundsEquals(displayMetricsSession.getDisplayMetrics(), bounds);
143 
144         windowContext.unregisterComponentCallbacks(callbacks);
145     }
146 
147     /**
148      * Verifies if window context on the secondary display receives global configuration changes.
149      */
150     @Test
testWindowContextGlobalConfigChanges()151     public void testWindowContextGlobalConfigChanges() {
152         final TestComponentCallbacks callbacks = new TestComponentCallbacks();
153         final WindowManagerState.DisplayContent display = createManagedVirtualDisplaySession()
154                 .setPublicDisplay(true).createDisplay();
155         final FontScaleSession fontScaleSession = createManagedFontScaleSession();
156         final Context windowContext = createWindowContext(display.mId);
157         windowContext.registerComponentCallbacks(callbacks);
158         final float expectedFontScale = fontScaleSession.get() + 0.3f;
159         fontScaleSession.set(expectedFontScale);
160 
161         // Wait for TestComponentCallbacks#mConfiguration to be assigned.
162         callbacks.waitForConfigChanged();
163 
164         // We don't rely on latch to verify the result because we may receive two configuration
165         // changes. One may from that WindowContext attaches to a DisplayArea although it is before
166         // ComponentCallback registration), the other is from font the scale change, which is what
167         // we want to verify.
168         waitForOrFail("font scale to match " + expectedFontScale, () ->
169                 expectedFontScale == callbacks.mConfiguration.fontScale);
170 
171         windowContext.unregisterComponentCallbacks(callbacks);
172     }
173 
174     /**
175      * Verify the {@link WindowProviderService} lifecycle:
176      * <ul>
177      *     <li>In {@link WindowProviderService#onCreate()}, register to the DisplayArea with
178      *     given value from {@link WindowProviderService#getWindowType()} and
179      *     {@link WindowProviderService#getWindowContextOptions()}} and receive a
180      *     {@link Configuration} update which matches DisplayArea's metrics.</li>
181      *     <li>After {@link WindowProviderService#attachToWindowToken(IBinder)}, the
182      *     {@link WindowProviderService} must be switched to register to the Window Token and
183      *     receive a configuration update which matches Window Token's metrics.</li>
184      * </ul>
185      */
186     @Test
testWindowProviderServiceLifecycle()187     public void testWindowProviderServiceLifecycle() throws Exception {
188         // Start an activity for WindowProviderService to attach
189         TestActivity activity = startActivity(TestActivity.class);
190         final ComponentName activityName = activity.getComponentName();
191 
192         // If the device supports multi-window, make this Activity to multi-window mode.
193         // In this way, we can verify if the WindowProviderService's metrics matches
194         // the split-screen Activity's metrics, which is different from TaskDisplayArea's metrics.
195         if (supportsSplitScreenMultiWindow()) {
196             mWmState.computeState(activityName);
197 
198             putActivityInPrimarySplit(activityName);
199 
200             activity.waitAndAssertConfigurationChanged();
201         }
202 
203         // Obtain the TestWindowService instance.
204         final Context context = ApplicationProvider.getApplicationContext();
205         final Intent intent = new Intent(context, TestWindowService.class);
206         final ServiceTestRule serviceRule = new ServiceTestRule();
207         try {
208             TestToken token = (TestToken) serviceRule.bindService(intent);
209             final TestWindowService service = token.getService();
210 
211             mWmState.computeState(activityName);
212             final WindowManagerState.DisplayArea da = mWmState.getTaskDisplayArea(activityName);
213             final Rect daBounds = da.mFullConfiguration.windowConfiguration.getBounds();
214             final Rect maxDaBounds = da.mFullConfiguration.windowConfiguration.getMaxBounds();
215 
216             waitAndAssertWindowMetricsBoundsMatches(service, daBounds, maxDaBounds,
217                     "WindowProviderService bounds must match DisplayArea bounds.");
218 
219             // Obtain the Activity's token and attach it to TestWindowService.
220             final IBinder windowToken = activity.getWindow().getAttributes().token;
221             service.attachToWindowToken(windowToken);
222 
223             final WindowManager wm = activity.getWindowManager();
224             final Rect currentBounds = wm.getCurrentWindowMetrics().getBounds();
225             final Rect maxBounds = wm.getMaximumWindowMetrics().getBounds();
226 
227             // After TestWindowService attaches the Activity's token, which is also a WindowToken,
228             // it is expected to receive a config update which matches the WindowMetrics of
229             // the Activity.
230             waitAndAssertWindowMetricsBoundsMatches(service, currentBounds, maxBounds,
231                     "WindowProviderService bounds must match WindowToken bounds.");
232         } finally {
233             serviceRule.unbindService();
234         }
235     }
236 
waitAndAssertWindowMetricsBoundsMatches(Context context, Rect currentBounds, Rect maxBounds, String message)237     private void waitAndAssertWindowMetricsBoundsMatches(Context context, Rect currentBounds,
238             Rect maxBounds, String message) {
239         final WindowManager wm = context.getSystemService(WindowManager.class);
240         waitForOrFail(message, () -> {
241             final Rect currentWindowBounds = wm.getCurrentWindowMetrics().getBounds();
242             final Rect maxWindowBounds = wm.getMaximumWindowMetrics().getBounds();
243             return currentBounds.equals(currentWindowBounds) && maxBounds.equals(maxWindowBounds);
244         });
245     }
246 
247     public static class TestActivity extends WindowManagerTestBase.FocusableActivity {
248         final CountDownLatch mLatch = new CountDownLatch(1);
249 
250         @Override
onConfigurationChanged(@onNull Configuration newConfig)251         public void onConfigurationChanged(@NonNull Configuration newConfig) {
252             super.onConfigurationChanged(newConfig);
253             mLatch.countDown();
254         }
255 
waitAndAssertConfigurationChanged()256         private void waitAndAssertConfigurationChanged() throws Exception {
257             assertThat(mLatch.await(4, TimeUnit.SECONDS)).isTrue();
258         }
259     }
260 
261     public static class TestWindowService extends WindowProviderService {
262         private final IBinder mToken = new TestToken();
263 
264         @Override
getWindowType()265         public int getWindowType() {
266             return TYPE_APPLICATION;
267         }
268 
269         @Nullable
270         @Override
onBind(Intent intent)271         public IBinder onBind(Intent intent) {
272             return mToken;
273         }
274 
275         public class TestToken extends Binder {
getService()276             private TestWindowService getService() {
277                 return TestWindowService.this;
278             }
279         }
280     }
281 
282     private static class TestComponentCallbacks implements ComponentCallbacks {
283         private Configuration mConfiguration;
284         private CountDownLatch mLatch = new CountDownLatch(1);
285 
waitForConfigChanged()286         private void waitForConfigChanged() {
287             try {
288                 assertThat(mLatch.await(4, TimeUnit.SECONDS)).isTrue();
289             } catch (InterruptedException e) {
290                 throw new RuntimeException(e);
291             }
292         }
293 
294         @Override
onConfigurationChanged(@onNull Configuration newConfig)295         public void onConfigurationChanged(@NonNull Configuration newConfig) {
296             mConfiguration = newConfig;
297             mLatch.countDown();
298         }
299 
300         @Override
onLowMemory()301         public void onLowMemory() {}
302     }
303 }
304