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 com.android.server.wm; 18 19 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; 20 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED; 21 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; 22 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; 23 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; 24 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; 25 import static android.content.res.Configuration.ORIENTATION_PORTRAIT; 26 import static android.view.Surface.ROTATION_90; 27 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 28 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY; 29 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; 30 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; 31 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; 32 import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER; 33 import static android.window.DisplayAreaOrganizer.FEATURE_IME_PLACEHOLDER; 34 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST; 35 import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION; 36 37 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; 38 import static com.android.server.wm.SizeCompatTests.prepareLimitedBounds; 39 import static com.android.server.wm.SizeCompatTests.prepareUnresizable; 40 import static com.android.server.wm.SizeCompatTests.rotateDisplay; 41 42 import static com.google.common.truth.Truth.assertThat; 43 44 import static org.mockito.ArgumentMatchers.any; 45 import static org.mockito.Mockito.doReturn; 46 import static org.mockito.Mockito.mock; 47 import static org.mockito.Mockito.never; 48 import static org.mockito.Mockito.verify; 49 import static org.mockito.Mockito.when; 50 51 import android.annotation.NonNull; 52 import android.content.res.Configuration; 53 import android.graphics.Rect; 54 import android.os.Binder; 55 import android.platform.test.annotations.Presubmit; 56 import android.view.Display; 57 import android.window.IDisplayAreaOrganizer; 58 59 import androidx.test.filters.SmallTest; 60 61 import com.google.android.collect.Lists; 62 63 import org.junit.Before; 64 import org.junit.Test; 65 import org.junit.runner.RunWith; 66 67 import java.util.ArrayList; 68 import java.util.List; 69 70 /** 71 * Tests for the Dual DisplayAreaGroup device behavior. 72 * 73 * Build/Install/Run: 74 * atest WmTests:DualDisplayAreaGroupPolicyTest 75 */ 76 @SmallTest 77 @Presubmit 78 @RunWith(WindowTestRunner.class) 79 public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { 80 private static final int FEATURE_FIRST_ROOT = FEATURE_VENDOR_FIRST; 81 private static final int FEATURE_FIRST_TASK_CONTAINER = FEATURE_DEFAULT_TASK_CONTAINER; 82 private static final int FEATURE_SECOND_ROOT = FEATURE_VENDOR_FIRST + 1; 83 private static final int FEATURE_SECOND_TASK_CONTAINER = FEATURE_VENDOR_FIRST + 2; 84 85 private DualDisplayContent mDisplay; 86 private DisplayAreaGroup mFirstRoot; 87 private DisplayAreaGroup mSecondRoot; 88 private TaskDisplayArea mFirstTda; 89 private TaskDisplayArea mSecondTda; 90 private Task mFirstTask; 91 private Task mSecondTask; 92 private ActivityRecord mFirstActivity; 93 private ActivityRecord mSecondActivity; 94 95 @Before setUp()96 public void setUp() { 97 // Let the Display to be created with the DualDisplay policy. 98 setupDisplay(new DualDisplayTestPolicyProvider(mWm)); 99 } 100 101 /** Populates fields for the test display. */ setupDisplay(@onNull DisplayAreaPolicy.Provider policyProvider)102 private void setupDisplay(@NonNull DisplayAreaPolicy.Provider policyProvider) { 103 doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider(); 104 105 // Display: 1920x1200 (landscape). First and second display are both 860x1200 (portrait). 106 mDisplay = new DualDisplayContent.Builder(mAtm, 1920, 1200).build(); 107 mFirstRoot = mDisplay.mFirstRoot; 108 mSecondRoot = mDisplay.mSecondRoot; 109 mFirstTda = mDisplay.getTaskDisplayArea(FEATURE_FIRST_TASK_CONTAINER); 110 mSecondTda = mDisplay.getTaskDisplayArea(FEATURE_SECOND_TASK_CONTAINER); 111 mFirstTask = new TaskBuilder(mSupervisor) 112 .setTaskDisplayArea(mFirstTda) 113 .setCreateActivity(true) 114 .build() 115 .getBottomMostTask(); 116 mSecondTask = new TaskBuilder(mSupervisor) 117 .setTaskDisplayArea(mSecondTda) 118 .setCreateActivity(true) 119 .build() 120 .getBottomMostTask(); 121 mFirstActivity = mFirstTask.getTopNonFinishingActivity(); 122 mSecondActivity = mSecondTask.getTopNonFinishingActivity(); 123 124 spyOn(mDisplay); 125 spyOn(mFirstRoot); 126 spyOn(mSecondRoot); 127 } 128 129 @Test testNotIgnoreOrientationRequest_differentOrientationFromDisplay_reversesRequest()130 public void testNotIgnoreOrientationRequest_differentOrientationFromDisplay_reversesRequest() { 131 mFirstRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */); 132 mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda); 133 134 prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_LANDSCAPE); 135 136 assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_PORTRAIT); 137 assertThat(mFirstActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE); 138 139 prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_PORTRAIT); 140 141 assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_LANDSCAPE); 142 assertThat(mFirstActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT); 143 } 144 145 @Test testNotIgnoreOrientationRequest_onlyRespectsFocusedTaskDisplayArea()146 public void testNotIgnoreOrientationRequest_onlyRespectsFocusedTaskDisplayArea() { 147 mFirstRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */); 148 mSecondRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */); 149 mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda); 150 151 // Second TDA is not focused, so Display won't get the request 152 prepareUnresizable(mSecondActivity, SCREEN_ORIENTATION_LANDSCAPE); 153 154 assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_UNSPECIFIED); 155 156 // First TDA is focused, so Display gets the request 157 prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_LANDSCAPE); 158 159 assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_PORTRAIT); 160 } 161 162 @Test testIgnoreOrientationRequest_displayDoesNotReceiveOrientationChange()163 public void testIgnoreOrientationRequest_displayDoesNotReceiveOrientationChange() { 164 mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); 165 mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); 166 mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda); 167 168 prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_LANDSCAPE); 169 170 verify(mFirstRoot).onDescendantOrientationChanged(any()); 171 verify(mDisplay, never()).onDescendantOrientationChanged(any()); 172 } 173 174 @Test testIgnoreOrientationRequest_displayReceiveOrientationChangeForNoSensor()175 public void testIgnoreOrientationRequest_displayReceiveOrientationChangeForNoSensor() { 176 mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); 177 mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); 178 mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda); 179 180 prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_NOSENSOR); 181 182 verify(mFirstRoot).onDescendantOrientationChanged(any()); 183 verify(mDisplay).onDescendantOrientationChanged(any()); 184 } 185 186 @Test testIgnoreOrientationRequest_displayReceiveOrientationChangeForLocked()187 public void testIgnoreOrientationRequest_displayReceiveOrientationChangeForLocked() { 188 mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); 189 mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); 190 mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda); 191 192 prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_LOCKED); 193 194 verify(mFirstRoot).onDescendantOrientationChanged(any()); 195 verify(mDisplay).onDescendantOrientationChanged(any()); 196 } 197 198 @Test testLaunchPortraitApp_fillsDisplayAreaGroup()199 public void testLaunchPortraitApp_fillsDisplayAreaGroup() { 200 mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); 201 mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); 202 mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda); 203 204 prepareLimitedBounds(mFirstActivity, SCREEN_ORIENTATION_PORTRAIT, 205 false /* isUnresizable */); 206 final Rect dagBounds = new Rect(mFirstRoot.getBounds()); 207 final Rect taskBounds = new Rect(mFirstTask.getBounds()); 208 final Rect activityBounds = new Rect(mFirstActivity.getBounds()); 209 210 // DAG is portrait (860x1200), so Task and Activity fill DAG. 211 assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); 212 assertThat(mFirstActivity.inSizeCompatMode()).isFalse(); 213 assertThat(taskBounds).isEqualTo(dagBounds); 214 assertThat(activityBounds).isEqualTo(taskBounds); 215 } 216 217 @Test testLaunchPortraitApp_sizeCompatAfterRotation()218 public void testLaunchPortraitApp_sizeCompatAfterRotation() { 219 mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); 220 mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); 221 mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda); 222 223 prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_PORTRAIT); 224 final Rect dagBounds = new Rect(mFirstRoot.getBounds()); 225 final Rect activityBounds = new Rect(mFirstActivity.getBounds()); 226 227 rotateDisplay(mDisplay, ROTATION_90); 228 final Rect newDagBounds = new Rect(mFirstRoot.getBounds()); 229 final Rect newTaskBounds = new Rect(mFirstTask.getBounds()); 230 final Rect activitySizeCompatBounds = new Rect(mFirstActivity.getBounds()); 231 final Rect activityConfigBounds = 232 new Rect(mFirstActivity.getConfiguration().windowConfiguration.getBounds()); 233 234 // DAG is landscape (1200x860), no fixed orientation letterbox 235 assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); 236 assertThat(mFirstActivity.inSizeCompatMode()).isTrue(); 237 assertThat(newDagBounds.width()).isEqualTo(dagBounds.height()); 238 assertThat(newDagBounds.height()).isEqualTo(dagBounds.width()); 239 assertThat(newTaskBounds).isEqualTo(newDagBounds); 240 241 // Activity config bounds is unchanged, size compat bounds is (860x[860x860/1200=616]) 242 assertThat(mFirstActivity.getCompatScale()).isLessThan(1f); 243 assertThat(activityConfigBounds.width()).isEqualTo(activityBounds.width()); 244 assertThat(activityConfigBounds.height()).isEqualTo(activityBounds.height()); 245 assertThat(activitySizeCompatBounds.height()).isEqualTo(newTaskBounds.height()); 246 assertThat(activitySizeCompatBounds.width()).isEqualTo( 247 newTaskBounds.height() * newTaskBounds.height() / newTaskBounds.width()); 248 } 249 250 @Test testLaunchNoSensorApp_noSizeCompatAfterRotation()251 public void testLaunchNoSensorApp_noSizeCompatAfterRotation() { 252 mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); 253 mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); 254 mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda); 255 256 prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_NOSENSOR); 257 assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); 258 assertThat(mFirstActivity.inSizeCompatMode()).isFalse(); 259 260 rotateDisplay(mDisplay, ROTATION_90); 261 assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); 262 assertThat(mFirstActivity.inSizeCompatMode()).isFalse(); 263 } 264 265 @Test testLaunchLandscapeApp_activityIsLetterboxForFixedOrientationInDisplayAreaGroup()266 public void testLaunchLandscapeApp_activityIsLetterboxForFixedOrientationInDisplayAreaGroup() { 267 mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); 268 mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); 269 mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda); 270 271 prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_LANDSCAPE); 272 final Rect dagBounds = new Rect(mFirstRoot.getBounds()); 273 final Rect taskBounds = new Rect(mFirstTask.getBounds()); 274 final Rect activityBounds = new Rect(mFirstActivity.getBounds()); 275 276 // DAG is portrait (860x1200), and activity is letterboxed for fixed orientation 277 // (860x[860x860/1200=616]). Task fills DAG. 278 assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isTrue(); 279 assertThat(mFirstActivity.inSizeCompatMode()).isFalse(); 280 assertThat(taskBounds).isEqualTo(dagBounds); 281 assertThat(activityBounds.width()).isEqualTo(dagBounds.width()); 282 assertThat(activityBounds.height()) 283 .isEqualTo(dagBounds.width() * dagBounds.width() / dagBounds.height()); 284 } 285 286 @Test testLaunchNoSensorApp_activityIsNotLetterboxForFixedOrientationDisplayAreaGroup()287 public void testLaunchNoSensorApp_activityIsNotLetterboxForFixedOrientationDisplayAreaGroup() { 288 mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); 289 mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); 290 mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda); 291 292 prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_NOSENSOR); 293 assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); 294 } 295 296 @Test testLaunchLockedApp_activityIsNotLetterboxForFixedOrientationInDisplayAreaGroup()297 public void testLaunchLockedApp_activityIsNotLetterboxForFixedOrientationInDisplayAreaGroup() { 298 mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); 299 mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); 300 mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda); 301 302 prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_LOCKED); 303 assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); 304 } 305 306 @Test testLaunchLandscapeApp_fixedOrientationLetterboxBecomesSizeCompatAfterRotation()307 public void testLaunchLandscapeApp_fixedOrientationLetterboxBecomesSizeCompatAfterRotation() { 308 mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); 309 mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); 310 mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda); 311 312 prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_LANDSCAPE); 313 final Rect dagBounds = new Rect(mFirstRoot.getBounds()); 314 final Rect activityBounds = new Rect(mFirstActivity.getBounds()); 315 316 rotateDisplay(mDisplay, ROTATION_90); 317 final Rect newDagBounds = new Rect(mFirstRoot.getBounds()); 318 final Rect newTaskBounds = new Rect(mFirstTask.getBounds()); 319 final Rect newActivityBounds = new Rect(mFirstActivity.getBounds()); 320 321 // DAG is landscape (1200x860), no fixed orientation letterbox 322 assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); 323 assertThat(mFirstActivity.inSizeCompatMode()).isTrue(); 324 assertThat(newDagBounds.width()).isEqualTo(dagBounds.height()); 325 assertThat(newDagBounds.height()).isEqualTo(dagBounds.width()); 326 assertThat(newTaskBounds).isEqualTo(newDagBounds); 327 328 // Because we don't scale up, there is no size compat bounds and app bounds is the same as 329 // the previous bounds. 330 assertThat(mFirstActivity.hasSizeCompatBounds()).isFalse(); 331 assertThat(newActivityBounds.width()).isEqualTo(activityBounds.width()); 332 assertThat(newActivityBounds.height()).isEqualTo(activityBounds.height()); 333 } 334 335 @Test testLaunchNoSensorApp_fixedOrientationLetterboxBecomesSizeCompatAfterRotation()336 public void testLaunchNoSensorApp_fixedOrientationLetterboxBecomesSizeCompatAfterRotation() { 337 mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); 338 mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); 339 mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda); 340 341 prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_NOSENSOR); 342 343 rotateDisplay(mDisplay, ROTATION_90); 344 345 assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); 346 assertThat(mFirstActivity.inSizeCompatMode()).isFalse(); 347 } 348 349 @Test testPlaceImeContainer_reparentToTargetDisplayAreaGroup()350 public void testPlaceImeContainer_reparentToTargetDisplayAreaGroup() { 351 setupImeWindow(); 352 final DisplayArea.Tokens imeContainer = mDisplay.getImeContainer(); 353 final WindowToken imeToken = tokenOfType(TYPE_INPUT_METHOD); 354 355 // By default, the ime container is attached to DC as defined in DAPolicy. 356 assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mDisplay); 357 assertThat(mDisplay.findAreaForTokenInLayer(imeToken)).isEqualTo(imeContainer); 358 359 final WindowState firstActivityWin = 360 createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mFirstActivity, 361 "firstActivityWin"); 362 spyOn(firstActivityWin); 363 final WindowState secondActivityWin = 364 createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mSecondActivity, 365 "firstActivityWin"); 366 spyOn(secondActivityWin); 367 368 // firstActivityWin should be the target 369 doReturn(true).when(firstActivityWin).canBeImeTarget(); 370 doReturn(false).when(secondActivityWin).canBeImeTarget(); 371 372 WindowState imeTarget = mDisplay.computeImeTarget(true /* updateImeTarget */); 373 374 assertThat(imeTarget).isEqualTo(firstActivityWin); 375 verify(mFirstRoot).placeImeContainer(imeContainer); 376 assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mFirstRoot); 377 assertThat(imeContainer.getParent().asDisplayArea().mFeatureId) 378 .isEqualTo(FEATURE_IME_PLACEHOLDER); 379 assertThat(mDisplay.findAreaForTokenInLayer(imeToken)).isNull(); 380 assertThat(mFirstRoot.findAreaForTokenInLayer(imeToken)).isEqualTo(imeContainer); 381 assertThat(mSecondRoot.findAreaForTokenInLayer(imeToken)).isNull(); 382 383 // secondActivityWin should be the target 384 doReturn(false).when(firstActivityWin).canBeImeTarget(); 385 doReturn(true).when(secondActivityWin).canBeImeTarget(); 386 387 imeTarget = mDisplay.computeImeTarget(true /* updateImeTarget */); 388 389 assertThat(imeTarget).isEqualTo(secondActivityWin); 390 verify(mSecondRoot).placeImeContainer(imeContainer); 391 assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondRoot); 392 assertThat(imeContainer.getParent().asDisplayArea().mFeatureId) 393 .isEqualTo(FEATURE_IME_PLACEHOLDER); 394 assertThat(mDisplay.findAreaForTokenInLayer(imeToken)).isNull(); 395 assertThat(mFirstRoot.findAreaForTokenInLayer(imeToken)).isNull(); 396 assertThat(mSecondRoot.findAreaForTokenInLayer(imeToken)).isEqualTo(imeContainer); 397 } 398 399 @Test testPlaceImeContainer_hidesImeWhenParentChanges()400 public void testPlaceImeContainer_hidesImeWhenParentChanges() { 401 setupImeWindow(); 402 final DisplayArea.Tokens imeContainer = mDisplay.getImeContainer(); 403 final WindowToken imeToken = tokenOfType(TYPE_INPUT_METHOD); 404 final WindowState firstActivityWin = 405 createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mFirstActivity, 406 "firstActivityWin"); 407 spyOn(firstActivityWin); 408 final WindowState secondActivityWin = 409 createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mSecondActivity, 410 "secondActivityWin"); 411 spyOn(secondActivityWin); 412 413 // firstActivityWin should be the target 414 doReturn(true).when(firstActivityWin).canBeImeTarget(); 415 doReturn(false).when(secondActivityWin).canBeImeTarget(); 416 417 WindowState imeTarget = mDisplay.computeImeTarget(true /* updateImeTarget */); 418 assertThat(imeTarget).isEqualTo(firstActivityWin); 419 verify(mFirstRoot).placeImeContainer(imeContainer); 420 421 // secondActivityWin should be the target 422 doReturn(false).when(firstActivityWin).canBeImeTarget(); 423 doReturn(true).when(secondActivityWin).canBeImeTarget(); 424 425 spyOn(mDisplay.mInputMethodWindow); 426 imeTarget = mDisplay.computeImeTarget(true /* updateImeTarget */); 427 428 assertThat(imeTarget).isEqualTo(secondActivityWin); 429 verify(mSecondRoot).placeImeContainer(imeContainer); 430 // verify hide() was called on InputMethodWindow. 431 verify(mDisplay.mInputMethodWindow).hide(false /* doAnimation */, false /* requestAnim */); 432 } 433 434 @Test testPlaceImeContainer_skipReparentForOrganizedImeContainer()435 public void testPlaceImeContainer_skipReparentForOrganizedImeContainer() { 436 setupImeWindow(); 437 final DisplayArea.Tokens imeContainer = mDisplay.getImeContainer(); 438 final WindowToken imeToken = tokenOfType(TYPE_INPUT_METHOD); 439 440 // By default, the ime container is attached to DC as defined in DAPolicy. 441 assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mDisplay); 442 assertThat(mDisplay.findAreaForTokenInLayer(imeToken)).isEqualTo(imeContainer); 443 444 final WindowState firstActivityWin = 445 createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mFirstActivity, 446 "firstActivityWin"); 447 spyOn(firstActivityWin); 448 // firstActivityWin should be the target 449 doReturn(true).when(firstActivityWin).canBeImeTarget(); 450 451 // Main precondition for this test: organize the ImeContainer. 452 final IDisplayAreaOrganizer mockImeOrganizer = mock(IDisplayAreaOrganizer.class); 453 when(mockImeOrganizer.asBinder()).thenReturn(new Binder()); 454 imeContainer.setOrganizer(mockImeOrganizer); 455 456 WindowState imeTarget = mDisplay.computeImeTarget(true /* updateImeTarget */); 457 458 // The IME target must be updated but the don't reparent organized ImeContainers. 459 // See DisplayAreaOrganizer#FEATURE_IME. 460 assertThat(imeTarget).isEqualTo(firstActivityWin); 461 verify(mFirstRoot, never()).placeImeContainer(imeContainer); 462 463 // Clean up organizer. 464 imeContainer.setOrganizer(null); 465 } 466 467 @Test testPlaceImeContainer_noReparentIfRootDoesNotHaveImePlaceholder()468 public void testPlaceImeContainer_noReparentIfRootDoesNotHaveImePlaceholder() { 469 // Define the DualDisplayArea hierarchy without IME_PLACEHOLDER in DAGs. 470 setupDisplay(new DualDisplayTestPolicyProvider(new ArrayList<>(), new ArrayList<>())); 471 setupImeWindow(); 472 final DisplayArea.Tokens imeContainer = mDisplay.getImeContainer(); 473 final WindowToken imeToken = tokenOfType(TYPE_INPUT_METHOD); 474 475 // By default, the ime container is attached to DC as defined in DAPolicy. 476 assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mDisplay); 477 478 // firstActivityWin should be the target 479 final WindowState firstActivityWin = 480 createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mFirstActivity, 481 "firstActivityWin"); 482 spyOn(firstActivityWin); 483 doReturn(true).when(firstActivityWin).canBeImeTarget(); 484 WindowState imeTarget = mDisplay.computeImeTarget(true /* updateImeTarget */); 485 486 // There is no IME_PLACEHOLDER in the firstRoot, so the ImeContainer will not be reparented. 487 assertThat(imeTarget).isEqualTo(firstActivityWin); 488 verify(mFirstRoot).placeImeContainer(imeContainer); 489 assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mDisplay); 490 assertThat(imeContainer.getParent().asDisplayArea().mFeatureId) 491 .isEqualTo(FEATURE_IME_PLACEHOLDER); 492 assertThat(mDisplay.findAreaForTokenInLayer(imeToken)).isEqualTo(imeContainer); 493 assertThat(mFirstRoot.findAreaForTokenInLayer(imeToken)).isNull(); 494 assertThat(mSecondRoot.findAreaForTokenInLayer(imeToken)).isNull(); 495 } 496 497 @Test testResizableFixedOrientationApp_fixedOrientationLetterboxing()498 public void testResizableFixedOrientationApp_fixedOrientationLetterboxing() { 499 mFirstRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */); 500 mSecondRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */); 501 502 // Launch portrait on first DAG 503 mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda); 504 prepareLimitedBounds(mFirstActivity, SCREEN_ORIENTATION_PORTRAIT, 505 false /* isUnresizable */); 506 507 // Display in landscape (as opposite to DAG), first DAG and activity in portrait 508 assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_LANDSCAPE); 509 assertThat(mFirstRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT); 510 assertThat(mFirstActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT); 511 assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); 512 assertThat(mFirstActivity.inSizeCompatMode()).isFalse(); 513 514 // Launch portrait on second DAG 515 mDisplay.onLastFocusedTaskDisplayAreaChanged(mSecondTda); 516 prepareLimitedBounds(mSecondActivity, SCREEN_ORIENTATION_LANDSCAPE, 517 false /* isUnresizable */); 518 519 // Display in portrait (as opposite to DAG), first DAG and activity in landscape 520 assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_PORTRAIT); 521 assertThat(mSecondRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE); 522 assertThat(mSecondActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE); 523 assertThat(mSecondActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); 524 assertThat(mSecondActivity.inSizeCompatMode()).isFalse(); 525 526 // First activity is letterboxed in portrait as requested. 527 assertThat(mFirstRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE); 528 assertThat(mFirstActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT); 529 assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isTrue(); 530 assertThat(mFirstActivity.inSizeCompatMode()).isFalse(); 531 532 } 533 setupImeWindow()534 private void setupImeWindow() { 535 final WindowState imeWindow = createWindow(null /* parent */, 536 TYPE_INPUT_METHOD, mDisplay, "mImeWindow"); 537 imeWindow.mAttrs.flags |= FLAG_NOT_FOCUSABLE; 538 mDisplay.mInputMethodWindow = imeWindow; 539 } 540 tokenOfType(int type)541 private WindowToken tokenOfType(int type) { 542 return new WindowToken.Builder(mWm, new Binder(), type) 543 .setDisplayContent(mDisplay).build(); 544 } 545 546 /** Display with two {@link DisplayAreaGroup}. Each of them take half of the screen. */ 547 static class DualDisplayContent extends TestDisplayContent { 548 final DisplayAreaGroup mFirstRoot; 549 final DisplayAreaGroup mSecondRoot; 550 final Rect mLastDisplayBounds; 551 552 /** Please use the {@link Builder} to create. */ DualDisplayContent(RootWindowContainer rootWindowContainer, Display display)553 DualDisplayContent(RootWindowContainer rootWindowContainer, 554 Display display) { 555 super(rootWindowContainer, display, mock(DeviceStateController.class)); 556 557 mFirstRoot = getGroupRoot(FEATURE_FIRST_ROOT); 558 mSecondRoot = getGroupRoot(FEATURE_SECOND_ROOT); 559 mLastDisplayBounds = new Rect(getBounds()); 560 updateDisplayAreaGroupBounds(); 561 } 562 getGroupRoot(int rootFeatureId)563 DisplayAreaGroup getGroupRoot(int rootFeatureId) { 564 DisplayArea da = getDisplayArea(rootFeatureId); 565 assertThat(da).isInstanceOf(DisplayAreaGroup.class); 566 return (DisplayAreaGroup) da; 567 } 568 getTaskDisplayArea(int tdaFeatureId)569 TaskDisplayArea getTaskDisplayArea(int tdaFeatureId) { 570 DisplayArea da = getDisplayArea(tdaFeatureId); 571 assertThat(da).isInstanceOf(TaskDisplayArea.class); 572 return (TaskDisplayArea) da; 573 } 574 getDisplayArea(int featureId)575 DisplayArea getDisplayArea(int featureId) { 576 final DisplayArea displayArea = 577 getItemFromDisplayAreas(da -> da.mFeatureId == featureId ? da : null); 578 assertThat(displayArea).isNotNull(); 579 return displayArea; 580 } 581 582 @Override onConfigurationChanged(Configuration newParentConfig)583 public void onConfigurationChanged(Configuration newParentConfig) { 584 super.onConfigurationChanged(newParentConfig); 585 586 final Rect curBounds = getBounds(); 587 if (mLastDisplayBounds != null && !mLastDisplayBounds.equals(curBounds)) { 588 mLastDisplayBounds.set(curBounds); 589 updateDisplayAreaGroupBounds(); 590 } 591 } 592 593 /** Updates first and second {@link DisplayAreaGroup} to take half of the screen. */ updateDisplayAreaGroupBounds()594 private void updateDisplayAreaGroupBounds() { 595 if (mFirstRoot == null || mSecondRoot == null) { 596 return; 597 } 598 599 final Rect bounds = mLastDisplayBounds; 600 Rect groupBounds1, groupBounds2; 601 if (bounds.width() >= bounds.height()) { 602 groupBounds1 = new Rect(bounds.left, bounds.top, 603 (bounds.right + bounds.left) / 2, bounds.bottom); 604 605 groupBounds2 = new Rect((bounds.right + bounds.left) / 2, bounds.top, 606 bounds.right, bounds.bottom); 607 } else { 608 groupBounds1 = new Rect(bounds.left, bounds.top, 609 bounds.right, (bounds.top + bounds.bottom) / 2); 610 611 groupBounds2 = new Rect(bounds.left, 612 (bounds.top + bounds.bottom) / 2, bounds.right, bounds.bottom); 613 } 614 mFirstRoot.setBounds(groupBounds1); 615 mSecondRoot.setBounds(groupBounds2); 616 } 617 618 static class Builder extends TestDisplayContent.Builder { 619 Builder(ActivityTaskManagerService service, int width, int height)620 Builder(ActivityTaskManagerService service, int width, int height) { 621 super(service, width, height); 622 } 623 624 @Override createInternal(Display display)625 TestDisplayContent createInternal(Display display) { 626 return new DualDisplayContent(mService.mRootWindowContainer, display); 627 } 628 build()629 DualDisplayContent build() { 630 return (DualDisplayContent) super.build(); 631 } 632 } 633 } 634 635 /** Policy to create a dual {@link DisplayAreaGroup} policy in test. */ 636 static class DualDisplayTestPolicyProvider implements DisplayAreaPolicy.Provider { 637 638 @NonNull 639 private final List<DisplayAreaPolicyBuilder.Feature> mFirstRootFeatures = new ArrayList<>(); 640 @NonNull 641 private final List<DisplayAreaPolicyBuilder.Feature> mSecondRootFeatures = 642 new ArrayList<>(); 643 DualDisplayTestPolicyProvider(@onNull WindowManagerService wmService)644 DualDisplayTestPolicyProvider(@NonNull WindowManagerService wmService) { 645 // Add IME_PLACEHOLDER by default. 646 this(Lists.newArrayList(new DisplayAreaPolicyBuilder.Feature.Builder( 647 wmService.mPolicy, 648 "ImePlaceholder", FEATURE_IME_PLACEHOLDER) 649 .and(TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG) 650 .build()), 651 Lists.newArrayList(new DisplayAreaPolicyBuilder.Feature.Builder( 652 wmService.mPolicy, 653 "ImePlaceholder", FEATURE_IME_PLACEHOLDER) 654 .and(TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG) 655 .build())); 656 } 657 DualDisplayTestPolicyProvider( @onNull List<DisplayAreaPolicyBuilder.Feature> firstRootFeatures, @NonNull List<DisplayAreaPolicyBuilder.Feature> secondRootFeatures)658 DualDisplayTestPolicyProvider( 659 @NonNull List<DisplayAreaPolicyBuilder.Feature> firstRootFeatures, 660 @NonNull List<DisplayAreaPolicyBuilder.Feature> secondRootFeatures) { 661 mFirstRootFeatures.addAll(firstRootFeatures); 662 mSecondRootFeatures.addAll(secondRootFeatures); 663 } 664 665 @Override instantiate(@onNull WindowManagerService wmService, @NonNull DisplayContent content, @NonNull RootDisplayArea root, @NonNull DisplayArea.Tokens imeContainer)666 public DisplayAreaPolicy instantiate(@NonNull WindowManagerService wmService, 667 @NonNull DisplayContent content, @NonNull RootDisplayArea root, 668 @NonNull DisplayArea.Tokens imeContainer) { 669 // Root 670 // Include FEATURE_WINDOWED_MAGNIFICATION because it will be used as the screen rotation 671 // layer 672 DisplayAreaPolicyBuilder.HierarchyBuilder rootHierarchy = 673 new DisplayAreaPolicyBuilder.HierarchyBuilder(root) 674 .setImeContainer(imeContainer) 675 .addFeature(new DisplayAreaPolicyBuilder.Feature.Builder( 676 wmService.mPolicy, 677 "WindowedMagnification", FEATURE_WINDOWED_MAGNIFICATION) 678 .upTo(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY) 679 .except(TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY) 680 .setNewDisplayAreaSupplier(DisplayArea.Dimmable::new) 681 .build()) 682 .addFeature(new DisplayAreaPolicyBuilder.Feature.Builder( 683 wmService.mPolicy, 684 "ImePlaceholder", FEATURE_IME_PLACEHOLDER) 685 .and(TYPE_INPUT_METHOD, TYPE_INPUT_METHOD_DIALOG) 686 .build()); 687 688 // First 689 final RootDisplayArea firstRoot = new DisplayAreaGroup(wmService, "FirstRoot", 690 FEATURE_FIRST_ROOT); 691 final TaskDisplayArea firstTaskDisplayArea = new TaskDisplayArea(content, wmService, 692 "FirstTaskDisplayArea", FEATURE_FIRST_TASK_CONTAINER); 693 final List<TaskDisplayArea> firstTdaList = new ArrayList<>(); 694 firstTdaList.add(firstTaskDisplayArea); 695 DisplayAreaPolicyBuilder.HierarchyBuilder firstHierarchy = 696 new DisplayAreaPolicyBuilder.HierarchyBuilder(firstRoot) 697 .setTaskDisplayAreas(firstTdaList); 698 for (DisplayAreaPolicyBuilder.Feature feature : mFirstRootFeatures) { 699 firstHierarchy.addFeature(feature); 700 } 701 702 // Second 703 final RootDisplayArea secondRoot = new DisplayAreaGroup(wmService, "SecondRoot", 704 FEATURE_SECOND_ROOT); 705 final TaskDisplayArea secondTaskDisplayArea = new TaskDisplayArea(content, wmService, 706 "SecondTaskDisplayArea", FEATURE_SECOND_TASK_CONTAINER); 707 final List<TaskDisplayArea> secondTdaList = new ArrayList<>(); 708 secondTdaList.add(secondTaskDisplayArea); 709 DisplayAreaPolicyBuilder.HierarchyBuilder secondHierarchy = 710 new DisplayAreaPolicyBuilder.HierarchyBuilder(secondRoot) 711 .setTaskDisplayAreas(secondTdaList); 712 for (DisplayAreaPolicyBuilder.Feature feature : mSecondRootFeatures) { 713 secondHierarchy.addFeature(feature); 714 } 715 716 return new DisplayAreaPolicyBuilder() 717 .setRootHierarchy(rootHierarchy) 718 .addDisplayAreaGroupHierarchy(firstHierarchy) 719 .addDisplayAreaGroupHierarchy(secondHierarchy) 720 .build(wmService); 721 } 722 } 723 } 724