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