1 /* 2 * Copyright (C) 2018 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.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; 20 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 21 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; 22 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; 23 import static android.view.Display.INVALID_DISPLAY; 24 25 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; 26 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; 27 import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq; 28 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; 29 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; 30 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; 31 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; 32 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; 33 import static com.android.server.wm.ActivityStarter.Request; 34 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS; 35 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE; 36 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_DONE; 37 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP; 38 39 import static org.junit.Assert.assertEquals; 40 import static org.junit.Assert.assertNotEquals; 41 42 import android.app.ActivityOptions; 43 import android.content.ComponentName; 44 import android.content.pm.ActivityInfo.WindowLayout; 45 import android.graphics.Rect; 46 import android.platform.test.annotations.Presubmit; 47 import android.util.ArrayMap; 48 import android.util.SparseArray; 49 50 import androidx.test.filters.MediumTest; 51 52 import com.android.server.wm.LaunchParamsController.LaunchParams; 53 import com.android.server.wm.LaunchParamsController.LaunchParamsModifier; 54 55 import org.junit.Before; 56 import org.junit.Test; 57 import org.junit.runner.RunWith; 58 59 import java.util.Map; 60 61 /** 62 * Tests for exercising {@link LaunchParamsController}. 63 * 64 * Build/Install/Run: 65 * atest WmTests:LaunchParamsControllerTests 66 */ 67 @MediumTest 68 @Presubmit 69 @RunWith(WindowTestRunner.class) 70 public class LaunchParamsControllerTests extends WindowTestsBase { 71 private LaunchParamsController mController; 72 private TestLaunchParamsPersister mPersister; 73 74 @Before setUp()75 public void setUp() throws Exception { 76 mPersister = new TestLaunchParamsPersister(); 77 mController = new LaunchParamsController(mAtm, mPersister); 78 } 79 80 /** 81 * Makes sure positioners get values passed to controller. 82 */ 83 @Test testArgumentPropagation()84 public void testArgumentPropagation() { 85 final LaunchParamsModifier 86 positioner = mock(LaunchParamsModifier.class); 87 mController.registerModifier(positioner); 88 89 final ActivityRecord record = new ActivityBuilder(mAtm).build(); 90 final ActivityRecord source = new ActivityBuilder(mAtm).build(); 91 final WindowLayout layout = new WindowLayout(0, 0, 0, 0, 0, 0, 0); 92 final ActivityOptions options = mock(ActivityOptions.class); 93 final Request request = new Request(); 94 95 mController.calculate(record.getTask(), layout, record, source, options, request, 96 PHASE_BOUNDS, new LaunchParams()); 97 verify(positioner, times(1)).onCalculate(eq(record.getTask()), eq(layout), eq(record), 98 eq(source), eq(options), eq(request), anyInt(), any(), any()); 99 } 100 101 /** 102 * Makes sure controller passes stored params to modifiers. 103 */ 104 @Test testStoredParamsRecovery()105 public void testStoredParamsRecovery() { 106 final LaunchParamsModifier positioner = mock(LaunchParamsModifier.class); 107 mController.registerModifier(positioner); 108 109 final ComponentName name = new ComponentName("com.android.foo", ".BarActivity"); 110 final int userId = 0; 111 final ActivityRecord activity = new ActivityBuilder(mAtm).setComponent(name) 112 .setUid(userId).build(); 113 final LaunchParams expected = new LaunchParams(); 114 expected.mPreferredTaskDisplayArea = mock(TaskDisplayArea.class); 115 expected.mWindowingMode = WINDOWING_MODE_PINNED; 116 expected.mBounds.set(200, 300, 400, 500); 117 118 mPersister.putLaunchParams(userId, name, expected); 119 120 mController.calculate(activity.getTask(), null /*layout*/, activity, null /*source*/, 121 null /*options*/, null /*request*/, PHASE_BOUNDS, new LaunchParams()); 122 verify(positioner, times(1)).onCalculate(any(), any(), any(), any(), any(), any(), 123 anyInt(), eq(expected), any()); 124 } 125 126 /** 127 * Ensures positioners further down the chain are not called when RESULT_DONE is returned. 128 */ 129 @Test testEarlyExit()130 public void testEarlyExit() { 131 final LaunchParamsModifier 132 ignoredPositioner = mock(LaunchParamsModifier.class); 133 final LaunchParamsModifier earlyExitPositioner = 134 (task, layout, activity, source, options, phase, currentParams, outParams, request) 135 -> RESULT_DONE; 136 137 mController.registerModifier(ignoredPositioner); 138 mController.registerModifier(earlyExitPositioner); 139 140 mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, 141 null /*source*/, null /*options*/, null /*request*/, 142 PHASE_BOUNDS, new LaunchParams()); 143 verify(ignoredPositioner, never()).onCalculate(any(), any(), any(), any(), any(), any(), 144 anyInt(), any(), any()); 145 } 146 147 /** 148 * Ensures that positioners are called in the correct order. 149 */ 150 @Test testRegistration()151 public void testRegistration() { 152 LaunchParamsModifier earlyExitPositioner = 153 new InstrumentedPositioner(RESULT_DONE, new LaunchParams()); 154 155 final LaunchParamsModifier firstPositioner = spy(earlyExitPositioner); 156 157 mController.registerModifier(firstPositioner); 158 159 mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, 160 null /*source*/, null /*options*/, null /*request*/, PHASE_BOUNDS, 161 new LaunchParams()); 162 verify(firstPositioner, times(1)).onCalculate(any(), any(), any(), any(), any(), any(), 163 anyInt(), any(), any()); 164 165 final LaunchParamsModifier secondPositioner = spy(earlyExitPositioner); 166 167 mController.registerModifier(secondPositioner); 168 169 mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, 170 null /*source*/, null /*options*/, null /*request*/, PHASE_BOUNDS, 171 new LaunchParams()); 172 verify(firstPositioner, times(1)).onCalculate(any(), any(), any(), any(), any(), any(), 173 anyInt(), any(), any()); 174 verify(secondPositioner, times(1)).onCalculate(any(), any(), any(), any(), any(), any(), 175 anyInt(), any(), any()); 176 } 177 178 /** 179 * Makes sure positioners further down the registration chain are called. 180 */ 181 @Test testPassThrough()182 public void testPassThrough() { 183 final LaunchParamsModifier 184 positioner1 = mock(LaunchParamsModifier.class); 185 final LaunchParams params = new LaunchParams(); 186 params.mWindowingMode = WINDOWING_MODE_FREEFORM; 187 params.mBounds.set(0, 0, 30, 20); 188 params.mPreferredTaskDisplayArea = mock(TaskDisplayArea.class); 189 190 final InstrumentedPositioner positioner2 = new InstrumentedPositioner(RESULT_CONTINUE, 191 params); 192 193 mController.registerModifier(positioner1); 194 mController.registerModifier(positioner2); 195 196 mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/, 197 null /*options*/, null /*request*/, PHASE_BOUNDS, new LaunchParams()); 198 199 verify(positioner1, times(1)).onCalculate(any(), any(), any(), any(), any(), any(), 200 anyInt(), eq(positioner2.getLaunchParams()), any()); 201 } 202 203 /** 204 * Ensures skipped results are not propagated. 205 */ 206 @Test testSkip()207 public void testSkip() { 208 final LaunchParams params1 = new LaunchParams(); 209 params1.mBounds.set(0, 0, 10, 10); 210 final InstrumentedPositioner positioner1 = new InstrumentedPositioner(RESULT_SKIP, params1); 211 212 final LaunchParams params2 = new LaunchParams(); 213 params2.mBounds.set(0, 0, 20, 30); 214 final InstrumentedPositioner positioner2 = 215 new InstrumentedPositioner(RESULT_CONTINUE, params2); 216 217 mController.registerModifier(positioner1); 218 mController.registerModifier(positioner2); 219 220 final LaunchParams result = new LaunchParams(); 221 222 mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/, 223 null /*options*/, null /*request*/, PHASE_BOUNDS, result); 224 225 assertEquals(result, positioner2.getLaunchParams()); 226 } 227 228 /** 229 * Tests preferred display id calculation for VR. 230 */ 231 @Test testVrPreferredDisplay()232 public void testVrPreferredDisplay() { 233 final TestDisplayContent vrDisplay = createNewDisplayContent(); 234 mAtm.mVr2dDisplayId = vrDisplay.mDisplayId; 235 236 final LaunchParams result = new LaunchParams(); 237 final ActivityRecord vrActivity = new ActivityBuilder(mAtm).build(); 238 vrActivity.requestedVrComponent = vrActivity.mActivityComponent; 239 240 // VR activities should always land on default display. 241 mController.calculate(null /*task*/, null /*layout*/, vrActivity /*activity*/, 242 null /*source*/, null /*options*/, null/*request*/, PHASE_BOUNDS, result); 243 assertEquals(mRootWindowContainer.getDefaultTaskDisplayArea(), 244 result.mPreferredTaskDisplayArea); 245 246 // Otherwise, always lands on VR 2D display. 247 final ActivityRecord vr2dActivity = new ActivityBuilder(mAtm).build(); 248 mController.calculate(null /*task*/, null /*layout*/, vr2dActivity /*activity*/, 249 null /*source*/, null /*options*/, null /*request*/, PHASE_BOUNDS, result); 250 assertEquals(vrDisplay.getDefaultTaskDisplayArea(), result.mPreferredTaskDisplayArea); 251 mController.calculate(null /*task*/, null /*layout*/, null /*activity*/, null /*source*/, 252 null /*options*/, null /*request*/, PHASE_BOUNDS, result); 253 assertEquals(vrDisplay.getDefaultTaskDisplayArea(), result.mPreferredTaskDisplayArea); 254 255 mAtm.mVr2dDisplayId = INVALID_DISPLAY; 256 } 257 258 259 /** 260 * Ensures that {@link LaunchParamsController} calculates to {@link PHASE_BOUNDS} phase by 261 * default. 262 */ 263 @Test testCalculatePhase()264 public void testCalculatePhase() { 265 final LaunchParamsModifier positioner = mock(LaunchParamsModifier.class); 266 mController.registerModifier(positioner); 267 268 final ActivityRecord record = new ActivityBuilder(mAtm).build(); 269 final ActivityRecord source = new ActivityBuilder(mAtm).build(); 270 final WindowLayout layout = new WindowLayout(0, 0, 0, 0, 0, 0, 0); 271 final ActivityOptions options = mock(ActivityOptions.class); 272 273 mController.calculate(record.getTask(), layout, record, source, options, null/*request*/, 274 PHASE_BOUNDS, new LaunchParams()); 275 verify(positioner, times(1)).onCalculate(eq(record.getTask()), eq(layout), eq(record), 276 eq(source), eq(options), any(), eq(PHASE_BOUNDS), any(), any()); 277 } 278 279 /** 280 * Ensures that {@link LaunchParamsModifier} doesn't alter non-root tasks' windowingMode. 281 */ 282 @Test testLayoutNonRootTaskWindowingModeChange()283 public void testLayoutNonRootTaskWindowingModeChange() { 284 final LaunchParams params = new LaunchParams(); 285 final int windowingMode = WINDOWING_MODE_FREEFORM; 286 params.mWindowingMode = windowingMode; 287 final InstrumentedPositioner positioner = new InstrumentedPositioner(RESULT_DONE, params); 288 final Task task = new TaskBuilder(mAtm.mTaskSupervisor).setCreateParentTask(true).build(); 289 task.getRootTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); 290 291 mController.registerModifier(positioner); 292 293 final int beforeWindowMode = task.getWindowingMode(); 294 assertNotEquals(windowingMode, beforeWindowMode); 295 296 mController.layoutTask(task, null /* windowLayout */); 297 298 final int afterWindowMode = task.getWindowingMode(); 299 assertEquals(afterWindowMode, beforeWindowMode); 300 } 301 302 /** 303 * Ensures that {@link LaunchParamsModifier} requests specifying bounds during 304 * layout are honored if window is in freeform. 305 */ 306 @Test testLayoutTaskBoundsChangeFreeformWindow()307 public void testLayoutTaskBoundsChangeFreeformWindow() { 308 final Rect expected = new Rect(10, 20, 30, 40); 309 310 final LaunchParams params = new LaunchParams(); 311 params.mBounds.set(expected); 312 final InstrumentedPositioner positioner = new InstrumentedPositioner(RESULT_DONE, params); 313 final Task task = new TaskBuilder(mAtm.mTaskSupervisor) 314 .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); 315 316 mController.registerModifier(positioner); 317 318 assertNotEquals(expected, task.getBounds()); 319 320 mController.layoutTask(task, null /* windowLayout */); 321 322 // Task will make adjustments to requested bounds. We only need to guarantee that the 323 // reuqested bounds are expected. 324 assertEquals(expected, task.getRequestedOverrideBounds()); 325 } 326 327 /** 328 * Ensures that {@link LaunchParamsModifier} requests specifying bounds during 329 * layout are honored if window is in multiwindow mode. 330 */ 331 @Test testLayoutTaskBoundsChangeMultiWindow()332 public void testLayoutTaskBoundsChangeMultiWindow() { 333 final Rect expected = new Rect(10, 20, 30, 40); 334 335 final LaunchParams params = new LaunchParams(); 336 params.mBounds.set(expected); 337 final InstrumentedPositioner positioner = new InstrumentedPositioner(RESULT_DONE, params); 338 final Task task = new TaskBuilder(mAtm.mTaskSupervisor) 339 .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW).build(); 340 341 mController.registerModifier(positioner); 342 343 assertNotEquals(expected, task.getBounds()); 344 345 mController.layoutTask(task, null /* windowLayout */); 346 347 assertEquals(expected, task.getRequestedOverrideBounds()); 348 } 349 350 /** 351 * Ensures that {@link LaunchParamsModifier} requests specifying bounds during 352 * layout are set to last non-fullscreen bounds. 353 */ 354 @Test testLayoutTaskBoundsChangeFixedWindow()355 public void testLayoutTaskBoundsChangeFixedWindow() { 356 final Rect expected = new Rect(10, 20, 30, 40); 357 358 final LaunchParams params = new LaunchParams(); 359 params.mWindowingMode = WINDOWING_MODE_FULLSCREEN; 360 params.mBounds.set(expected); 361 final InstrumentedPositioner positioner = new InstrumentedPositioner(RESULT_DONE, params); 362 final Task task = new TaskBuilder(mAtm.mTaskSupervisor).build(); 363 364 mController.registerModifier(positioner); 365 366 assertNotEquals(expected, task.getBounds()); 367 368 mController.layoutTask(task, null /* windowLayout */); 369 370 assertNotEquals(expected, task.getBounds()); 371 assertEquals(expected, task.mLastNonFullscreenBounds); 372 } 373 374 public static class InstrumentedPositioner implements LaunchParamsModifier { 375 376 private final int mReturnVal; 377 private final LaunchParams mParams; 378 InstrumentedPositioner(int returnVal, LaunchParams params)379 InstrumentedPositioner(int returnVal, LaunchParams params) { 380 mReturnVal = returnVal; 381 mParams = params; 382 } 383 384 @Override onCalculate(Task task, WindowLayout layout, ActivityRecord activity, ActivityRecord source, ActivityOptions options, Request request, int phase, LaunchParams currentParams, LaunchParams outParams)385 public int onCalculate(Task task, WindowLayout layout, ActivityRecord activity, 386 ActivityRecord source, ActivityOptions options, Request request, int phase, 387 LaunchParams currentParams, LaunchParams outParams) { 388 outParams.set(mParams); 389 return mReturnVal; 390 } 391 getLaunchParams()392 LaunchParams getLaunchParams() { 393 return mParams; 394 } 395 } 396 397 /** 398 * Test double for {@link LaunchParamsPersister}. This class only manages an in-memory storage 399 * of a mapping from user ID and component name to launch params. 400 */ 401 static class TestLaunchParamsPersister extends LaunchParamsPersister { 402 403 private final SparseArray<Map<ComponentName, LaunchParams>> mMap = 404 new SparseArray<>(); 405 private final LaunchParams mTmpParams = new LaunchParams(); 406 TestLaunchParamsPersister()407 TestLaunchParamsPersister() { 408 super(null, null, null); 409 } 410 putLaunchParams(int userId, ComponentName name, LaunchParams params)411 void putLaunchParams(int userId, ComponentName name, LaunchParams params) { 412 Map<ComponentName, LaunchParams> map = mMap.get(userId); 413 if (map == null) { 414 map = new ArrayMap<>(); 415 mMap.put(userId, map); 416 } 417 418 LaunchParams paramRecord = map.get(name); 419 if (paramRecord == null) { 420 paramRecord = new LaunchParams(); 421 map.put(name, params); 422 } 423 424 paramRecord.set(params); 425 } 426 427 @Override onUnlockUser(int userId)428 void onUnlockUser(int userId) { 429 if (mMap.get(userId) == null) { 430 mMap.put(userId, new ArrayMap<>()); 431 } 432 } 433 434 @Override saveTask(Task task, DisplayContent display)435 void saveTask(Task task, DisplayContent display) { 436 final int userId = task.mUserId; 437 final ComponentName realActivity = task.realActivity; 438 mTmpParams.mPreferredTaskDisplayArea = task.getDisplayArea(); 439 mTmpParams.mWindowingMode = task.getWindowingMode(); 440 if (task.mLastNonFullscreenBounds != null) { 441 mTmpParams.mBounds.set(task.mLastNonFullscreenBounds); 442 } else { 443 mTmpParams.mBounds.setEmpty(); 444 } 445 putLaunchParams(userId, realActivity, mTmpParams); 446 } 447 448 @Override getLaunchParams(Task task, ActivityRecord activity, LaunchParams params)449 void getLaunchParams(Task task, ActivityRecord activity, LaunchParams params) { 450 final int userId = task != null ? task.mUserId : activity.mUserId; 451 final ComponentName name = task != null 452 ? task.realActivity : activity.mActivityComponent; 453 454 params.reset(); 455 final Map<ComponentName, LaunchParams> map = mMap.get(userId); 456 if (map == null) { 457 return; 458 } 459 460 final LaunchParams paramsRecord = map.get(name); 461 if (paramsRecord != null) { 462 params.set(paramsRecord); 463 } 464 } 465 } 466 createNewDisplayContent()467 private TestDisplayContent createNewDisplayContent() { 468 return addNewDisplayContentAt(DisplayContent.POSITION_TOP); 469 } 470 } 471