blob: f3c1ec5b200e9412554c6468622e895622d3377c [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.wm;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.ActivityInfo.FLAG_SHOW_WHEN_LOCKED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.Q;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.FLAG_PRIVATE;
import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
import static android.view.DisplayCutout.fromBoundingRect;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE;
import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.same;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
import static com.android.server.wm.DisplayContent.IME_TARGET_INPUT;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
import static com.google.common.truth.Truth.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doCallRealMethod;
import android.app.ActivityTaskManager;
import android.app.WindowConfiguration;
import android.app.servertransaction.FixedRotationAdjustmentsItem;
import android.content.res.Configuration;
import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.VirtualDisplay;
import android.metrics.LogMaker;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.util.DisplayMetrics;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.IDisplayWindowRotationCallback;
import android.view.IDisplayWindowRotationController;
import android.view.ISystemGestureExclusionListener;
import android.view.IWindowManager;
import android.view.InsetsState;
import android.view.InsetsVisibilities;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.View;
import android.view.WindowManager;
import android.window.WindowContainerToken;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.server.LocalServices;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.wm.utils.WmDisplayCutout;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
* Tests for the {@link DisplayContent} class.
*
* Build/Install/Run:
* atest WmTests:DisplayContentTests
*/
@SmallTest
@Presubmit
@RunWith(WindowTestRunner.class)
public class DisplayContentTests extends WindowTestsBase {
@UseTestDisplay(addAllCommonWindows = true)
@Test
public void testForAllWindows() {
final WindowState exitingAppWindow = createWindow(null, TYPE_BASE_APPLICATION,
mDisplayContent, "exiting app");
final ActivityRecord exitingApp = exitingAppWindow.mActivityRecord;
// Wait until everything in animation handler get executed to prevent the exiting window
// from being removed during WindowSurfacePlacer Traversal.
waitUntilHandlersIdle();
exitingApp.mIsExiting = true;
exitingApp.getTask().getRootTask().mExitingActivities.add(exitingApp);
assertForAllWindowsOrder(Arrays.asList(
mWallpaperWindow,
exitingAppWindow,
mChildAppWindowBelow,
mAppWindow,
mChildAppWindowAbove,
mDockedDividerWindow,
mImeWindow,
mImeDialogWindow,
mStatusBarWindow,
mNotificationShadeWindow,
mNavBarWindow));
}
@UseTestDisplay(addAllCommonWindows = true)
@Test
public void testForAllWindows_WithAppImeTarget() {
final WindowState imeAppTarget =
createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget");
mDisplayContent.setImeLayeringTarget(imeAppTarget);
assertForAllWindowsOrder(Arrays.asList(
mWallpaperWindow,
mChildAppWindowBelow,
mAppWindow,
mChildAppWindowAbove,
imeAppTarget,
mImeWindow,
mImeDialogWindow,
mDockedDividerWindow,
mStatusBarWindow,
mNotificationShadeWindow,
mNavBarWindow));
}
@UseTestDisplay(addAllCommonWindows = true)
@Test
public void testForAllWindows_WithChildWindowImeTarget() throws Exception {
mDisplayContent.setImeLayeringTarget(mChildAppWindowAbove);
assertForAllWindowsOrder(Arrays.asList(
mWallpaperWindow,
mChildAppWindowBelow,
mAppWindow,
mChildAppWindowAbove,
mImeWindow,
mImeDialogWindow,
mDockedDividerWindow,
mStatusBarWindow,
mNotificationShadeWindow,
mNavBarWindow));
}
@UseTestDisplay(addAllCommonWindows = true)
@Test
public void testForAllWindows_WithStatusBarImeTarget() throws Exception {
mDisplayContent.setImeLayeringTarget(mStatusBarWindow);
assertForAllWindowsOrder(Arrays.asList(
mWallpaperWindow,
mChildAppWindowBelow,
mAppWindow,
mChildAppWindowAbove,
mDockedDividerWindow,
mStatusBarWindow,
mImeWindow,
mImeDialogWindow,
mNotificationShadeWindow,
mNavBarWindow));
}
@UseTestDisplay(addAllCommonWindows = true)
@Test
public void testForAllWindows_WithNotificationShadeImeTarget() throws Exception {
mDisplayContent.setImeLayeringTarget(mNotificationShadeWindow);
assertForAllWindowsOrder(Arrays.asList(
mWallpaperWindow,
mChildAppWindowBelow,
mAppWindow,
mChildAppWindowAbove,
mDockedDividerWindow,
mStatusBarWindow,
mNotificationShadeWindow,
mImeWindow,
mImeDialogWindow,
mNavBarWindow));
}
@UseTestDisplay(addAllCommonWindows = true)
@Test
public void testForAllWindows_WithInBetweenWindowToken() {
// This window is set-up to be z-ordered between some windows that go in the same token like
// the nav bar and status bar.
final WindowState voiceInteractionWindow = createWindow(null, TYPE_VOICE_INTERACTION,
mDisplayContent, "voiceInteractionWindow");
assertForAllWindowsOrder(Arrays.asList(
mWallpaperWindow,
mChildAppWindowBelow,
mAppWindow,
mChildAppWindowAbove,
mDockedDividerWindow,
voiceInteractionWindow,
mImeWindow,
mImeDialogWindow,
mStatusBarWindow,
mNotificationShadeWindow,
mNavBarWindow));
}
@UseTestDisplay(addAllCommonWindows = true)
@Test
public void testComputeImeTarget() {
// Verify that an app window can be an ime target.
final WindowState appWin = createWindow(null, TYPE_APPLICATION, mDisplayContent, "appWin");
appWin.setHasSurface(true);
assertTrue(appWin.canBeImeTarget());
WindowState imeTarget = mDisplayContent.computeImeTarget(false /* updateImeTarget */);
assertEquals(appWin, imeTarget);
appWin.mHidden = false;
// Verify that an child window can be an ime target.
final WindowState childWin = createWindow(appWin,
TYPE_APPLICATION_ATTACHED_DIALOG, "childWin");
childWin.setHasSurface(true);
assertTrue(childWin.canBeImeTarget());
imeTarget = mDisplayContent.computeImeTarget(false /* updateImeTarget */);
assertEquals(childWin, imeTarget);
}
@UseTestDisplay(addAllCommonWindows = true)
@Test
public void testComputeImeTarget_startingWindow() {
ActivityRecord activity = createActivityRecord(mDisplayContent);
final WindowState startingWin = createWindow(null, TYPE_APPLICATION_STARTING, activity,
"startingWin");
startingWin.setHasSurface(true);
assertTrue(startingWin.canBeImeTarget());
WindowState imeTarget = mDisplayContent.computeImeTarget(false /* updateImeTarget */);
assertEquals(startingWin, imeTarget);
startingWin.mHidden = false;
// Verify that the starting window still be an ime target even an app window launching
// behind it.
final WindowState appWin = createWindow(null, TYPE_BASE_APPLICATION, activity, "appWin");
appWin.setHasSurface(true);
assertTrue(appWin.canBeImeTarget());
imeTarget = mDisplayContent.computeImeTarget(false /* updateImeTarget */);
assertEquals(startingWin, imeTarget);
appWin.mHidden = false;
// Verify that the starting window still be an ime target even the child window behind a
// launching app window
final WindowState childWin = createWindow(appWin,
TYPE_APPLICATION_ATTACHED_DIALOG, "childWin");
childWin.setHasSurface(true);
assertTrue(childWin.canBeImeTarget());
imeTarget = mDisplayContent.computeImeTarget(false /* updateImeTarget */);
assertEquals(startingWin, imeTarget);
}
@Test
public void testUpdateImeParent_forceUpdateRelativeLayer() {
final DisplayArea.Tokens imeContainer = mDisplayContent.getImeContainer();
final ActivityRecord activity = createActivityRecord(mDisplayContent);
final WindowState startingWin = createWindow(null, TYPE_APPLICATION_STARTING, activity,
"startingWin");
startingWin.setHasSurface(true);
assertTrue(startingWin.canBeImeTarget());
final SurfaceControl imeSurfaceParent = mock(SurfaceControl.class);
doReturn(imeSurfaceParent).when(mDisplayContent).computeImeParent();
spyOn(imeContainer);
mDisplayContent.setImeInputTarget(startingWin);
mDisplayContent.onConfigurationChanged(new Configuration());
verify(mDisplayContent).updateImeParent();
// Force reassign the relative layer when the IME surface parent is changed.
verify(imeContainer).assignRelativeLayer(any(), eq(imeSurfaceParent), anyInt(), eq(true));
}
@Test
public void testComputeImeTargetReturnsNull_windowDidntRequestIme() {
final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION,
new ActivityBuilder(mAtm).setCreateTask(true).build(), "app");
final WindowState win2 = createWindow(null, TYPE_BASE_APPLICATION,
new ActivityBuilder(mAtm).setCreateTask(true).build(), "app2");
mDisplayContent.setImeInputTarget(win1);
mDisplayContent.setImeLayeringTarget(win2);
doReturn(true).when(mDisplayContent).shouldImeAttachedToApp();
// Compute IME parent returns nothing if current target and window receiving input
// are different i.e. if current window didn't request IME.
assertNull("computeImeParent() should be null", mDisplayContent.computeImeParent());
}
/**
* This tests root task movement between displays and proper root task's, task's and app token's
* display container references updates.
*/
@Test
public void testMoveRootTaskBetweenDisplays() {
// Create a second display.
final DisplayContent dc = createNewDisplay();
// Add root task with activity.
final Task rootTask = createTask(dc);
assertEquals(dc.getDisplayId(), rootTask.getDisplayContent().getDisplayId());
assertEquals(dc, rootTask.getDisplayContent());
final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
final ActivityRecord activity = createNonAttachedActivityRecord(dc);
task.addChild(activity, 0);
assertEquals(dc, task.getDisplayContent());
assertEquals(dc, activity.getDisplayContent());
// Move root task to first display.
rootTask.reparent(mDisplayContent.getDefaultTaskDisplayArea(), true /* onTop */);
assertEquals(mDisplayContent.getDisplayId(), rootTask.getDisplayContent().getDisplayId());
assertEquals(mDisplayContent, rootTask.getDisplayContent());
assertEquals(mDisplayContent, task.getDisplayContent());
assertEquals(mDisplayContent, activity.getDisplayContent());
}
/**
* This tests override configuration updates for display content.
*/
@Test
public void testDisplayOverrideConfigUpdate() {
final Configuration currentOverrideConfig =
mDisplayContent.getRequestedOverrideConfiguration();
// Create new, slightly changed override configuration and apply it to the display.
final Configuration newOverrideConfig = new Configuration(currentOverrideConfig);
newOverrideConfig.densityDpi += 120;
newOverrideConfig.fontScale += 0.3;
mWm.setNewDisplayOverrideConfiguration(newOverrideConfig, mDisplayContent);
// Check that override config is applied.
assertEquals(newOverrideConfig, mDisplayContent.getRequestedOverrideConfiguration());
}
/**
* This tests global configuration updates when default display config is updated.
*/
@Test
public void testDefaultDisplayOverrideConfigUpdate() {
DisplayContent defaultDisplay = mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY);
final Configuration currentConfig = defaultDisplay.getConfiguration();
// Create new, slightly changed override configuration and apply it to the display.
final Configuration newOverrideConfig = new Configuration(currentConfig);
newOverrideConfig.densityDpi += 120;
newOverrideConfig.fontScale += 0.3;
mWm.setNewDisplayOverrideConfiguration(newOverrideConfig, defaultDisplay);
// Check that global configuration is updated, as we've updated default display's config.
Configuration globalConfig = mWm.mRoot.getConfiguration();
assertEquals(newOverrideConfig.densityDpi, globalConfig.densityDpi);
assertEquals(newOverrideConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */);
// Return back to original values.
mWm.setNewDisplayOverrideConfiguration(currentConfig, defaultDisplay);
globalConfig = mWm.mRoot.getConfiguration();
assertEquals(currentConfig.densityDpi, globalConfig.densityDpi);
assertEquals(currentConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */);
}
/**
* Tests tapping on a root task in different display results in window gaining focus.
*/
@Test
public void testInputEventBringsCorrectDisplayInFocus() {
DisplayContent dc0 = mWm.getDefaultDisplayContentLocked();
// Create a second display
final DisplayContent dc1 = createNewDisplay();
// Add root task with activity.
final Task rootTask0 = createTask(dc0);
final Task task0 = createTaskInRootTask(rootTask0, 0 /* userId */);
final ActivityRecord activity = createNonAttachedActivityRecord(dc0);
task0.addChild(activity, 0);
dc0.configureDisplayPolicy();
assertNotNull(dc0.mTapDetector);
final Task rootTask1 = createTask(dc1);
final Task task1 = createTaskInRootTask(rootTask1, 0 /* userId */);
final ActivityRecord activity1 = createNonAttachedActivityRecord(dc0);
task1.addChild(activity1, 0);
dc1.configureDisplayPolicy();
assertNotNull(dc1.mTapDetector);
// tap on primary display.
tapOnDisplay(dc0);
// Check focus is on primary display.
assertEquals(mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus,
dc0.findFocusedWindow());
// Tap on secondary display.
tapOnDisplay(dc1);
// Check focus is on secondary.
assertEquals(mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus,
dc1.findFocusedWindow());
}
@Test
public void testFocusedWindowMultipleDisplays() {
doTestFocusedWindowMultipleDisplays(false /* perDisplayFocusEnabled */, Q);
}
@Test
public void testFocusedWindowMultipleDisplaysPerDisplayFocusEnabled() {
doTestFocusedWindowMultipleDisplays(true /* perDisplayFocusEnabled */, Q);
}
@Test
public void testFocusedWindowMultipleDisplaysPerDisplayFocusEnabledLegacyApp() {
doTestFocusedWindowMultipleDisplays(true /* perDisplayFocusEnabled */, P);
}
private void doTestFocusedWindowMultipleDisplays(boolean perDisplayFocusEnabled,
int targetSdk) {
mWm.mPerDisplayFocusEnabled = perDisplayFocusEnabled;
// Create a focusable window and check that focus is calculated correctly
final WindowState window1 =
createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "window1");
window1.mActivityRecord.mTargetSdk = targetSdk;
updateFocusedWindow();
assertTrue(window1.isFocused());
assertEquals(window1, mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus);
// Check that a new display doesn't affect focus
final DisplayContent dc = createNewDisplay();
updateFocusedWindow();
assertTrue(window1.isFocused());
assertEquals(window1, mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus);
// Add a window to the second display, and it should be focused
final WindowState window2 = createWindow(null, TYPE_BASE_APPLICATION, dc, "window2");
window2.mActivityRecord.mTargetSdk = targetSdk;
updateFocusedWindow();
assertTrue(window2.isFocused());
assertEquals(perDisplayFocusEnabled && targetSdk >= Q, window1.isFocused());
assertEquals(window2, mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus);
// Move the first window to top including parents, and make sure focus is updated
window1.getParent().positionChildAt(POSITION_TOP, window1, true);
updateFocusedWindow();
assertTrue(window1.isFocused());
assertEquals(perDisplayFocusEnabled && targetSdk >= Q, window2.isFocused());
assertEquals(window1, mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus);
// Make sure top focused display not changed if there is a focused app.
window1.mActivityRecord.mVisibleRequested = false;
window1.getDisplayContent().setFocusedApp(window1.mActivityRecord);
updateFocusedWindow();
assertTrue(!window1.isFocused());
assertEquals(window1.getDisplayId(),
mWm.mRoot.getTopFocusedDisplayContent().getDisplayId());
}
@Test
public void testShouldWaitForSystemDecorWindowsOnBoot_OnDefaultDisplay() {
mWm.mSystemBooted = true;
final DisplayContent defaultDisplay = mWm.getDefaultDisplayContentLocked();
final WindowState[] windows = createNotDrawnWindowsOn(defaultDisplay,
TYPE_WALLPAPER, TYPE_APPLICATION);
final WindowState wallpaper = windows[0];
assertTrue(wallpaper.mIsWallpaper);
wallpaper.mToken.asWallpaperToken().setVisibility(false);
// By default WindowState#mWallpaperVisible is false.
assertFalse(wallpaper.isVisible());
// Verify waiting for windows to be drawn.
assertTrue(defaultDisplay.shouldWaitForSystemDecorWindowsOnBoot());
// Verify not waiting for drawn window and invisible wallpaper.
setDrawnState(WindowStateAnimator.READY_TO_SHOW, wallpaper);
setDrawnState(WindowStateAnimator.HAS_DRAWN, windows[1]);
assertFalse(defaultDisplay.shouldWaitForSystemDecorWindowsOnBoot());
}
@Test
public void testShouldWaitForSystemDecorWindowsOnBoot_OnSecondaryDisplay() {
mWm.mSystemBooted = true;
final DisplayContent secondaryDisplay = createNewDisplay();
final WindowState[] windows = createNotDrawnWindowsOn(secondaryDisplay,
TYPE_WALLPAPER, TYPE_APPLICATION);
// Verify not waiting for display without system decorations.
doReturn(false).when(secondaryDisplay).supportsSystemDecorations();
assertFalse(secondaryDisplay.shouldWaitForSystemDecorWindowsOnBoot());
// Verify waiting for non-drawn windows on display with system decorations.
reset(secondaryDisplay);
doReturn(true).when(secondaryDisplay).supportsSystemDecorations();
assertTrue(secondaryDisplay.shouldWaitForSystemDecorWindowsOnBoot());
// Verify not waiting for drawn windows on display with system decorations.
setDrawnState(WindowStateAnimator.HAS_DRAWN, windows);
assertFalse(secondaryDisplay.shouldWaitForSystemDecorWindowsOnBoot());
}
@Test
public void testImeIsAttachedToDisplayForLetterboxedApp() {
final DisplayContent dc = mDisplayContent;
final WindowState ws = createWindow(null, TYPE_APPLICATION, dc, "app window");
dc.setImeLayeringTarget(ws);
// Adjust bounds so that matchesRootDisplayAreaBounds() returns false.
final Rect bounds = new Rect(dc.getBounds());
bounds.scale(0.5f);
ws.mActivityRecord.setBounds(bounds);
assertFalse("matchesRootDisplayAreaBounds() should return false",
ws.matchesDisplayAreaBounds());
assertTrue("IME shouldn't be attached to app",
dc.computeImeParent() != dc.getImeTarget(IME_TARGET_LAYERING).getWindow()
.mActivityRecord.getSurfaceControl());
assertEquals("IME should be attached to display",
dc.getImeContainer().getParent().getSurfaceControl(), dc.computeImeParent());
}
private WindowState[] createNotDrawnWindowsOn(DisplayContent displayContent, int... types) {
final WindowState[] windows = new WindowState[types.length];
for (int i = 0; i < types.length; i++) {
final int type = types[i];
windows[i] = createWindow(null /* parent */, type, displayContent, "window-" + type);
windows[i].setHasSurface(true);
windows[i].mWinAnimator.mDrawState = WindowStateAnimator.DRAW_PENDING;
}
return windows;
}
private static void setDrawnState(int state, WindowState... windows) {
for (WindowState window : windows) {
window.mHasSurface = state != WindowStateAnimator.NO_SURFACE;
window.mWinAnimator.mDrawState = state;
}
}
/**
* This tests setting the maximum ui width on a display.
*/
@Test
public void testMaxUiWidth() {
// Prevent base display metrics for test from being updated to the value of real display.
final DisplayContent displayContent = createDisplayNoUpdateDisplayInfo();
final int baseWidth = 1440;
final int baseHeight = 2560;
final int baseDensity = 300;
displayContent.updateBaseDisplayMetrics(baseWidth, baseHeight, baseDensity);
final int maxWidth = 300;
final int resultingHeight = (maxWidth * baseHeight) / baseWidth;
final int resultingDensity = baseDensity;
displayContent.setMaxUiWidth(maxWidth);
verifySizes(displayContent, maxWidth, resultingHeight, resultingDensity);
// Assert setting values again does not change;
displayContent.updateBaseDisplayMetrics(baseWidth, baseHeight, baseDensity);
verifySizes(displayContent, maxWidth, resultingHeight, resultingDensity);
final int smallerWidth = 200;
final int smallerHeight = 400;
final int smallerDensity = 100;
// Specify smaller dimension, verify that it is honored
displayContent.updateBaseDisplayMetrics(smallerWidth, smallerHeight, smallerDensity);
verifySizes(displayContent, smallerWidth, smallerHeight, smallerDensity);
// Verify that setting the max width to a greater value than the base width has no effect
displayContent.setMaxUiWidth(maxWidth);
verifySizes(displayContent, smallerWidth, smallerHeight, smallerDensity);
}
@Test
public void testSetForcedSize() {
// Prevent base display metrics for test from being updated to the value of real display.
final DisplayContent displayContent = createDisplayNoUpdateDisplayInfo();
final int baseWidth = 1280;
final int baseHeight = 720;
final int baseDensity = 320;
displayContent.mInitialDisplayWidth = baseWidth;
displayContent.mInitialDisplayHeight = baseHeight;
displayContent.mInitialDisplayDensity = baseDensity;
displayContent.updateBaseDisplayMetrics(baseWidth, baseHeight, baseDensity);
final int forcedWidth = 1920;
final int forcedHeight = 1080;
// Verify that forcing the size is honored and the density doesn't change.
displayContent.setForcedSize(forcedWidth, forcedHeight);
verifySizes(displayContent, forcedWidth, forcedHeight, baseDensity);
// Verify that forcing the size is idempotent.
displayContent.setForcedSize(forcedWidth, forcedHeight);
verifySizes(displayContent, forcedWidth, forcedHeight, baseDensity);
}
@Test
public void testSetForcedSize_WithMaxUiWidth() {
// Prevent base display metrics for test from being updated to the value of real display.
final DisplayContent displayContent = createDisplayNoUpdateDisplayInfo();
final int baseWidth = 1280;
final int baseHeight = 720;
final int baseDensity = 320;
displayContent.mInitialDisplayWidth = baseWidth;
displayContent.mInitialDisplayHeight = baseHeight;
displayContent.mInitialDisplayDensity = baseDensity;
displayContent.updateBaseDisplayMetrics(baseWidth, baseHeight, baseDensity);
displayContent.setMaxUiWidth(baseWidth);
final int forcedWidth = 1920;
final int forcedHeight = 1080;
// Verify that forcing bigger size doesn't work and density doesn't change.
displayContent.setForcedSize(forcedWidth, forcedHeight);
verifySizes(displayContent, baseWidth, baseHeight, baseDensity);
// Verify that forcing the size is idempotent.
displayContent.setForcedSize(forcedWidth, forcedHeight);
verifySizes(displayContent, baseWidth, baseHeight, baseDensity);
}
@Test
public void testDisplayCutout_rot0() {
final DisplayContent dc = createNewDisplay();
dc.mInitialDisplayWidth = 200;
dc.mInitialDisplayHeight = 400;
final Rect r = new Rect(80, 0, 120, 10);
final DisplayCutout cutout = new WmDisplayCutout(
fromBoundingRect(r.left, r.top, r.right, r.bottom, BOUNDS_POSITION_TOP), null)
.computeSafeInsets(200, 400).getDisplayCutout();
dc.mInitialDisplayCutout = cutout;
dc.getDisplayRotation().setRotation(Surface.ROTATION_0);
dc.computeScreenConfiguration(new Configuration()); // recomputes dc.mDisplayInfo.
assertEquals(cutout, dc.getDisplayInfo().displayCutout);
}
@Test
public void testDisplayCutout_rot90() {
// Prevent mInitialDisplayCutout from being updated from real display (e.g. null
// if the device has no cutout).
final DisplayContent dc = createDisplayNoUpdateDisplayInfo();
// This test assumes it's a top cutout on a portrait display, so if it happens to be a
// landscape display let's rotate it.
if (dc.mInitialDisplayHeight < dc.mInitialDisplayWidth) {
int tmp = dc.mInitialDisplayHeight;
dc.mInitialDisplayHeight = dc.mInitialDisplayWidth;
dc.mInitialDisplayWidth = tmp;
}
// Rotation may use real display info to compute bound, so here also uses the
// same width and height.
final int displayWidth = dc.mInitialDisplayWidth;
final int displayHeight = dc.mInitialDisplayHeight;
final float density = dc.mInitialDisplayDensity;
final int cutoutWidth = 40;
final int cutoutHeight = 10;
final int left = (displayWidth - cutoutWidth) / 2;
final int top = 0;
final int right = (displayWidth + cutoutWidth) / 2;
final int bottom = cutoutHeight;
final Rect zeroRect = new Rect();
final Rect[] bounds = new Rect[]{zeroRect, new Rect(left, top, right, bottom), zeroRect,
zeroRect};
final DisplayCutout.CutoutPathParserInfo info = new DisplayCutout.CutoutPathParserInfo(
displayWidth, displayHeight, density, "", Surface.ROTATION_0, 1f);
final DisplayCutout cutout = new WmDisplayCutout(
DisplayCutout.constructDisplayCutout(bounds, Insets.NONE, info), null)
.computeSafeInsets(displayWidth, displayHeight).getDisplayCutout();
dc.mInitialDisplayCutout = cutout;
dc.getDisplayRotation().setRotation(Surface.ROTATION_90);
dc.computeScreenConfiguration(new Configuration()); // recomputes dc.mDisplayInfo.
// ----o---------- -------------
// | | | | |
// | ------o | o---
// | | | |
// | | -> | |
// | | ---o
// | | |
// | | -------------
final Rect[] bounds90 = new Rect[]{new Rect(top, left, bottom, right), zeroRect, zeroRect,
zeroRect};
final DisplayCutout.CutoutPathParserInfo info90 = new DisplayCutout.CutoutPathParserInfo(
displayWidth, displayHeight, density, "", Surface.ROTATION_90, 1f);
assertEquals(new WmDisplayCutout(
DisplayCutout.constructDisplayCutout(bounds90, Insets.NONE, info90), null)
.computeSafeInsets(displayHeight, displayWidth).getDisplayCutout(),
dc.getDisplayInfo().displayCutout);
}
@Test
public void testLayoutSeq_assignedDuringLayout() {
final DisplayContent dc = createNewDisplay();
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w");
performLayout(dc);
assertThat(win.mLayoutSeq, is(dc.mLayoutSeq));
}
@Test
public void testOrientationDefinedByKeyguard() {
final DisplayContent dc = mDisplayContent;
dc.getDisplayPolicy().setAwake(true);
// Create a window that requests landscape orientation. It will define device orientation
// by default.
final WindowState window = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w");
window.mActivityRecord.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
final WindowState keyguard = createWindow(null, TYPE_NOTIFICATION_SHADE , dc, "keyguard");
keyguard.mHasSurface = true;
keyguard.mAttrs.screenOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
assertEquals("Screen orientation must be defined by the app window by default",
SCREEN_ORIENTATION_LANDSCAPE, dc.getOrientation());
keyguard.mAttrs.screenOrientation = SCREEN_ORIENTATION_PORTRAIT;
mAtm.mKeyguardController.setKeyguardShown(true /* keyguardShowing */,
false /* aodShowing */);
assertEquals("Visible keyguard must influence device orientation",
SCREEN_ORIENTATION_PORTRAIT, dc.getOrientation());
mAtm.mKeyguardController.keyguardGoingAway(0 /* flags */);
assertEquals("Keyguard that is going away must not influence device orientation",
SCREEN_ORIENTATION_LANDSCAPE, dc.getOrientation());
}
@Test
public void testOrientationForAspectRatio() {
final DisplayContent dc = createNewDisplay();
// When display content is created its configuration is not yet initialized, which could
// cause unnecessary configuration propagation, so initialize it here.
final Configuration config = new Configuration();
dc.computeScreenConfiguration(config);
dc.onRequestedOverrideConfigurationChanged(config);
// Create a window that requests a fixed orientation. It will define device orientation
// by default.
final WindowState window = createWindow(null /* parent */, TYPE_APPLICATION_OVERLAY, dc,
"window");
window.mHasSurface = true;
window.mAttrs.screenOrientation = SCREEN_ORIENTATION_LANDSCAPE;
// --------------------------------
// Test non-close-to-square display
// --------------------------------
dc.mBaseDisplayWidth = 1000;
dc.mBaseDisplayHeight = (int) (dc.mBaseDisplayWidth * dc.mCloseToSquareMaxAspectRatio * 2f);
dc.configureDisplayPolicy();
assertEquals("Screen orientation must be defined by the window by default.",
window.mAttrs.screenOrientation, dc.getOrientation());
// ----------------------------
// Test close-to-square display - should be handled in the same way
// ----------------------------
dc.mBaseDisplayHeight = dc.mBaseDisplayWidth;
dc.configureDisplayPolicy();
assertEquals(
"Screen orientation must be defined by the window even on close-to-square display.",
window.mAttrs.screenOrientation, dc.getOrientation());
}
@Test
public void testDisableDisplayInfoOverrideFromWindowManager() {
final DisplayContent dc = createNewDisplay();
assertTrue(dc.mShouldOverrideDisplayConfiguration);
mWm.dontOverrideDisplayInfo(dc.getDisplayId());
assertFalse(dc.mShouldOverrideDisplayConfiguration);
verify(mWm.mDisplayManagerInternal, times(1))
.setDisplayInfoOverrideFromWindowManager(dc.getDisplayId(), null);
}
@Test
public void testGetPreferredOptionsPanelGravityFromDifferentDisplays() {
final DisplayContent portraitDisplay = createNewDisplay();
portraitDisplay.mInitialDisplayHeight = 2000;
portraitDisplay.mInitialDisplayWidth = 1000;
portraitDisplay.getDisplayRotation().setRotation(Surface.ROTATION_0);
assertFalse(isOptionsPanelAtRight(portraitDisplay.getDisplayId()));
portraitDisplay.getDisplayRotation().setRotation(ROTATION_90);
assertTrue(isOptionsPanelAtRight(portraitDisplay.getDisplayId()));
final DisplayContent landscapeDisplay = createNewDisplay();
landscapeDisplay.mInitialDisplayHeight = 1000;
landscapeDisplay.mInitialDisplayWidth = 2000;
landscapeDisplay.getDisplayRotation().setRotation(Surface.ROTATION_0);
assertTrue(isOptionsPanelAtRight(landscapeDisplay.getDisplayId()));
landscapeDisplay.getDisplayRotation().setRotation(ROTATION_90);
assertFalse(isOptionsPanelAtRight(landscapeDisplay.getDisplayId()));
}
@UseTestDisplay(addWindows = W_INPUT_METHOD)
@Test
public void testInputMethodTargetUpdateWhenSwitchingOnDisplays() {
final DisplayContent newDisplay = createNewDisplay();
final WindowState appWin = createWindow(null, TYPE_APPLICATION, mDisplayContent, "appWin");
final Task rootTask = mDisplayContent.getTopRootTask();
final ActivityRecord activity = rootTask.topRunningActivity();
doReturn(true).when(activity).shouldBeVisibleUnchecked();
final WindowState appWin1 = createWindow(null, TYPE_APPLICATION, newDisplay, "appWin1");
final Task rootTask1 = newDisplay.getTopRootTask();
final ActivityRecord activity1 = rootTask1.topRunningActivity();
doReturn(true).when(activity1).shouldBeVisibleUnchecked();
appWin.setHasSurface(true);
appWin1.setHasSurface(true);
// Set current input method window on default display, make sure the input method target
// is appWin & null on the other display.
mDisplayContent.setInputMethodWindowLocked(mImeWindow);
newDisplay.setInputMethodWindowLocked(null);
assertEquals("appWin should be IME target window",
appWin, mDisplayContent.getImeTarget(IME_TARGET_LAYERING));
assertNull("newDisplay Ime target: ", newDisplay.getImeTarget(IME_TARGET_LAYERING));
// Switch input method window on new display & make sure the input method target also
// switched as expected.
newDisplay.setInputMethodWindowLocked(mImeWindow);
mDisplayContent.setInputMethodWindowLocked(null);
assertEquals("appWin1 should be IME target window", appWin1,
newDisplay.getImeTarget(IME_TARGET_LAYERING));
assertNull("default display Ime target: ",
mDisplayContent.getImeTarget(IME_TARGET_LAYERING));
}
@UseTestDisplay(addWindows = W_INPUT_METHOD)
@Test
public void testInputMethodSet_listenOnDisplayAreaConfigurationChanged() {
spyOn(mAtm);
mDisplayContent.setInputMethodWindowLocked(mImeWindow);
verify(mAtm).onImeWindowSetOnDisplayArea(
mImeWindow.mSession.mPid, mDisplayContent.getImeContainer());
}
@Test
public void testAllowsTopmostFullscreenOrientation() {
final DisplayContent dc = createNewDisplay();
dc.getDisplayRotation().setFixedToUserRotation(
IWindowManager.FIXED_TO_USER_ROTATION_DISABLED);
final Task rootTask = new TaskBuilder(mSupervisor)
.setDisplay(dc)
.setCreateActivity(true)
.build();
doReturn(true).when(rootTask).isVisible();
final Task freeformRootTask = new TaskBuilder(mSupervisor)
.setDisplay(dc)
.setCreateActivity(true)
.setWindowingMode(WINDOWING_MODE_FREEFORM)
.build();
doReturn(true).when(freeformRootTask).isVisible();
freeformRootTask.getTopChild().setBounds(100, 100, 300, 400);
assertTrue(dc.getDefaultTaskDisplayArea().isRootTaskVisible(WINDOWING_MODE_FREEFORM));
freeformRootTask.getTopNonFinishingActivity().setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
rootTask.getTopNonFinishingActivity().setOrientation(SCREEN_ORIENTATION_PORTRAIT);
assertEquals(SCREEN_ORIENTATION_PORTRAIT, dc.getOrientation());
rootTask.getTopNonFinishingActivity().setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
freeformRootTask.getTopNonFinishingActivity().setOrientation(SCREEN_ORIENTATION_PORTRAIT);
assertEquals(SCREEN_ORIENTATION_LANDSCAPE, dc.getOrientation());
}
@Test
public void testOnDescendantOrientationRequestChanged() {
final DisplayContent dc = createNewDisplay();
dc.getDisplayRotation().setFixedToUserRotation(
IWindowManager.FIXED_TO_USER_ROTATION_DISABLED);
final int newOrientation = getRotatedOrientation(dc);
final Task task = new TaskBuilder(mSupervisor)
.setDisplay(dc).setCreateActivity(true).build();
final ActivityRecord activity = task.getTopMostTask().getTopNonFinishingActivity();
dc.setFocusedApp(activity);
activity.setRequestedOrientation(newOrientation);
assertTrue("The display should be rotated.", dc.getRotation() % 2 == 1);
}
@Test
public void testOnDescendantOrientationRequestChanged_FrozenToUserRotation() {
final DisplayContent dc = createNewDisplay();
dc.getDisplayRotation().setFixedToUserRotation(
IWindowManager.FIXED_TO_USER_ROTATION_ENABLED);
dc.getDisplayRotation().setUserRotation(
WindowManagerPolicy.USER_ROTATION_LOCKED, ROTATION_180);
final int newOrientation = getRotatedOrientation(dc);
final Task task = new TaskBuilder(mSupervisor)
.setDisplay(dc).setCreateActivity(true).build();
final ActivityRecord activity = task.getTopMostTask().getTopNonFinishingActivity();
dc.setFocusedApp(activity);
activity.setRequestedOrientation(newOrientation);
verify(dc, never()).updateDisplayOverrideConfigurationLocked(any(), eq(activity),
anyBoolean(), same(null));
assertEquals(ROTATION_180, dc.getRotation());
}
@Test
public void testFixedToUserRotationChanged() {
final DisplayContent dc = createNewDisplay();
dc.getDisplayRotation().setFixedToUserRotation(
IWindowManager.FIXED_TO_USER_ROTATION_ENABLED);
dc.getDisplayRotation().setUserRotation(
WindowManagerPolicy.USER_ROTATION_LOCKED, ROTATION_0);
final int newOrientation = getRotatedOrientation(dc);
final Task task = new TaskBuilder(mSupervisor)
.setDisplay(dc).setCreateActivity(true).build();
final ActivityRecord activity = task.getTopMostTask().getTopNonFinishingActivity();
dc.setFocusedApp(activity);
activity.setRequestedOrientation(newOrientation);
dc.getDisplayRotation().setFixedToUserRotation(
IWindowManager.FIXED_TO_USER_ROTATION_DISABLED);
assertTrue("The display should be rotated.", dc.getRotation() % 2 == 1);
}
@Test
public void testComputeImeParent_app() throws Exception {
final DisplayContent dc = createNewDisplay();
dc.setImeLayeringTarget(createWindow(null, TYPE_BASE_APPLICATION, "app"));
dc.setImeInputTarget(dc.getImeTarget(IME_TARGET_LAYERING).getWindow());
assertEquals(dc.getImeTarget(IME_TARGET_LAYERING).getWindow()
.mActivityRecord.getSurfaceControl(), dc.computeImeParent());
}
@Test
public void testComputeImeParent_app_notFullscreen() throws Exception {
final DisplayContent dc = createNewDisplay();
dc.setImeLayeringTarget(createWindow(null, TYPE_STATUS_BAR, "app"));
dc.getImeTarget(IME_TARGET_LAYERING).getWindow().setWindowingMode(
WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
assertEquals(dc.getImeContainer().getParentSurfaceControl(), dc.computeImeParent());
}
@UseTestDisplay(addWindows = W_ACTIVITY)
@Test
public void testComputeImeParent_app_notMatchParentBounds() {
spyOn(mAppWindow.mActivityRecord);
doReturn(false).when(mAppWindow.mActivityRecord).matchParentBounds();
mDisplayContent.setImeLayeringTarget(mAppWindow);
// The surface parent of IME should be the display instead of app window.
assertEquals(mDisplayContent.getImeContainer().getParentSurfaceControl(),
mDisplayContent.computeImeParent());
}
@Test
public void testComputeImeParent_noApp() throws Exception {
final DisplayContent dc = createNewDisplay();
dc.setImeLayeringTarget(createWindow(null, TYPE_STATUS_BAR, "statusBar"));
assertEquals(dc.getImeContainer().getParentSurfaceControl(), dc.computeImeParent());
}
@Test
public void testInputMethodInputTarget_isClearedWhenWindowStateIsRemoved() throws Exception {
final DisplayContent dc = createNewDisplay();
WindowState app = createWindow(null, TYPE_BASE_APPLICATION, dc, "app");
dc.setImeInputTarget(app);
assertEquals(app, dc.computeImeControlTarget());
app.removeImmediately();
assertNull(dc.getImeTarget(IME_TARGET_INPUT));
assertNull(dc.computeImeControlTarget());
}
@Test
public void testComputeImeControlTarget() throws Exception {
final DisplayContent dc = createNewDisplay();
dc.setRemoteInsetsController(createDisplayWindowInsetsController());
dc.setImeInputTarget(createWindow(null, TYPE_BASE_APPLICATION, "app"));
dc.setImeLayeringTarget(dc.getImeTarget(IME_TARGET_INPUT).getWindow());
assertEquals(dc.getImeTarget(IME_TARGET_INPUT).getWindow(), dc.computeImeControlTarget());
}
@Test
public void testComputeImeControlTarget_splitscreen() throws Exception {
final DisplayContent dc = createNewDisplay();
dc.setImeInputTarget(createWindow(null, TYPE_BASE_APPLICATION, "app"));
dc.getImeTarget(IME_TARGET_INPUT).getWindow().setWindowingMode(
WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
dc.setImeLayeringTarget(dc.getImeTarget(IME_TARGET_INPUT).getWindow());
dc.setRemoteInsetsController(createDisplayWindowInsetsController());
assertNotEquals(dc.getImeTarget(IME_TARGET_INPUT).getWindow(),
dc.computeImeControlTarget());
}
@UseTestDisplay(addWindows = W_ACTIVITY)
@Test
public void testComputeImeControlTarget_notMatchParentBounds() throws Exception {
spyOn(mAppWindow.mActivityRecord);
doReturn(false).when(mAppWindow.mActivityRecord).matchParentBounds();
mDisplayContent.setImeInputTarget(mAppWindow);
mDisplayContent.setImeLayeringTarget(
mDisplayContent.getImeTarget(IME_TARGET_INPUT).getWindow());
mDisplayContent.setRemoteInsetsController(createDisplayWindowInsetsController());
assertEquals(mAppWindow, mDisplayContent.computeImeControlTarget());
}
@Test
public void testUpdateSystemGestureExclusion() throws Exception {
final DisplayContent dc = createNewDisplay();
final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "win");
win.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
win.setSystemGestureExclusion(Collections.singletonList(new Rect(10, 20, 30, 40)));
performLayout(dc);
win.setHasSurface(true);
dc.updateSystemGestureExclusion();
final boolean[] invoked = { false };
final ISystemGestureExclusionListener.Stub verifier =
new ISystemGestureExclusionListener.Stub() {
@Override
public void onSystemGestureExclusionChanged(int displayId, Region actual,
Region unrestricted) {
Region expected = Region.obtain();
expected.set(10, 20, 30, 40);
assertEquals(expected, actual);
invoked[0] = true;
}
};
try {
dc.registerSystemGestureExclusionListener(verifier);
} finally {
dc.unregisterSystemGestureExclusionListener(verifier);
}
assertTrue("SystemGestureExclusionListener was not invoked", invoked[0]);
}
@Test
public void testCalculateSystemGestureExclusion() throws Exception {
final DisplayContent dc = createNewDisplay();
final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "win");
win.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
win.setSystemGestureExclusion(Collections.singletonList(new Rect(10, 20, 30, 40)));
final WindowState win2 = createWindow(null, TYPE_APPLICATION, dc, "win2");
win2.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
win2.setSystemGestureExclusion(Collections.singletonList(new Rect(20, 30, 40, 50)));
performLayout(dc);
win.setHasSurface(true);
win2.setHasSurface(true);
final Region expected = Region.obtain();
expected.set(20, 30, 40, 50);
assertEquals(expected, calculateSystemGestureExclusion(dc));
}
private Region calculateSystemGestureExclusion(DisplayContent dc) {
Region out = Region.obtain();
Region unrestricted = Region.obtain();
dc.calculateSystemGestureExclusion(out, unrestricted);
return out;
}
@Test
public void testCalculateSystemGestureExclusion_modal() throws Exception {
final DisplayContent dc = createNewDisplay();
final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "base");
win.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
win.setSystemGestureExclusion(Collections.singletonList(new Rect(0, 0, 1000, 1000)));
final WindowState win2 = createWindow(null, TYPE_APPLICATION, dc, "modal");
win2.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
win2.getAttrs().privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
win2.getAttrs().width = 10;
win2.getAttrs().height = 10;
win2.setSystemGestureExclusion(Collections.emptyList());
performLayout(dc);
win.setHasSurface(true);
win2.setHasSurface(true);
final Region expected = Region.obtain();
assertEquals(expected, calculateSystemGestureExclusion(dc));
}
@Test
public void testCalculateSystemGestureExclusion_immersiveStickyLegacyWindow() throws Exception {
mWm.mConstants.mSystemGestureExcludedByPreQStickyImmersive = true;
final DisplayContent dc = createNewDisplay();
final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "win");
win.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
win.getAttrs().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
win.getAttrs().privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
win.getAttrs().insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
requestedVisibilities.setVisibility(ITYPE_NAVIGATION_BAR, false);
requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
win.setRequestedVisibilities(requestedVisibilities);
win.mActivityRecord.mTargetSdk = P;
performLayout(dc);
win.setHasSurface(true);
final Region expected = Region.obtain();
expected.set(dc.getBounds());
assertEquals(expected, calculateSystemGestureExclusion(dc));
win.setHasSurface(false);
}
@UseTestDisplay(addWindows = { W_ABOVE_ACTIVITY, W_ACTIVITY})
@Test
public void testRequestResizeForEmptyFrames() {
final WindowState win = mChildAppWindowAbove;
makeWindowVisible(win, win.getParentWindow());
win.setRequestedSize(mDisplayContent.mBaseDisplayWidth, 0 /* height */);
win.mAttrs.width = win.mAttrs.height = WindowManager.LayoutParams.WRAP_CONTENT;
win.mAttrs.gravity = Gravity.CENTER;
performLayout(mDisplayContent);
// The frame is empty because the requested height is zero.
assertTrue(win.getFrame().isEmpty());
// The window should be scheduled to resize then the client may report a new non-empty size.
win.updateResizingWindowIfNeeded();
assertThat(mWm.mResizingWindows).contains(win);
}
@Test
public void testOrientationChangeLogging() {
MetricsLogger mockLogger = mock(MetricsLogger.class);
Configuration oldConfig = new Configuration();
oldConfig.orientation = Configuration.ORIENTATION_LANDSCAPE;
Configuration newConfig = new Configuration();
newConfig.orientation = Configuration.ORIENTATION_PORTRAIT;
final DisplayContent displayContent = createNewDisplay();
Mockito.doReturn(mockLogger).when(displayContent).getMetricsLogger();
Mockito.doReturn(oldConfig).doReturn(newConfig).when(displayContent).getConfiguration();
displayContent.onConfigurationChanged(newConfig);
ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class);
verify(mockLogger).write(logMakerCaptor.capture());
assertThat(logMakerCaptor.getValue().getCategory(),
is(MetricsProto.MetricsEvent.ACTION_PHONE_ORIENTATION_CHANGED));
assertThat(logMakerCaptor.getValue().getSubtype(),
is(Configuration.ORIENTATION_PORTRAIT));
}
@Test
public void testHybridRotationAnimation() {
final DisplayContent displayContent = mDefaultDisplay;
final WindowState statusBar = createWindow(null, TYPE_STATUS_BAR, "statusBar");
final WindowState navBar = createWindow(null, TYPE_NAVIGATION_BAR, "navBar");
final WindowState app = createWindow(null, TYPE_BASE_APPLICATION, "app");
final WindowState[] windows = { statusBar, navBar, app };
makeWindowVisible(windows);
final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
displayPolicy.addWindowLw(statusBar, statusBar.mAttrs);
displayPolicy.addWindowLw(navBar, navBar.mAttrs);
final ScreenRotationAnimation rotationAnim = new ScreenRotationAnimation(displayContent,
displayContent.getRotation());
spyOn(rotationAnim);
// Assume that the display rotation is changed so it is frozen in preparation for animation.
doReturn(true).when(rotationAnim).hasScreenshot();
mWm.mDisplayFrozen = true;
displayContent.getDisplayRotation().setRotation((displayContent.getRotation() + 1) % 4);
displayContent.setRotationAnimation(rotationAnim);
// The fade rotation animation also starts to hide some non-app windows.
assertNotNull(displayContent.getFadeRotationAnimationController());
assertTrue(statusBar.isAnimating(PARENTS, ANIMATION_TYPE_FIXED_TRANSFORM));
for (WindowState w : windows) {
w.setOrientationChanging(true);
}
// The display only waits for the app window to unfreeze.
assertFalse(displayContent.waitForUnfreeze(statusBar));
assertFalse(displayContent.waitForUnfreeze(navBar));
assertTrue(displayContent.waitForUnfreeze(app));
// If all windows animated by fade rotation animation have done the orientation change,
// the animation controller should be cleared.
statusBar.setOrientationChanging(false);
navBar.setOrientationChanging(false);
assertNull(displayContent.getFadeRotationAnimationController());
}
@UseTestDisplay(addWindows = { W_ACTIVITY, W_WALLPAPER, W_STATUS_BAR, W_NAVIGATION_BAR,
W_INPUT_METHOD, W_NOTIFICATION_SHADE })
@Test
public void testApplyTopFixedRotationTransform() {
final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
// Only non-movable (gesture) navigation bar will be animated by fixed rotation animation.
doReturn(false).when(displayPolicy).navigationBarCanMove();
displayPolicy.addWindowLw(mStatusBarWindow, mStatusBarWindow.mAttrs);
displayPolicy.addWindowLw(mNavBarWindow, mNavBarWindow.mAttrs);
displayPolicy.addWindowLw(mNotificationShadeWindow, mNotificationShadeWindow.mAttrs);
makeWindowVisible(mStatusBarWindow, mNavBarWindow);
final Configuration config90 = new Configuration();
mDisplayContent.computeScreenConfiguration(config90, ROTATION_90);
final Configuration config = new Configuration();
mDisplayContent.getDisplayRotation().setRotation(ROTATION_0);
mDisplayContent.computeScreenConfiguration(config);
mDisplayContent.onRequestedOverrideConfigurationChanged(config);
assertNotEquals(config90.windowConfiguration.getMaxBounds(),
config.windowConfiguration.getMaxBounds());
final ActivityRecord app = mAppWindow.mActivityRecord;
app.setVisible(false);
mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_OPEN);
mDisplayContent.mOpeningApps.add(app);
final int newOrientation = getRotatedOrientation(mDisplayContent);
app.setRequestedOrientation(newOrientation);
assertTrue(app.isFixedRotationTransforming());
assertTrue(mAppWindow.matchesDisplayAreaBounds());
assertFalse(mAppWindow.areAppWindowBoundsLetterboxed());
assertTrue(mDisplayContent.getDisplayRotation().shouldRotateSeamlessly(
ROTATION_0 /* oldRotation */, ROTATION_90 /* newRotation */,
false /* forceUpdate */));
assertNotNull(mDisplayContent.getFadeRotationAnimationController());
assertTrue(mStatusBarWindow.isAnimating(PARENTS, ANIMATION_TYPE_FIXED_TRANSFORM));
assertTrue(mNavBarWindow.isAnimating(PARENTS, ANIMATION_TYPE_FIXED_TRANSFORM));
// Notification shade may have its own view animation in real case so do not fade out it.
assertFalse(mNotificationShadeWindow.isAnimating(PARENTS, ANIMATION_TYPE_FIXED_TRANSFORM));
// If the visibility of insets state is changed, the rotated state should be updated too.
final InsetsState rotatedState = app.getFixedRotationTransformInsetsState();
final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState();
assertEquals(state.getSource(ITYPE_STATUS_BAR).isVisible(),
rotatedState.getSource(ITYPE_STATUS_BAR).isVisible());
state.getSource(ITYPE_STATUS_BAR).setVisible(
!rotatedState.getSource(ITYPE_STATUS_BAR).isVisible());
mDisplayContent.getInsetsStateController().notifyInsetsChanged();
assertEquals(state.getSource(ITYPE_STATUS_BAR).isVisible(),
rotatedState.getSource(ITYPE_STATUS_BAR).isVisible());
final Rect outFrame = new Rect();
final Rect outInsets = new Rect();
final Rect outStableInsets = new Rect();
final Rect outSurfaceInsets = new Rect();
mAppWindow.getAnimationFrames(outFrame, outInsets, outStableInsets, outSurfaceInsets);
// The animation frames should not be rotated because display hasn't rotated.
assertEquals(mDisplayContent.getBounds(), outFrame);
// The display should keep current orientation and the rotated configuration should apply
// to the activity.
assertEquals(config.orientation, mDisplayContent.getConfiguration().orientation);
assertEquals(config90.orientation, app.getConfiguration().orientation);
assertEquals(config90.windowConfiguration.getBounds(), app.getBounds());
// Make wallaper laid out with the fixed rotation transform.
final WindowToken wallpaperToken = mWallpaperWindow.mToken;
wallpaperToken.linkFixedRotationTransform(app);
mWallpaperWindow.mLayoutNeeded = true;
performLayout(mDisplayContent);
// Force the negative offset to verify it can be updated.
mWallpaperWindow.mXOffset = mWallpaperWindow.mYOffset = -1;
assertTrue(mDisplayContent.mWallpaperController.updateWallpaperOffset(mWallpaperWindow,
false /* sync */));
assertThat(mWallpaperWindow.mXOffset).isGreaterThan(-1);
assertThat(mWallpaperWindow.mYOffset).isGreaterThan(-1);
// The wallpaper need to animate with transformed position, so its surface position should
// not be reset.
final Transaction t = wallpaperToken.getPendingTransaction();
spyOn(t);
mWallpaperWindow.mToken.onAnimationLeashCreated(t, null /* leash */);
verify(t, never()).setPosition(any(), eq(0), eq(0));
// Launch another activity before the transition is finished.
final Task task2 = new TaskBuilder(mSupervisor).setDisplay(mDisplayContent).build();
final ActivityRecord app2 = new ActivityBuilder(mAtm).setTask(task2)
.setUseProcess(app.app).build();
app2.setVisible(false);
mDisplayContent.mOpeningApps.add(app2);
app2.setRequestedOrientation(newOrientation);
// The activity should share the same transform state as the existing one. The activity
// should also be the fixed rotation launching app because it is the latest top.
assertTrue(app.hasFixedRotationTransform(app2));
assertTrue(mDisplayContent.isFixedRotationLaunchingApp(app2));
final Configuration expectedProcConfig = new Configuration(app2.app.getConfiguration());
expectedProcConfig.windowConfiguration.setActivityType(
WindowConfiguration.ACTIVITY_TYPE_UNDEFINED);
assertEquals("The process should receive rotated configuration for compatibility",
expectedProcConfig, app2.app.getConfiguration());
// If the rotated activity requests to show IME, the IME window should use the
// transformation from activity to lay out in the same orientation.
mDisplayContent.setImeLayeringTarget(mAppWindow);
LocalServices.getService(WindowManagerInternal.class).onToggleImeRequested(true /* show */,
app.token, app.token, mDisplayContent.mDisplayId);
assertTrue(mImeWindow.mToken.hasFixedRotationTransform());
assertTrue(mImeWindow.isAnimating(PARENTS, ANIMATION_TYPE_FIXED_TRANSFORM));
// The fixed rotation transform can only be finished when all animation finished.
doReturn(false).when(app2).isAnimating(anyInt(), anyInt());
mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(app2.token);
assertTrue(app.hasFixedRotationTransform());
assertTrue(app2.hasFixedRotationTransform());
// The display should be rotated after the launch is finished.
doReturn(false).when(app).isAnimating(anyInt(), anyInt());
mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(app.token);
// The fixed rotation should be cleared and the new rotation is applied to display.
assertFalse(app.hasFixedRotationTransform());
assertFalse(app2.hasFixedRotationTransform());
assertEquals(config90.orientation, mDisplayContent.getConfiguration().orientation);
assertNull(mDisplayContent.getFadeRotationAnimationController());
}
@Test
public void testFinishFixedRotationNoAppTransitioningTask() {
unblockDisplayRotation(mDisplayContent);
final ActivityRecord app = createActivityRecord(mDisplayContent);
final Task task = app.getTask();
final ActivityRecord app2 = new ActivityBuilder(mWm.mAtmService).setTask(task).build();
mDisplayContent.setFixedRotationLaunchingApp(app2, (mDisplayContent.getRotation() + 1) % 4);
doReturn(true).when(task).isAppTransitioning();
// If the task is animating transition, this should be no-op.
mDisplayContent.mFixedRotationTransitionListener.onAppTransitionFinishedLocked(app.token);
assertTrue(app2.hasFixedRotationTransform());
assertTrue(mDisplayContent.hasTopFixedRotationLaunchingApp());
doReturn(false).when(task).isAppTransitioning();
// Although this notifies app instead of app2 that uses the fixed rotation, app2 should
// still finish the transform because there is no more transition event.
mDisplayContent.mFixedRotationTransitionListener.onAppTransitionFinishedLocked(app.token);
assertFalse(app2.hasFixedRotationTransform());
assertFalse(mDisplayContent.hasTopFixedRotationLaunchingApp());
}
@UseTestDisplay(addWindows = W_ACTIVITY)
@Test
public void testRotateSeamlesslyWithFixedRotation() {
final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
final ActivityRecord app = mAppWindow.mActivityRecord;
mDisplayContent.setFixedRotationLaunchingAppUnchecked(app);
mAppWindow.mAttrs.rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
// Use seamless rotation if the top app is rotated.
assertTrue(displayRotation.shouldRotateSeamlessly(ROTATION_0 /* oldRotation */,
ROTATION_90 /* newRotation */, false /* forceUpdate */));
mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(app);
// Use normal rotation because animating recents is an intermediate state.
assertFalse(displayRotation.shouldRotateSeamlessly(ROTATION_0 /* oldRotation */,
ROTATION_90 /* newRotation */, false /* forceUpdate */));
}
@Test
public void testFixedRotationWithPip() {
final DisplayContent displayContent = mDefaultDisplay;
unblockDisplayRotation(displayContent);
// Unblock the condition in PinnedTaskController#continueOrientationChangeIfNeeded.
doNothing().when(displayContent).prepareAppTransition(anyInt());
// Make resume-top really update the activity state.
setBooted(mAtm);
clearInvocations(mWm);
// Speed up the test by a few seconds.
mAtm.deferWindowLayout();
final ActivityRecord homeActivity = createActivityRecord(
displayContent.getDefaultTaskDisplayArea().getRootHomeTask());
final ActivityRecord pinnedActivity = new ActivityBuilder(mAtm).setCreateTask(true).build();
final Task pinnedTask = pinnedActivity.getRootTask();
doReturn((displayContent.getRotation() + 1) % 4).when(displayContent)
.rotationForActivityInDifferentOrientation(eq(homeActivity));
// Enter PiP from fullscreen.
pinnedTask.setWindowingMode(WINDOWING_MODE_PINNED);
assertTrue(displayContent.hasTopFixedRotationLaunchingApp());
assertTrue(displayContent.mPinnedTaskController.shouldDeferOrientationChange());
verify(mWm, never()).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
clearInvocations(pinnedTask);
// Assume that the PiP enter animation is done then the new bounds are set. Expect the
// orientation update is no longer deferred.
displayContent.mPinnedTaskController.setEnterPipBounds(pinnedTask.getBounds());
// The Task Configuration was frozen to skip the change of orientation.
verify(pinnedTask, never()).onConfigurationChanged(any());
assertFalse(displayContent.mPinnedTaskController.shouldDeferOrientationChange());
assertFalse(displayContent.hasTopFixedRotationLaunchingApp());
assertEquals(homeActivity.getConfiguration().orientation,
displayContent.getConfiguration().orientation);
doReturn((displayContent.getRotation() + 1) % 4).when(displayContent)
.rotationForActivityInDifferentOrientation(eq(pinnedActivity));
// Leave PiP to fullscreen. Simulate the step of PipTaskOrganizer that sets the activity
// to fullscreen, so fixed rotation will apply on it.
pinnedActivity.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
assertTrue(displayContent.hasTopFixedRotationLaunchingApp());
// Assume the animation of PipTaskOrganizer is done and then commit fullscreen to task.
pinnedTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
displayContent.continueUpdateOrientationForDiffOrienLaunchingApp();
assertFalse(displayContent.mPinnedTaskController.isFreezingTaskConfig(pinnedTask));
assertEquals(pinnedActivity.getConfiguration().orientation,
displayContent.getConfiguration().orientation);
}
@Test
public void testNoFixedRotationOnResumedScheduledApp() {
unblockDisplayRotation(mDisplayContent);
final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
app.setVisible(false);
app.setState(ActivityRecord.State.RESUMED, "test");
mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_OPEN);
mDisplayContent.mOpeningApps.add(app);
final int newOrientation = getRotatedOrientation(mDisplayContent);
app.setRequestedOrientation(newOrientation);
// The condition should reject using fixed rotation because the resumed client in real case
// might get display info immediately. And the fixed rotation adjustments haven't arrived
// client side so the info may be inconsistent with the requested orientation.
verify(mDisplayContent).handleTopActivityLaunchingInDifferentOrientation(eq(app),
eq(true) /* checkOpening */);
assertFalse(app.isFixedRotationTransforming());
assertFalse(mDisplayContent.hasTopFixedRotationLaunchingApp());
}
@Test
public void testRecentsNotRotatingWithFixedRotation() {
unblockDisplayRotation(mDisplayContent);
final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
// Skip freezing so the unrelated conditions in updateRotationUnchecked won't disturb.
doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
final ActivityRecord activity = createActivityRecord(mDisplayContent);
final ActivityRecord recentsActivity = createActivityRecord(mDisplayContent);
recentsActivity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
doReturn(mock(RecentsAnimationController.class)).when(mWm).getRecentsAnimationController();
// Do not rotate if the recents animation is animating on top.
mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity);
displayRotation.setRotation((displayRotation.getRotation() + 1) % 4);
assertFalse(displayRotation.updateRotationUnchecked(false));
// Rotation can be updated if the recents animation is finished.
mDisplayContent.mFixedRotationTransitionListener.onFinishRecentsAnimation();
assertTrue(displayRotation.updateRotationUnchecked(false));
// Rotation can be updated if the policy is not ok to animate (e.g. going to sleep).
mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity);
displayRotation.setRotation((displayRotation.getRotation() + 1) % 4);
((TestWindowManagerPolicy) mWm.mPolicy).mOkToAnimate = false;
assertTrue(displayRotation.updateRotationUnchecked(false));
// Rotation can be updated if the recents animation is animating but it is not on top, e.g.
// switching activities in different orientations by quickstep gesture.
mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity);
mDisplayContent.setFixedRotationLaunchingAppUnchecked(activity);
displayRotation.setRotation((displayRotation.getRotation() + 1) % 4);
assertTrue(displayRotation.updateRotationUnchecked(false));
// The recents activity should not apply fixed rotation if the top activity is not opaque.
mDisplayContent.mFocusedApp = activity;
doReturn(false).when(mDisplayContent.mFocusedApp).occludesParent();
doReturn(ROTATION_90).when(mDisplayContent).rotationForActivityInDifferentOrientation(
eq(recentsActivity));
mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity);
assertFalse(recentsActivity.hasFixedRotationTransform());
}
@Test
public void testClearIntermediateFixedRotationAdjustments() throws RemoteException {
final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
mDisplayContent.setFixedRotationLaunchingApp(activity,
(mDisplayContent.getRotation() + 1) % 4);
// Create a window so FixedRotationAdjustmentsItem can be sent.
createWindow(null, TYPE_APPLICATION_STARTING, activity, "AppWin");
final ActivityRecord activity2 = new ActivityBuilder(mAtm).setCreateTask(true).build();
activity2.setVisible(false);
clearInvocations(mAtm.getLifecycleManager());
// The first activity has applied fixed rotation but the second activity becomes the top
// before the transition is done and it has the same rotation as display, so the dispatched
// rotation adjustment of first activity must be cleared.
mDisplayContent.handleTopActivityLaunchingInDifferentOrientation(activity2,
false /* checkOpening */);
final ArgumentCaptor<FixedRotationAdjustmentsItem> adjustmentsCaptor =
ArgumentCaptor.forClass(FixedRotationAdjustmentsItem.class);
verify(mAtm.getLifecycleManager(), atLeastOnce()).scheduleTransaction(
eq(activity.app.getThread()), adjustmentsCaptor.capture());
// The transformation is kept for animation in real case.
assertTrue(activity.hasFixedRotationTransform());
final FixedRotationAdjustmentsItem clearAdjustments = FixedRotationAdjustmentsItem.obtain(
activity.token, null /* fixedRotationAdjustments */);
// The captor may match other items. The first one must be the item to clear adjustments.
assertEquals(clearAdjustments, adjustmentsCaptor.getAllValues().get(0));
}
@Test
public void testRemoteRotation() {
DisplayContent dc = createNewDisplay();
final DisplayRotation dr = dc.getDisplayRotation();
doCallRealMethod().when(dr).updateRotationUnchecked(anyBoolean());
// Rotate 180 degree so the display doesn't have configuration change. This condition is
// used for the later verification of stop-freezing (without setting mWaitingForConfig).
doReturn((dr.getRotation() + 2) % 4).when(dr).rotationForOrientation(anyInt(), anyInt());
final boolean[] continued = new boolean[1];
doAnswer(
invocation -> {
continued[0] = true;
return true;
}).when(dc).updateDisplayOverrideConfigurationLocked();
final boolean[] called = new boolean[1];
mWm.mDisplayRotationController =
new IDisplayWindowRotationController.Stub() {
@Override
public void onRotateDisplay(int displayId, int fromRotation, int toRotation,
IDisplayWindowRotationCallback callback) {
called[0] = true;
try {
callback.continueRotateDisplay(toRotation, null);
} catch (RemoteException e) {
assertTrue(false);
}
}
};
// kill any existing rotation animation (vestigial from test setup).
dc.setRotationAnimation(null);
mWm.updateRotation(true /* alwaysSendConfiguration */, false /* forceRelayout */);
// If remote rotation is not finished, the display should not be able to unfreeze.
mWm.stopFreezingDisplayLocked();
assertTrue(mWm.mDisplayFrozen);
assertTrue(called[0]);
waitUntilHandlersIdle();
assertTrue(continued[0]);
mWm.stopFreezingDisplayLocked();
assertFalse(mWm.mDisplayFrozen);
}
@Test
public void testShellTransitRotation() {
DisplayContent dc = createNewDisplay();
final TestTransitionPlayer testPlayer = registerTestTransitionPlayer();
final DisplayRotation dr = dc.getDisplayRotation();
doCallRealMethod().when(dr).updateRotationUnchecked(anyBoolean());
// Rotate 180 degree so the display doesn't have configuration change. This condition is
// used for the later verification of stop-freezing (without setting mWaitingForConfig).
doReturn((dr.getRotation() + 2) % 4).when(dr).rotationForOrientation(anyInt(), anyInt());
mWm.mDisplayRotationController =
new IDisplayWindowRotationController.Stub() {
@Override
public void onRotateDisplay(int displayId, int fromRotation, int toRotation,
IDisplayWindowRotationCallback callback) {
try {
callback.continueRotateDisplay(toRotation, null);
} catch (RemoteException e) {
assertTrue(false);
}
}
};
// kill any existing rotation animation (vestigial from test setup).
dc.setRotationAnimation(null);
final int origRot = dc.getConfiguration().windowConfiguration.getRotation();
mWm.updateRotation(true /* alwaysSendConfiguration */, false /* forceRelayout */);
// Should create a transition request without performing rotation
assertNotNull(testPlayer.mLastRequest);
assertEquals(origRot, dc.getConfiguration().windowConfiguration.getRotation());
// Once transition starts, rotation is applied and transition shows DC rotating.
testPlayer.start();
assertNotEquals(origRot, dc.getConfiguration().windowConfiguration.getRotation());
assertNotNull(testPlayer.mLastReady);
assertEquals(dc, DisplayRotation.getDisplayFromTransition(testPlayer.mLastTransit));
WindowContainerToken dcToken = dc.mRemoteToken.toWindowContainerToken();
assertNotEquals(testPlayer.mLastReady.getChange(dcToken).getEndRotation(),
testPlayer.mLastReady.getChange(dcToken).getStartRotation());
testPlayer.finish();
}
@Test
public void testValidWindowingLayer() {
final SurfaceControl windowingLayer = mDisplayContent.getWindowingLayer();
assertNotNull(windowingLayer);
final List<DisplayArea<?>> windowedMagnificationAreas =
mDisplayContent.mDisplayAreaPolicy.getDisplayAreas(FEATURE_WINDOWED_MAGNIFICATION);
if (windowedMagnificationAreas != null) {
assertEquals("There should be only one DisplayArea for FEATURE_WINDOWED_MAGNIFICATION",
1, windowedMagnificationAreas.size());
assertEquals(windowedMagnificationAreas.get(0).mSurfaceControl, windowingLayer);
} else {
assertNotEquals(mDisplayContent.mSurfaceControl, windowingLayer);
}
}
@Test
public void testFindScrollCaptureTargetWindow_behindWindow() {
DisplayContent display = createNewDisplay();
Task rootTask = createTask(display);
Task task = createTaskInRootTask(rootTask, 0 /* userId */);
WindowState activityWindow = createAppWindow(task, TYPE_APPLICATION, "App Window");
WindowState behindWindow = createWindow(null, TYPE_SCREENSHOT, display, "Screenshot");
WindowState result = display.findScrollCaptureTargetWindow(behindWindow,
ActivityTaskManager.INVALID_TASK_ID);
assertEquals(activityWindow, result);
}
@Test
public void testFindScrollCaptureTargetWindow_cantReceiveKeys() {
DisplayContent display = createNewDisplay();
Task rootTask = createTask(display);
Task task = createTaskInRootTask(rootTask, 0 /* userId */);
WindowState activityWindow = createAppWindow(task, TYPE_APPLICATION, "App Window");
WindowState invisible = createWindow(null, TYPE_APPLICATION, "invisible");
invisible.mViewVisibility = View.INVISIBLE; // make canReceiveKeys return false
WindowState result = display.findScrollCaptureTargetWindow(null,
ActivityTaskManager.INVALID_TASK_ID);
assertEquals(activityWindow, result);
}
@Test
public void testFindScrollCaptureTargetWindow_secure() {
DisplayContent display = createNewDisplay();
Task rootTask = createTask(display);
Task task = createTaskInRootTask(rootTask, 0 /* userId */);
WindowState secureWindow = createWindow(null, TYPE_APPLICATION, "Secure Window");
secureWindow.mAttrs.flags |= FLAG_SECURE;
WindowState result = display.findScrollCaptureTargetWindow(null,
ActivityTaskManager.INVALID_TASK_ID);
assertNull(result);
}
@Test
public void testFindScrollCaptureTargetWindow_secureTaskId() {
DisplayContent display = createNewDisplay();
Task rootTask = createTask(display);
Task task = createTaskInRootTask(rootTask, 0 /* userId */);
WindowState secureWindow = createWindow(null, TYPE_APPLICATION, "Secure Window");
secureWindow.mAttrs.flags |= FLAG_SECURE;
WindowState result = display.findScrollCaptureTargetWindow(null, task.mTaskId);
assertNull(result);
}
@Test
public void testFindScrollCaptureTargetWindow_taskId() {
DisplayContent display = createNewDisplay();
Task rootTask = createTask(display);
Task task = createTaskInRootTask(rootTask, 0 /* userId */);
WindowState window = createAppWindow(task, TYPE_APPLICATION, "App Window");
WindowState behindWindow = createWindow(null, TYPE_SCREENSHOT, display, "Screenshot");
WindowState result = display.findScrollCaptureTargetWindow(null, task.mTaskId);
assertEquals(window, result);
}
@Test
public void testFindScrollCaptureTargetWindow_taskIdCantReceiveKeys() {
DisplayContent display = createNewDisplay();
Task rootTask = createTask(display);
Task task = createTaskInRootTask(rootTask, 0 /* userId */);
WindowState window = createAppWindow(task, TYPE_APPLICATION, "App Window");
window.mViewVisibility = View.INVISIBLE; // make canReceiveKeys return false
WindowState behindWindow = createWindow(null, TYPE_SCREENSHOT, display, "Screenshot");
WindowState result = display.findScrollCaptureTargetWindow(null, task.mTaskId);
assertEquals(window, result);
}
@Test
public void testEnsureActivitiesVisibleNotRecursive() {
final TaskDisplayArea mockTda = mock(TaskDisplayArea.class);
final boolean[] called = { false };
doAnswer(invocation -> {
// The assertion will fail if DisplayArea#ensureActivitiesVisible is called twice.
assertFalse(called[0]);
called[0] = true;
mDisplayContent.ensureActivitiesVisible(null, 0, false, false);
return null;
}).when(mockTda).ensureActivitiesVisible(any(), anyInt(), anyBoolean(), anyBoolean());
mDisplayContent.ensureActivitiesVisible(null, 0, false, false);
}
@Test
public void testSetWindowingModeAtomicallyUpdatesWindoingModeAndDisplayWindowingMode() {
final DisplayContent dc = createNewDisplay();
final Task rootTask = new TaskBuilder(mSupervisor)
.setDisplay(dc)
.build();
doAnswer(invocation -> {
Object[] args = invocation.getArguments();
final Configuration config = ((Configuration) args[0]);
assertEquals(config.windowConfiguration.getWindowingMode(),
config.windowConfiguration.getDisplayWindowingMode());
return null;
}).when(rootTask).onConfigurationChanged(any());
dc.setWindowingMode(WINDOWING_MODE_FREEFORM);
dc.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
}
@Test
public void testForceDesktopMode() {
mWm.mForceDesktopModeOnExternalDisplays = true;
// Not applicable for default display
assertFalse(mDefaultDisplay.forceDesktopMode());
// Not applicable for private secondary display.
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.copyFrom(mDisplayInfo);
displayInfo.flags = FLAG_PRIVATE;
final DisplayContent privateDc = createNewDisplay(displayInfo);
assertFalse(privateDc.forceDesktopMode());
// Applicable for public secondary display.
final DisplayContent publicDc = createNewDisplay();
assertTrue(publicDc.forceDesktopMode());
// Make sure forceDesktopMode() is false when the force config is disabled.
mWm.mForceDesktopModeOnExternalDisplays = false;
assertFalse(publicDc.forceDesktopMode());
}
@Test
public void testDisplaySettingsReappliedWhenDisplayChanged() {
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.copyFrom(mDisplayInfo);
final DisplayContent dc = createNewDisplay(displayInfo);
// Generate width/height/density values different from the default of the display.
final int forcedWidth = dc.mBaseDisplayWidth + 1;
final int forcedHeight = dc.mBaseDisplayHeight + 1;;
final int forcedDensity = dc.mBaseDisplayDensity + 1;;
// Update the forced size and density in settings and the unique id to simualate a display
// remap.
dc.mWmService.mDisplayWindowSettings.setForcedSize(dc, forcedWidth, forcedHeight);
dc.mWmService.mDisplayWindowSettings.setForcedDensity(dc, forcedDensity, 0 /* userId */);
dc.mCurrentUniqueDisplayId = mDisplayInfo.uniqueId + "-test";
// Trigger display changed.
dc.onDisplayChanged();
// Ensure overridden size and denisty match the most up-to-date values in settings for the
// display.
verifySizes(dc, forcedWidth, forcedHeight, forcedDensity);
}
@UseTestDisplay(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
@Test
public void testComputeImeTarget_shouldNotCheckOutdatedImeTargetLayerWhenRemoved() {
final WindowState child1 = createWindow(mAppWindow, FIRST_SUB_WINDOW, "child1");
final WindowState nextImeTargetApp = createWindow(null /* parent */,
TYPE_BASE_APPLICATION, "nextImeTargetApp");
spyOn(child1);
doReturn(true).when(child1).inSplitScreenWindowingMode();
mDisplayContent.setImeLayeringTarget(child1);
spyOn(nextImeTargetApp);
spyOn(mAppWindow);
doReturn(true).when(nextImeTargetApp).canBeImeTarget();
doReturn(true).when(nextImeTargetApp).isActivityTypeHome();
doReturn(false).when(mAppWindow).canBeImeTarget();
child1.removeImmediately();
verify(mDisplayContent).computeImeTarget(true);
assertNull(mDisplayContent.getImeTarget(IME_TARGET_INPUT));
verify(child1, never()).needsRelativeLayeringToIme();
}
@UseTestDisplay(addWindows = {W_INPUT_METHOD}, addAllCommonWindows = true)
@Test
public void testAttachAndShowImeScreenshotOnTarget() {
// Preparation: Simulate screen state is on.
spyOn(mWm.mPolicy);
doReturn(true).when(mWm.mPolicy).isScreenOn();
// Preparation: Simulate snapshot IME surface.
spyOn(mWm.mTaskSnapshotController);
doReturn(mock(SurfaceControl.ScreenshotHardwareBuffer.class)).when(
mWm.mTaskSnapshotController).snapshotImeFromAttachedTask(any());
final SurfaceControl imeSurface = mock(SurfaceControl.class);
spyOn(imeSurface);
doReturn(true).when(imeSurface).isValid();
doReturn(imeSurface).when(mDisplayContent).createImeSurface(any(), any());
// Preparation: Simulate snapshot Task.
ActivityRecord act1 = createActivityRecord(mDisplayContent);
final WindowState appWin1 = createWindow(null, TYPE_BASE_APPLICATION, act1, "appWin1");
spyOn(appWin1);
spyOn(appWin1.mWinAnimator);
appWin1.setHasSurface(true);
assertTrue(appWin1.canBeImeTarget());
doReturn(true).when(appWin1.mWinAnimator).getShown();
doReturn(true).when(appWin1.mActivityRecord).isSurfaceShowing();
appWin1.mWinAnimator.mLastAlpha = 1f;
// Test step 1: appWin1 is the current IME target and soft-keyboard is visible.
mDisplayContent.computeImeTarget(true);
assertEquals(appWin1, mDisplayContent.getImeTarget(IME_TARGET_LAYERING));
spyOn(mDisplayContent.mInputMethodWindow);
doReturn(true).when(mDisplayContent.mInputMethodWindow).isVisible();
mDisplayContent.getInsetsStateController().getImeSourceProvider().setImeShowing(true);
// Test step 2: Simulate launching appWin2 and appWin1 is in app transition.
ActivityRecord act2 = createActivityRecord(mDisplayContent);
final WindowState appWin2 = createWindow(null, TYPE_BASE_APPLICATION, act2, "appWin2");
appWin2.setHasSurface(true);
assertTrue(appWin2.canBeImeTarget());
doReturn(true).when(appWin1).isAnimating(PARENTS | TRANSITION,
ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS);
// Test step 3: Verify appWin2 will be the next IME target and the IME snapshot surface will
// be shown at this time.
final Transaction t = mDisplayContent.getPendingTransaction();
spyOn(t);
mDisplayContent.setImeInputTarget(appWin2);
mDisplayContent.computeImeTarget(true);
assertEquals(appWin2, mDisplayContent.getImeTarget(IME_TARGET_LAYERING));
assertTrue(mDisplayContent.shouldImeAttachedToApp());
verify(mDisplayContent, atLeast(1)).attachAndShowImeScreenshotOnTarget();
verify(mWm.mTaskSnapshotController).snapshotImeFromAttachedTask(appWin1.getTask());
assertNotNull(mDisplayContent.mImeScreenshot);
verify(t).show(mDisplayContent.mImeScreenshot);
}
@UseTestDisplay(addWindows = {W_INPUT_METHOD}, addAllCommonWindows = true)
@Test
public void testShowImeScreenshot() {
final Task rootTask = createTask(mDisplayContent);
final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win");
task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE);
doReturn(true).when(task).okToAnimate();
ArrayList<WindowContainer> sources = new ArrayList<>();
sources.add(activity);
mDisplayContent.setImeLayeringTarget(win);
spyOn(mDisplayContent);
// Expecting the IME screenshot only be attached when performing task closing transition.
task.applyAnimation(null, TRANSIT_OLD_TASK_CLOSE, false /* enter */,
false /* isVoiceInteraction */, sources);
verify(mDisplayContent).showImeScreenshot();
clearInvocations(mDisplayContent);
activity.applyAnimation(null, TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE, false /* enter */,
false /* isVoiceInteraction */, sources);
verify(mDisplayContent, never()).showImeScreenshot();
}
@Test
public void testRotateBounds_keepSamePhysicalPosition() {
final DisplayContent dc =
new TestDisplayContent.Builder(mAtm, 1000, 2000).build();
final Rect initBounds = new Rect(0, 0, 700, 1500);
final Rect rotateBounds = new Rect(initBounds);
// Rotate from 0 to 0
dc.rotateBounds(ROTATION_0, ROTATION_0, rotateBounds);
assertEquals(new Rect(0, 0, 700, 1500), rotateBounds);
// Rotate from 0 to 90
rotateBounds.set(initBounds);
dc.rotateBounds(ROTATION_0, ROTATION_90, rotateBounds);
assertEquals(new Rect(0, 300, 1500, 1000), rotateBounds);
// Rotate from 0 to 180
rotateBounds.set(initBounds);
dc.rotateBounds(ROTATION_0, ROTATION_180, rotateBounds);
assertEquals(new Rect(300, 500, 1000, 2000), rotateBounds);
// Rotate from 0 to 270
rotateBounds.set(initBounds);
dc.rotateBounds(ROTATION_0, ROTATION_270, rotateBounds);
assertEquals(new Rect(500, 0, 2000, 700), rotateBounds);
}
/**
* Creates a TestDisplayContent using the constructor that takes in display width and height as
* parameters and validates that the newly-created TestDisplayContent's DisplayInfo and
* WindowConfiguration match the parameters passed into the constructor. Additionally, this test
* checks that device-specific overrides are not applied.
*/
@Test
public void testCreateTestDisplayContentFromDimensions() {
final int displayWidth = 1000;
final int displayHeight = 2000;
final int windowingMode = WINDOWING_MODE_FULLSCREEN;
final boolean ignoreOrientationRequests = false;
final float fixedOrientationLetterboxRatio = 0;
final DisplayContent testDisplayContent = new TestDisplayContent.Builder(mAtm, displayWidth,
displayHeight).build();
// test display info
final DisplayInfo di = testDisplayContent.getDisplayInfo();
assertEquals(displayWidth, di.logicalWidth);
assertEquals(displayHeight, di.logicalHeight);
assertEquals(TestDisplayContent.DEFAULT_LOGICAL_DISPLAY_DENSITY, di.logicalDensityDpi);
// test configuration
final WindowConfiguration windowConfig = testDisplayContent.getConfiguration()
.windowConfiguration;
assertEquals(displayWidth, windowConfig.getBounds().width());
assertEquals(displayHeight, windowConfig.getBounds().height());
assertEquals(windowingMode, windowConfig.getWindowingMode());
// test misc display overrides
assertEquals(ignoreOrientationRequests, testDisplayContent.mIgnoreOrientationRequest);
assertEquals(fixedOrientationLetterboxRatio,
mWm.mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio(),
0 /* delta */);
}
/**
* Creates a TestDisplayContent using the constructor that takes in a DisplayInfo as a parameter
* and validates that the newly-created TestDisplayContent's DisplayInfo and WindowConfiguration
* match the width, height, and density values set in the DisplayInfo passed as a parameter.
* Additionally, this test checks that device-specific overrides are not applied.
*/
@Test
public void testCreateTestDisplayContentFromDisplayInfo() {
final int displayWidth = 1000;
final int displayHeight = 2000;
final int windowingMode = WINDOWING_MODE_FULLSCREEN;
final boolean ignoreOrientationRequests = false;
final float fixedOrientationLetterboxRatio = 0;
final DisplayInfo testDisplayInfo = new DisplayInfo();
mContext.getDisplay().getDisplayInfo(testDisplayInfo);
testDisplayInfo.logicalWidth = displayWidth;
testDisplayInfo.logicalHeight = displayHeight;
testDisplayInfo.logicalDensityDpi = TestDisplayContent.DEFAULT_LOGICAL_DISPLAY_DENSITY;
final DisplayContent testDisplayContent = new TestDisplayContent.Builder(mAtm,
testDisplayInfo).build();
// test display info
final DisplayInfo di = testDisplayContent.getDisplayInfo();
assertEquals(displayWidth, di.logicalWidth);
assertEquals(displayHeight, di.logicalHeight);
assertEquals(TestDisplayContent.DEFAULT_LOGICAL_DISPLAY_DENSITY, di.logicalDensityDpi);
// test configuration
final WindowConfiguration windowConfig = testDisplayContent.getConfiguration()
.windowConfiguration;
assertEquals(displayWidth, windowConfig.getBounds().width());
assertEquals(displayHeight, windowConfig.getBounds().height());
assertEquals(windowingMode, windowConfig.getWindowingMode());
// test misc display overrides
assertEquals(ignoreOrientationRequests, testDisplayContent.mIgnoreOrientationRequest);
assertEquals(fixedOrientationLetterboxRatio,
mWm.mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio(),
0 /* delta */);
}
/**
* Verifies {@link DisplayContent#remove} should not resume home root task on the removing
* display.
*/
@Test
public void testNotResumeHomeRootTaskOnRemovingDisplay() {
// Create a display which supports system decoration and allows reparenting root tasks to
// another display when the display is removed.
final DisplayContent display = new TestDisplayContent.Builder(
mAtm, 1000, 1500).setSystemDecorations(true).build();
doReturn(false).when(display).shouldDestroyContentOnRemove();
// Put home root task on the display.
final Task homeRootTask = new TaskBuilder(mSupervisor)
.setDisplay(display).setActivityType(ACTIVITY_TYPE_HOME).build();
// Put a finishing standard activity which will be reparented.
final Task rootTask = createTaskWithActivity(display.getDefaultTaskDisplayArea(),
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP, true /* twoLevelTask */);
rootTask.topRunningActivity().makeFinishingLocked();
clearInvocations(homeRootTask);
display.remove();
// The removed display should have no focused root task and its home root task should never
// resume.
assertNull(display.getFocusedRootTask());
verify(homeRootTask, never()).resumeTopActivityUncheckedLocked(any(), any());
}
/**
* Verifies the correct activity is returned when querying the top running activity.
*/
@Test
public void testTopRunningActivity() {
final DisplayContent display = mRootWindowContainer.getDefaultDisplay();
final KeyguardController keyguard = mSupervisor.getKeyguardController();
final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
final ActivityRecord activity = rootTask.getTopNonFinishingActivity();
// Create empty root task on top.
final Task emptyRootTask = new TaskBuilder(mSupervisor).build();
// Make sure the top running activity is not affected when keyguard is not locked.
assertTopRunningActivity(activity, display);
// Check to make sure activity not reported when it cannot show on lock and lock is on.
doReturn(true).when(keyguard).isKeyguardLocked();
assertEquals(activity, display.topRunningActivity());
assertNull(display.topRunningActivity(true /* considerKeyguardState */));
// Move root task with activity to top.
rootTask.moveToFront("testRootTaskToFront");
assertEquals(rootTask, display.getFocusedRootTask());
assertEquals(activity, display.topRunningActivity());
assertNull(display.topRunningActivity(true /* considerKeyguardState */));
// Add activity that should be shown on the keyguard.
final ActivityRecord showWhenLockedActivity = new ActivityBuilder(mAtm)
.setTask(rootTask)
.setActivityFlags(FLAG_SHOW_WHEN_LOCKED)
.build();
// Ensure the show when locked activity is returned.
assertTopRunningActivity(showWhenLockedActivity, display);
// Move empty root task to front. The running activity in focusable root task which below
// the empty root task should be returned.
emptyRootTask.moveToFront("emptyRootTaskToFront");
assertEquals(rootTask, display.getFocusedRootTask());
assertTopRunningActivity(showWhenLockedActivity, display);
}
private static void assertTopRunningActivity(ActivityRecord top, DisplayContent display) {
assertEquals(top, display.topRunningActivity());
assertEquals(top, display.topRunningActivity(true /* considerKeyguardState */));
}
@Test
public void testKeyguardGoingAwayWhileAodShown() {
mDisplayContent.getDisplayPolicy().setAwake(true);
final WindowState appWin = createWindow(null, TYPE_APPLICATION, mDisplayContent, "appWin");
final ActivityRecord activity = appWin.mActivityRecord;
mAtm.mKeyguardController.setKeyguardShown(true /* keyguardShowing */,
true /* aodShowing */);
assertFalse(activity.isVisibleRequested());
mAtm.mKeyguardController.keyguardGoingAway(0 /* flags */);
assertTrue(activity.isVisibleRequested());
}
@Test
public void testRemoveRootTaskInWindowingModes() {
removeRootTaskTests(() -> mRootWindowContainer.removeRootTasksInWindowingModes(
WINDOWING_MODE_FULLSCREEN));
}
@Test
public void testRemoveRootTaskWithActivityTypes() {
removeRootTaskTests(() -> mRootWindowContainer.removeRootTasksWithActivityTypes(
ACTIVITY_TYPE_STANDARD));
}
@UseTestDisplay(addWindows = W_INPUT_METHOD)
@Test
public void testImeChildWindowFocusWhenImeLayeringTargetChanges() {
final WindowState imeChildWindow =
createWindow(mImeWindow, TYPE_APPLICATION_ATTACHED_DIALOG, "imeChildWindow");
makeWindowVisibleAndDrawn(imeChildWindow, mImeWindow);
assertTrue(imeChildWindow.canReceiveKeys());
mDisplayContent.setInputMethodWindowLocked(mImeWindow);
// Verify imeChildWindow can be focused window if the next IME target requests IME visible.
final WindowState imeAppTarget =
createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget");
mDisplayContent.setImeLayeringTarget(imeAppTarget);
spyOn(imeAppTarget);
doReturn(true).when(imeAppTarget).getRequestedVisibility(ITYPE_IME);
assertEquals(imeChildWindow, mDisplayContent.findFocusedWindow());
// Verify imeChildWindow doesn't be focused window if the next IME target does not
// request IME visible.
final WindowState nextImeAppTarget =
createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "nextImeAppTarget");
mDisplayContent.setImeLayeringTarget(nextImeAppTarget);
assertNotEquals(imeChildWindow, mDisplayContent.findFocusedWindow());
}
@UseTestDisplay(addWindows = W_INPUT_METHOD)
@Test
public void testImeMenuDialogFocusWhenImeLayeringTargetChanges() {
final WindowState imeMenuDialog =
createWindow(mImeWindow, TYPE_INPUT_METHOD_DIALOG, "imeMenuDialog");
makeWindowVisibleAndDrawn(imeMenuDialog, mImeWindow);
assertTrue(imeMenuDialog.canReceiveKeys());
mDisplayContent.setInputMethodWindowLocked(mImeWindow);
// Verify imeMenuDialog can be focused window if the next IME target requests IME visible.
final WindowState imeAppTarget =
createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget");
mDisplayContent.setImeLayeringTarget(imeAppTarget);
spyOn(imeAppTarget);
doReturn(true).when(imeAppTarget).getRequestedVisibility(ITYPE_IME);
assertEquals(imeMenuDialog, mDisplayContent.findFocusedWindow());
// Verify imeMenuDialog doesn't be focused window if the next IME target does not
// request IME visible.
final WindowState nextImeAppTarget =
createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "nextImeAppTarget");
spyOn(nextImeAppTarget);
doReturn(true).when(nextImeAppTarget).isAnimating(PARENTS | TRANSITION,
ANIMATION_TYPE_APP_TRANSITION);
mDisplayContent.setImeLayeringTarget(nextImeAppTarget);
assertNotEquals(imeMenuDialog, mDisplayContent.findFocusedWindow());
}
@Test
public void testVirtualDisplayContent() {
MockitoSession mockSession = mockitoSession()
.initMocks(this)
.spyStatic(SurfaceControl.class)
.strictness(Strictness.LENIENT)
.startMocking();
// GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
// mirror.
final IBinder tokenToMirror = setUpDefaultTaskDisplayAreaWindowToken();
// GIVEN SurfaceControl can successfully mirror the provided surface.
Point surfaceSize = new Point(
mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
surfaceControlMirrors(surfaceSize);
// WHEN creating the DisplayContent for a new virtual display.
final DisplayContent virtualDisplay = new TestDisplayContent.Builder(mAtm,
mDisplayInfo).build();
// THEN mirroring is initiated for the default display's DisplayArea.
assertThat(virtualDisplay.mTokenToMirror).isEqualTo(tokenToMirror);
mockSession.finishMocking();
}
@Test
public void testVirtualDisplayContent_capturedAreaResized() {
MockitoSession mockSession = mockitoSession()
.initMocks(this)
.spyStatic(SurfaceControl.class)
.strictness(Strictness.LENIENT)
.startMocking();
// GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
// mirror.
final IBinder tokenToMirror = setUpDefaultTaskDisplayAreaWindowToken();
// GIVEN SurfaceControl can successfully mirror the provided surface.
Point surfaceSize = new Point(
mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
SurfaceControl mirroredSurface = surfaceControlMirrors(surfaceSize);
// WHEN creating the DisplayContent for a new virtual display.
final DisplayContent virtualDisplay = new TestDisplayContent.Builder(mAtm,
mDisplayInfo).build();
// THEN mirroring is initiated for the default display's DisplayArea.
assertThat(virtualDisplay.mTokenToMirror).isEqualTo(tokenToMirror);
float xScale = 0.7f;
float yScale = 2f;
Rect displayAreaBounds = new Rect(0, 0, Math.round(surfaceSize.x * xScale),
Math.round(surfaceSize.y * yScale));
virtualDisplay.updateMirroredSurface(mTransaction, displayAreaBounds, surfaceSize);
// THEN content in the captured DisplayArea is scaled to fit the surface size.
verify(mTransaction, atLeastOnce()).setMatrix(mirroredSurface, 1.0f / yScale, 0, 0,
1.0f / yScale);
// THEN captured content is positioned in the centre of the output surface.
float scaledWidth = displayAreaBounds.width() / xScale;
float xInset = (surfaceSize.x - scaledWidth) / 2;
verify(mTransaction, atLeastOnce()).setPosition(mirroredSurface, xInset, 0);
mockSession.finishMocking();
}
@Test
public void testVirtualDisplayContent_withoutSurface() {
MockitoSession mockSession = mockitoSession()
.initMocks(this)
.spyStatic(SurfaceControl.class)
.strictness(Strictness.LENIENT)
.startMocking();
// GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
// mirror.
setUpDefaultTaskDisplayAreaWindowToken();
// GIVEN SurfaceControl does not mirror a null surface.
Point surfaceSize = new Point(
mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
// GIVEN a new VirtualDisplay with an associated surface.
final VirtualDisplay display = createVirtualDisplay(surfaceSize, null /* surface */);
final int displayId = display.getDisplay().getDisplayId();
mWm.mRoot.onDisplayAdded(displayId);
// WHEN getting the DisplayContent for the new virtual display.
DisplayContent actualDC = mWm.mRoot.getDisplayContent(displayId);
// THEN mirroring is not started, since a null surface indicates the VirtualDisplay is off.
assertThat(actualDC.mTokenToMirror).isNull();
display.release();
mockSession.finishMocking();
}
@Test
public void testVirtualDisplayContent_withSurface() {
MockitoSession mockSession = mockitoSession()
.initMocks(this)
.spyStatic(SurfaceControl.class)
.strictness(Strictness.LENIENT)
.startMocking();
// GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
// mirror.
final IBinder tokenToMirror = setUpDefaultTaskDisplayAreaWindowToken();
// GIVEN SurfaceControl can successfully mirror the provided surface.
Point surfaceSize = new Point(
mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
surfaceControlMirrors(surfaceSize);
// GIVEN a new VirtualDisplay with an associated surface.
final VirtualDisplay display = createVirtualDisplay(surfaceSize, new Surface());
final int displayId = display.getDisplay().getDisplayId();
mWm.mRoot.onDisplayAdded(displayId);
// WHEN getting the DisplayContent for the new virtual display.
DisplayContent actualDC = mWm.mRoot.getDisplayContent(displayId);
// THEN mirroring is initiated for the default display's DisplayArea.
assertThat(actualDC.mTokenToMirror).isEqualTo(tokenToMirror);
display.release();
mockSession.finishMocking();
}
private class TestToken extends Binder {
}
/**
* Creates a WindowToken associated with the default task DisplayArea, in order for that
* DisplayArea to be mirrored.
*/
private IBinder setUpDefaultTaskDisplayAreaWindowToken() {
// GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
// mirror.
final IBinder tokenToMirror = new TestToken();
doReturn(tokenToMirror).when(mWm.mDisplayManagerInternal).getWindowTokenClientToMirror(
anyInt());
// GIVEN the default task display area is represented by the WindowToken.
spyOn(mWm.mWindowContextListenerController);
doReturn(mDefaultDisplay.getDefaultTaskDisplayArea()).when(
mWm.mWindowContextListenerController).getContainer(any());
return tokenToMirror;
}
/**
* SurfaceControl successfully creates a mirrored surface of the given size.
*/
private SurfaceControl surfaceControlMirrors(Point surfaceSize) {
// Do not set the parent, since the mirrored surface is the root of a new surface hierarchy.
SurfaceControl mirroredSurface = new SurfaceControl.Builder()
.setName("mirroredSurface")
.setBufferSize(surfaceSize.x, surfaceSize.y)
.setCallsite("mirrorSurface")
.build();
doReturn(mirroredSurface).when(() -> SurfaceControl.mirrorSurface(any()));
doReturn(surfaceSize).when(mWm.mDisplayManagerInternal).getDisplaySurfaceDefaultSize(
anyInt());
return mirroredSurface;
}
private VirtualDisplay createVirtualDisplay(Point size, Surface surface) {
return mWm.mDisplayManager.createVirtualDisplay("VirtualDisplay", size.x, size.y,
DisplayMetrics.DENSITY_140, surface, VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR);
}
private void removeRootTaskTests(Runnable runnable) {
final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
final Task rootTask1 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_STANDARD, ON_TOP);
final Task rootTask2 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_STANDARD, ON_TOP);
final Task rootTask3 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_STANDARD, ON_TOP);
final Task rootTask4 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_STANDARD, ON_TOP);
final Task task1 = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask1).build();
final Task task2 = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask2).build();
final Task task3 = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask3).build();
final Task task4 = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask4).build();
// Reordering root tasks while removing root tasks.
doAnswer(invocation -> {
taskDisplayArea.positionChildAt(POSITION_TOP, rootTask3, false /*includingParents*/);
return true;
}).when(mSupervisor).removeTask(eq(task4), anyBoolean(), anyBoolean(), any());
// Removing root tasks from the display while removing root tasks.
doAnswer(invocation -> {
taskDisplayArea.removeRootTask(rootTask2);
return true;
}).when(mSupervisor).removeTask(eq(task2), anyBoolean(), anyBoolean(), any());
runnable.run();
verify(mSupervisor).removeTask(eq(task4), anyBoolean(), anyBoolean(), any());
verify(mSupervisor).removeTask(eq(task3), anyBoolean(), anyBoolean(), any());
verify(mSupervisor).removeTask(eq(task2), anyBoolean(), anyBoolean(), any());
verify(mSupervisor).removeTask(eq(task1), anyBoolean(), anyBoolean(), any());
}
private boolean isOptionsPanelAtRight(int displayId) {
return (mWm.getPreferredOptionsPanelGravity(displayId) & Gravity.RIGHT) == Gravity.RIGHT;
}
private static void verifySizes(DisplayContent displayContent, int expectedBaseWidth,
int expectedBaseHeight, int expectedBaseDensity) {
assertEquals(expectedBaseWidth, displayContent.mBaseDisplayWidth);
assertEquals(expectedBaseHeight, displayContent.mBaseDisplayHeight);
assertEquals(expectedBaseDensity, displayContent.mBaseDisplayDensity);
}
private void updateFocusedWindow() {
mWm.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, false /* updateInputWindows */);
}
static void performLayout(DisplayContent dc) {
dc.setLayoutNeeded();
dc.performLayout(true /* initial */, false /* updateImeWindows */);
}
/**
* Create DisplayContent that does not update display base/initial values from device to keep
* the values set by test.
*/
private DisplayContent createDisplayNoUpdateDisplayInfo() {
final DisplayContent displayContent = createNewDisplay();
doNothing().when(displayContent).updateDisplayInfo();
return displayContent;
}
private void assertForAllWindowsOrder(List<WindowState> expectedWindowsBottomToTop) {
final LinkedList<WindowState> actualWindows = new LinkedList<>();
// Test forward traversal.
mDisplayContent.forAllWindows(actualWindows::addLast, false /* traverseTopToBottom */);
assertThat("bottomToTop", actualWindows, is(expectedWindowsBottomToTop));
actualWindows.clear();
// Test backward traversal.
mDisplayContent.forAllWindows(actualWindows::addLast, true /* traverseTopToBottom */);
assertThat("topToBottom", actualWindows, is(reverseList(expectedWindowsBottomToTop)));
}
static int getRotatedOrientation(DisplayContent dc) {
return dc.mBaseDisplayWidth > dc.mBaseDisplayHeight
? SCREEN_ORIENTATION_PORTRAIT
: SCREEN_ORIENTATION_LANDSCAPE;
}
private static List<WindowState> reverseList(List<WindowState> list) {
final ArrayList<WindowState> result = new ArrayList<>(list);
Collections.reverse(result);
return result;
}
private void tapOnDisplay(final DisplayContent dc) {
final DisplayMetrics dm = dc.getDisplayMetrics();
final float x = dm.widthPixels / 2;
final float y = dm.heightPixels / 2;
final long downTime = SystemClock.uptimeMillis();
final long eventTime = SystemClock.uptimeMillis() + 100;
// sending ACTION_DOWN
final MotionEvent downEvent = MotionEvent.obtain(
downTime,
downTime,
MotionEvent.ACTION_DOWN,
x,
y,
0 /*metaState*/);
downEvent.setDisplayId(dc.getDisplayId());
dc.mTapDetector.onPointerEvent(downEvent);
// sending ACTION_UP
final MotionEvent upEvent = MotionEvent.obtain(
downTime,
eventTime,
MotionEvent.ACTION_UP,
x,
y,
0 /*metaState*/);
upEvent.setDisplayId(dc.getDisplayId());
dc.mTapDetector.onPointerEvent(upEvent);
}
}