blob: 71c837e2b46ed5061d57e1514299dbe65c033a3e [file] [log] [blame]
/*
* 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.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 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.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityManager.StackInfo;
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.ITaskOrganizer;
import android.view.IWindowContainer;
import android.view.SurfaceControl;
import android.view.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
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.app.ITaskOrganizerController}.
*
* Build/Install/Run:
* atest WmTests:TaskOrganizerTests
*/
@SmallTest
@Presubmit
@RunWith(WindowTestRunner.class)
public class TaskOrganizerTests 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);
}
@Test
public void testAppearVanish() throws RemoteException {
final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
final Task task = createTaskInStack(stack, 0 /* userId */);
final ITaskOrganizer organizer = registerMockOrganizer();
task.setTaskOrganizer(organizer);
verify(organizer).taskAppeared(any());
task.removeImmediately();
verify(organizer).taskVanished(any());
}
@Test
public void testSwapOrganizer() throws RemoteException {
final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
final Task task = createTaskInStack(stack, 0 /* userId */);
final ITaskOrganizer organizer = registerMockOrganizer(WINDOWING_MODE_MULTI_WINDOW);
final ITaskOrganizer organizer2 = registerMockOrganizer(WINDOWING_MODE_PINNED);
task.setTaskOrganizer(organizer);
verify(organizer).taskAppeared(any());
task.setTaskOrganizer(organizer2);
verify(organizer).taskVanished(any());
verify(organizer2).taskAppeared(any());
}
@Test
public void testSwapWindowingModes() throws RemoteException {
final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
final Task task = createTaskInStack(stack, 0 /* userId */);
final ITaskOrganizer organizer = registerMockOrganizer(WINDOWING_MODE_MULTI_WINDOW);
final ITaskOrganizer organizer2 = registerMockOrganizer(WINDOWING_MODE_PINNED);
stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
verify(organizer).taskAppeared(any());
stack.setWindowingMode(WINDOWING_MODE_PINNED);
verify(organizer).taskVanished(any());
verify(organizer2).taskAppeared(any());
}
@Test
public void testClearOrganizer() throws RemoteException {
final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
final Task task = createTaskInStack(stack, 0 /* userId */);
final ITaskOrganizer organizer = registerMockOrganizer();
stack.setTaskOrganizer(organizer);
verify(organizer).taskAppeared(any());
assertTrue(stack.isControlledByTaskOrganizer());
stack.setTaskOrganizer(null);
verify(organizer).taskVanished(any());
assertFalse(stack.isControlledByTaskOrganizer());
}
@Test
public void testUnregisterOrganizer() throws RemoteException {
final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
final Task task = createTaskInStack(stack, 0 /* userId */);
final ITaskOrganizer organizer = registerMockOrganizer();
stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
verify(organizer).taskAppeared(any());
assertTrue(stack.isControlledByTaskOrganizer());
mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer);
verify(organizer).taskVanished(any());
assertFalse(stack.isControlledByTaskOrganizer());
}
@Test
public void testUnregisterOrganizerReturnsRegistrationToPrevious() throws RemoteException {
final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
final Task task = createTaskInStack(stack, 0 /* userId */);
final ActivityStack stack2 = createTaskStackOnDisplay(mDisplayContent);
final Task task2 = createTaskInStack(stack2, 0 /* userId */);
final ActivityStack stack3 = createTaskStackOnDisplay(mDisplayContent);
final Task task3 = createTaskInStack(stack3, 0 /* userId */);
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)).taskAppeared(any());
assertTrue(stack.isControlledByTaskOrganizer());
// 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);
verify(organizer2).taskAppeared(any());
assertTrue(stack2.isControlledByTaskOrganizer());
// 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);
stack3.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
verify(organizer, times(2)).taskAppeared(any());
assertTrue(stack3.isControlledByTaskOrganizer());
}
@Test
public void testRegisterTaskOrganizerStackWindowingModeChanges() throws RemoteException {
final ITaskOrganizer organizer = registerMockOrganizer(WINDOWING_MODE_PINNED);
final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
final Task task = createTaskInStack(stack, 0 /* userId */);
final Task task2 = createTaskInStack(stack, 0 /* userId */);
stack.setWindowingMode(WINDOWING_MODE_PINNED);
verify(organizer, times(1)).taskAppeared(any());
stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
verify(organizer, times(1)).taskVanished(any());
}
@Test
public void testRegisterTaskOrganizerWithExistingTasks() throws RemoteException {
final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
final Task task = createTaskInStack(stack, 0 /* userId */);
stack.setWindowingMode(WINDOWING_MODE_PINNED);
final ITaskOrganizer organizer = registerMockOrganizer(WINDOWING_MODE_PINNED);
verify(organizer, times(1)).taskAppeared(any());
}
@Test
public void testTaskTransaction() {
removeGlobalMinSizeRestriction();
final ActivityStack stack = new ActivityTestsBase.StackBuilder(mWm.mRoot)
.setWindowingMode(WINDOWING_MODE_FREEFORM).build();
final Task task = stack.getTopMostTask();
WindowContainerTransaction t = new WindowContainerTransaction();
Rect newBounds = new Rect(10, 10, 100, 100);
t.setBounds(task.mRemoteToken, new Rect(10, 10, 100, 100));
mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null);
assertEquals(newBounds, task.getBounds());
}
@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);
WindowContainerTransaction t = new WindowContainerTransaction();
assertEquals(stack.mRemoteToken, info.stackToken);
Rect newBounds = new Rect(10, 10, 100, 100);
t.setBounds(info.stackToken, new Rect(10, 10, 100, 100));
mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null);
assertEquals(newBounds, stack.getBounds());
}
@Test
public void testSetWindowingMode() {
final ActivityStack stack = new ActivityTestsBase.StackBuilder(mWm.mRoot)
.setWindowingMode(WINDOWING_MODE_FREEFORM).build();
final WindowContainerTransaction t = new WindowContainerTransaction();
t.setWindowingMode(stack.mRemoteToken, WINDOWING_MODE_FULLSCREEN);
mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null);
assertEquals(WINDOWING_MODE_FULLSCREEN, stack.getWindowingMode());
}
@Test
public void testSetActivityWindowingMode() {
final ActivityRecord record = makePipableActivity();
final ActivityStack stack = record.getStack();
final WindowContainerTransaction t = new WindowContainerTransaction();
t.setWindowingMode(stack.mRemoteToken, WINDOWING_MODE_PINNED);
t.setActivityWindowingMode(stack.mRemoteToken, WINDOWING_MODE_FULLSCREEN);
mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null);
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, false);
mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null);
assertFalse(task.isFocusable());
t.setFocusable(stack.mRemoteToken, true);
mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null);
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, true);
mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null);
assertFalse(stack.shouldBeVisible(null));
t.setHidden(stack.mRemoteToken, false);
mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null);
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();
t.setBounds(task.mRemoteToken, new Rect(10, 10, 100, 100));
mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null);
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, origScreenWDp, origScreenHDp);
t.setBounds(task.mRemoteToken, new Rect(10, 10, 150, 200));
mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null);
assertEquals(origScreenHDp, task.getConfiguration().screenHeightDp);
t = new WindowContainerTransaction();
t.setScreenSizeDp(stack.mRemoteToken, Configuration.SCREEN_WIDTH_DP_UNDEFINED,
Configuration.SCREEN_HEIGHT_DP_UNDEFINED);
mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(t, null);
assertNotEquals(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<TaskTile> infos = getTaskTiles(dc);
assertEquals(2, infos.size());
assertTrue(mWm.mAtmService.mTaskOrganizerController.deleteRootTask(info1.token));
infos = getTaskTiles(dc);
assertEquals(1, infos.size());
assertEquals(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, infos.get(0).getWindowingMode());
}
@Test
public void testTileAddRemoveChild() {
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());
TaskTile tile1 = TaskTile.forToken(info1.token.asBinder());
tile1.addChild(stack, 0 /* index */);
assertEquals(info1.configuration.windowConfiguration.getWindowingMode(),
stack.getWindowingMode());
// Info should reflect new membership
List<TaskTile> tiles = getTaskTiles(mDisplayContent);
info1 = tiles.get(0).getTaskInfo();
assertEquals(ACTIVITY_TYPE_STANDARD, info1.topActivityType);
// Children inherit configuration
Rect newSize = new Rect(10, 10, 300, 300);
Configuration c = new Configuration(tile1.getRequestedOverrideConfiguration());
c.windowConfiguration.setBounds(newSize);
doNothing().when(stack).adjustForMinimalTaskDimensions(any(), any());
tile1.onRequestedOverrideConfigurationChanged(c);
assertEquals(newSize, stack.getBounds());
tile1.removeChild(stack);
assertEquals(mDisplayContent.getWindowingMode(), stack.getWindowingMode());
tiles = getTaskTiles(mDisplayContent);
info1 = tiles.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 taskAppeared(RunningTaskInfo taskInfo) { }
@Override
public void taskVanished(RunningTaskInfo container) { }
@Override
public void transactionReady(int id, SurfaceControl.Transaction t) { }
@Override
public void onTaskInfoChanged(RunningTaskInfo info) throws RemoteException {
lastReportedTiles.add(info);
called[0] = true;
}
};
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);
TaskTile tile1 = TaskTile.forToken(info1.token.asBinder());
tile1.addChild(stack, 0 /* index */);
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);
tile1.addChild(stack2, 0 /* index */);
assertTrue(called[0]);
assertEquals(ACTIVITY_TYPE_HOME, lastReportedTiles.get(0).topActivityType);
lastReportedTiles.clear();
called[0] = false;
mDisplayContent.positionStackAtTop(stack, false /* includingParents */);
assertTrue(called[0]);
assertEquals(ACTIVITY_TYPE_STANDARD, lastReportedTiles.get(0).topActivityType);
lastReportedTiles.clear();
called[0] = false;
tile1.removeAllChildren();
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 taskAppeared(RunningTaskInfo taskInfo) { }
@Override
public void taskVanished(RunningTaskInfo container) { }
@Override
public void transactionReady(int id, SurfaceControl.Transaction t) { }
@Override
public void onTaskInfoChanged(RunningTaskInfo info) {
lastReportedTiles.put(info.token.asBinder(), info);
}
};
mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(
listener, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
RunningTaskInfo info1 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
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, info1.token, true /* onTop */);
wct.reparent(stack2.mRemoteToken, info2.token, true /* onTop */);
mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(wct,
null /* organizer */);
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, info1.token, false /* onTop */);
mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(wct,
null /* organizer */);
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, true /* onTop */);
mWm.mAtmService.mTaskOrganizerController.applyContainerTransaction(wct,
null /* organizer */);
// 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<TaskTile> getTaskTiles(DisplayContent dc) {
ArrayList<TaskTile> out = new ArrayList<>();
for (int i = dc.getStackCount() - 1; i >= 0; --i) {
final TaskTile t = dc.getStackAt(i).asTile();
if (t != null) {
out.add(t);
}
}
return out;
}
@Test
public void testTrivialBLASTCallback() throws RemoteException {
final ActivityStack stackController1 = createTaskStackOnDisplay(mDisplayContent);
final Task task = createTaskInStack(stackController1, 0 /* userId */);
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)
.transactionReady(anyInt(), any());
}
@Test
public void testOverlappingBLASTCallback() throws RemoteException {
final ActivityStack stackController1 = createTaskStackOnDisplay(mDisplayContent);
final Task task = createTaskInStack(stackController1, 0 /* userId */);
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 = createTaskStackOnDisplay(mDisplayContent);
final Task task = createTaskInStack(stackController1, 0 /* userId */);
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())
.transactionReady(anyInt(), any());
w.finishDrawing(null);
verify(transactionListener)
.transactionReady(anyInt(), any());
}
@Test
public void testBLASTCallbackWithInvisibleWindow() {
final ActivityStack stackController1 = createTaskStackOnDisplay(mDisplayContent);
final Task task = createTaskInStack(stackController1, 0 /* userId */);
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)
.transactionReady(anyInt(), any());
}
@Test
public void testBLASTCallbackWithChildWindow() {
final ActivityStack stackController1 = createTaskStackOnDisplay(mDisplayContent);
final Task task = createTaskInStack(stackController1, 0 /* userId */);
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())
.transactionReady(anyInt(), any());
reset(transactionListener);
child.finishDrawing(null);
// Ah finally! Done
verify(transactionListener)
.transactionReady(anyInt(), any());
}
class StubOrganizer extends ITaskOrganizer.Stub {
RunningTaskInfo mInfo;
@Override
public void taskAppeared(RunningTaskInfo info) {
mInfo = info;
}
@Override
public void taskVanished(RunningTaskInfo info) {
}
@Override
public void transactionReady(int id, SurfaceControl.Transaction t) {
}
@Override
public void onTaskInfoChanged(RunningTaskInfo info) {
}
};
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());
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());
}
}