blob: b9e1d4b733b36cd9178ed0391349fe3b14a7226c [file] [log] [blame]
Evan Rosky01775072019-09-11 17:28:07 -07001/*
2 * Copyright (C) 2019 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
17package com.android.server.wm;
18
19import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
20import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
21import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
22import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
23import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
24import static android.view.Surface.ROTATION_270;
25import static android.view.Surface.ROTATION_90;
26
27import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
28import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
29import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
30import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
31import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
32import static com.android.server.wm.ActivityStack.ActivityState.STOPPED;
33
34import static org.junit.Assert.assertEquals;
35import static org.junit.Assert.assertFalse;
36import static org.junit.Assert.assertNotEquals;
37import static org.junit.Assert.assertTrue;
38import static org.mockito.ArgumentMatchers.anyString;
39import static org.mockito.Mockito.when;
40
41import android.app.ActivityManager;
42import android.app.ActivityManagerInternal;
43import android.app.TaskStackListener;
44import android.app.WindowConfiguration;
45import android.content.pm.ActivityInfo;
46import android.content.res.Configuration;
47import android.graphics.Rect;
48import android.os.IBinder;
49import android.platform.test.annotations.Presubmit;
50
51import androidx.test.filters.MediumTest;
52
53import org.junit.Test;
54import org.junit.runner.RunWith;
55
56import java.util.ArrayList;
57import java.util.concurrent.TimeUnit;
58
59/**
60 * Tests for Size Compatibility mode.
61 *
62 * Build/Install/Run:
63 * atest WmTests:SizeCompatTests
64 */
65@MediumTest
66@Presubmit
67@RunWith(WindowTestRunner.class)
68public class SizeCompatTests extends ActivityTestsBase {
69 private ActivityStack mStack;
70 private Task mTask;
71 private ActivityRecord mActivity;
72
73 private void setUpApp(ActivityDisplay display) {
74 mStack = new StackBuilder(mRootActivityContainer).setDisplay(display).build();
75 mTask = mStack.getChildAt(0);
Wale Ogunwale21e06482019-11-18 05:14:15 -080076 mActivity = mTask.getTopNonFinishingActivity();
Evan Rosky01775072019-09-11 17:28:07 -070077 }
78
79 private void ensureActivityConfiguration() {
80 mActivity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
81 }
82
83 @Test
84 public void testRestartProcessIfVisible() {
85 setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
86 doNothing().when(mSupervisor).scheduleRestartTimeout(mActivity);
Issei Suzuki1669ea42019-11-06 14:20:59 +010087 mActivity.mVisibleRequested = true;
Evan Rosky01775072019-09-11 17:28:07 -070088 mActivity.setSavedState(null /* savedState */);
89 mActivity.setState(ActivityStack.ActivityState.RESUMED, "testRestart");
90 prepareUnresizable(1.5f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);
91
92 final Rect originalOverrideBounds = new Rect(mActivity.getBounds());
93 resizeDisplay(mStack.getDisplay(), 600, 1200);
94 // The visible activity should recompute configuration according to the last parent bounds.
95 mService.restartActivityProcessIfVisible(mActivity.appToken);
96
97 assertEquals(ActivityStack.ActivityState.RESTARTING_PROCESS, mActivity.getState());
98 assertNotEquals(originalOverrideBounds, mActivity.getBounds());
99 }
100
101 @Test
102 public void testKeepBoundsWhenChangingFromFreeformToFullscreen() {
103 removeGlobalMinSizeRestriction();
104 // create freeform display and a freeform app
105 ActivityDisplay display = new TestActivityDisplay.Builder(mService, 2000, 1000)
106 .setCanRotate(false)
107 .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM).build();
108 setUpApp(display);
109
110 // Put app window into freeform and then make it a compat app.
111 mTask.setBounds(100, 100, 400, 600);
112 prepareUnresizable(-1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
113
114 final Rect bounds = new Rect(mActivity.getBounds());
115 final int density = mActivity.getConfiguration().densityDpi;
116
117 // change display configuration to fullscreen
118 Configuration c = new Configuration(display.getRequestedOverrideConfiguration());
119 c.windowConfiguration.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
120 display.onRequestedOverrideConfigurationChanged(c);
121
122 // check if dimensions stay the same
123 assertTrue(mActivity.inSizeCompatMode());
124 assertEquals(bounds.width(), mActivity.getBounds().width());
125 assertEquals(bounds.height(), mActivity.getBounds().height());
126 assertEquals(density, mActivity.getConfiguration().densityDpi);
127 }
128
129 @Test
130 public void testFixedAspectRatioBoundsWithDecor() {
131 final int decorHeight = 200; // e.g. The device has cutout.
132 setUpApp(new TestActivityDisplay.Builder(mService, 600, 800)
133 .setNotch(decorHeight).build());
134
135 mActivity.info.minAspectRatio = mActivity.info.maxAspectRatio = 1;
136 prepareUnresizable(-1f, SCREEN_ORIENTATION_UNSPECIFIED);
137
138 // The parent configuration doesn't change since the first resolved configuration, so the
139 // activity shouldn't be in the size compatibility mode.
140 assertFalse(mActivity.inSizeCompatMode());
141
142 final Rect appBounds = mActivity.getWindowConfiguration().getAppBounds();
143 // Ensure the app bounds keep the declared aspect ratio.
144 assertEquals(appBounds.width(), appBounds.height());
145 // The decor height should be a part of the effective bounds.
146 assertEquals(mActivity.getBounds().height(), appBounds.height() + decorHeight);
147
148 mTask.getConfiguration().windowConfiguration.setRotation(ROTATION_90);
149 mActivity.onConfigurationChanged(mTask.getConfiguration());
150 // After changing orientation, the aspect ratio should be the same.
151 assertEquals(appBounds.width(), appBounds.height());
152 // The decor height will be included in width.
153 assertEquals(mActivity.getBounds().width(), appBounds.width() + decorHeight);
154 }
155
156 @Test
157 public void testFixedScreenConfigurationWhenMovingToDisplay() {
158 setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
159
160 // Make a new less-tall display with lower density
161 final ActivityDisplay newDisplay =
162 new TestActivityDisplay.Builder(mService, 1000, 2000)
163 .setDensityDpi(200).build();
164
165 mActivity = new ActivityBuilder(mService)
166 .setTask(mTask)
167 .setResizeMode(RESIZE_MODE_UNRESIZEABLE)
168 .setMaxAspectRatio(1.5f)
169 .build();
Issei Suzuki1669ea42019-11-06 14:20:59 +0100170 mActivity.mVisibleRequested = true;
Evan Rosky01775072019-09-11 17:28:07 -0700171
172 final Rect originalBounds = new Rect(mActivity.getBounds());
173 final int originalDpi = mActivity.getConfiguration().densityDpi;
174
175 // Move the non-resizable activity to the new display.
176 mStack.reparent(newDisplay.mDisplayContent, true /* onTop */);
177
178 assertEquals(originalBounds.width(), mActivity.getBounds().width());
179 assertEquals(originalBounds.height(), mActivity.getBounds().height());
180 assertEquals(originalDpi, mActivity.getConfiguration().densityDpi);
181 assertTrue(mActivity.inSizeCompatMode());
182 }
183
184 @Test
185 public void testFixedScreenBoundsWhenDisplaySizeChanged() {
186 setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
187 prepareUnresizable(-1f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
188 assertFalse(mActivity.inSizeCompatMode());
189
190 final Rect origBounds = new Rect(mActivity.getBounds());
191
192 // Change the size of current display.
193 resizeDisplay(mStack.getDisplay(), 1000, 2000);
194 ensureActivityConfiguration();
195
196 assertEquals(origBounds.width(), mActivity.getWindowConfiguration().getBounds().width());
197 assertEquals(origBounds.height(), mActivity.getWindowConfiguration().getBounds().height());
198 assertTrue(mActivity.inSizeCompatMode());
199
200 // Change display size to a different orientation
201 resizeDisplay(mStack.getDisplay(), 2000, 1000);
202 ensureActivityConfiguration();
203 assertEquals(origBounds.width(), mActivity.getWindowConfiguration().getBounds().width());
204 assertEquals(origBounds.height(), mActivity.getWindowConfiguration().getBounds().height());
205 }
206
207 @Test
208 public void testLetterboxFullscreenBounds() {
209 setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
210
211 // Fill out required fields on default display since WM-side is mocked out
212 prepareUnresizable(-1.f /* maxAspect */, SCREEN_ORIENTATION_LANDSCAPE);
213 assertFalse(mActivity.inSizeCompatMode());
214 assertTrue(mActivity.getBounds().width() > mActivity.getBounds().height());
215 }
216
217 @Test
218 public void testMoveToDifferentOrientDisplay() {
219 setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
220
221 final ActivityDisplay newDisplay =
222 new TestActivityDisplay.Builder(mService, 2000, 1000)
223 .setCanRotate(false).build();
224
225 prepareUnresizable(-1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
226 assertFalse(mActivity.inSizeCompatMode());
227
228 final Rect origBounds = new Rect(mActivity.getBounds());
229
230 // Move the non-resizable activity to the new display.
231 mStack.reparent(newDisplay.mDisplayContent, true /* onTop */);
232 ensureActivityConfiguration();
233 assertEquals(origBounds.width(), mActivity.getWindowConfiguration().getBounds().width());
234 assertEquals(origBounds.height(), mActivity.getWindowConfiguration().getBounds().height());
235 assertTrue(mActivity.inSizeCompatMode());
236 }
237
238 @Test
239 public void testFixedOrientRotateCutoutDisplay() {
240 // Create a display with a notch/cutout
241 setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).setNotch(60).build());
242 prepareUnresizable(1.4f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
243
244 final Rect origBounds = new Rect(mActivity.getBounds());
245 final Rect origAppBounds = new Rect(mActivity.getWindowConfiguration().getAppBounds());
246
247 // Rotate the display
248 Configuration c = new Configuration();
249 mStack.getDisplay().mDisplayContent.getDisplayRotation().setRotation(ROTATION_270);
250 mStack.getDisplay().mDisplayContent.computeScreenConfiguration(c);
251 mStack.getDisplay().onRequestedOverrideConfigurationChanged(c);
252
253 // Make sure the app size is the same
254 assertEquals(ROTATION_270, mStack.getWindowConfiguration().getRotation());
255 assertEquals(origBounds.width(), mActivity.getWindowConfiguration().getBounds().width());
256 assertEquals(origBounds.height(), mActivity.getWindowConfiguration().getBounds().height());
257 assertEquals(origAppBounds.width(),
258 mActivity.getWindowConfiguration().getAppBounds().width());
259 assertEquals(origAppBounds.height(),
260 mActivity.getWindowConfiguration().getAppBounds().height());
261 }
262
263 @Test
264 public void testFixedAspOrientChangeOrient() {
265 setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
266
267 prepareUnresizable(1.4f /* maxAspect */, SCREEN_ORIENTATION_LANDSCAPE);
268 assertTrue(mActivity.inSizeCompatMode());
269
270 final Rect originalBounds = new Rect(mActivity.getBounds());
271 final Rect originalAppBounds = new Rect(mActivity.getWindowConfiguration().getAppBounds());
272
273 // Change the fixed orientation
274 mActivity.mOrientation = SCREEN_ORIENTATION_PORTRAIT;
275 mActivity.info.screenOrientation = SCREEN_ORIENTATION_PORTRAIT;
276 // TaskRecord's configuration actually depends on the activity config right now for
277 // pillarboxing.
278 mActivity.getTask().onRequestedOverrideConfigurationChanged(
279 mActivity.getTask().getRequestedOverrideConfiguration());
280
281 assertEquals(originalBounds.width(), mActivity.getBounds().height());
282 assertEquals(originalBounds.height(), mActivity.getBounds().width());
283 assertEquals(originalAppBounds.width(),
284 mActivity.getWindowConfiguration().getAppBounds().height());
285 assertEquals(originalAppBounds.height(),
286 mActivity.getWindowConfiguration().getAppBounds().width());
287 }
288
289 @Test
290 public void testFixedScreenLayoutSizeBits() {
291 setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
292 final int fixedScreenLayout = Configuration.SCREENLAYOUT_LONG_NO
293 | Configuration.SCREENLAYOUT_SIZE_NORMAL;
294 final int layoutMask = Configuration.SCREENLAYOUT_LONG_MASK
295 | Configuration.SCREENLAYOUT_SIZE_MASK
296 | Configuration.SCREENLAYOUT_LAYOUTDIR_MASK;
297 Configuration c = new Configuration(mTask.getRequestedOverrideConfiguration());
298 c.screenLayout = fixedScreenLayout | Configuration.SCREENLAYOUT_LAYOUTDIR_LTR;
299 mTask.onRequestedOverrideConfigurationChanged(c);
300 prepareUnresizable(1.5f, SCREEN_ORIENTATION_UNSPECIFIED);
301
302 // The initial configuration should inherit from parent.
303 assertEquals(fixedScreenLayout | Configuration.SCREENLAYOUT_LAYOUTDIR_LTR,
304 mActivity.getConfiguration().screenLayout & layoutMask);
305
306 mTask.getConfiguration().screenLayout = Configuration.SCREENLAYOUT_LAYOUTDIR_RTL
307 | Configuration.SCREENLAYOUT_LONG_YES | Configuration.SCREENLAYOUT_SIZE_LARGE;
308 mActivity.onConfigurationChanged(mTask.getConfiguration());
309
310 // The size and aspect ratio bits don't change, but the layout direction should be updated.
311 assertEquals(fixedScreenLayout | Configuration.SCREENLAYOUT_LAYOUTDIR_RTL,
312 mActivity.getConfiguration().screenLayout & layoutMask);
313 }
314
315 @Test
316 public void testResetNonVisibleActivity() {
317 setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2500).build());
318 final ActivityDisplay display = mStack.getDisplay();
319 spyOn(display);
320
321 prepareUnresizable(1.5f, SCREEN_ORIENTATION_UNSPECIFIED);
322 mActivity.setState(STOPPED, "testSizeCompatMode");
Issei Suzuki1669ea42019-11-06 14:20:59 +0100323 mActivity.mVisibleRequested = false;
Evan Rosky01775072019-09-11 17:28:07 -0700324 mActivity.app.setReportedProcState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY);
325 // Make the parent bounds to be different so the activity is in size compatibility mode.
326 mTask.getWindowConfiguration().setAppBounds(new Rect(0, 0, 600, 1200));
327
328 // Simulate the display changes orientation.
329 when(display.getLastOverrideConfigurationChanges()).thenReturn(
330 ActivityInfo.CONFIG_SCREEN_SIZE | CONFIG_ORIENTATION
331 | ActivityInfo.CONFIG_WINDOW_CONFIGURATION);
332 mActivity.onConfigurationChanged(mTask.getConfiguration());
333 when(display.getLastOverrideConfigurationChanges()).thenCallRealMethod();
334 // The override configuration should not change so it is still in size compatibility mode.
335 assertTrue(mActivity.inSizeCompatMode());
336
337 // Change display density
338 final DisplayContent displayContent = mStack.getDisplay().mDisplayContent;
339 displayContent.mBaseDisplayDensity = (int) (0.7f * displayContent.mBaseDisplayDensity);
340 final Configuration c = new Configuration();
341 displayContent.computeScreenConfiguration(c);
342 mService.mAmInternal = mock(ActivityManagerInternal.class);
343 mStack.getDisplay().onRequestedOverrideConfigurationChanged(c);
344
345 // The override configuration should be reset and the activity's process will be killed.
346 assertFalse(mActivity.inSizeCompatMode());
347 verify(mActivity).restartProcessIfVisible();
348 mLockRule.runWithScissors(mService.mH, () -> { }, TimeUnit.SECONDS.toMillis(3));
349 verify(mService.mAmInternal).killProcess(
350 eq(mActivity.app.mName), eq(mActivity.app.mUid), anyString());
351 }
352
353 /**
354 * Ensures that {@link TaskStackListener} can receive callback about the activity in size
355 * compatibility mode.
356 */
357 @Test
358 public void testHandleActivitySizeCompatMode() {
359 setUpApp(new TestActivityDisplay.Builder(mService, 1000, 2000).build());
360 ActivityRecord activity = mActivity;
361 activity.setState(ActivityStack.ActivityState.RESUMED, "testHandleActivitySizeCompatMode");
362 prepareUnresizable(-1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
363 ensureActivityConfiguration();
364 assertFalse(mActivity.inSizeCompatMode());
365
366 final ArrayList<IBinder> compatTokens = new ArrayList<>();
367 mService.getTaskChangeNotificationController().registerTaskStackListener(
368 new TaskStackListener() {
369 @Override
370 public void onSizeCompatModeActivityChanged(int displayId,
371 IBinder activityToken) {
372 compatTokens.add(activityToken);
373 }
374 });
375
376 // Resize the display so that the activity exercises size-compat mode.
377 resizeDisplay(mStack.getDisplay(), 1000, 2500);
378
379 // Expect the exact token when the activity is in size compatibility mode.
380 assertEquals(1, compatTokens.size());
381 assertEquals(activity.appToken, compatTokens.get(0));
382
383 compatTokens.clear();
384 // Make the activity resizable again by restarting it
385 activity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
Issei Suzuki1669ea42019-11-06 14:20:59 +0100386 activity.mVisibleRequested = true;
Evan Rosky01775072019-09-11 17:28:07 -0700387 activity.restartProcessIfVisible();
388 // The full lifecycle isn't hooked up so manually set state to resumed
389 activity.setState(ActivityStack.ActivityState.RESUMED, "testHandleActivitySizeCompatMode");
390 mStack.getDisplay().handleActivitySizeCompatModeIfNeeded(activity);
391
392 // Expect null token when switching to non-size-compat mode activity.
393 assertEquals(1, compatTokens.size());
394 assertEquals(null, compatTokens.get(0));
395 }
396
397 /**
398 * Setup {@link #mActivity} as a size-compat-mode-able activity with fixed aspect and/or
399 * orientation.
400 */
401 private void prepareUnresizable(float maxAspect, int screenOrientation) {
402 mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
Issei Suzuki1669ea42019-11-06 14:20:59 +0100403 mActivity.mVisibleRequested = true;
Evan Rosky01775072019-09-11 17:28:07 -0700404 if (maxAspect >= 0) {
405 mActivity.info.maxAspectRatio = maxAspect;
406 }
407 if (screenOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
408 mActivity.mOrientation = screenOrientation;
409 mActivity.info.screenOrientation = screenOrientation;
410 // TaskRecord's configuration actually depends on the activity config right now for
411 // pillarboxing.
412 mActivity.getTask().onRequestedOverrideConfigurationChanged(
413 mActivity.getTask().getRequestedOverrideConfiguration());
414 }
415 ensureActivityConfiguration();
416 }
417
418 private void resizeDisplay(ActivityDisplay display, int width, int height) {
419 final DisplayContent displayContent = display.mDisplayContent;
420 displayContent.mBaseDisplayWidth = width;
421 displayContent.mBaseDisplayHeight = height;
422 Configuration c = new Configuration();
423 displayContent.computeScreenConfiguration(c);
424 display.onRequestedOverrideConfigurationChanged(c);
425 }
426}