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 import static com.google.common.truth.Truth.assertWithMessage;
26 
27 import static org.junit.Assert.assertEquals;
28 import static org.junit.Assert.assertTrue;
29 import static org.junit.Assume.assumeTrue;
30 
31 import android.app.WindowConfiguration;
32 import android.content.ComponentCallbacks;
33 import android.content.ComponentName;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.content.ServiceConnection;
37 import android.content.pm.ActivityInfo;
38 import android.content.res.Configuration;
39 import android.graphics.Rect;
40 import android.os.Binder;
41 import android.os.IBinder;
42 import android.platform.test.annotations.AppModeFull;
43 import android.platform.test.annotations.Presubmit;
44 import android.server.wm.WindowContextTests.TestWindowService.TestToken;
45 import android.view.View;
46 import android.view.WindowManager;
47 import android.window.WindowProviderService;
48 
49 import androidx.annotation.NonNull;
50 import androidx.annotation.Nullable;
51 import androidx.test.core.app.ApplicationProvider;
52 import androidx.test.rule.ServiceTestRule;
53 
54 import org.junit.Test;
55 
56 import java.util.concurrent.CountDownLatch;
57 import java.util.concurrent.TimeUnit;
58 import java.util.concurrent.TimeoutException;
59 
60 /**
61  * Tests that verify the behavior of window context
62  *
63  * Build/Install/Run:
64  *     atest CtsWindowManagerDeviceTestCases:WindowContextTests
65  */
66 @Presubmit
67 public class WindowContextTests extends WindowContextTestBase {
68     @Test
69     @AppModeFull
testWindowContextConfigChanges()70     public void testWindowContextConfigChanges() {
71         createAllowSystemAlertWindowAppOpSession();
72         final WindowManagerState.DisplayContent display = createManagedVirtualDisplaySession()
73                 .setSimulateDisplay(true).createDisplay();
74         final Context windowContext = createWindowContext(display.mId);
75         mInstrumentation.runOnMainSync(() -> {
76             final View view = new View(windowContext);
77             WindowManager wm = windowContext.getSystemService(WindowManager.class);
78             wm.addView(view, new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY));
79         });
80         final DisplayMetricsSession displayMetricsSession =
81                 createManagedDisplayMetricsSession(display.mId);
82 
83         mWmState.computeState();
84 
85         Rect bounds = windowContext.getSystemService(WindowManager.class).getCurrentWindowMetrics()
86                 .getBounds();
87         assertBoundsEquals(displayMetricsSession.getDisplayMetrics(), bounds);
88 
89         displayMetricsSession.changeDisplayMetrics(1.2 /* sizeRatio */, 1.1 /* densityRatio */);
90 
91         mWmState.computeState();
92 
93         bounds = windowContext.getSystemService(WindowManager.class).getCurrentWindowMetrics()
94                 .getBounds();
95         assertBoundsEquals(displayMetricsSession.getDisplayMetrics(), bounds);
96     }
97 
assertBoundsEquals(ReportedDisplayMetrics expectedMetrics, Rect bounds)98     private void assertBoundsEquals(ReportedDisplayMetrics expectedMetrics,
99             Rect bounds) {
100         assertEquals(expectedMetrics.getSize().getWidth(), bounds.width());
101         assertEquals(expectedMetrics.getSize().getHeight(), bounds.height());
102     }
103 
104     @Test
105     @AppModeFull
testWindowContextBindService()106     public void testWindowContextBindService() {
107         createAllowSystemAlertWindowAppOpSession();
108         final Context windowContext = createWindowContext(DEFAULT_DISPLAY);
109         final ServiceConnection serviceConnection = new ServiceConnection() {
110             @Override
111             public void onServiceConnected(ComponentName name, IBinder service) {}
112 
113             @Override
114             public void onServiceDisconnected(ComponentName name) {}
115         };
116         try {
117             assertTrue("WindowContext must bind service successfully.",
118                     windowContext.bindService(new Intent(windowContext, TestLogService.class),
119                             serviceConnection, Context.BIND_AUTO_CREATE));
120         } finally {
121             windowContext.unbindService(serviceConnection);
122         }
123     }
124 
125     /**
126      * Verify if the {@link ComponentCallbacks#onConfigurationChanged(Configuration)} callback
127      * is received when the window context configuration changes.
128      */
129     @Test
testWindowContextRegisterComponentCallbacks()130     public void testWindowContextRegisterComponentCallbacks() {
131         final TestComponentCallbacks callbacks = new TestComponentCallbacks();
132         final WindowManagerState.DisplayContent display = createManagedVirtualDisplaySession()
133                 .setSimulateDisplay(true).createDisplay();
134         final Context windowContext = createWindowContext(display.mId);
135         final DisplayMetricsSession displayMetricsSession =
136                 createManagedDisplayMetricsSession(display.mId);
137 
138         windowContext.registerComponentCallbacks(callbacks);
139 
140         callbacks.mLatch = new CountDownLatch(1);
141 
142         displayMetricsSession.changeDisplayMetrics(1.2 /* sizeRatio */, 1.1 /* densityRatio */);
143 
144         // verify if there is a callback from the window context configuration change.
145         try {
146             assertTrue(callbacks.mLatch.await(4, TimeUnit.SECONDS));
147         } catch(InterruptedException e) {
148             throw new RuntimeException(e);
149         }
150         Rect bounds = callbacks.mConfiguration.windowConfiguration.getBounds();
151         assertBoundsEquals(displayMetricsSession.getDisplayMetrics(), bounds);
152 
153         windowContext.unregisterComponentCallbacks(callbacks);
154     }
155 
156     /**
157      * Verifies if window context on the secondary display receives global configuration changes.
158      */
159     @Test
testWindowContextGlobalConfigChanges()160     public void testWindowContextGlobalConfigChanges() {
161         final TestComponentCallbacks callbacks = new TestComponentCallbacks();
162         final WindowManagerState.DisplayContent display = createManagedVirtualDisplaySession()
163                 .setPublicDisplay(true).createDisplay();
164         final FontScaleSession fontScaleSession = createManagedFontScaleSession();
165         final Context windowContext = createWindowContext(display.mId);
166         windowContext.registerComponentCallbacks(callbacks);
167         final float expectedFontScale = fontScaleSession.get() + 0.3f;
168         fontScaleSession.set(expectedFontScale);
169 
170         // Wait for TestComponentCallbacks#mConfiguration to be assigned.
171         callbacks.waitForConfigChanged();
172 
173         // We don't rely on latch to verify the result because we may receive two configuration
174         // changes. One may from that WindowContext attaches to a DisplayArea although it is before
175         // ComponentCallback registration), the other is from font the scale change, which is what
176         // we want to verify.
177         waitForOrFail("font scale to match " + expectedFontScale, () ->
178                 expectedFontScale == callbacks.mConfiguration.fontScale);
179 
180         windowContext.unregisterComponentCallbacks(callbacks);
181     }
182 
183     /**
184      * Verify the {@link WindowProviderService} lifecycle:
185      * <ul>
186      *     <li>In {@link WindowProviderService#onCreate()}, register to the DisplayArea with
187      *     given value from {@link WindowProviderService#getWindowType()} and
188      *     {@link WindowProviderService#getWindowContextOptions()}} and receive a
189      *     {@link Configuration} update which matches DisplayArea's metrics.</li>
190      *     <li>After {@link WindowProviderService#attachToWindowToken(IBinder)}, the
191      *     {@link WindowProviderService} must be switched to register to the Window Token and
192      *     receive a configuration update which matches Window Token's metrics.</li>
193      * </ul>
194      */
195     @Test
testWindowProviderServiceLifecycle()196     public void testWindowProviderServiceLifecycle() {
197         assumeTrue(supportsSplitScreenMultiWindow());
198 
199         // Start an activity for WindowProviderService to attach
200         final TestActivity activity = startActivity(TestActivity.class);
201         final ComponentName activityName = activity.getComponentName();
202 
203         // If the device supports multi-window, make this Activity to multi-window mode.
204         // In this way, we can verify if the WindowProviderService's metrics matches
205         // the split-screen Activity's metrics, which is different from TaskDisplayArea's metrics.
206         mWmState.computeState(activityName);
207 
208         putActivityInPrimarySplit(activityName);
209 
210         activity.waitAndAssertConfigurationChanged();
211 
212         // Obtain the TestWindowService instance.
213         final TestWindowService service = createManagedWindowServiceSession().getService();
214 
215         // Compute state to obtain associated TaskActivityArea information.
216         mWmState.computeState(activityName);
217         final WindowManagerState.DisplayArea da = mWmState.getTaskDisplayArea(activityName);
218         final Rect daBounds = da.mFullConfiguration.windowConfiguration.getBounds();
219         final Rect maxDaBounds = da.mFullConfiguration.windowConfiguration.getMaxBounds();
220 
221         assertBoundsMatches(service, daBounds, maxDaBounds,
222                 "WindowProviderService bounds must match DisplayArea bounds.");
223 
224         // Obtain the Activity's token and attach it to TestWindowService.
225         final IBinder windowToken = activity.getWindow().getAttributes().token;
226         service.attachToWindowToken(windowToken);
227 
228         final WindowManager wm = activity.getWindowManager();
229         final Rect currentBounds = wm.getCurrentWindowMetrics().getBounds();
230         final Rect maxBounds = wm.getMaximumWindowMetrics().getBounds();
231 
232         service.waitAndAssertConfigurationChanged();
233         // After TestWindowService attaches the Activity's token, which is also a WindowToken,
234         // it is expected to receive a config update which matches the WindowMetrics of
235         // the Activity.
236         assertBoundsMatches(service, currentBounds, maxBounds,
237                 "WindowProviderService bounds must match WindowToken bounds.");
238     }
239 
240     /**
241      * Verifies if:
242      * <ul>
243      *     <li>{@link android.view.WindowMetrics} bounds matches provided bounds.</li>
244      *     <li>Bounds from {@link WindowProviderService#onConfigurationChanged(Configuration)}
245      *     callback matches provided bounds.</li>
246      * </ul>
247      */
assertBoundsMatches(TestWindowService service, Rect currentBounds, Rect maxBounds, String message)248     private void assertBoundsMatches(TestWindowService service, Rect currentBounds,
249             Rect maxBounds, String message) {
250         final WindowConfiguration winConfig = service.mConfiguration.windowConfiguration;
251         assertWithMessage(message + " WindowConfiguration#getBounds not matched.")
252                 .that(winConfig.getBounds()).isEqualTo(currentBounds);
253         assertWithMessage(message +  " WindowConfiguration#getMaxBounds not "
254                 + "matched.").that(winConfig.getMaxBounds()).isEqualTo(maxBounds);
255 
256         final WindowManager wm = service.getSystemService(WindowManager.class);
257         final Rect currentWindowBounds = wm.getCurrentWindowMetrics().getBounds();
258         final Rect maxWindowBounds = wm.getMaximumWindowMetrics().getBounds();
259         assertWithMessage(message + " Current WindowMetrics bounds not matched.")
260                 .that(currentWindowBounds).isEqualTo(currentBounds);
261         assertWithMessage(message  + " Maximum WindowMetrics bounds not matched.")
262                 .that(maxWindowBounds).isEqualTo(maxBounds);
263     }
264 
265     @Test
testWidowProviderServiceGlobalConfigChanges()266     public void testWidowProviderServiceGlobalConfigChanges() {
267         final TestWindowService service = createManagedWindowServiceSession().getService();
268 
269         // Obtain the original config
270         final Configuration originalConfiguration =
271                 new Configuration(service.getResources().getConfiguration());
272 
273         final FontScaleSession fontScaleSession = createManagedFontScaleSession();
274         final float expectedFontScale = fontScaleSession.get() + 0.3f;
275         fontScaleSession.set(expectedFontScale);
276 
277         service.waitAndAssertConfigurationChanged();
278 
279         assertThat(service.mConfiguration.fontScale).isEqualTo(expectedFontScale);
280         // Also check Configuration obtained from WindowProviderService's Resources
281         assertWithMessage("Configuration update must contains font scale change.")
282                 .that(originalConfiguration.diff(service.mConfiguration)
283                         & ActivityInfo.CONFIG_FONT_SCALE).isNotEqualTo(0);
284         assertWithMessage("Font scale must be updated to WindowProviderService Resources.")
285                 .that(service.getResources().getConfiguration().fontScale)
286                 .isEqualTo(expectedFontScale);
287     }
288 
289     @Test
testWindowProviderServiceCallWmBeforeOnCreateNotCrash()290     public void testWindowProviderServiceCallWmBeforeOnCreateNotCrash() {
291         final TestWindowService service =
292                 createManagedWindowServiceSession(true /* verifyWmInOnCreate */).getService();
293         if (service.mThrowableFromOnCreate != null) {
294             throw new AssertionError("Calling WindowManager APIs before"
295                     + " WindowProviderService#onCreate must not throw Throwable, but did.",
296                     service.mThrowableFromOnCreate);
297         }
298     }
299 
300     public static class TestActivity extends WindowManagerTestBase.FocusableActivity {
301         final CountDownLatch mLatch = new CountDownLatch(1);
302 
303         @Override
onConfigurationChanged(@onNull Configuration newConfig)304         public void onConfigurationChanged(@NonNull Configuration newConfig) {
305             super.onConfigurationChanged(newConfig);
306             mLatch.countDown();
307         }
308 
waitAndAssertConfigurationChanged()309         private void waitAndAssertConfigurationChanged() {
310             try {
311                 assertThat(mLatch.await(4, TimeUnit.SECONDS)).isTrue();
312             } catch(InterruptedException e) {
313                 throw new RuntimeException(e);
314             }
315         }
316     }
317 
createManagedWindowServiceSession()318     private TestWindowServiceSession createManagedWindowServiceSession() {
319         return mObjectTracker.manage(new TestWindowServiceSession(false /* verifyWmInOnCreate */));
320     }
321 
createManagedWindowServiceSession(boolean verifyWmInOnCreate)322     private TestWindowServiceSession createManagedWindowServiceSession(boolean verifyWmInOnCreate) {
323         return mObjectTracker.manage(new TestWindowServiceSession(verifyWmInOnCreate));
324     }
325 
326     private static class TestWindowServiceSession implements AutoCloseable {
327         private final ServiceTestRule mServiceRule = new ServiceTestRule();
328         private final TestWindowService mService;
329         private static boolean sVerifyWmInOnCreate = false;
330 
TestWindowServiceSession(boolean verifyWmInOnCreate)331         private TestWindowServiceSession(boolean verifyWmInOnCreate) {
332             final Context context = ApplicationProvider.getApplicationContext();
333             final Intent intent = new Intent(context, TestWindowService.class);
334             sVerifyWmInOnCreate = verifyWmInOnCreate;
335             try {
336                 final TestToken token = (TestToken) mServiceRule.bindService(intent);
337                 mService = token.getService();
338             } catch (TimeoutException e) {
339                 throw new RuntimeException(e);
340             }
341         }
342 
getService()343         private TestWindowService getService() {
344             return mService;
345         }
346 
347         @Override
close()348         public void close() {
349             mServiceRule.unbindService();
350         }
351     }
352 
353     public static class TestWindowService extends WindowProviderService {
354         private final IBinder mToken = new TestToken();
355         private CountDownLatch mLatch = new CountDownLatch(1);
356         private Configuration mConfiguration;
357         private Throwable mThrowableFromOnCreate;
358 
359         @Override
getWindowType()360         public int getWindowType() {
361             return TYPE_APPLICATION;
362         }
363 
364         @Nullable
365         @Override
onBind(Intent intent)366         public IBinder onBind(Intent intent) {
367             return mToken;
368         }
369 
370         @Override
onCreate()371         public void onCreate() {
372             // Verify if call WindowManager before WindowProviderService#onCreate throws Exception.
373             if (TestWindowServiceSession.sVerifyWmInOnCreate) {
374                 try {
375                     getSystemService(WindowManager.class).getCurrentWindowMetrics();
376                 } catch (Throwable t) {
377                     mThrowableFromOnCreate = t;
378                 }
379             }
380             super.onCreate();
381             mConfiguration = getResources().getConfiguration();
382         }
383 
384         @Override
onConfigurationChanged(Configuration newConfig)385         public void onConfigurationChanged(Configuration newConfig) {
386             super.onConfigurationChanged(newConfig);
387             mConfiguration = newConfig;
388             mLatch.countDown();
389         }
390 
resetLatch()391         private void resetLatch() {
392             mLatch = new CountDownLatch(1);
393         }
394 
waitAndAssertConfigurationChanged()395         private void waitAndAssertConfigurationChanged() {
396             try {
397                 assertThat(mLatch.await(4, TimeUnit.SECONDS)).isTrue();
398             } catch(InterruptedException e) {
399                 throw new RuntimeException(e);
400             }
401         }
402 
403         public class TestToken extends Binder {
getService()404             private TestWindowService getService() {
405                 return TestWindowService.this;
406             }
407         }
408     }
409 
410     private static class TestComponentCallbacks implements ComponentCallbacks {
411         private Configuration mConfiguration;
412         private CountDownLatch mLatch = new CountDownLatch(1);
413 
waitForConfigChanged()414         private void waitForConfigChanged() {
415             try {
416                 assertThat(mLatch.await(4, TimeUnit.SECONDS)).isTrue();
417             } catch (InterruptedException e) {
418                 throw new RuntimeException(e);
419             }
420         }
421 
422         @Override
onConfigurationChanged(@onNull Configuration newConfig)423         public void onConfigurationChanged(@NonNull Configuration newConfig) {
424             mConfiguration = newConfig;
425             mLatch.countDown();
426         }
427 
428         @Override
onLowMemory()429         public void onLowMemory() {}
430     }
431 }
432