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