blob: 540741217ae8d47efc50a647b42860f9570a249b [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.ActivityManager.START_CANCELED;
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_UNDEFINED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.InsetsState.ITYPE_LOCAL_NAVIGATION_BAR_1;
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.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.ActivityRecord.State.RESUMED;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.WindowContainer.SYNC_STATE_READY;
import static com.google.common.truth.Truth.assertThat;
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 static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager.RootTaskInfo;
import android.app.IRequestFinishCallback;
import android.app.PictureInPictureParams;
import android.content.pm.ActivityInfo;
import android.content.pm.ParceledListSlice;
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.IWindowContainerTransactionCallback;
import android.window.StartingWindowInfo;
import android.window.StartingWindowRemovalInfo;
import android.window.TaskAppearedInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
import com.android.server.wm.TaskOrganizerController.PendingTaskEvent;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
import java.util.HashSet;
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 createMockOrganizer() {
final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
when(organizer.asBinder()).thenReturn(new Binder());
return organizer;
}
private ITaskOrganizer registerMockOrganizer(ArrayList<TaskAppearedInfo> existingTasks) {
final ITaskOrganizer organizer = createMockOrganizer();
ParceledListSlice<TaskAppearedInfo> tasks =
mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(organizer);
if (existingTasks != null) {
existingTasks.addAll(tasks.getList());
}
return organizer;
}
private ITaskOrganizer registerMockOrganizer() {
return registerMockOrganizer(null);
}
Task createTask(Task rootTask, boolean fakeDraw) {
final Task task = createTaskInRootTask(rootTask, 0);
if (fakeDraw) {
task.setHasBeenVisible(true);
}
return task;
}
Task createTask(Task rootTask) {
// Fake draw notifications for most of our tests.
return createTask(rootTask, true);
}
Task createRootTask() {
return createTask(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 ITaskOrganizer organizer = registerMockOrganizer();
final Task rootTask = createRootTask();
final Task task = createTask(rootTask);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
rootTask.removeImmediately();
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer).onTaskVanished(any());
}
@Test
public void testAppearWaitsForVisibility() throws RemoteException {
final ITaskOrganizer organizer = registerMockOrganizer();
final Task rootTask = createRootTask();
final Task task = createTask(rootTask, false);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer, never())
.onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
rootTask.setHasBeenVisible(true);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
assertTrue(rootTask.getHasBeenVisible());
verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
rootTask.removeImmediately();
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer).onTaskVanished(any());
}
@Test
public void testNoVanishedIfNoAppear() throws RemoteException {
final ITaskOrganizer organizer = registerMockOrganizer();
final Task rootTask = createRootTask();
final Task task = createTask(rootTask, false /* hasBeenVisible */);
// 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.
rootTask.setTaskOrganizer(organizer);
verify(organizer, never())
.onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
rootTask.removeImmediately();
verify(organizer, never()).onTaskVanished(any());
}
@Test
public void testTaskNoDraw() throws RemoteException {
final ITaskOrganizer organizer = registerMockOrganizer();
final Task rootTask = createRootTask();
final Task task = createTask(rootTask, false /* fakeDraw */);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer, never())
.onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
assertTrue(rootTask.isOrganized());
mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer, times(0)).onTaskVanished(any());
assertFalse(rootTask.isOrganized());
}
@Test
public void testClearOrganizer() throws RemoteException {
final ITaskOrganizer organizer = registerMockOrganizer();
final Task rootTask = createRootTask();
final Task task = createTask(rootTask);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
assertTrue(rootTask.isOrganized());
rootTask.setTaskOrganizer(null);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer).onTaskVanished(any());
assertFalse(rootTask.isOrganized());
}
@Test
public void testRemoveWithOrganizerRemovesTask() throws RemoteException {
final ITaskOrganizer organizer = registerMockOrganizer();
final Task rootTask = createRootTask();
final Task task = createTask(rootTask);
rootTask.mRemoveWithTaskOrganizer = true;
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
assertTrue(rootTask.isOrganized());
spyOn(mWm.mAtmService);
rootTask.setTaskOrganizer(null);
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(mWm.mAtmService).removeTask(eq(rootTask.mTaskId));
}
@Test
public void testNoRemoveWithOrganizerNoRemoveTask() throws RemoteException {
final ITaskOrganizer organizer = registerMockOrganizer();
final Task rootTask = createRootTask();
final Task task = createTask(rootTask);
rootTask.mRemoveWithTaskOrganizer = false;
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
assertTrue(rootTask.isOrganized());
spyOn(mWm.mAtmService);
rootTask.setTaskOrganizer(null);
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(mWm.mAtmService, never()).removeTask(eq(rootTask.mTaskId));
}
@Test
public void testUnregisterOrganizer() throws RemoteException {
final ITaskOrganizer organizer = registerMockOrganizer();
final Task rootTask = createRootTask();
final Task task = createTask(rootTask);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer).onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
assertTrue(rootTask.isOrganized());
mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer, times(0)).onTaskVanished(any());
assertFalse(rootTask.isOrganized());
}
@Test
public void testUnregisterOrganizerReturnsRegistrationToPrevious() throws RemoteException {
final Task rootTask = createRootTask();
final Task task = createTask(rootTask);
final Task rootTask2 = createRootTask();
final Task task2 = createTask(rootTask2);
final Task rootTask3 = createRootTask();
final Task task3 = createTask(rootTask3);
final ArrayList<TaskAppearedInfo> existingTasks = new ArrayList<>();
final ITaskOrganizer organizer = registerMockOrganizer(existingTasks);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
// verify that tasks are returned and taskAppeared is not called
assertContainsTasks(existingTasks, rootTask, rootTask2, rootTask3);
verify(organizer, times(0)).onTaskAppeared(any(RunningTaskInfo.class),
any(SurfaceControl.class));
verify(organizer, times(0)).onTaskVanished(any());
assertTrue(rootTask.isOrganized());
// Now we replace the registration and verify the new organizer receives existing tasks
final ArrayList<TaskAppearedInfo> existingTasks2 = new ArrayList<>();
final ITaskOrganizer organizer2 = registerMockOrganizer(existingTasks2);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
assertContainsTasks(existingTasks2, rootTask, rootTask2, rootTask3);
verify(organizer2, times(0)).onTaskAppeared(any(RunningTaskInfo.class),
any(SurfaceControl.class));
verify(organizer2, times(0)).onTaskVanished(any());
// Removed tasks from the original organizer
assertTaskVanished(organizer, true /* expectVanished */, rootTask, rootTask2, rootTask3);
assertTrue(rootTask2.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);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer, times(3))
.onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
verify(organizer2, times(0)).onTaskVanished(any());
}
@Test
public void testUnregisterOrganizer_removesTasksCreatedByIt() throws RemoteException {
final Task rootTask = createRootTask();
final Task task = createTask(rootTask);
final Task rootTask2 = createRootTask();
rootTask2.mCreatedByOrganizer = true;
final Task task2 = createTask(rootTask2);
final ArrayList<TaskAppearedInfo> existingTasks = new ArrayList<>();
final ITaskOrganizer organizer = registerMockOrganizer(existingTasks);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
// verify that tasks are returned and taskAppeared is called only for rootTask2 since it
// is the one created by this organizer.
assertContainsTasks(existingTasks, rootTask);
verify(organizer, times(1)).onTaskAppeared(any(RunningTaskInfo.class),
any(SurfaceControl.class));
verify(organizer, times(0)).onTaskVanished(any());
assertTrue(rootTask.isOrganized());
// Now we replace the registration and verify the new organizer receives existing tasks
final ArrayList<TaskAppearedInfo> existingTasks2 = new ArrayList<>();
final ITaskOrganizer organizer2 = registerMockOrganizer(existingTasks2);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
assertContainsTasks(existingTasks2, rootTask);
verify(organizer2, never()).onTaskAppeared(any(RunningTaskInfo.class),
any(SurfaceControl.class));
verify(organizer2, times(0)).onTaskVanished(any());
// The non-CreatedByOrganizer task is removed from the original organizer.
assertTaskVanished(organizer, true /* expectVanished */, rootTask);
assertEquals(organizer2, rootTask.mTaskOrganizer);
// The CreatedByOrganizer task should be still organized by the original organizer.
assertEquals(organizer, rootTask2.mTaskOrganizer);
clearInvocations(organizer);
// 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);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer, times(2))
.onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
// Unregister the first one. The CreatedByOrganizer task created by it must be removed.
mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer);
assertFalse(rootTask2.isAttached());
assertFalse(task2.isAttached());
// Normal task should keep.
assertTrue(task.isAttached());
verify(organizer2, times(0)).onTaskVanished(any());
}
@Test
public void testOrganizerDeathReturnsRegistrationToPrevious() throws RemoteException {
final Task rootTask = createRootTask();
final Task task = createTask(rootTask);
final Task rootTask2 = createRootTask();
final Task task2 = createTask(rootTask2);
final Task rootTask3 = createRootTask();
final Task task3 = createTask(rootTask3);
final ArrayList<TaskAppearedInfo> existingTasks = new ArrayList<>();
final ITaskOrganizer organizer = registerMockOrganizer(existingTasks);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
// verify that tasks are returned and taskAppeared is not called
assertContainsTasks(existingTasks, rootTask, rootTask2, rootTask3);
verify(organizer, times(0)).onTaskAppeared(any(RunningTaskInfo.class),
any(SurfaceControl.class));
verify(organizer, times(0)).onTaskVanished(any());
assertTrue(rootTask.isOrganized());
// Now we replace the registration and verify the new organizer receives existing tasks
final ArrayList<TaskAppearedInfo> existingTasks2 = new ArrayList<>();
final ITaskOrganizer organizer2 = registerMockOrganizer(existingTasks2);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
assertContainsTasks(existingTasks2, rootTask, rootTask2, rootTask3);
verify(organizer2, times(0)).onTaskAppeared(any(RunningTaskInfo.class),
any(SurfaceControl.class));
verify(organizer2, times(0)).onTaskVanished(any());
// Removed tasks from the original organizer
assertTaskVanished(organizer, true /* expectVanished */, rootTask, rootTask2, rootTask3);
assertTrue(rootTask2.isOrganized());
// Trigger binderDied for second one, the first one should automatically be reregistered
// so we verify that it's now seeing changes.
mWm.mAtmService.mTaskOrganizerController.getTaskOrganizerState(organizer2.asBinder())
.getDeathRecipient().binderDied();
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer, times(3))
.onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
verify(organizer2, times(0)).onTaskVanished(any());
}
@Test
public void testRegisterTaskOrganizerWithExistingTasks() throws RemoteException {
final Task rootTask = createRootTask();
final Task task = createTask(rootTask);
final Task rootTask2 = createRootTask();
final Task task2 = createTask(rootTask2);
ArrayList<TaskAppearedInfo> existingTasks = new ArrayList<>();
final ITaskOrganizer organizer = registerMockOrganizer(existingTasks);
assertContainsTasks(existingTasks, rootTask, rootTask2);
// Verify we don't get onTaskAppeared if we are returned the tasks
verify(organizer, never())
.onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
}
@Test
public void testRegisterTaskOrganizerWithExistingTasks_noSurfaceControl()
throws RemoteException {
final Task rootTask = createRootTask();
final Task task = createTask(rootTask);
final Task rootTask2 = createRootTask();
final Task task2 = createTask(rootTask2);
rootTask2.setSurfaceControl(null);
ArrayList<TaskAppearedInfo> existingTasks = new ArrayList<>();
final ITaskOrganizer organizer = registerMockOrganizer(existingTasks);
assertContainsTasks(existingTasks, rootTask);
// Verify we don't get onTaskAppeared if we are returned the tasks
verify(organizer, never())
.onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
}
@Test
public void testTaskTransaction() {
removeGlobalMinSizeRestriction();
final Task rootTask = new TaskBuilder(mSupervisor)
.setWindowingMode(WINDOWING_MODE_FREEFORM).build();
final Task task = rootTask.getTopMostTask();
testTransaction(task);
}
@Test
public void testRootTaskTransaction() {
removeGlobalMinSizeRestriction();
final Task rootTask = new TaskBuilder(mSupervisor)
.setWindowingMode(WINDOWING_MODE_FREEFORM).build();
RootTaskInfo info =
mWm.mAtmService.getRootTaskInfo(WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
assertEquals(rootTask.mRemoteToken.toWindowContainerToken(), info.token);
testTransaction(rootTask);
}
@Test
public void testDisplayAreaTransaction() {
removeGlobalMinSizeRestriction();
final DisplayArea displayArea = mDisplayContent.getDefaultTaskDisplayArea();
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 Task rootTask = new TaskBuilder(mSupervisor)
.setWindowingMode(WINDOWING_MODE_FREEFORM).build();
testSetWindowingMode(rootTask);
final DisplayArea displayArea = mDisplayContent.getDefaultTaskDisplayArea();
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 Task rootTask = record.getRootTask();
final WindowContainerTransaction t = new WindowContainerTransaction();
t.setWindowingMode(rootTask.mRemoteToken.toWindowContainerToken(), WINDOWING_MODE_PINNED);
t.setActivityWindowingMode(
rootTask.mRemoteToken.toWindowContainerToken(), WINDOWING_MODE_FULLSCREEN);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
assertEquals(WINDOWING_MODE_FULLSCREEN, record.getWindowingMode());
// Get the root task from the PIP activity record again, since the PIP root task may have
// changed when the activity entered PIP mode.
final Task pipRootTask = record.getRootTask();
assertEquals(WINDOWING_MODE_PINNED, pipRootTask.getWindowingMode());
}
@Test
public void testContainerFocusableChanges() {
removeGlobalMinSizeRestriction();
final Task rootTask = new TaskBuilder(mSupervisor)
.setWindowingMode(WINDOWING_MODE_FREEFORM).build();
final Task task = rootTask.getTopMostTask();
WindowContainerTransaction t = new WindowContainerTransaction();
assertTrue(task.isFocusable());
t.setFocusable(rootTask.mRemoteToken.toWindowContainerToken(), false);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
assertFalse(task.isFocusable());
t.setFocusable(rootTask.mRemoteToken.toWindowContainerToken(), true);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
assertTrue(task.isFocusable());
}
@Test
public void testContainerHiddenChanges() {
removeGlobalMinSizeRestriction();
final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
.setWindowingMode(WINDOWING_MODE_FREEFORM).build();
WindowContainerTransaction t = new WindowContainerTransaction();
assertTrue(rootTask.shouldBeVisible(null));
t.setHidden(rootTask.mRemoteToken.toWindowContainerToken(), true);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
assertFalse(rootTask.shouldBeVisible(null));
t.setHidden(rootTask.mRemoteToken.toWindowContainerToken(), false);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
assertTrue(rootTask.shouldBeVisible(null));
}
@Test
public void testSetIgnoreOrientationRequest_taskDisplayArea() {
removeGlobalMinSizeRestriction();
final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
final Task rootTask = taskDisplayArea.createRootTask(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(rootTask).build();
taskDisplayArea.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
mDisplayContent.setFocusedApp(activity);
activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
// TDA returns UNSET when ignoreOrientationRequest == true
// DC is UNSPECIFIED when child returns UNSET
assertThat(taskDisplayArea.getOrientation()).isEqualTo(SCREEN_ORIENTATION_UNSET);
assertThat(mDisplayContent.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_UNSPECIFIED);
WindowContainerTransaction t = new WindowContainerTransaction();
t.setIgnoreOrientationRequest(
taskDisplayArea.mRemoteToken.toWindowContainerToken(),
false /* ignoreOrientationRequest */);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
// TDA returns app request orientation when ignoreOrientationRequest == false
// DC uses the same as TDA returns when it is not UNSET.
assertThat(mDisplayContent.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_LANDSCAPE);
assertThat(taskDisplayArea.getOrientation()).isEqualTo(SCREEN_ORIENTATION_LANDSCAPE);
t.setIgnoreOrientationRequest(
taskDisplayArea.mRemoteToken.toWindowContainerToken(),
true /* ignoreOrientationRequest */);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
// TDA returns UNSET when ignoreOrientationRequest == true
// DC is UNSPECIFIED when child returns UNSET
assertThat(taskDisplayArea.getOrientation()).isEqualTo(SCREEN_ORIENTATION_UNSET);
assertThat(mDisplayContent.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_UNSPECIFIED);
}
@Test
public void testSetIgnoreOrientationRequest_displayContent() {
removeGlobalMinSizeRestriction();
final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
final Task rootTask = taskDisplayArea.createRootTask(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(rootTask).build();
mDisplayContent.setFocusedApp(activity);
activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
// DC uses the orientation request from app
assertThat(mDisplayContent.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_LANDSCAPE);
WindowContainerTransaction t = new WindowContainerTransaction();
t.setIgnoreOrientationRequest(
mDisplayContent.mRemoteToken.toWindowContainerToken(),
true /* ignoreOrientationRequest */);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
// DC returns UNSPECIFIED when ignoreOrientationRequest == true
assertThat(mDisplayContent.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_UNSPECIFIED);
t.setIgnoreOrientationRequest(
mDisplayContent.mRemoteToken.toWindowContainerToken(),
false /* ignoreOrientationRequest */);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
// DC uses the orientation request from app after mIgnoreOrientationRequest is set to false
assertThat(mDisplayContent.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_LANDSCAPE);
}
@Test
public void testOverrideConfigSize() {
removeGlobalMinSizeRestriction();
final Task rootTask = new TaskBuilder(mSupervisor)
.setWindowingMode(WINDOWING_MODE_FREEFORM).build();
final Task task = rootTask.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(rootTask.mRemoteToken
.toWindowContainerToken(), origScreenWDp, origScreenHDp / 2);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
assertEquals(origScreenHDp / 2, task.getConfiguration().screenHeightDp);
t = new WindowContainerTransaction();
t.setScreenSizeDp(rootTask.mRemoteToken.toWindowContainerToken(), SCREEN_WIDTH_DP_UNDEFINED,
SCREEN_HEIGHT_DP_UNDEFINED);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
assertEquals(origScreenHDp, task.getConfiguration().screenHeightDp);
}
@Test
public void testCreateDeleteRootTasks() {
DisplayContent dc = mWm.mRoot.getDisplayContent(Display.DEFAULT_DISPLAY);
Task task1 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
dc, WINDOWING_MODE_FULLSCREEN, null);
RunningTaskInfo info1 = task1.getTaskInfo();
assertEquals(WINDOWING_MODE_FULLSCREEN,
info1.configuration.windowConfiguration.getWindowingMode());
assertEquals(ACTIVITY_TYPE_UNDEFINED, info1.topActivityType);
Task task2 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
dc, WINDOWING_MODE_MULTI_WINDOW, null);
RunningTaskInfo info2 = task2.getTaskInfo();
assertEquals(WINDOWING_MODE_MULTI_WINDOW,
info2.configuration.windowConfiguration.getWindowingMode());
assertEquals(ACTIVITY_TYPE_UNDEFINED, info2.topActivityType);
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_MULTI_WINDOW, infos.get(0).getWindowingMode());
}
@Test
public void testSetAdjacentLaunchRoot() {
DisplayContent dc = mWm.mRoot.getDisplayContent(Display.DEFAULT_DISPLAY);
final Task task1 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
dc, WINDOWING_MODE_MULTI_WINDOW, null);
final RunningTaskInfo info1 = task1.getTaskInfo();
final Task task2 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
dc, WINDOWING_MODE_MULTI_WINDOW, null);
final RunningTaskInfo info2 = task2.getTaskInfo();
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setAdjacentRoots(info1.token, info2.token);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
assertEquals(task1.getAdjacentTaskFragment(), task2);
assertEquals(task2.getAdjacentTaskFragment(), task1);
wct = new WindowContainerTransaction();
wct.setLaunchAdjacentFlagRoot(info1.token);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
assertEquals(dc.getDefaultTaskDisplayArea().mLaunchAdjacentFlagRootTask, task1);
task1.setAdjacentTaskFragment(null);
task2.setAdjacentTaskFragment(null);
wct = new WindowContainerTransaction();
wct.clearLaunchAdjacentFlagRoot(info1.token);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
assertEquals(dc.getDefaultTaskDisplayArea().mLaunchAdjacentFlagRootTask, null);
}
@Test
public void testTileAddRemoveChild() {
final StubOrganizer listener = new StubOrganizer();
mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener);
Task task = mWm.mAtmService.mTaskOrganizerController.createRootTask(
mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, null);
RunningTaskInfo info1 = task.getTaskInfo();
final Task rootTask = createTask(
mDisplayContent, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD);
assertEquals(mDisplayContent.getWindowingMode(), rootTask.getWindowingMode());
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reparent(rootTask.mRemoteToken.toWindowContainerToken(), info1.token, true /* onTop */);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
assertEquals(info1.configuration.windowConfiguration.getWindowingMode(),
rootTask.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(rootTask).adjustForMinimalTaskDimensions(any(), any(), any());
task1.onRequestedOverrideConfigurationChanged(c);
assertEquals(newSize, rootTask.getBounds());
wct = new WindowContainerTransaction();
wct.reparent(rootTask.mRemoteToken.toWindowContainerToken(), null, true /* onTop */);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
assertEquals(mDisplayContent.getWindowingMode(), rootTask.getWindowingMode());
infos = getTasksCreatedByOrganizer(mDisplayContent);
info1 = infos.get(0).getTaskInfo();
assertEquals(ACTIVITY_TYPE_UNDEFINED, info1.topActivityType);
}
@Test
public void testAddRectInsetsProvider() {
final Task rootTask = createTask(mDisplayContent);
final Task navigationBarInsetsReceiverTask = createTaskInRootTask(rootTask, 0);
navigationBarInsetsReceiverTask.getConfiguration().windowConfiguration.setBounds(new Rect(
0, 200, 1080, 700));
final Rect navigationBarInsetsProviderRect = new Rect(0, 0, 1080, 200);
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.addRectInsetsProvider(navigationBarInsetsReceiverTask.mRemoteToken
.toWindowContainerToken(), navigationBarInsetsProviderRect,
new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
assertThat(navigationBarInsetsReceiverTask.mLocalInsetsSourceProviders
.valueAt(0).getSource().getType()).isEqualTo(ITYPE_LOCAL_NAVIGATION_BAR_1);
}
@Test
public void testRemoveInsetsProvider() {
final Task rootTask = createTask(mDisplayContent);
final Task navigationBarInsetsReceiverTask = createTaskInRootTask(rootTask, 0);
navigationBarInsetsReceiverTask.getConfiguration().windowConfiguration.setBounds(new Rect(
0, 200, 1080, 700));
final Rect navigationBarInsetsProviderRect = new Rect(0, 0, 1080, 200);
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.addRectInsetsProvider(navigationBarInsetsReceiverTask.mRemoteToken
.toWindowContainerToken(), navigationBarInsetsProviderRect,
new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
final WindowContainerTransaction wct2 = new WindowContainerTransaction();
wct2.removeInsetsProvider(navigationBarInsetsReceiverTask.mRemoteToken
.toWindowContainerToken(), new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct2);
assertThat(navigationBarInsetsReceiverTask.mLocalInsetsSourceProviders.size()).isEqualTo(0);
}
@UseTestDisplay
@Test
public void testTaskInfoCallback() {
final ArrayList<RunningTaskInfo> lastReportedTiles = new ArrayList<>();
final boolean[] called = {false};
final StubOrganizer listener = new StubOrganizer() {
@Override
public void onTaskInfoChanged(RunningTaskInfo info) {
lastReportedTiles.add(info);
called[0] = true;
}
};
mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener);
Task task = mWm.mAtmService.mTaskOrganizerController.createRootTask(
mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, null);
RunningTaskInfo info1 = task.getTaskInfo();
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
lastReportedTiles.clear();
called[0] = false;
final Task rootTask = createTask(
mDisplayContent, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD);
Task task1 = WindowContainer.fromBinder(info1.token.asBinder()).asTask();
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reparent(rootTask.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 Task rootTask2 = createTask(
mDisplayContent, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
wct = new WindowContainerTransaction();
wct.reparent(rootTask2.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, rootTask, false /* includingParents */);
assertTrue(called[0]);
assertEquals(ACTIVITY_TYPE_STANDARD, lastReportedTiles.get(0).topActivityType);
lastReportedTiles.clear();
called[0] = false;
wct = new WindowContainerTransaction();
wct.reparent(rootTask.mRemoteToken.toWindowContainerToken(),
null, true /* onTop */);
wct.reparent(rootTask2.mRemoteToken.toWindowContainerToken(),
null, true /* onTop */);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
assertTrue(called[0]);
assertEquals(ACTIVITY_TYPE_UNDEFINED, lastReportedTiles.get(0).topActivityType);
}
@UseTestDisplay
@Test
public void testHierarchyTransaction() {
final ArrayMap<IBinder, RunningTaskInfo> lastReportedTiles = new ArrayMap<>();
final StubOrganizer listener = new StubOrganizer() {
@Override
public void onTaskInfoChanged(RunningTaskInfo info) {
lastReportedTiles.put(info.token.asBinder(), info);
}
};
mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener);
Task task1 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, null);
RunningTaskInfo info1 = task1.getTaskInfo();
Task task2 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, null);
RunningTaskInfo info2 = task2.getTaskInfo();
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
final int initialRootTaskCount = mWm.mAtmService.mTaskOrganizerController.getRootTasks(
mDisplayContent.mDisplayId, null /* activityTypes */).size();
final Task rootTask = createTask(
mDisplayContent, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD);
final Task rootTask2 = createTask(
mDisplayContent, WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
// 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(rootTask.mRemoteToken.toWindowContainerToken(),
info1.token, true /* onTop */);
wct.reparent(rootTask2.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(rootTask2.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(rootTask2.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);
// This just needs to not crash (ie. it should be possible to reparent to display twice)
wct = new WindowContainerTransaction();
wct.reparent(rootTask2.mRemoteToken.toWindowContainerToken(), null, true /* onTop */);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
wct = new WindowContainerTransaction();
wct.reparent(rootTask2.mRemoteToken.toWindowContainerToken(), null, true /* onTop */);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
}
private List<Task> getTasksCreatedByOrganizer(DisplayContent dc) {
final ArrayList<Task> out = new ArrayList<>();
dc.forAllRootTasks(task -> {
if (task.mCreatedByOrganizer) {
out.add(task);
}
});
return out;
}
@Test
public void testBLASTCallbackWithActivityChildren() {
final Task rootTaskController1 = createRootTask();
final Task task = createTask(rootTaskController1);
final WindowState w = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window");
w.mActivityRecord.mVisibleRequested = true;
w.mActivityRecord.setVisible(true);
BLASTSyncEngine bse = new BLASTSyncEngine(mWm);
BLASTSyncEngine.TransactionReadyListener transactionListener =
mock(BLASTSyncEngine.TransactionReadyListener.class);
int id = bse.startSyncSet(transactionListener);
bse.addToSyncSet(id, task);
bse.setReady(id);
bse.onSurfacePlacement();
// Even though w is invisible (and thus activity isn't waiting on it), activity will
// continue to wait until it has at-least 1 visible window.
// Since we have a child window we still shouldn't be done.
verify(transactionListener, never()).onTransactionReady(anyInt(), any());
makeWindowVisible(w);
bse.onSurfacePlacement();
w.immediatelyNotifyBlastSync();
bse.onSurfacePlacement();
verify(transactionListener).onTransactionReady(anyInt(), any());
}
static class StubOrganizer extends ITaskOrganizer.Stub {
RunningTaskInfo mInfo;
@Override
public void addStartingWindow(StartingWindowInfo info, IBinder appToken) { }
@Override
public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) { }
@Override
public void copySplashScreenView(int taskId) { }
@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) {
}
@Override
public void onImeDrawnOnTask(int taskId) throws RemoteException {
}
@Override
public void onAppSplashScreenViewRemoved(int taskId) {
}
};
private ActivityRecord makePipableActivity() {
final ActivityRecord record = createActivityRecordWithParentTask(mDisplayContent,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
record.info.flags |= ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE;
record.setPictureInPictureParams(new PictureInPictureParams.Builder()
.setAutoEnterEnabled(true).build());
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);
final ActivityRecord record = makePipableActivity();
final PictureInPictureParams p = new PictureInPictureParams.Builder()
.setAspectRatio(new Rational(1, 2)).build();
assertTrue(mWm.mAtmService.mActivityClientController.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);
final ActivityRecord record = makePipableActivity();
final PictureInPictureParams p = new PictureInPictureParams.Builder()
.setAspectRatio(new Rational(1, 2)).build();
assertTrue(mWm.mAtmService.mActivityClientController.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.mActivityClientController.setPictureInPictureParams(record.token, p2);
waitUntilHandlersIdle();
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
assertNotNull(o.mChangedInfo);
assertNotNull(o.mChangedInfo.pictureInPictureParams);
final Rational ratio = o.mChangedInfo.pictureInPictureParams.getAspectRatio();
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);
final Task rootTask = createRootTask();
final Task task = createTask(rootTask);
final ActivityRecord record = createActivityRecord(rootTask.mDisplayContent, task);
rootTask.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 ITaskOrganizer organizer = registerMockOrganizer();
final Task rootTask = createRootTask();
final Task task = createTask(rootTask, false /* fakeDraw */);
rootTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
rootTask.setTaskOrganizer(organizer);
// setHasBeenVisible was already called once by the set-up code.
rootTask.setHasBeenVisible(true);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer, times(1))
.onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
rootTask.setTaskOrganizer(null);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer, times(1)).onTaskVanished(any());
rootTask.setTaskOrganizer(organizer);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer, times(2))
.onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
rootTask.removeImmediately();
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer, times(2)).onTaskVanished(any());
}
@Test
public void testInterceptBackPressedOnTaskRoot() throws RemoteException {
final ITaskOrganizer organizer = registerMockOrganizer();
final Task rootTask = createRootTask();
final Task task = createTask(rootTask);
final ActivityRecord activity = createActivityRecord(rootTask.mDisplayContent, task);
final Task rootTask2 = createRootTask();
final Task task2 = createTask(rootTask2);
final ActivityRecord activity2 = createActivityRecord(rootTask.mDisplayContent, task2);
assertTrue(rootTask.isOrganized());
assertTrue(rootTask2.isOrganized());
// Verify a back pressed does not call the organizer
mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token,
new IRequestFinishCallback.Default());
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer, never()).onBackPressedOnTaskRoot(any());
// Enable intercepting back
mWm.mAtmService.mTaskOrganizerController.setInterceptBackPressedOnTaskRoot(
rootTask.mRemoteToken.toWindowContainerToken(), true);
// Verify now that the back press does call the organizer
mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token,
new IRequestFinishCallback.Default());
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer, times(1)).onBackPressedOnTaskRoot(any());
// Disable intercepting back
mWm.mAtmService.mTaskOrganizerController.setInterceptBackPressedOnTaskRoot(
rootTask.mRemoteToken.toWindowContainerToken(), false);
// Verify now that the back press no longer calls the organizer
mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token,
new IRequestFinishCallback.Default());
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer, times(1)).onBackPressedOnTaskRoot(any());
}
@Test
public void testBLASTCallbackWithWindows() throws Exception {
final Task rootTaskController = createRootTask();
final Task task = createTask(rootTaskController);
final WindowState w1 = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window 1");
final WindowState w2 = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window 2");
makeWindowVisible(w1);
makeWindowVisible(w2);
IWindowContainerTransactionCallback mockCallback =
mock(IWindowContainerTransactionCallback.class);
int id = mWm.mAtmService.mWindowOrganizerController.startSyncWithOrganizer(mockCallback);
mWm.mAtmService.mWindowOrganizerController.addToSyncSet(id, task);
mWm.mAtmService.mWindowOrganizerController.setSyncReady(id);
// Since we have a window we have to wait for it to draw to finish sync.
verify(mockCallback, never()).onTransactionReady(anyInt(), any());
assertTrue(w1.useBLASTSync());
assertTrue(w2.useBLASTSync());
// Make second (bottom) ready. If we started with the top, since activities fillsParent
// by default, the sync would be considered finished.
w2.immediatelyNotifyBlastSync();
mWm.mSyncEngine.onSurfacePlacement();
verify(mockCallback, never()).onTransactionReady(anyInt(), any());
assertEquals(SYNC_STATE_READY, w2.mSyncState);
// Even though one Window finished drawing, both windows should still be using blast sync
assertTrue(w1.useBLASTSync());
assertTrue(w2.useBLASTSync());
w1.immediatelyNotifyBlastSync();
mWm.mSyncEngine.onSurfacePlacement();
verify(mockCallback).onTransactionReady(anyInt(), any());
assertFalse(w1.useBLASTSync());
assertFalse(w2.useBLASTSync());
}
@Test
public void testDisplayAreaHiddenTransaction() {
removeGlobalMinSizeRestriction();
WindowContainerTransaction trx = new WindowContainerTransaction();
TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
trx.setHidden(taskDisplayArea.mRemoteToken.toWindowContainerToken(), true);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(trx);
taskDisplayArea.forAllTasks(daTask -> {
assertTrue(daTask.isForceHidden());
});
trx.setHidden(taskDisplayArea.mRemoteToken.toWindowContainerToken(), false);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(trx);
taskDisplayArea.forAllTasks(daTask -> {
assertFalse(daTask.isForceHidden());
});
}
@Test
public void testReparentToOrganizedTask() {
final ITaskOrganizer organizer = registerMockOrganizer();
Task rootTask = mWm.mAtmService.mTaskOrganizerController.createRootTask(
mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, null);
final Task task1 = createRootTask();
final Task task2 = createTask(rootTask, false /* fakeDraw */);
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reparent(task1.mRemoteToken.toWindowContainerToken(),
rootTask.mRemoteToken.toWindowContainerToken(), true /* onTop */);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
assertTrue(task1.isOrganized());
assertTrue(task2.isOrganized());
}
@Test
public void testAppearDeferThenInfoChange() {
final ITaskOrganizer organizer = registerMockOrganizer();
final Task rootTask = createRootTask();
// Assume layout defer
mWm.mWindowPlacerLocked.deferLayout();
final Task task = createTask(rootTask);
final ActivityRecord record = createActivityRecord(rootTask.mDisplayContent, task);
rootTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription"));
waitUntilHandlersIdle();
ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask);
assertEquals(1, pendingEvents.size());
assertEquals(PendingTaskEvent.EVENT_APPEARED, pendingEvents.get(0).mEventType);
assertEquals("TestDescription",
pendingEvents.get(0).mTask.getTaskInfo().taskDescription.getLabel());
}
@Test
public void testAppearDeferThenVanish() {
final ITaskOrganizer organizer = registerMockOrganizer();
final Task rootTask = createRootTask();
// Assume layout defer
mWm.mWindowPlacerLocked.deferLayout();
final Task task = createTask(rootTask);
rootTask.removeImmediately();
waitUntilHandlersIdle();
ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask);
assertEquals(0, pendingEvents.size());
}
@Test
public void testInfoChangeDeferMultiple() {
final ITaskOrganizer organizer = registerMockOrganizer();
final Task rootTask = createRootTask();
final Task task = createTask(rootTask);
final ActivityRecord record = createActivityRecord(rootTask.mDisplayContent, task);
// Assume layout defer
mWm.mWindowPlacerLocked.deferLayout();
rootTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription"));
waitUntilHandlersIdle();
ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask);
assertEquals(1, pendingEvents.size());
assertEquals(PendingTaskEvent.EVENT_INFO_CHANGED, pendingEvents.get(0).mEventType);
assertEquals("TestDescription",
pendingEvents.get(0).mTask.getTaskInfo().taskDescription.getLabel());
record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription2"));
waitUntilHandlersIdle();
pendingEvents = getTaskPendingEvent(organizer, rootTask);
assertEquals(1, pendingEvents.size());
assertEquals(PendingTaskEvent.EVENT_INFO_CHANGED, pendingEvents.get(0).mEventType);
assertEquals("TestDescription2",
pendingEvents.get(0).mTask.getTaskInfo().taskDescription.getLabel());
}
@Test
public void testInfoChangDeferThenVanish() {
final ITaskOrganizer organizer = registerMockOrganizer();
final Task rootTask = createRootTask();
final Task task = createTask(rootTask);
final ActivityRecord record = createActivityRecord(rootTask.mDisplayContent, task);
// Assume layout defer
mWm.mWindowPlacerLocked.deferLayout();
rootTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription"));
rootTask.removeImmediately();
waitUntilHandlersIdle();
ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask);
assertEquals(1, pendingEvents.size());
assertEquals(PendingTaskEvent.EVENT_VANISHED, pendingEvents.get(0).mEventType);
assertEquals("TestDescription",
pendingEvents.get(0).mTask.getTaskInfo().taskDescription.getLabel());
}
@Test
public void testVanishDeferThenInfoChange() {
final ITaskOrganizer organizer = registerMockOrganizer();
final Task rootTask = createRootTask();
final Task task = createTask(rootTask);
final ActivityRecord record = createActivityRecord(rootTask.mDisplayContent, task);
// Assume layout defer
mWm.mWindowPlacerLocked.deferLayout();
rootTask.removeImmediately();
rootTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
waitUntilHandlersIdle();
ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask);
assertEquals(1, pendingEvents.size());
assertEquals(PendingTaskEvent.EVENT_VANISHED, pendingEvents.get(0).mEventType);
}
@Test
public void testVanishDeferThenBackOnRoot() {
final ITaskOrganizer organizer = registerMockOrganizer();
final Task rootTask = createRootTask();
final Task task = createTask(rootTask);
final ActivityRecord record = createActivityRecord(rootTask.mDisplayContent, task);
// Assume layout defer
mWm.mWindowPlacerLocked.deferLayout();
rootTask.removeImmediately();
mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(record.token,
new IRequestFinishCallback.Default());
waitUntilHandlersIdle();
ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask);
assertEquals(1, pendingEvents.size());
assertEquals(PendingTaskEvent.EVENT_VANISHED, pendingEvents.get(0).mEventType);
}
private ArrayList<PendingTaskEvent> getTaskPendingEvent(ITaskOrganizer organizer, Task task) {
ArrayList<PendingTaskEvent> total =
mWm.mAtmService.mTaskOrganizerController
.getTaskOrganizerPendingEvents(organizer.asBinder())
.getPendingEventList();
ArrayList<PendingTaskEvent> result = new ArrayList();
for (int i = 0; i < total.size(); i++) {
PendingTaskEvent entry = total.get(i);
if (entry.mTask.mTaskId == task.mTaskId) {
result.add(entry);
}
}
return result;
}
@Test
public void testReparentNonResizableTaskToSplitScreen() {
final ActivityRecord activity = new ActivityBuilder(mAtm)
.setCreateTask(true)
.setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE)
.setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
.build();
final Task rootTask = activity.getRootTask();
rootTask.setResizeMode(activity.info.resizeMode);
final Task splitPrimaryRootTask = mWm.mAtmService.mTaskOrganizerController.createRootTask(
mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, null);
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reparent(rootTask.mRemoteToken.toWindowContainerToken(),
splitPrimaryRootTask.mRemoteToken.toWindowContainerToken(), true /* onTop */);
// Can't reparent non-resizable to split screen
mAtm.mSupportsNonResizableMultiWindow = -1;
mAtm.mWindowOrganizerController.applyTransaction(wct);
assertEquals(rootTask, activity.getRootTask());
// Allow reparent non-resizable to split screen
mAtm.mSupportsNonResizableMultiWindow = 1;
mAtm.mWindowOrganizerController.applyTransaction(wct);
assertEquals(splitPrimaryRootTask, activity.getRootTask());
}
@Test
public void testSizeCompatModeChangedOnFirstOrganizedTask() throws RemoteException {
final ITaskOrganizer organizer = registerMockOrganizer();
final Task rootTask = createRootTask();
final Task task = createTask(rootTask);
final ActivityRecord activity = createActivityRecord(rootTask.mDisplayContent, task);
final ArgumentCaptor<RunningTaskInfo> infoCaptor =
ArgumentCaptor.forClass(RunningTaskInfo.class);
assertTrue(rootTask.isOrganized());
spyOn(activity);
doReturn(true).when(activity).inSizeCompatMode();
doReturn(true).when(activity).isState(RESUMED);
// Ensure task info show top activity in size compat.
rootTask.onSizeCompatActivityChanged();
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer).onTaskInfoChanged(infoCaptor.capture());
RunningTaskInfo info = infoCaptor.getValue();
assertEquals(rootTask.mTaskId, info.taskId);
assertTrue(info.topActivityInSizeCompat);
// Ensure task info show top activity that is not in foreground as not in size compat.
clearInvocations(organizer);
doReturn(false).when(activity).isState(RESUMED);
rootTask.onSizeCompatActivityChanged();
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer).onTaskInfoChanged(infoCaptor.capture());
info = infoCaptor.getValue();
assertEquals(rootTask.mTaskId, info.taskId);
assertFalse(info.topActivityInSizeCompat);
// Ensure task info show non size compat top activity as not in size compat.
clearInvocations(organizer);
doReturn(true).when(activity).isState(RESUMED);
doReturn(false).when(activity).inSizeCompatMode();
rootTask.onSizeCompatActivityChanged();
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer).onTaskInfoChanged(infoCaptor.capture());
info = infoCaptor.getValue();
assertEquals(rootTask.mTaskId, info.taskId);
assertFalse(info.topActivityInSizeCompat);
}
@Test
public void testStartTasksInTransaction() {
WindowContainerTransaction wct = new WindowContainerTransaction();
ActivityOptions testOptions = ActivityOptions.makeBasic();
testOptions.setTransientLaunch();
wct.startTask(1, null /* options */);
wct.startTask(2, testOptions.toBundle());
spyOn(mWm.mAtmService.mTaskSupervisor);
doReturn(START_CANCELED).when(mWm.mAtmService.mTaskSupervisor).startActivityFromRecents(
anyInt(), anyInt(), anyInt(), any());
clearInvocations(mWm.mAtmService);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
verify(mWm.mAtmService.mTaskSupervisor, times(1)).startActivityFromRecents(
anyInt(), anyInt(), eq(1), any());
final ArgumentCaptor<SafeActivityOptions> optionsCaptor =
ArgumentCaptor.forClass(SafeActivityOptions.class);
verify(mWm.mAtmService.mTaskSupervisor, times(1)).startActivityFromRecents(
anyInt(), anyInt(), eq(2), optionsCaptor.capture());
assertTrue(optionsCaptor.getValue().getOriginalOptions().getTransientLaunch());
}
@Test
public void testResumeTopsWhenLeavingPinned() {
final ActivityRecord record = makePipableActivity();
final Task rootTask = record.getRootTask();
clearInvocations(mWm.mAtmService.mRootWindowContainer);
final WindowContainerTransaction t = new WindowContainerTransaction();
WindowContainerToken wct = rootTask.mRemoteToken.toWindowContainerToken();
t.setWindowingMode(wct, WINDOWING_MODE_PINNED);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivities();
clearInvocations(mWm.mAtmService.mRootWindowContainer);
// The token for the PIP root task may have changed when the task entered PIP mode, so do
// not reuse the one from above.
final WindowContainerToken newToken =
record.getRootTask().mRemoteToken.toWindowContainerToken();
t.setWindowingMode(newToken, WINDOWING_MODE_FULLSCREEN);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivities();
}
/**
* Verifies that task vanished is called for a specific task.
*/
private void assertTaskVanished(ITaskOrganizer organizer, boolean expectVanished, Task... tasks)
throws RemoteException {
ArgumentCaptor<RunningTaskInfo> arg = ArgumentCaptor.forClass(RunningTaskInfo.class);
verify(organizer, atLeastOnce()).onTaskVanished(arg.capture());
List<RunningTaskInfo> taskInfos = arg.getAllValues();
HashSet<Integer> vanishedTaskIds = new HashSet<>();
for (int i = 0; i < taskInfos.size(); i++) {
vanishedTaskIds.add(taskInfos.get(i).taskId);
}
HashSet<Integer> taskIds = new HashSet<>();
for (int i = 0; i < tasks.length; i++) {
taskIds.add(tasks[i].mTaskId);
}
assertTrue(expectVanished
? vanishedTaskIds.containsAll(taskIds)
: !vanishedTaskIds.removeAll(taskIds));
}
private void assertContainsTasks(List<TaskAppearedInfo> taskInfos, Task... expectedTasks) {
HashSet<Integer> taskIds = new HashSet<>();
for (int i = 0; i < taskInfos.size(); i++) {
taskIds.add(taskInfos.get(i).getTaskInfo().taskId);
}
for (int i = 0; i < expectedTasks.length; i++) {
assertTrue(taskIds.contains(expectedTasks[i].mTaskId));
}
}
}