| /* |
| * Copyright (C) 2020 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.ACTIVITY_TYPE_UNDEFINED; |
| import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; |
| import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; |
| import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; |
| import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; |
| import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; |
| import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; |
| import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; |
| import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED; |
| import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED; |
| import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; |
| |
| 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.never; |
| import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset; |
| 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.dx.mockito.inline.extended.ExtendedMockito.when; |
| import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS; |
| import static com.android.server.wm.WindowContainer.POSITION_TOP; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.ArgumentMatchers.anyBoolean; |
| import static org.mockito.ArgumentMatchers.anyInt; |
| |
| import android.app.ActivityManager; |
| import android.app.ActivityManager.RunningTaskInfo; |
| import android.app.ActivityManager.StackInfo; |
| import android.app.IRequestFinishCallback; |
| import android.app.PictureInPictureParams; |
| import android.content.pm.ActivityInfo; |
| import android.content.res.Configuration; |
| import android.graphics.Rect; |
| import android.os.Binder; |
| import android.os.IBinder; |
| import android.os.RemoteException; |
| import android.platform.test.annotations.Presubmit; |
| import android.util.ArrayMap; |
| import android.util.Rational; |
| import android.view.Display; |
| import android.view.SurfaceControl; |
| import android.window.ITaskOrganizer; |
| import android.window.WindowContainerTransaction; |
| |
| import androidx.test.filters.SmallTest; |
| |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * Test class for {@link ITaskOrganizer} and {@link android.window.ITaskOrganizerController}. |
| * |
| * Build/Install/Run: |
| * atest WmTests:WindowOrganizerTests |
| */ |
| @SmallTest |
| @Presubmit |
| @RunWith(WindowTestRunner.class) |
| public class WindowOrganizerTests extends WindowTestsBase { |
| private ITaskOrganizer registerMockOrganizer(int windowingMode) { |
| final ITaskOrganizer organizer = mock(ITaskOrganizer.class); |
| when(organizer.asBinder()).thenReturn(new Binder()); |
| |
| mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer( |
| organizer, windowingMode); |
| |
| return organizer; |
| } |
| |
| private ITaskOrganizer registerMockOrganizer() { |
| return registerMockOrganizer(WINDOWING_MODE_MULTI_WINDOW); |
| } |
| |
| Task createTask(ActivityStack stack, boolean fakeDraw) { |
| final Task task = createTaskInStack(stack, 0); |
| |
| if (fakeDraw) { |
| task.setHasBeenVisible(true); |
| } |
| return task; |
| } |
| |
| Task createTask(ActivityStack stack) { |
| // Fake draw notifications for most of our tests. |
| return createTask(stack, true); |
| } |
| |
| ActivityStack createStack() { |
| return createTaskStackOnDisplay(mDisplayContent); |
| } |
| |
| @Before |
| public void setUp() { |
| // We defer callbacks since we need to adjust task surface visibility, but for these tests, |
| // just run the callbacks synchronously |
| mWm.mAtmService.mTaskOrganizerController.setDeferTaskOrgCallbacksConsumer((r) -> r.run()); |
| } |
| |
| @Test |
| public void testAppearVanish() throws RemoteException { |
| final ActivityStack stack = createStack(); |
| final Task task = createTask(stack); |
| final ITaskOrganizer organizer = registerMockOrganizer(); |
| |
| task.setTaskOrganizer(organizer); |
| verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); |
| |
| |
| task.removeImmediately(); |
| verify(organizer).onTaskVanished(any()); |
| } |
| |
| @Test |
| public void testAppearWaitsForVisibility() throws RemoteException { |
| final ActivityStack stack = createStack(); |
| final Task task = createTask(stack, false); |
| final ITaskOrganizer organizer = registerMockOrganizer(); |
| |
| task.setTaskOrganizer(organizer); |
| |
| verify(organizer, never()) |
| .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); |
| task.setHasBeenVisible(true); |
| assertTrue(stack.getHasBeenVisible()); |
| |
| verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); |
| |
| task.removeImmediately(); |
| verify(organizer).onTaskVanished(any()); |
| } |
| |
| @Test |
| public void testNoVanishedIfNoAppear() throws RemoteException { |
| final ActivityStack stack = createStack(); |
| final Task task = createTask(stack, false /* hasBeenVisible */); |
| final ITaskOrganizer organizer = registerMockOrganizer(); |
| |
| // In this test we skip making the Task visible, and verify |
| // that even though a TaskOrganizer is set remove doesn't emit |
| // a vanish callback, because we never emitted appear. |
| task.setTaskOrganizer(organizer); |
| verify(organizer, never()) |
| .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); |
| task.removeImmediately(); |
| verify(organizer, never()).onTaskVanished(any()); |
| } |
| |
| @Test |
| public void testSwapOrganizer() throws RemoteException { |
| final ActivityStack stack = createStack(); |
| final Task task = createTask(stack); |
| final ITaskOrganizer organizer = registerMockOrganizer(WINDOWING_MODE_MULTI_WINDOW); |
| final ITaskOrganizer organizer2 = registerMockOrganizer(WINDOWING_MODE_PINNED); |
| |
| task.setTaskOrganizer(organizer); |
| verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); |
| task.setTaskOrganizer(organizer2); |
| verify(organizer).onTaskVanished(any()); |
| verify(organizer2).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); |
| } |
| |
| @Test |
| public void testSwapWindowingModes() throws RemoteException { |
| final ActivityStack stack = createStack(); |
| final Task task = createTask(stack); |
| final ITaskOrganizer organizer = registerMockOrganizer(WINDOWING_MODE_MULTI_WINDOW); |
| final ITaskOrganizer organizer2 = registerMockOrganizer(WINDOWING_MODE_PINNED); |
| |
| stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); |
| verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); |
| stack.setWindowingMode(WINDOWING_MODE_PINNED); |
| verify(organizer).onTaskVanished(any()); |
| verify(organizer2).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); |
| } |
| |
| @Test |
| public void testTaskNoDraw() throws RemoteException { |
| final ActivityStack stack = createStack(); |
| final Task task = createTask(stack, false /* fakeDraw */); |
| final ITaskOrganizer organizer = registerMockOrganizer(); |
| |
| stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); |
| verify(organizer, never()) |
| .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); |
| assertTrue(stack.isOrganized()); |
| |
| mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer); |
| verify(organizer, never()).onTaskVanished(any()); |
| assertFalse(stack.isOrganized()); |
| } |
| |
| @Test |
| public void testClearOrganizer() throws RemoteException { |
| final ActivityStack stack = createStack(); |
| final Task task = createTask(stack); |
| final ITaskOrganizer organizer = registerMockOrganizer(); |
| |
| stack.setTaskOrganizer(organizer); |
| verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); |
| assertTrue(stack.isOrganized()); |
| |
| stack.setTaskOrganizer(null); |
| verify(organizer).onTaskVanished(any()); |
| assertFalse(stack.isOrganized()); |
| } |
| |
| @Test |
| public void testUnregisterOrganizer() throws RemoteException { |
| final ActivityStack stack = createStack(); |
| final Task task = createTask(stack); |
| final ITaskOrganizer organizer = registerMockOrganizer(); |
| |
| stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); |
| verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); |
| assertTrue(stack.isOrganized()); |
| |
| mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer); |
| verify(organizer).onTaskVanished(any()); |
| assertFalse(stack.isOrganized()); |
| } |
| |
| @Test |
| public void testUnregisterOrganizerReturnsRegistrationToPrevious() throws RemoteException { |
| final ActivityStack stack = createStack(); |
| final Task task = createTask(stack); |
| final ActivityStack stack2 = createStack(); |
| final Task task2 = createTask(stack2); |
| final ActivityStack stack3 = createStack(); |
| final Task task3 = createTask(stack3); |
| final ITaskOrganizer organizer = registerMockOrganizer(WINDOWING_MODE_MULTI_WINDOW); |
| |
| // First organizer is registered, verify a task appears when changing windowing mode |
| stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); |
| verify(organizer, times(1)) |
| .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); |
| assertTrue(stack.isOrganized()); |
| |
| // Now we replace the registration and1 verify the new organizer receives tasks |
| // newly entering the windowing mode. |
| final ITaskOrganizer organizer2 = registerMockOrganizer(WINDOWING_MODE_MULTI_WINDOW); |
| stack2.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); |
| // One each for task and task2 |
| verify(organizer2, times(2)) |
| .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); |
| verify(organizer2, times(0)).onTaskVanished(any()); |
| // One for task |
| verify(organizer).onTaskVanished(any()); |
| assertTrue(stack2.isOrganized()); |
| |
| // Now we unregister the second one, the first one should automatically be reregistered |
| // so we verify that it's now seeing changes. |
| mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer2); |
| verify(organizer, times(3)) |
| .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); |
| verify(organizer2, times(2)).onTaskVanished(any()); |
| |
| stack3.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); |
| verify(organizer, times(4)) |
| .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); |
| verify(organizer2, times(2)).onTaskVanished(any()); |
| assertTrue(stack3.isOrganized()); |
| } |
| |
| @Test |
| public void testRegisterTaskOrganizerStackWindowingModeChanges() throws RemoteException { |
| final ITaskOrganizer organizer = registerMockOrganizer(WINDOWING_MODE_PINNED); |
| |
| final ActivityStack stack = createStack(); |
| final Task task = createTask(stack); |
| final Task task2 = createTask(stack); |
| stack.setWindowingMode(WINDOWING_MODE_PINNED); |
| verify(organizer, times(1)) |
| .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); |
| |
| stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); |
| verify(organizer, times(1)).onTaskVanished(any()); |
| } |
| |
| @Test |
| public void testRegisterTaskOrganizerWithExistingTasks() throws RemoteException { |
| final ActivityStack stack = createStack(); |
| final Task task = createTask(stack); |
| stack.setWindowingMode(WINDOWING_MODE_PINNED); |
| |
| final ITaskOrganizer organizer = registerMockOrganizer(WINDOWING_MODE_PINNED); |
| verify(organizer, times(1)) |
| .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); |
| } |
| |
| @Test |
| public void testTaskTransaction() { |
| removeGlobalMinSizeRestriction(); |
| final ActivityStack stack = new ActivityTestsBase.StackBuilder(mWm.mRoot) |
| .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); |
| final Task task = stack.getTopMostTask(); |
| testTransaction(task); |
| } |
| |
| @Test |
| public void testStackTransaction() { |
| removeGlobalMinSizeRestriction(); |
| final ActivityStack stack = new ActivityTestsBase.StackBuilder(mWm.mRoot) |
| .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); |
| StackInfo info = |
| mWm.mAtmService.getStackInfo(WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD); |
| assertEquals(stack.mRemoteToken.toWindowContainerToken(), info.stackToken); |
| testTransaction(stack); |
| } |
| |
| @Test |
| public void testDisplayAreaTransaction() { |
| removeGlobalMinSizeRestriction(); |
| final DisplayArea displayArea = new DisplayArea<>(mWm, ABOVE_TASKS, "DisplayArea"); |
| testTransaction(displayArea); |
| } |
| |
| private void testTransaction(WindowContainer wc) { |
| WindowContainerTransaction t = new WindowContainerTransaction(); |
| Rect newBounds = new Rect(10, 10, 100, 100); |
| t.setBounds(wc.mRemoteToken.toWindowContainerToken(), new Rect(10, 10, 100, 100)); |
| mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); |
| assertEquals(newBounds, wc.getBounds()); |
| } |
| |
| @Test |
| public void testSetWindowingMode() { |
| final ActivityStack stack = new ActivityTestsBase.StackBuilder(mWm.mRoot) |
| .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); |
| testSetWindowingMode(stack); |
| |
| final DisplayArea displayArea = new DisplayArea<>(mWm, ABOVE_TASKS, "DisplayArea"); |
| displayArea.setWindowingMode(WINDOWING_MODE_FREEFORM); |
| testSetWindowingMode(displayArea); |
| } |
| |
| private void testSetWindowingMode(WindowContainer wc) { |
| final WindowContainerTransaction t = new WindowContainerTransaction(); |
| t.setWindowingMode(wc.mRemoteToken.toWindowContainerToken(), WINDOWING_MODE_FULLSCREEN); |
| mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); |
| assertEquals(WINDOWING_MODE_FULLSCREEN, wc.getWindowingMode()); |
| } |
| |
| @Test |
| public void testSetActivityWindowingMode() { |
| final ActivityRecord record = makePipableActivity(); |
| final ActivityStack stack = record.getStack(); |
| final WindowContainerTransaction t = new WindowContainerTransaction(); |
| |
| t.setWindowingMode(stack.mRemoteToken.toWindowContainerToken(), WINDOWING_MODE_PINNED); |
| t.setActivityWindowingMode( |
| stack.mRemoteToken.toWindowContainerToken(), WINDOWING_MODE_FULLSCREEN); |
| mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); |
| |
| assertEquals(WINDOWING_MODE_FULLSCREEN, record.getWindowingMode()); |
| assertEquals(WINDOWING_MODE_PINNED, stack.getWindowingMode()); |
| } |
| |
| @Test |
| public void testContainerFocusableChanges() { |
| removeGlobalMinSizeRestriction(); |
| final ActivityStack stack = new ActivityTestsBase.StackBuilder(mWm.mRoot) |
| .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); |
| final Task task = stack.getTopMostTask(); |
| WindowContainerTransaction t = new WindowContainerTransaction(); |
| assertTrue(task.isFocusable()); |
| t.setFocusable(stack.mRemoteToken.toWindowContainerToken(), false); |
| mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); |
| assertFalse(task.isFocusable()); |
| t.setFocusable(stack.mRemoteToken.toWindowContainerToken(), true); |
| mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); |
| assertTrue(task.isFocusable()); |
| } |
| |
| @Test |
| public void testContainerHiddenChanges() { |
| removeGlobalMinSizeRestriction(); |
| final ActivityStack stack = new ActivityTestsBase.StackBuilder(mWm.mRoot) |
| .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); |
| WindowContainerTransaction t = new WindowContainerTransaction(); |
| assertTrue(stack.shouldBeVisible(null)); |
| t.setHidden(stack.mRemoteToken.toWindowContainerToken(), true); |
| mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); |
| assertFalse(stack.shouldBeVisible(null)); |
| t.setHidden(stack.mRemoteToken.toWindowContainerToken(), false); |
| mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); |
| assertTrue(stack.shouldBeVisible(null)); |
| } |
| |
| @Test |
| public void testOverrideConfigSize() { |
| removeGlobalMinSizeRestriction(); |
| final ActivityStack stack = new ActivityTestsBase.StackBuilder(mWm.mRoot) |
| .setWindowingMode(WINDOWING_MODE_FREEFORM).build(); |
| final Task task = stack.getTopMostTask(); |
| WindowContainerTransaction t = new WindowContainerTransaction(); |
| mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); |
| final int origScreenWDp = task.getConfiguration().screenHeightDp; |
| final int origScreenHDp = task.getConfiguration().screenHeightDp; |
| t = new WindowContainerTransaction(); |
| // verify that setting config overrides on parent restricts children. |
| t.setScreenSizeDp(stack.mRemoteToken |
| .toWindowContainerToken(), origScreenWDp, origScreenHDp / 2); |
| mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); |
| assertEquals(origScreenHDp / 2, task.getConfiguration().screenHeightDp); |
| t = new WindowContainerTransaction(); |
| t.setScreenSizeDp(stack.mRemoteToken.toWindowContainerToken(), SCREEN_WIDTH_DP_UNDEFINED, |
| SCREEN_HEIGHT_DP_UNDEFINED); |
| mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); |
| assertEquals(origScreenHDp, task.getConfiguration().screenHeightDp); |
| } |
| |
| @Test |
| public void testCreateDeleteRootTasks() { |
| RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask( |
| Display.DEFAULT_DISPLAY, |
| WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); |
| assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, |
| info1.configuration.windowConfiguration.getWindowingMode()); |
| assertEquals(ACTIVITY_TYPE_UNDEFINED, info1.topActivityType); |
| |
| RunningTaskInfo info2 = mWm.mAtmService.mTaskOrganizerController.createRootTask( |
| Display.DEFAULT_DISPLAY, |
| WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); |
| assertEquals(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, |
| info2.configuration.windowConfiguration.getWindowingMode()); |
| assertEquals(ACTIVITY_TYPE_UNDEFINED, info2.topActivityType); |
| |
| DisplayContent dc = mWm.mRoot.getDisplayContent(Display.DEFAULT_DISPLAY); |
| List<Task> infos = getTasksCreatedByOrganizer(dc); |
| assertEquals(2, infos.size()); |
| |
| assertTrue(mWm.mAtmService.mTaskOrganizerController.deleteRootTask(info1.token)); |
| infos = getTasksCreatedByOrganizer(dc); |
| assertEquals(1, infos.size()); |
| assertEquals(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, infos.get(0).getWindowingMode()); |
| } |
| |
| @Test |
| public void testTileAddRemoveChild() { |
| ITaskOrganizer listener = new ITaskOrganizer.Stub() { |
| @Override |
| public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { } |
| |
| @Override |
| public void onTaskVanished(RunningTaskInfo container) { } |
| |
| @Override |
| public void onTaskInfoChanged(RunningTaskInfo info) throws RemoteException { |
| } |
| |
| @Override |
| public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) { |
| } |
| }; |
| mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener, |
| WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); |
| RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask( |
| mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); |
| |
| final ActivityStack stack = createTaskStackOnDisplay( |
| WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent); |
| assertEquals(mDisplayContent.getWindowingMode(), stack.getWindowingMode()); |
| WindowContainerTransaction wct = new WindowContainerTransaction(); |
| wct.reparent(stack.mRemoteToken.toWindowContainerToken(), info1.token, true /* onTop */); |
| mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); |
| assertEquals(info1.configuration.windowConfiguration.getWindowingMode(), |
| stack.getWindowingMode()); |
| |
| // Info should reflect new membership |
| List<Task> infos = getTasksCreatedByOrganizer(mDisplayContent); |
| info1 = infos.get(0).getTaskInfo(); |
| assertEquals(ACTIVITY_TYPE_STANDARD, info1.topActivityType); |
| |
| // Children inherit configuration |
| Rect newSize = new Rect(10, 10, 300, 300); |
| Task task1 = WindowContainer.fromBinder(info1.token.asBinder()).asTask(); |
| Configuration c = new Configuration(task1.getRequestedOverrideConfiguration()); |
| c.windowConfiguration.setBounds(newSize); |
| doNothing().when(stack).adjustForMinimalTaskDimensions(any(), any(), any()); |
| task1.onRequestedOverrideConfigurationChanged(c); |
| assertEquals(newSize, stack.getBounds()); |
| |
| wct = new WindowContainerTransaction(); |
| wct.reparent(stack.mRemoteToken.toWindowContainerToken(), null, true /* onTop */); |
| mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); |
| assertEquals(mDisplayContent.getWindowingMode(), stack.getWindowingMode()); |
| infos = getTasksCreatedByOrganizer(mDisplayContent); |
| info1 = infos.get(0).getTaskInfo(); |
| assertEquals(ACTIVITY_TYPE_UNDEFINED, info1.topActivityType); |
| } |
| |
| @Test |
| public void testTaskInfoCallback() { |
| final ArrayList<RunningTaskInfo> lastReportedTiles = new ArrayList<>(); |
| final boolean[] called = {false}; |
| ITaskOrganizer listener = new ITaskOrganizer.Stub() { |
| @Override |
| public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { } |
| |
| @Override |
| public void onTaskVanished(RunningTaskInfo container) { } |
| |
| @Override |
| public void onTaskInfoChanged(RunningTaskInfo info) throws RemoteException { |
| lastReportedTiles.add(info); |
| called[0] = true; |
| } |
| |
| @Override |
| public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) { |
| } |
| }; |
| mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener, |
| WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); |
| RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask( |
| mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); |
| lastReportedTiles.clear(); |
| called[0] = false; |
| |
| final ActivityStack stack = createTaskStackOnDisplay( |
| WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent); |
| Task task1 = WindowContainer.fromBinder(info1.token.asBinder()).asTask(); |
| WindowContainerTransaction wct = new WindowContainerTransaction(); |
| wct.reparent(stack.mRemoteToken.toWindowContainerToken(), info1.token, true /* onTop */); |
| mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); |
| assertTrue(called[0]); |
| assertEquals(ACTIVITY_TYPE_STANDARD, lastReportedTiles.get(0).topActivityType); |
| |
| lastReportedTiles.clear(); |
| called[0] = false; |
| final ActivityStack stack2 = createTaskStackOnDisplay( |
| WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, mDisplayContent); |
| wct = new WindowContainerTransaction(); |
| wct.reparent(stack2.mRemoteToken.toWindowContainerToken(), info1.token, true /* onTop */); |
| mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); |
| assertTrue(called[0]); |
| assertEquals(ACTIVITY_TYPE_HOME, lastReportedTiles.get(0).topActivityType); |
| |
| lastReportedTiles.clear(); |
| called[0] = false; |
| task1.positionChildAt(POSITION_TOP, stack, false /* includingParents */); |
| assertTrue(called[0]); |
| assertEquals(ACTIVITY_TYPE_STANDARD, lastReportedTiles.get(0).topActivityType); |
| |
| lastReportedTiles.clear(); |
| called[0] = false; |
| wct = new WindowContainerTransaction(); |
| wct.reparent(stack.mRemoteToken.toWindowContainerToken(), null, true /* onTop */); |
| wct.reparent(stack2.mRemoteToken.toWindowContainerToken(), null, true /* onTop */); |
| mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); |
| assertTrue(called[0]); |
| assertEquals(ACTIVITY_TYPE_UNDEFINED, lastReportedTiles.get(0).topActivityType); |
| } |
| |
| @Test |
| public void testHierarchyTransaction() { |
| final ArrayMap<IBinder, RunningTaskInfo> lastReportedTiles = new ArrayMap<>(); |
| ITaskOrganizer listener = new ITaskOrganizer.Stub() { |
| @Override |
| public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { } |
| |
| @Override |
| public void onTaskVanished(RunningTaskInfo container) { } |
| |
| @Override |
| public void onTaskInfoChanged(RunningTaskInfo info) { |
| lastReportedTiles.put(info.token.asBinder(), info); |
| } |
| |
| @Override |
| public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) { |
| } |
| }; |
| mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer( |
| listener, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); |
| mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer( |
| listener, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); |
| RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask( |
| mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); |
| RunningTaskInfo info2 = mWm.mAtmService.mTaskOrganizerController.createRootTask( |
| mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); |
| |
| final int initialRootTaskCount = mWm.mAtmService.mTaskOrganizerController.getRootTasks( |
| mDisplayContent.mDisplayId, null /* activityTypes */).size(); |
| |
| final ActivityStack stack = createTaskStackOnDisplay( |
| WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent); |
| final ActivityStack stack2 = createTaskStackOnDisplay( |
| WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, mDisplayContent); |
| |
| // Check getRootTasks works |
| List<RunningTaskInfo> roots = mWm.mAtmService.mTaskOrganizerController.getRootTasks( |
| mDisplayContent.mDisplayId, null /* activityTypes */); |
| assertEquals(initialRootTaskCount + 2, roots.size()); |
| |
| lastReportedTiles.clear(); |
| WindowContainerTransaction wct = new WindowContainerTransaction(); |
| wct.reparent(stack.mRemoteToken.toWindowContainerToken(), info1.token, true /* onTop */); |
| wct.reparent(stack2.mRemoteToken.toWindowContainerToken(), info2.token, true /* onTop */); |
| mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); |
| assertFalse(lastReportedTiles.isEmpty()); |
| assertEquals(ACTIVITY_TYPE_STANDARD, |
| lastReportedTiles.get(info1.token.asBinder()).topActivityType); |
| assertEquals(ACTIVITY_TYPE_HOME, |
| lastReportedTiles.get(info2.token.asBinder()).topActivityType); |
| |
| lastReportedTiles.clear(); |
| wct = new WindowContainerTransaction(); |
| wct.reparent(stack2.mRemoteToken.toWindowContainerToken(), info1.token, false /* onTop */); |
| mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); |
| assertFalse(lastReportedTiles.isEmpty()); |
| // Standard should still be on top of tile 1, so no change there |
| assertFalse(lastReportedTiles.containsKey(info1.token.asBinder())); |
| // But tile 2 has no children, so should become undefined |
| assertEquals(ACTIVITY_TYPE_UNDEFINED, |
| lastReportedTiles.get(info2.token.asBinder()).topActivityType); |
| |
| // Check the getChildren call |
| List<RunningTaskInfo> children = |
| mWm.mAtmService.mTaskOrganizerController.getChildTasks(info1.token, |
| null /* activityTypes */); |
| assertEquals(2, children.size()); |
| children = mWm.mAtmService.mTaskOrganizerController.getChildTasks(info2.token, |
| null /* activityTypes */); |
| assertEquals(0, children.size()); |
| |
| // Check that getRootTasks doesn't include children of tiles |
| roots = mWm.mAtmService.mTaskOrganizerController.getRootTasks(mDisplayContent.mDisplayId, |
| null /* activityTypes */); |
| assertEquals(initialRootTaskCount, roots.size()); |
| |
| lastReportedTiles.clear(); |
| wct = new WindowContainerTransaction(); |
| wct.reorder(stack2.mRemoteToken.toWindowContainerToken(), true /* onTop */); |
| mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); |
| // Home should now be on top. No change occurs in second tile, so not reported |
| assertEquals(1, lastReportedTiles.size()); |
| assertEquals(ACTIVITY_TYPE_HOME, |
| lastReportedTiles.get(info1.token.asBinder()).topActivityType); |
| } |
| |
| private List<Task> getTasksCreatedByOrganizer(DisplayContent dc) { |
| ArrayList<Task> out = new ArrayList<>(); |
| for (int tdaNdx = dc.getTaskDisplayAreaCount() - 1; tdaNdx >= 0; --tdaNdx) { |
| final TaskDisplayArea taskDisplayArea = dc.getTaskDisplayAreaAt(tdaNdx); |
| for (int sNdx = taskDisplayArea.getStackCount() - 1; sNdx >= 0; --sNdx) { |
| final Task t = taskDisplayArea.getStackAt(sNdx); |
| if (t.mCreatedByOrganizer) out.add(t); |
| } |
| } |
| return out; |
| } |
| |
| @Test |
| public void testTrivialBLASTCallback() throws RemoteException { |
| final ActivityStack stackController1 = createStack(); |
| final Task task = createTask(stackController1); |
| final ITaskOrganizer organizer = registerMockOrganizer(); |
| |
| spyOn(task); |
| doReturn(true).when(task).isVisible(); |
| |
| BLASTSyncEngine bse = new BLASTSyncEngine(); |
| |
| BLASTSyncEngine.TransactionReadyListener transactionListener = |
| mock(BLASTSyncEngine.TransactionReadyListener.class); |
| |
| int id = bse.startSyncSet(transactionListener); |
| bse.addToSyncSet(id, task); |
| bse.setReady(id); |
| // Since this task has no windows the sync is trivial and completes immediately. |
| verify(transactionListener) |
| .onTransactionReady(anyInt(), any()); |
| } |
| |
| @Test |
| public void testOverlappingBLASTCallback() throws RemoteException { |
| final ActivityStack stackController1 = createStack(); |
| final Task task = createTask(stackController1); |
| final ITaskOrganizer organizer = registerMockOrganizer(); |
| |
| spyOn(task); |
| doReturn(true).when(task).isVisible(); |
| final WindowState w = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window"); |
| makeWindowVisible(w); |
| |
| BLASTSyncEngine bse = new BLASTSyncEngine(); |
| |
| BLASTSyncEngine.TransactionReadyListener transactionListener = |
| mock(BLASTSyncEngine.TransactionReadyListener.class); |
| |
| int id = bse.startSyncSet(transactionListener); |
| assertEquals(true, bse.addToSyncSet(id, task)); |
| bse.setReady(id); |
| |
| int id2 = bse.startSyncSet(transactionListener); |
| // We should be rejected from the second sync since we are already |
| // in one. |
| assertEquals(false, bse.addToSyncSet(id2, task)); |
| w.finishDrawing(null); |
| assertEquals(true, bse.addToSyncSet(id2, task)); |
| bse.setReady(id2); |
| } |
| |
| @Test |
| public void testBLASTCallbackWithWindow() { |
| final ActivityStack stackController1 = createStack(); |
| final Task task = createTask(stackController1); |
| final ITaskOrganizer organizer = registerMockOrganizer(); |
| final WindowState w = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window"); |
| makeWindowVisible(w); |
| |
| BLASTSyncEngine bse = new BLASTSyncEngine(); |
| |
| BLASTSyncEngine.TransactionReadyListener transactionListener = |
| mock(BLASTSyncEngine.TransactionReadyListener.class); |
| |
| int id = bse.startSyncSet(transactionListener); |
| bse.addToSyncSet(id, task); |
| bse.setReady(id); |
| // Since we have a window we have to wait for it to draw to finish sync. |
| verify(transactionListener, never()) |
| .onTransactionReady(anyInt(), any()); |
| w.finishDrawing(null); |
| verify(transactionListener) |
| .onTransactionReady(anyInt(), any()); |
| } |
| |
| @Test |
| public void testBLASTCallbackNoDoubleAdd() { |
| final ActivityStack stackController1 = createStack(); |
| final Task task = createTask(stackController1); |
| final ITaskOrganizer organizer = registerMockOrganizer(); |
| final WindowState w = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window"); |
| makeWindowVisible(w); |
| |
| BLASTSyncEngine bse = new BLASTSyncEngine(); |
| |
| BLASTSyncEngine.TransactionReadyListener transactionListener = |
| mock(BLASTSyncEngine.TransactionReadyListener.class); |
| |
| int id = bse.startSyncSet(transactionListener); |
| assertTrue(bse.addToSyncSet(id, w)); |
| assertFalse(bse.addToSyncSet(id, w)); |
| |
| // Clean-up |
| bse.setReady(id); |
| } |
| |
| |
| @Test |
| public void testBLASTCallbackWithInvisibleWindow() { |
| final ActivityStack stackController1 = createStack(); |
| final Task task = createTask(stackController1); |
| final ITaskOrganizer organizer = registerMockOrganizer(); |
| final WindowState w = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window"); |
| |
| BLASTSyncEngine bse = new BLASTSyncEngine(); |
| |
| BLASTSyncEngine.TransactionReadyListener transactionListener = |
| mock(BLASTSyncEngine.TransactionReadyListener.class); |
| |
| int id = bse.startSyncSet(transactionListener); |
| bse.addToSyncSet(id, task); |
| bse.setReady(id); |
| |
| // Since the window was invisible, the Task had no visible leaves and the sync should |
| // complete as soon as we call setReady. |
| verify(transactionListener) |
| .onTransactionReady(anyInt(), any()); |
| } |
| |
| @Test |
| public void testBLASTCallbackWithChildWindow() { |
| final ActivityStack stackController1 = createStack(); |
| final Task task = createTask(stackController1); |
| final ITaskOrganizer organizer = registerMockOrganizer(); |
| final WindowState w = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window"); |
| final WindowState child = createWindow(w, TYPE_APPLICATION, "Other Window"); |
| |
| w.mActivityRecord.setVisible(true); |
| makeWindowVisible(w, child); |
| |
| BLASTSyncEngine bse = new BLASTSyncEngine(); |
| |
| BLASTSyncEngine.TransactionReadyListener transactionListener = |
| mock(BLASTSyncEngine.TransactionReadyListener.class); |
| |
| int id = bse.startSyncSet(transactionListener); |
| assertEquals(true, bse.addToSyncSet(id, task)); |
| bse.setReady(id); |
| w.finishDrawing(null); |
| |
| // Since we have a child window we still shouldn't be done. |
| verify(transactionListener, never()) |
| .onTransactionReady(anyInt(), any()); |
| reset(transactionListener); |
| |
| child.finishDrawing(null); |
| // Ah finally! Done |
| verify(transactionListener) |
| .onTransactionReady(anyInt(), any()); |
| } |
| |
| class StubOrganizer extends ITaskOrganizer.Stub { |
| RunningTaskInfo mInfo; |
| |
| @Override |
| public void onTaskAppeared(RunningTaskInfo info, SurfaceControl leash) { |
| mInfo = info; |
| } |
| @Override |
| public void onTaskVanished(RunningTaskInfo info) { |
| } |
| @Override |
| public void onTaskInfoChanged(RunningTaskInfo info) { |
| } |
| @Override |
| public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) { |
| } |
| }; |
| |
| private ActivityRecord makePipableActivity() { |
| final ActivityRecord record = createActivityRecord(mDisplayContent, |
| WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); |
| record.info.flags |= ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE; |
| spyOn(record); |
| doReturn(true).when(record).checkEnterPictureInPictureState(any(), anyBoolean()); |
| |
| record.getTask().setHasBeenVisible(true); |
| return record; |
| } |
| |
| @Test |
| public void testEnterPipParams() { |
| final StubOrganizer o = new StubOrganizer(); |
| mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(o, WINDOWING_MODE_PINNED); |
| final ActivityRecord record = makePipableActivity(); |
| |
| final PictureInPictureParams p = new PictureInPictureParams.Builder() |
| .setAspectRatio(new Rational(1, 2)).build(); |
| assertTrue(mWm.mAtmService.enterPictureInPictureMode(record.token, p)); |
| waitUntilHandlersIdle(); |
| assertNotNull(o.mInfo); |
| assertNotNull(o.mInfo.pictureInPictureParams); |
| } |
| |
| @Test |
| public void testChangePipParams() { |
| class ChangeSavingOrganizer extends StubOrganizer { |
| RunningTaskInfo mChangedInfo; |
| @Override |
| public void onTaskInfoChanged(RunningTaskInfo info) { |
| mChangedInfo = info; |
| } |
| } |
| ChangeSavingOrganizer o = new ChangeSavingOrganizer(); |
| mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(o, WINDOWING_MODE_PINNED); |
| |
| final ActivityRecord record = makePipableActivity(); |
| final PictureInPictureParams p = new PictureInPictureParams.Builder() |
| .setAspectRatio(new Rational(1, 2)).build(); |
| assertTrue(mWm.mAtmService.enterPictureInPictureMode(record.token, p)); |
| waitUntilHandlersIdle(); |
| assertNotNull(o.mInfo); |
| assertNotNull(o.mInfo.pictureInPictureParams); |
| |
| final PictureInPictureParams p2 = new PictureInPictureParams.Builder() |
| .setAspectRatio(new Rational(3, 4)).build(); |
| mWm.mAtmService.setPictureInPictureParams(record.token, p2); |
| waitUntilHandlersIdle(); |
| assertNotNull(o.mChangedInfo); |
| assertNotNull(o.mChangedInfo.pictureInPictureParams); |
| final Rational ratio = o.mChangedInfo.pictureInPictureParams.getAspectRatioRational(); |
| assertEquals(3, ratio.getNumerator()); |
| assertEquals(4, ratio.getDenominator()); |
| } |
| |
| @Test |
| public void testChangeTaskDescription() { |
| class ChangeSavingOrganizer extends StubOrganizer { |
| RunningTaskInfo mChangedInfo; |
| @Override |
| public void onTaskInfoChanged(RunningTaskInfo info) { |
| mChangedInfo = info; |
| } |
| } |
| ChangeSavingOrganizer o = new ChangeSavingOrganizer(); |
| mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(o, |
| WINDOWING_MODE_MULTI_WINDOW); |
| |
| final ActivityStack stack = createStack(); |
| final Task task = createTask(stack); |
| final ActivityRecord record = WindowTestUtils.createActivityRecordInTask( |
| stack.mDisplayContent, task); |
| |
| stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); |
| record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription")); |
| waitUntilHandlersIdle(); |
| assertEquals("TestDescription", o.mChangedInfo.taskDescription.getLabel()); |
| } |
| |
| @Test |
| public void testPreventDuplicateAppear() throws RemoteException { |
| final ActivityStack stack = createStack(); |
| final Task task = createTask(stack); |
| final ITaskOrganizer organizer = registerMockOrganizer(); |
| |
| task.setTaskOrganizer(organizer); |
| // setHasBeenVisible was already called once by the set-up code. |
| task.setHasBeenVisible(true); |
| verify(organizer, times(1)) |
| .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); |
| |
| task.setTaskOrganizer(null); |
| verify(organizer, times(1)).onTaskVanished(any()); |
| task.setTaskOrganizer(organizer); |
| verify(organizer, times(2)) |
| .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class)); |
| |
| task.removeImmediately(); |
| verify(organizer, times(2)).onTaskVanished(any()); |
| } |
| |
| @Test |
| public void testInterceptBackPressedOnTaskRoot() throws RemoteException { |
| final ActivityStack stack = createStack(); |
| final Task task = createTask(stack); |
| final ActivityRecord activity = WindowTestUtils.createActivityRecordInTask( |
| stack.mDisplayContent, task); |
| final ITaskOrganizer organizer = registerMockOrganizer(WINDOWING_MODE_MULTI_WINDOW); |
| |
| // Setup the task to be controlled by the MW mode organizer |
| stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); |
| assertTrue(stack.isOrganized()); |
| |
| // Verify a back pressed does not call the organizer |
| mWm.mAtmService.onBackPressedOnTaskRoot(activity.token, |
| new IRequestFinishCallback.Default()); |
| verify(organizer, never()).onBackPressedOnTaskRoot(any()); |
| |
| // Enable intercepting back |
| mWm.mAtmService.mTaskOrganizerController.setInterceptBackPressedOnTaskRoot(organizer, |
| true); |
| |
| // Verify now that the back press does call the organizer |
| mWm.mAtmService.onBackPressedOnTaskRoot(activity.token, |
| new IRequestFinishCallback.Default()); |
| verify(organizer, times(1)).onBackPressedOnTaskRoot(any()); |
| } |
| } |