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