| /* |
| * 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 android.app.ActivityManager.TaskDescription; |
| import android.content.res.Configuration; |
| import android.graphics.Rect; |
| import android.hardware.display.DisplayManagerGlobal; |
| import android.os.Binder; |
| import android.view.Display; |
| import android.view.DisplayInfo; |
| import android.view.IApplicationToken; |
| import org.junit.Assert; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.mockito.MockitoAnnotations; |
| |
| import android.app.ActivityManager.TaskSnapshot; |
| import android.content.Context; |
| import android.os.IBinder; |
| import android.support.test.InstrumentationRegistry; |
| import android.view.IWindow; |
| import android.view.WindowManager; |
| |
| import static android.app.ActivityManager.StackId.FIRST_DYNAMIC_STACK_ID; |
| import static android.app.AppOpsManager.OP_NONE; |
| import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; |
| import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; |
| import static android.content.res.Configuration.EMPTY; |
| import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; |
| import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; |
| import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; |
| import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; |
| import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY; |
| import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; |
| import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; |
| import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; |
| 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_STATUS_BAR; |
| import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; |
| import static com.android.server.wm.WindowContainer.POSITION_TOP; |
| import static org.mockito.Mockito.mock; |
| |
| import com.android.server.AttributeCache; |
| |
| import java.util.HashSet; |
| |
| /** |
| * Common base class for window manager unit test classes. |
| */ |
| class WindowTestsBase { |
| static WindowManagerService sWm = null; |
| static TestWindowManagerPolicy sPolicy = null; |
| private final static IWindow sIWindow = new TestIWindow(); |
| private final static Session sMockSession = mock(Session.class); |
| private static int sNextDisplayId = Display.DEFAULT_DISPLAY + 1; |
| static int sNextStackId = FIRST_DYNAMIC_STACK_ID; |
| private static int sNextTaskId = 0; |
| |
| private static boolean sOneTimeSetupDone = false; |
| static DisplayContent sDisplayContent; |
| static DisplayInfo sDisplayInfo = new DisplayInfo(); |
| static WindowLayersController sLayersController; |
| static WindowState sWallpaperWindow; |
| static WindowState sImeWindow; |
| static WindowState sImeDialogWindow; |
| static WindowState sStatusBarWindow; |
| static WindowState sDockedDividerWindow; |
| static WindowState sNavBarWindow; |
| static WindowState sAppWindow; |
| static WindowState sChildAppWindowAbove; |
| static WindowState sChildAppWindowBelow; |
| static HashSet<WindowState> sCommonWindows; |
| |
| @Before |
| public void setUp() throws Exception { |
| if (sOneTimeSetupDone) { |
| return; |
| } |
| sOneTimeSetupDone = true; |
| MockitoAnnotations.initMocks(this); |
| final Context context = InstrumentationRegistry.getTargetContext(); |
| AttributeCache.init(context); |
| sWm = TestWindowManagerPolicy.getWindowManagerService(context); |
| sPolicy = (TestWindowManagerPolicy) sWm.mPolicy; |
| sLayersController = new WindowLayersController(sWm); |
| sDisplayContent = sWm.mRoot.getDisplayContent(context.getDisplay().getDisplayId()); |
| if (sDisplayContent != null) { |
| sDisplayContent.removeImmediately(); |
| } |
| // Make sure that display ids don't overlap, so there won't be several displays with same |
| // ids among RootWindowContainer children. |
| for (DisplayContent dc : sWm.mRoot.mChildren) { |
| if (dc.getDisplayId() >= sNextDisplayId) { |
| sNextDisplayId = dc.getDisplayId() + 1; |
| } |
| } |
| context.getDisplay().getDisplayInfo(sDisplayInfo); |
| sDisplayContent = createNewDisplay(); |
| sWm.mDisplayEnabled = true; |
| sWm.mDisplayReady = true; |
| |
| // Set-up some common windows. |
| sCommonWindows = new HashSet(); |
| sWallpaperWindow = createCommonWindow(null, TYPE_WALLPAPER, "wallpaperWindow"); |
| sImeWindow = createCommonWindow(null, TYPE_INPUT_METHOD, "sImeWindow"); |
| sImeDialogWindow = createCommonWindow(null, TYPE_INPUT_METHOD_DIALOG, "sImeDialogWindow"); |
| sStatusBarWindow = createCommonWindow(null, TYPE_STATUS_BAR, "sStatusBarWindow"); |
| sNavBarWindow = createCommonWindow(null, TYPE_NAVIGATION_BAR, "sNavBarWindow"); |
| sDockedDividerWindow = createCommonWindow(null, TYPE_DOCK_DIVIDER, "sDockedDividerWindow"); |
| sAppWindow = createCommonWindow(null, TYPE_BASE_APPLICATION, "sAppWindow"); |
| sChildAppWindowAbove = createCommonWindow(sAppWindow, TYPE_APPLICATION_ATTACHED_DIALOG, |
| "sChildAppWindowAbove"); |
| sChildAppWindowBelow = createCommonWindow(sAppWindow, TYPE_APPLICATION_MEDIA_OVERLAY, |
| "sChildAppWindowBelow"); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| sWm.mRoot.forAllWindows(w -> { |
| if (!sCommonWindows.contains(w)) { |
| w.removeImmediately(); |
| } |
| }, true /* traverseTopToBottom */); |
| } |
| |
| private static WindowState createCommonWindow(WindowState parent, int type, String name) { |
| final WindowState win = createWindow(parent, type, name); |
| sCommonWindows.add(win); |
| return win; |
| } |
| |
| /** Asserts that the first entry is greater than the second entry. */ |
| void assertGreaterThan(int first, int second) throws Exception { |
| Assert.assertTrue("Excepted " + first + " to be greater than " + second, first > second); |
| } |
| |
| /** |
| * Waits until the main handler for WM has processed all messages. |
| */ |
| void waitUntilHandlerIdle() { |
| sWm.mH.runWithScissors(() -> { }, 0); |
| } |
| |
| private static WindowToken createWindowToken(DisplayContent dc, int type) { |
| if (type < FIRST_APPLICATION_WINDOW || type > LAST_APPLICATION_WINDOW) { |
| return new TestWindowToken(type, dc); |
| } |
| |
| final TaskStack stack = createTaskStackOnDisplay(dc); |
| final Task task = createTaskInStack(stack, 0 /* userId */); |
| final TestAppWindowToken token = new TestAppWindowToken(dc); |
| task.addChild(token, 0); |
| return token; |
| } |
| |
| static WindowState createWindow(WindowState parent, int type, String name) { |
| return (parent == null) |
| ? createWindow(parent, type, sDisplayContent, name) |
| : createWindow(parent, type, parent.mToken, name); |
| } |
| |
| WindowState createAppWindow(Task task, int type, String name) { |
| final AppWindowToken token = new TestAppWindowToken(sDisplayContent); |
| task.addChild(token, 0); |
| return createWindow(null, type, token, name); |
| } |
| |
| static WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name) { |
| final WindowToken token = createWindowToken(dc, type); |
| return createWindow(parent, type, token, name); |
| } |
| |
| static WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name, |
| boolean ownerCanAddInternalSystemWindow) { |
| final WindowToken token = createWindowToken(dc, type); |
| return createWindow(parent, type, token, name, ownerCanAddInternalSystemWindow); |
| } |
| |
| static WindowState createWindow(WindowState parent, int type, WindowToken token, String name) { |
| return createWindow(parent, type, token, name, false /* ownerCanAddInternalSystemWindow */); |
| } |
| |
| static WindowState createWindow(WindowState parent, int type, WindowToken token, String name, |
| boolean ownerCanAddInternalSystemWindow) { |
| final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type); |
| attrs.setTitle(name); |
| |
| final WindowState w = new WindowState(sWm, sMockSession, sIWindow, token, parent, OP_NONE, |
| 0, attrs, 0, 0, ownerCanAddInternalSystemWindow); |
| // TODO: Probably better to make this call in the WindowState ctor to avoid errors with |
| // adding it to the token... |
| token.addWindow(w); |
| return w; |
| } |
| |
| /** Creates a {@link TaskStack} and adds it to the specified {@link DisplayContent}. */ |
| static TaskStack createTaskStackOnDisplay(DisplayContent dc) { |
| return createStackControllerOnDisplay(dc).mContainer; |
| } |
| |
| static StackWindowController createStackControllerOnDisplay(DisplayContent dc) { |
| final int stackId = ++sNextStackId; |
| return new StackWindowController(stackId, null, dc.getDisplayId(), |
| true /* onTop */, new Rect(), sWm); |
| } |
| |
| /** Creates a {@link Task} and adds it to the specified {@link TaskStack}. */ |
| static Task createTaskInStack(TaskStack stack, int userId) { |
| final Task newTask = new Task(sNextTaskId++, stack, userId, sWm, null, EMPTY, 0, false, |
| false, new TaskDescription(), null); |
| stack.addTask(newTask, POSITION_TOP); |
| return newTask; |
| } |
| |
| /** Creates a {@link DisplayContent} and adds it to the system. */ |
| DisplayContent createNewDisplay() { |
| final int displayId = sNextDisplayId++; |
| final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId, |
| sDisplayInfo, DEFAULT_DISPLAY_ADJUSTMENTS); |
| return new DisplayContent(display, sWm, sLayersController, new WallpaperController(sWm)); |
| } |
| |
| /* Used so we can gain access to some protected members of the {@link WindowToken} class */ |
| static class TestWindowToken extends WindowToken { |
| |
| TestWindowToken(int type, DisplayContent dc) { |
| this(type, dc, false /* persistOnEmpty */); |
| } |
| |
| TestWindowToken(int type, DisplayContent dc, boolean persistOnEmpty) { |
| super(sWm, mock(IBinder.class), type, persistOnEmpty, dc, |
| false /* ownerCanManageAppTokens */); |
| } |
| |
| int getWindowsCount() { |
| return mChildren.size(); |
| } |
| |
| boolean hasWindow(WindowState w) { |
| return mChildren.contains(w); |
| } |
| } |
| |
| /** Used so we can gain access to some protected members of the {@link AppWindowToken} class. */ |
| static class TestAppWindowToken extends AppWindowToken { |
| |
| TestAppWindowToken(DisplayContent dc) { |
| super(sWm, null, false, dc, true /* fillsParent */); |
| } |
| |
| TestAppWindowToken(WindowManagerService service, IApplicationToken token, |
| boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos, |
| boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation, |
| int rotationAnimationHint, int configChanges, boolean launchTaskBehind, |
| boolean alwaysFocusable, AppWindowContainerController controller) { |
| super(service, token, voiceInteraction, dc, inputDispatchingTimeoutNanos, fullscreen, |
| showForAllUsers, targetSdk, orientation, rotationAnimationHint, configChanges, |
| launchTaskBehind, alwaysFocusable, controller); |
| } |
| |
| int getWindowsCount() { |
| return mChildren.size(); |
| } |
| |
| boolean hasWindow(WindowState w) { |
| return mChildren.contains(w); |
| } |
| |
| WindowState getFirstChild() { |
| return mChildren.getFirst(); |
| } |
| |
| WindowState getLastChild() { |
| return mChildren.getLast(); |
| } |
| |
| int positionInParent() { |
| return getParent().mChildren.indexOf(this); |
| } |
| } |
| |
| /* Used so we can gain access to some protected members of the {@link Task} class */ |
| class TestTask extends Task { |
| |
| boolean mShouldDeferRemoval = false; |
| boolean mOnDisplayChangedCalled = false; |
| private boolean mUseLocalIsAnimating = false; |
| private boolean mIsAnimating = false; |
| |
| TestTask(int taskId, TaskStack stack, int userId, WindowManagerService service, Rect bounds, |
| Configuration overrideConfig, int resizeMode, boolean supportsPictureInPicture, |
| boolean homeTask, TaskWindowContainerController controller) { |
| super(taskId, stack, userId, service, bounds, overrideConfig, resizeMode, |
| supportsPictureInPicture, homeTask, new TaskDescription(), controller); |
| } |
| |
| boolean shouldDeferRemoval() { |
| return mShouldDeferRemoval; |
| } |
| |
| int positionInParent() { |
| return getParent().mChildren.indexOf(this); |
| } |
| |
| @Override |
| void onDisplayChanged(DisplayContent dc) { |
| super.onDisplayChanged(dc); |
| mOnDisplayChangedCalled = true; |
| } |
| |
| @Override |
| boolean isAnimating() { |
| return mUseLocalIsAnimating ? mIsAnimating : super.isAnimating(); |
| } |
| |
| void setLocalIsAnimating(boolean isAnimating) { |
| mUseLocalIsAnimating = true; |
| mIsAnimating = isAnimating; |
| } |
| } |
| |
| /** |
| * Used so we can gain access to some protected members of {@link TaskWindowContainerController} |
| * class. |
| */ |
| class TestTaskWindowContainerController extends TaskWindowContainerController { |
| |
| TestTaskWindowContainerController() { |
| this(createStackControllerOnDisplay(sDisplayContent)); |
| } |
| |
| TestTaskWindowContainerController(StackWindowController stackController) { |
| super(sNextTaskId++, new TaskWindowContainerListener() { |
| @Override |
| public void onSnapshotChanged(TaskSnapshot snapshot) { |
| |
| } |
| |
| @Override |
| public void requestResize(Rect bounds, int resizeMode) { |
| |
| } |
| }, stackController, 0 /* userId */, null /* bounds */, |
| EMPTY /* overrideConfig*/, RESIZE_MODE_UNRESIZEABLE, |
| false /* supportsPictureInPicture */, false /* homeTask*/, true /* toTop*/, |
| true /* showForAllUsers */, new TaskDescription(), sWm); |
| } |
| |
| @Override |
| TestTask createTask(int taskId, TaskStack stack, int userId, Rect bounds, |
| Configuration overrideConfig, int resizeMode, boolean supportsPictureInPicture, |
| boolean homeTask, TaskDescription taskDescription) { |
| return new TestTask(taskId, stack, userId, mService, bounds, overrideConfig, resizeMode, |
| supportsPictureInPicture, homeTask, this); |
| } |
| } |
| |
| class TestAppWindowContainerController extends AppWindowContainerController { |
| |
| final IApplicationToken mToken; |
| |
| TestAppWindowContainerController(TestTaskWindowContainerController taskController) { |
| this(taskController, new TestIApplicationToken()); |
| } |
| |
| TestAppWindowContainerController(TestTaskWindowContainerController taskController, |
| IApplicationToken token) { |
| super(taskController, token, null /* listener */, 0 /* index */, |
| SCREEN_ORIENTATION_UNSPECIFIED, true /* fullscreen */, |
| true /* showForAllUsers */, 0 /* configChanges */, false /* voiceInteraction */, |
| false /* launchTaskBehind */, false /* alwaysFocusable */, |
| 0 /* targetSdkVersion */, 0 /* rotationAnimationHint */, |
| 0 /* inputDispatchingTimeoutNanos */, sWm); |
| mToken = token; |
| } |
| |
| @Override |
| AppWindowToken createAppWindow(WindowManagerService service, IApplicationToken token, |
| boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos, |
| boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation, |
| int rotationAnimationHint, int configChanges, boolean launchTaskBehind, |
| boolean alwaysFocusable, AppWindowContainerController controller) { |
| return new TestAppWindowToken(service, token, voiceInteraction, dc, |
| inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdk, |
| orientation, |
| rotationAnimationHint, configChanges, launchTaskBehind, alwaysFocusable, |
| controller); |
| } |
| |
| AppWindowToken getAppWindowToken() { |
| return (AppWindowToken) sDisplayContent.getWindowToken(mToken.asBinder()); |
| } |
| } |
| |
| class TestIApplicationToken implements IApplicationToken { |
| |
| private final Binder mBinder = new Binder(); |
| @Override |
| public IBinder asBinder() { |
| return mBinder; |
| } |
| } |
| |
| /** Used to track resize reports. */ |
| class TestWindowState extends WindowState { |
| boolean resizeReported; |
| |
| TestWindowState(WindowManager.LayoutParams attrs, WindowToken token) { |
| super(sWm, sMockSession, sIWindow, token, null, OP_NONE, 0, attrs, 0, 0, |
| false /* ownerCanAddInternalSystemWindow */); |
| } |
| |
| @Override |
| void reportResized() { |
| super.reportResized(); |
| resizeReported = true; |
| } |
| |
| @Override |
| public boolean isGoneForLayoutLw() { |
| return false; |
| } |
| |
| @Override |
| void updateResizingWindowIfNeeded() { |
| // Used in AppWindowTokenTests#testLandscapeSeascapeRotationRelayout to deceive |
| // the system that it can actually update the window. |
| boolean hadSurface = mHasSurface; |
| mHasSurface = true; |
| |
| super.updateResizingWindowIfNeeded(); |
| |
| mHasSurface = hadSurface; |
| } |
| } |
| } |