blob: 11267268a6fa9c7022bef09b994cece490c655df [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_STANDARD;
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.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.window.TransitionInfo.FLAG_FILLS_TASK;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static android.window.TransitionInfo.isIndependent;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
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.spyOn;
import static com.android.server.wm.SnapshotController.TASK_CLOSE;
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.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
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.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.IDisplayAreaOrganizer;
import android.window.IRemoteTransition;
import android.window.ITaskFragmentOrganizer;
import android.window.ITaskOrganizer;
import android.window.ITransitionPlayer;
import android.window.RemoteTransition;
import android.window.TaskFragmentOrganizer;
import android.window.TransitionInfo;
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import com.android.internal.graphics.ColorUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* Build/Install/Run:
* atest WmTests:TransitionTests
*/
@SmallTest
@Presubmit
@RunWith(WindowTestRunner.class)
public class TransitionTests extends WindowTestsBase {
final SurfaceControl.Transaction mMockT = mock(SurfaceControl.Transaction.class);
private BLASTSyncEngine mSyncEngine;
private Transition createTestTransition(int transitType, TransitionController controller) {
return new Transition(transitType, 0 /* flags */, controller, controller.mSyncEngine);
}
private Transition createTestTransition(int transitType) {
final TransitionController controller = new TestTransitionController(
mock(ActivityTaskManagerService.class));
mSyncEngine = createTestBLASTSyncEngine();
controller.setSyncEngine(mSyncEngine);
final Transition out = createTestTransition(transitType, controller);
out.startCollecting(0 /* timeoutMs */);
return out;
}
@Test
public void testCreateInfo_NewTask() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> participants = transition.mParticipants;
final Task newTask = createTask(mDisplayContent);
final Task oldTask = createTask(mDisplayContent);
final ActivityRecord closing = createActivityRecord(oldTask);
final ActivityRecord opening = createActivityRecord(newTask);
// Start states.
changes.put(newTask, new Transition.ChangeInfo(newTask, false /* vis */, true /* exChg */));
changes.put(oldTask, new Transition.ChangeInfo(oldTask, true /* vis */, true /* exChg */));
changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */));
changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, true /* exChg */));
fillChangeMap(changes, newTask);
// End states.
closing.setVisibleRequested(false);
opening.setVisibleRequested(true);
final int transit = transition.mType;
int flags = 0;
// Check basic both tasks participating
participants.add(oldTask);
participants.add(newTask);
ArrayList<Transition.ChangeInfo> targets =
Transition.calculateTargets(participants, changes);
TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertEquals(transit, info.getType());
// Check that children are pruned
participants.add(opening);
participants.add(closing);
targets = Transition.calculateTargets(participants, changes);
info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
// Check combined prune and promote
participants.remove(newTask);
targets = Transition.calculateTargets(participants, changes);
info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
// Check multi promote
participants.remove(oldTask);
targets = Transition.calculateTargets(participants, changes);
info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
}
@Test
public void testCreateInfo_NestedTasks() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> participants = transition.mParticipants;
final Task newTask = createTask(mDisplayContent);
final Task newNestedTask = createTaskInRootTask(newTask, 0);
final Task newNestedTask2 = createTaskInRootTask(newTask, 0);
final Task oldTask = createTask(mDisplayContent);
final ActivityRecord closing = createActivityRecord(oldTask);
final ActivityRecord opening = createActivityRecord(newNestedTask);
final ActivityRecord opening2 = createActivityRecord(newNestedTask2);
// Start states.
changes.put(newTask, new Transition.ChangeInfo(newTask, false /* vis */, true /* exChg */));
changes.put(newNestedTask,
new Transition.ChangeInfo(newNestedTask, false /* vis */, true /* exChg */));
changes.put(newNestedTask2,
new Transition.ChangeInfo(newNestedTask2, false /* vis */, true /* exChg */));
changes.put(oldTask, new Transition.ChangeInfo(oldTask, true /* vis */, true /* exChg */));
changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */));
changes.put(opening2,
new Transition.ChangeInfo(opening2, false /* vis */, true /* exChg */));
changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, true /* exChg */));
fillChangeMap(changes, newTask);
// End states.
closing.setVisibleRequested(false);
opening.setVisibleRequested(true);
opening2.setVisibleRequested(true);
final int transit = transition.mType;
int flags = 0;
// Check full promotion from leaf
participants.add(oldTask);
participants.add(opening);
participants.add(opening2);
ArrayList<Transition.ChangeInfo> targets =
Transition.calculateTargets(participants, changes);
TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertEquals(transit, info.getType());
assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
// Check that unchanging but visible descendant of sibling prevents promotion
participants.remove(opening2);
targets = Transition.calculateTargets(participants, changes);
info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertNotNull(info.getChange(newNestedTask.mRemoteToken.toWindowContainerToken()));
assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
}
@Test
public void testCreateInfo_DisplayArea() {
assumeTrue(mDisplayContent.mTransitionController.useShellTransitionsRotation());
final Transition transition = createTestTransition(TRANSIT_OPEN);
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> participants = transition.mParticipants;
final Task showTask = createTask(mDisplayContent);
final Task showNestedTask = createTaskInRootTask(showTask, 0);
final Task showTask2 = createTask(mDisplayContent);
final DisplayArea tda = showTask.getDisplayArea();
final ActivityRecord showing = createActivityRecord(showNestedTask);
final ActivityRecord showing2 = createActivityRecord(showTask2);
// Start states.
changes.put(showTask,
new Transition.ChangeInfo(showTask, false /* vis */, true /* exChg */));
changes.put(showNestedTask,
new Transition.ChangeInfo(showNestedTask, false /* vis */, true /* exChg */));
changes.put(showTask2,
new Transition.ChangeInfo(showTask2, false /* vis */, true /* exChg */));
changes.put(tda, new Transition.ChangeInfo(tda, false /* vis */, true /* exChg */));
changes.put(showing, new Transition.ChangeInfo(showing, false /* vis */, true /* exChg */));
changes.put(showing2,
new Transition.ChangeInfo(showing2, false /* vis */, true /* exChg */));
fillChangeMap(changes, tda);
// End states.
showing.setVisibleRequested(true);
showing2.setVisibleRequested(true);
final int transit = transition.mType;
int flags = 0;
// Check promotion to DisplayArea
participants.add(showing);
participants.add(showing2);
ArrayList<Transition.ChangeInfo> targets =
Transition.calculateTargets(participants, changes);
TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(1, info.getChanges().size());
assertEquals(transit, info.getType());
assertNotNull(info.getChange(tda.mRemoteToken.toWindowContainerToken()));
// Check that organized tasks get reported even if not top
makeTaskOrganized(showTask);
targets = Transition.calculateTargets(participants, changes);
info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertNotNull(info.getChange(tda.mRemoteToken.toWindowContainerToken()));
assertNotNull(info.getChange(showTask.mRemoteToken.toWindowContainerToken()));
// Even if DisplayArea explicitly participating
participants.add(tda);
targets = Transition.calculateTargets(participants, changes);
info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
}
@Test
public void testCreateInfo_existenceChange() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
final Task openTask = createTask(mDisplayContent);
final ActivityRecord opening = createActivityRecord(openTask);
opening.setVisibleRequested(false); // starts invisible
final Task closeTask = createTask(mDisplayContent);
final ActivityRecord closing = createActivityRecord(closeTask);
closing.setVisibleRequested(true); // starts visible
transition.collectExistenceChange(openTask);
transition.collect(opening);
transition.collect(closing);
opening.setVisibleRequested(true);
closing.setVisibleRequested(false);
ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
TransitionInfo info = Transition.calculateTransitionInfo(0, 0, targets, mMockT);
assertEquals(2, info.getChanges().size());
// There was an existence change on open, so it should be OPEN rather than SHOW
assertEquals(TRANSIT_OPEN,
info.getChange(openTask.mRemoteToken.toWindowContainerToken()).getMode());
// No exestence change on closing, so HIDE rather than CLOSE
assertEquals(TRANSIT_TO_BACK,
info.getChange(closeTask.mRemoteToken.toWindowContainerToken()).getMode());
}
@Test
public void testCreateInfo_ordering() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
// pick some number with a high enough chance of being out-of-order when added to set.
final int taskCount = 6;
final Task[] tasks = new Task[taskCount];
for (int i = 0; i < taskCount; ++i) {
// Each add goes on top, so at the end of this, task[9] should be on top
tasks[i] = createTask(mDisplayContent,
WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
final ActivityRecord act = createActivityRecord(tasks[i]);
// alternate so that the transition doesn't get promoted to the display area
act.setVisibleRequested((i % 2) == 0); // starts invisible
}
// doesn't matter which order collected since participants is a set
for (int i = 0; i < taskCount; ++i) {
transition.collectExistenceChange(tasks[i]);
final ActivityRecord act = tasks[i].getTopMostActivity();
transition.collect(act);
tasks[i].getTopMostActivity().setVisibleRequested((i % 2) != 0);
}
ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
TransitionInfo info = Transition.calculateTransitionInfo(0, 0, targets, mMockT);
assertEquals(taskCount, info.getChanges().size());
// verify order is top-to-bottem
for (int i = 0; i < taskCount; ++i) {
assertEquals(tasks[taskCount - i - 1].mRemoteToken.toWindowContainerToken(),
info.getChanges().get(i).getContainer());
}
}
@Test
public void testCreateInfo_wallpaper() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
// pick some number with a high enough chance of being out-of-order when added to set.
final int taskCount = 4;
final int showWallpaperTask = 2;
final Task[] tasks = new Task[taskCount];
for (int i = 0; i < taskCount; ++i) {
// Each add goes on top, so at the end of this, task[9] should be on top
tasks[i] = createTask(mDisplayContent,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
final ActivityRecord act = createActivityRecord(tasks[i]);
// alternate so that the transition doesn't get promoted to the display area
act.setVisibleRequested((i % 2) == 0); // starts invisible
if (i == showWallpaperTask) {
doReturn(true).when(act).showWallpaper();
}
}
final WallpaperWindowToken wallpaperWindowToken = spy(new WallpaperWindowToken(mWm,
mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */));
final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, wallpaperWindowToken,
"wallpaperWindow");
wallpaperWindowToken.setVisibleRequested(false);
transition.collect(wallpaperWindowToken);
wallpaperWindowToken.setVisibleRequested(true);
wallpaperWindow.mHasSurface = true;
// doesn't matter which order collected since participants is a set
for (int i = 0; i < taskCount; ++i) {
transition.collectExistenceChange(tasks[i]);
final ActivityRecord act = tasks[i].getTopMostActivity();
transition.collect(act);
tasks[i].getTopMostActivity().setVisibleRequested((i % 2) != 0);
}
ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
TransitionInfo info = Transition.calculateTransitionInfo(0, 0, targets, mMockT);
// verify that wallpaper is at bottom
assertEquals(taskCount + 1, info.getChanges().size());
// The wallpaper is not organized, so it won't have a token; however, it will be marked
// as IS_WALLPAPER
assertEquals(FLAG_IS_WALLPAPER,
info.getChanges().get(info.getChanges().size() - 1).getFlags());
assertEquals(FLAG_SHOW_WALLPAPER, info.getChange(
tasks[showWallpaperTask].mRemoteToken.toWindowContainerToken()).getFlags());
}
@Test
public void testCreateInfo_PromoteSimilarClose() {
final Transition transition = createTestTransition(TRANSIT_CLOSE);
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> participants = transition.mParticipants;
final Task topTask = createTask(mDisplayContent);
final Task belowTask = createTask(mDisplayContent);
final ActivityRecord showing = createActivityRecord(belowTask);
final ActivityRecord hiding = createActivityRecord(topTask);
final ActivityRecord closing = createActivityRecord(topTask);
// Start states.
changes.put(topTask, new Transition.ChangeInfo(topTask, true /* vis */, false /* exChg */));
changes.put(belowTask,
new Transition.ChangeInfo(belowTask, false /* vis */, false /* exChg */));
changes.put(showing,
new Transition.ChangeInfo(showing, false /* vis */, false /* exChg */));
changes.put(hiding, new Transition.ChangeInfo(hiding, true /* vis */, false /* exChg */));
changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, true /* exChg */));
fillChangeMap(changes, topTask);
// End states.
showing.setVisibleRequested(true);
closing.setVisibleRequested(false);
hiding.setVisibleRequested(false);
participants.add(belowTask);
participants.add(hiding);
participants.add(closing);
ArrayList<Transition.ChangeInfo> targets =
Transition.calculateTargets(participants, changes);
assertEquals(2, targets.size());
assertTrue(Transition.containsChangeFor(belowTask, targets));
assertTrue(Transition.containsChangeFor(topTask, targets));
}
@Test
public void testCreateInfo_PromoteSimilarOpen() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> participants = transition.mParticipants;
final Task topTask = createTask(mDisplayContent);
final Task belowTask = createTask(mDisplayContent);
final ActivityRecord showing = createActivityRecord(topTask);
final ActivityRecord opening = createActivityRecord(topTask);
final ActivityRecord closing = createActivityRecord(belowTask);
// Start states.
changes.put(topTask,
new Transition.ChangeInfo(topTask, false /* vis */, false /* exChg */));
changes.put(belowTask,
new Transition.ChangeInfo(belowTask, true /* vis */, false /* exChg */));
changes.put(showing,
new Transition.ChangeInfo(showing, false /* vis */, false /* exChg */));
changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */));
changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, false /* exChg */));
fillChangeMap(changes, topTask);
// End states.
showing.setVisibleRequested(true);
opening.setVisibleRequested(true);
closing.setVisibleRequested(false);
participants.add(belowTask);
participants.add(showing);
participants.add(opening);
ArrayList<Transition.ChangeInfo> targets =
Transition.calculateTargets(participants, changes);
assertEquals(2, targets.size());
assertTrue(Transition.containsChangeFor(belowTask, targets));
assertTrue(Transition.containsChangeFor(topTask, targets));
}
@Test
public void testCreateInfo_NoAnimation() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> participants = transition.mParticipants;
final Task newTask = createTask(mDisplayContent);
final Task oldTask = createTask(mDisplayContent);
final ActivityRecord closing = createActivityRecord(oldTask);
final ActivityRecord opening = createActivityRecord(newTask);
// Start states.
changes.put(newTask, new Transition.ChangeInfo(newTask, false /* vis */, true /* exChg */));
changes.put(oldTask, new Transition.ChangeInfo(oldTask, true /* vis */, true /* exChg */));
changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */));
changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, true /* exChg */));
transition.setNoAnimation(opening);
fillChangeMap(changes, newTask);
// End states.
closing.setVisibleRequested(false);
opening.setVisibleRequested(true);
final int transit = transition.mType;
int flags = 0;
// Check that no-animation flag is promoted
participants.add(oldTask);
participants.add(newTask);
participants.add(opening);
participants.add(closing);
ArrayList<Transition.ChangeInfo> targets =
Transition.calculateTargets(participants, changes);
TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
assertTrue(info.getChange(newTask.mRemoteToken.toWindowContainerToken())
.hasFlags(TransitionInfo.FLAG_NO_ANIMATION));
// Check that no-animation flag is NOT promoted if at-least on child *is* animated
final ActivityRecord opening2 = createActivityRecord(newTask);
changes.put(opening2,
new Transition.ChangeInfo(opening2, false /* vis */, true /* exChg */));
participants.add(opening2);
targets = Transition.calculateTargets(participants, changes);
info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
assertFalse(info.getChange(newTask.mRemoteToken.toWindowContainerToken())
.hasFlags(TransitionInfo.FLAG_NO_ANIMATION));
}
@Test
public void testCreateInfo_MultiDisplay() {
DisplayContent otherDisplay = createNewDisplay();
final Transition transition = createTestTransition(TRANSIT_OPEN);
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> participants = transition.mParticipants;
final Task display0Task = createTask(mDisplayContent);
final Task display1Task = createTask(otherDisplay);
// Start states.
changes.put(display0Task,
new Transition.ChangeInfo(display0Task, false /* vis */, true /* exChg */));
changes.put(display1Task,
new Transition.ChangeInfo(display1Task, false /* vis */, true /* exChg */));
fillChangeMap(changes, display0Task);
fillChangeMap(changes, display1Task);
// End states.
display0Task.setVisibleRequested(true);
display1Task.setVisibleRequested(true);
final int transit = transition.mType;
int flags = 0;
participants.add(display0Task);
participants.add(display1Task);
ArrayList<Transition.ChangeInfo> targets =
Transition.calculateTargets(participants, changes);
TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getRootCount());
// Check that the changes are assigned to the correct display
assertEquals(mDisplayContent.getDisplayId(), info.getChange(
display0Task.mRemoteToken.toWindowContainerToken()).getEndDisplayId());
assertEquals(otherDisplay.getDisplayId(), info.getChange(
display1Task.mRemoteToken.toWindowContainerToken()).getEndDisplayId());
// Check that roots can be found by display and have the correct display
assertEquals(mDisplayContent.getDisplayId(),
info.getRoot(info.findRootIndex(mDisplayContent.getDisplayId())).getDisplayId());
assertEquals(otherDisplay.getDisplayId(),
info.getRoot(info.findRootIndex(otherDisplay.getDisplayId())).getDisplayId());
}
@Test
public void testTargets_noIntermediatesToWallpaper() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */);
// Make DA organized so we can check that they don't get included.
WindowContainer parent = wallpaperWindowToken.getParent();
makeDisplayAreaOrganized(parent, mDisplayContent);
final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, wallpaperWindowToken,
"wallpaperWindow");
wallpaperWindowToken.setVisibleRequested(false);
transition.collect(wallpaperWindowToken);
wallpaperWindowToken.setVisibleRequested(true);
wallpaperWindow.mHasSurface = true;
doReturn(true).when(mDisplayContent).isAttached();
transition.collect(mDisplayContent);
assertFalse("The change of non-interesting window container should be skipped",
transition.mChanges.containsKey(mDisplayContent.getParent()));
mDisplayContent.getWindowConfiguration().setRotation(
(mDisplayContent.getWindowConfiguration().getRotation() + 1) % 4);
ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
TransitionInfo info = Transition.calculateTransitionInfo(0, 0, targets, mMockT);
// The wallpaper is not organized, so it won't have a token; however, it will be marked
// as IS_WALLPAPER
assertEquals(FLAG_IS_WALLPAPER, info.getChanges().get(0).getFlags());
// Make sure no intermediate display areas were pulled in between wallpaper and display.
assertEquals(mDisplayContent.mRemoteToken.toWindowContainerToken(),
info.getChanges().get(0).getParent());
}
@Test
public void testRunningRemoteTransition() {
final TestTransitionPlayer testPlayer = new TestTransitionPlayer(
mAtm.getTransitionController(), mAtm.mWindowOrganizerController);
final WindowProcessController playerProc = mSystemServicesTestRule.addProcess(
"pkg.player", "proc.player", 5000 /* pid */, 5000 /* uid */);
testPlayer.mController.registerTransitionPlayer(testPlayer, playerProc);
doReturn(mock(IBinder.class)).when(playerProc.getThread()).asBinder();
final WindowProcessController delegateProc = mSystemServicesTestRule.addProcess(
"pkg.delegate", "proc.delegate", 6000 /* pid */, 6000 /* uid */);
doReturn(mock(IBinder.class)).when(delegateProc.getThread()).asBinder();
final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true)
.setVisible(false).build();
final Task task = app.getTask();
task.setTaskOrganizer(mock(ITaskOrganizer.class), true /* skipTaskAppeared */);
app.setVisibleRequested(true);
final TransitionController controller = app.mTransitionController;
final Transition transition = controller.createTransition(TRANSIT_OPEN);
final RemoteTransition remoteTransition = new RemoteTransition(
mock(IRemoteTransition.class));
remoteTransition.setAppThread(delegateProc.getThread());
transition.collect(app);
controller.requestStartTransition(transition, null /* startTask */, remoteTransition,
null /* displayChange */);
testPlayer.startTransition();
app.onStartingWindowDrawn();
// The task appeared event should be deferred until transition ready.
assertFalse(task.taskAppearedReady());
testPlayer.onTransactionReady(app.getSyncTransaction());
assertTrue(task.taskAppearedReady());
assertTrue(playerProc.isRunningRemoteTransition());
assertTrue(delegateProc.isRunningRemoteTransition());
assertTrue(controller.mRemotePlayer.reportRunning(delegateProc.getThread()));
assertTrue(app.isVisible());
testPlayer.finish();
assertFalse(playerProc.isRunningRemoteTransition());
assertFalse(delegateProc.isRunningRemoteTransition());
assertFalse(controller.mRemotePlayer.reportRunning(delegateProc.getThread()));
}
@Test
public void testOpenActivityInTheSameTaskWithDisplayChange() {
final ActivityRecord closing = createActivityRecord(mDisplayContent);
closing.setVisibleRequested(true);
final Task task = closing.getTask();
makeTaskOrganized(task);
final ActivityRecord opening = createActivityRecord(task);
opening.setVisibleRequested(false);
makeDisplayAreaOrganized(mDisplayContent.getDefaultTaskDisplayArea(), mDisplayContent);
final WindowContainer<?>[] wcs = { closing, opening, task, mDisplayContent };
final Transition transition = createTestTransition(TRANSIT_OPEN);
for (WindowContainer<?> wc : wcs) {
transition.collect(wc);
}
closing.setVisibleRequested(false);
opening.setVisibleRequested(true);
final int newRotation = mDisplayContent.getWindowConfiguration().getRotation() + 1;
for (WindowContainer<?> wc : wcs) {
wc.getWindowConfiguration().setRotation(newRotation);
}
final ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
// Especially the activities must be in the targets.
for (WindowContainer<?> wc : wcs) {
assertTrue(Transition.containsChangeFor(wc, targets));
}
}
@Test
public void testIndependent() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> participants = transition.mParticipants;
final Task openTask = createTask(mDisplayContent);
final Task openInOpenTask = createTaskInRootTask(openTask, 0);
final ActivityRecord openInOpen = createActivityRecord(openInOpenTask);
final Task changeTask = createTask(mDisplayContent);
final Task changeInChangeTask = createTaskInRootTask(changeTask, 0);
final Task openInChangeTask = createTaskInRootTask(changeTask, 0);
final ActivityRecord changeInChange = createActivityRecord(changeInChangeTask);
final ActivityRecord openInChange = createActivityRecord(openInChangeTask);
// set organizer for everything so that they all get added to transition info
makeTaskOrganized(openTask, openInOpenTask, changeTask, changeInChangeTask,
openInChangeTask);
// Start states.
changes.put(openTask,
new Transition.ChangeInfo(openTask, false /* vis */, true /* exChg */));
changes.put(changeTask,
new Transition.ChangeInfo(changeTask, true /* vis */, false /* exChg */));
changes.put(openInOpenTask,
new Transition.ChangeInfo(openInOpenTask, false /* vis */, true /* exChg */));
changes.put(openInChangeTask,
new Transition.ChangeInfo(openInChangeTask, false /* vis */, true /* exChg */));
changes.put(changeInChangeTask,
new Transition.ChangeInfo(changeInChangeTask, true /* vis */, false /* exChg */));
changes.put(openInOpen,
new Transition.ChangeInfo(openInOpen, false /* vis */, true /* exChg */));
changes.put(openInChange,
new Transition.ChangeInfo(openInChange, false /* vis */, true /* exChg */));
changes.put(changeInChange,
new Transition.ChangeInfo(changeInChange, true /* vis */, false /* exChg */));
fillChangeMap(changes, openTask);
// End states.
changeInChange.setVisibleRequested(true);
openInOpen.setVisibleRequested(true);
openInChange.setVisibleRequested(true);
// Force the change-type changes to be "dirty" so they aren't skipped
changes.get(changeTask).mKnownConfigChanges = 1;
changes.get(changeInChangeTask).mKnownConfigChanges = 1;
changes.get(changeInChange).mKnownConfigChanges = 1;
final int transit = transition.mType;
int flags = 0;
// Check full promotion from leaf
participants.add(changeInChange);
participants.add(openInOpen);
participants.add(openInChange);
// Explicitly add changeTask (to test independence with parents)
participants.add(changeTask);
final ArrayList<Transition.ChangeInfo> targets =
Transition.calculateTargets(participants, changes);
TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
// Root changes should always be considered independent
assertTrue(isIndependent(
info.getChange(openTask.mRemoteToken.toWindowContainerToken()), info));
assertTrue(isIndependent(
info.getChange(changeTask.mRemoteToken.toWindowContainerToken()), info));
// Children of a open/close change are not independent
assertFalse(isIndependent(
info.getChange(openInOpenTask.mRemoteToken.toWindowContainerToken()), info));
// Non-root changes are not independent
assertFalse(isIndependent(
info.getChange(changeInChangeTask.mRemoteToken.toWindowContainerToken()), info));
// open/close within a change are independent
assertTrue(isIndependent(
info.getChange(openInChangeTask.mRemoteToken.toWindowContainerToken()), info));
}
@Test
public void testOpenOpaqueTask() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> participants = transition.mParticipants;
final Task oldTask = createTask(mDisplayContent);
final Task newTask = createTask(mDisplayContent);
final ActivityRecord closing = createActivityRecord(oldTask);
closing.setOccludesParent(true);
final ActivityRecord opening = createActivityRecord(newTask);
opening.setOccludesParent(true);
// Start states.
changes.put(newTask, new Transition.ChangeInfo(newTask, false /* vis */, true /* exChg */));
changes.put(oldTask, new Transition.ChangeInfo(oldTask, true /* vis */, false /* exChg */));
changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */));
changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, false /* exChg */));
fillChangeMap(changes, newTask);
// End states.
closing.setVisibleRequested(false);
opening.setVisibleRequested(true);
final int transit = transition.mType;
int flags = 0;
// Check basic both tasks participating
participants.add(oldTask);
participants.add(newTask);
ArrayList<Transition.ChangeInfo> targets =
Transition.calculateTargets(participants, changes);
TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertEquals(transit, info.getType());
assertFalse(info.getChanges().get(0).hasFlags(FLAG_TRANSLUCENT));
assertFalse(info.getChanges().get(1).hasFlags(FLAG_TRANSLUCENT));
}
@Test
public void testOpenTranslucentTask() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> participants = transition.mParticipants;
final Task oldTask = createTask(mDisplayContent);
final Task newTask = createTask(mDisplayContent);
final ActivityRecord closing = createActivityRecord(oldTask);
closing.setOccludesParent(true);
final ActivityRecord opening = createActivityRecord(newTask);
opening.setOccludesParent(false);
// Start states.
changes.put(newTask, new Transition.ChangeInfo(newTask, false /* vis */, true /* exChg */));
changes.put(oldTask, new Transition.ChangeInfo(oldTask, true /* vis */, false /* exChg */));
changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */));
changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, false /* exChg */));
fillChangeMap(changes, newTask);
// End states.
closing.setVisibleRequested(false);
opening.setVisibleRequested(true);
final int transit = transition.mType;
int flags = 0;
// Check basic both tasks participating
participants.add(oldTask);
participants.add(newTask);
ArrayList<Transition.ChangeInfo> targets =
Transition.calculateTargets(participants, changes);
TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertEquals(transit, info.getType());
assertTrue(info.getChanges().get(0).hasFlags(FLAG_TRANSLUCENT));
assertFalse(info.getChanges().get(1).hasFlags(FLAG_TRANSLUCENT));
}
@Test
public void testOpenOpaqueTaskFragment() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> participants = transition.mParticipants;
final Task task = createTask(mDisplayContent);
final TaskFragment closingTaskFragment = createTaskFragmentWithActivity(task);
final TaskFragment openingTaskFragment = createTaskFragmentWithActivity(task);
final ActivityRecord closing = closingTaskFragment.getTopMostActivity();
closing.setOccludesParent(true);
final ActivityRecord opening = openingTaskFragment.getTopMostActivity();
opening.setOccludesParent(true);
// Start states.
changes.put(openingTaskFragment, new Transition.ChangeInfo(openingTaskFragment,
false /* vis */, true /* exChg */));
changes.put(closingTaskFragment, new Transition.ChangeInfo(closingTaskFragment,
true /* vis */, false /* exChg */));
changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */));
changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, false /* exChg */));
fillChangeMap(changes, openingTaskFragment);
// End states.
closing.setVisibleRequested(false);
opening.setVisibleRequested(true);
final int transit = transition.mType;
int flags = 0;
// Check basic both tasks participating
participants.add(closingTaskFragment);
participants.add(openingTaskFragment);
ArrayList<Transition.ChangeInfo> targets =
Transition.calculateTargets(participants, changes);
TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertEquals(transit, info.getType());
assertFalse(info.getChanges().get(0).hasFlags(FLAG_TRANSLUCENT));
assertFalse(info.getChanges().get(1).hasFlags(FLAG_TRANSLUCENT));
}
@Test
public void testOpenTranslucentTaskFragment() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> participants = transition.mParticipants;
final Task task = createTask(mDisplayContent);
final TaskFragment closingTaskFragment = createTaskFragmentWithActivity(task);
final TaskFragment openingTaskFragment = createTaskFragmentWithActivity(task);
final ActivityRecord closing = closingTaskFragment.getTopMostActivity();
closing.setOccludesParent(true);
final ActivityRecord opening = openingTaskFragment.getTopMostActivity();
opening.setOccludesParent(false);
// Start states.
changes.put(openingTaskFragment, new Transition.ChangeInfo(openingTaskFragment,
false /* vis */, true /* exChg */));
changes.put(closingTaskFragment, new Transition.ChangeInfo(closingTaskFragment,
true /* vis */, false /* exChg */));
changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */));
changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, false /* exChg */));
fillChangeMap(changes, openingTaskFragment);
// End states.
closing.setVisibleRequested(false);
opening.setVisibleRequested(true);
final int transit = transition.mType;
int flags = 0;
// Check basic both tasks participating
participants.add(closingTaskFragment);
participants.add(openingTaskFragment);
ArrayList<Transition.ChangeInfo> targets =
Transition.calculateTargets(participants, changes);
TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertEquals(transit, info.getType());
assertTrue(info.getChanges().get(0).hasFlags(FLAG_TRANSLUCENT));
assertFalse(info.getChanges().get(1).hasFlags(FLAG_TRANSLUCENT));
}
@Test
public void testCloseOpaqueTaskFragment_withFinishingActivity() {
final Transition transition = createTestTransition(TRANSIT_CLOSE);
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> participants = transition.mParticipants;
final Task task = createTask(mDisplayContent);
final TaskFragment openingTaskFragment = createTaskFragmentWithActivity(task);
final TaskFragment closingTaskFragment = createTaskFragmentWithActivity(task);
final ActivityRecord opening = openingTaskFragment.getTopMostActivity();
opening.setOccludesParent(true);
final ActivityRecord closing = closingTaskFragment.getTopMostActivity();
closing.setOccludesParent(true);
closing.finishing = true;
// Start states.
changes.put(openingTaskFragment, new Transition.ChangeInfo(openingTaskFragment,
false /* vis */, true /* exChg */));
changes.put(closingTaskFragment, new Transition.ChangeInfo(closingTaskFragment,
true /* vis */, false /* exChg */));
changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */));
changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, false /* exChg */));
fillChangeMap(changes, openingTaskFragment);
// End states.
closing.setVisibleRequested(false);
opening.setVisibleRequested(true);
final int transit = transition.mType;
int flags = 0;
// Check basic both tasks participating
participants.add(closingTaskFragment);
participants.add(openingTaskFragment);
ArrayList<Transition.ChangeInfo> targets =
Transition.calculateTargets(participants, changes);
TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertEquals(transit, info.getType());
assertFalse(info.getChanges().get(0).hasFlags(FLAG_TRANSLUCENT));
assertFalse(info.getChanges().get(1).hasFlags(FLAG_TRANSLUCENT));
}
@Test
public void testCloseTranslucentTaskFragment_withFinishingActivity() {
final Transition transition = createTestTransition(TRANSIT_CLOSE);
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> participants = transition.mParticipants;
final Task task = createTask(mDisplayContent);
final TaskFragment openingTaskFragment = createTaskFragmentWithActivity(task);
final TaskFragment closingTaskFragment = createTaskFragmentWithActivity(task);
final ActivityRecord opening = openingTaskFragment.getTopMostActivity();
opening.setOccludesParent(true);
final ActivityRecord closing = closingTaskFragment.getTopMostActivity();
closing.setOccludesParent(false);
closing.finishing = true;
// Start states.
changes.put(openingTaskFragment, new Transition.ChangeInfo(openingTaskFragment,
false /* vis */, true /* exChg */));
changes.put(closingTaskFragment, new Transition.ChangeInfo(closingTaskFragment,
true /* vis */, false /* exChg */));
changes.put(opening, new Transition.ChangeInfo(opening, false /* vis */, true /* exChg */));
changes.put(closing, new Transition.ChangeInfo(closing, true /* vis */, false /* exChg */));
fillChangeMap(changes, openingTaskFragment);
// End states.
closing.setVisibleRequested(false);
opening.setVisibleRequested(true);
final int transit = transition.mType;
int flags = 0;
// Check basic both tasks participating
participants.add(closingTaskFragment);
participants.add(openingTaskFragment);
ArrayList<Transition.ChangeInfo> targets =
Transition.calculateTargets(participants, changes);
TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, mMockT);
assertEquals(2, info.getChanges().size());
assertEquals(transit, info.getType());
assertTrue(info.getChanges().get(0).hasFlags(FLAG_TRANSLUCENT));
assertFalse(info.getChanges().get(1).hasFlags(FLAG_TRANSLUCENT));
}
@Test
public void testTimeout() {
final TransitionController controller = new TestTransitionController(mAtm);
final BLASTSyncEngine sync = new BLASTSyncEngine(mWm);
final CountDownLatch latch = new CountDownLatch(1);
// When the timeout is reached, it will finish the sync-group and notify transaction ready.
final Transition t = new Transition(TRANSIT_OPEN, 0 /* flags */, controller, sync) {
@Override
public void onTransactionReady(int syncId, SurfaceControl.Transaction transaction) {
latch.countDown();
}
};
t.startCollecting(10 /* timeoutMs */);
assertTrue(awaitInWmLock(() -> latch.await(3, TimeUnit.SECONDS)));
}
@Test
public void testTransitionBounds() {
registerTestTransitionPlayer();
final int offset = 10;
final Function<WindowContainer<?>, TransitionInfo.Change> test = wc -> {
final Transition transition = wc.mTransitionController.createTransition(TRANSIT_OPEN);
transition.collect(wc);
final int nextRotation = (wc.getWindowConfiguration().getRotation() + 1) % 4;
wc.getWindowConfiguration().setRotation(nextRotation);
wc.getWindowConfiguration().setDisplayRotation(nextRotation);
final Rect bounds = wc.getWindowConfiguration().getBounds();
// Flip the bounds with offset.
wc.getWindowConfiguration().setBounds(
new Rect(offset, offset, bounds.height(), bounds.width()));
final int flags = 0;
final TransitionInfo info = Transition.calculateTransitionInfo(transition.mType, flags,
Transition.calculateTargets(transition.mParticipants, transition.mChanges),
mMockT);
transition.abort();
return info.getChanges().get(0);
};
final ActivityRecord app = createActivityRecord(mDisplayContent);
final TransitionInfo.Change changeOfActivity = test.apply(app);
// There will be letterbox if the activity bounds don't match parent, so always use its
// parent bounds for animation.
assertEquals(app.getParent().getBounds(), changeOfActivity.getEndAbsBounds());
final int endRotation = app.mTransitionController.useShellTransitionsRotation()
? app.getWindowConfiguration().getRotation()
// Without shell rotation, fixed rotation is done by core so the info should not
// contain rotation change.
: app.getParent().getWindowConfiguration().getRotation();
assertEquals(endRotation, changeOfActivity.getEndRotation());
// Non-activity target always uses its configuration for end info.
final Task task = app.getTask();
final TransitionInfo.Change changeOfTask = test.apply(task);
assertEquals(task.getBounds(), changeOfTask.getEndAbsBounds());
assertEquals(new Point(offset, offset), changeOfTask.getEndRelOffset());
assertEquals(task.getWindowConfiguration().getRotation(), changeOfTask.getEndRotation());
}
@Test
public void testDisplayRotationChange() {
final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
spyOn(displayPolicy);
// Simulate gesture navigation (non-movable) so it is not seamless.
doReturn(false).when(displayPolicy).navigationBarCanMove();
final Task task = createActivityRecord(mDisplayContent).getTask();
final WindowState statusBar = createWindow(null, TYPE_STATUS_BAR, "statusBar");
final WindowState navBar = createWindow(null, TYPE_NAVIGATION_BAR, "navBar");
final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
final WindowToken decorToken = new WindowToken.Builder(mWm, mock(IBinder.class),
TYPE_NAVIGATION_BAR_PANEL).setDisplayContent(mDisplayContent)
.setRoundedCornerOverlay(true).build();
final WindowState screenDecor =
createWindow(null, decorToken.windowType, decorToken, "screenDecor");
final WindowState[] windows = { statusBar, navBar, ime, screenDecor };
makeWindowVisible(windows);
mDisplayContent.getDisplayPolicy().addWindowLw(statusBar, statusBar.mAttrs);
mDisplayContent.getDisplayPolicy().addWindowLw(navBar, navBar.mAttrs);
final TestTransitionPlayer player = registerTestTransitionPlayer();
mDisplayContent.getDisplayRotation().setRotation(mDisplayContent.getRotation() + 1);
mDisplayContent.setLastHasContent();
mDisplayContent.requestChangeTransitionIfNeeded(1 /* any changes */,
null /* displayChange */);
assertEquals(WindowContainer.SYNC_STATE_NONE, statusBar.mSyncState);
assertEquals(WindowContainer.SYNC_STATE_NONE, navBar.mSyncState);
assertEquals(WindowContainer.SYNC_STATE_NONE, screenDecor.mSyncState);
assertEquals(WindowContainer.SYNC_STATE_WAITING_FOR_DRAW, ime.mSyncState);
final AsyncRotationController asyncRotationController =
mDisplayContent.getAsyncRotationController();
assertNotNull(asyncRotationController);
player.startTransition();
assertFalse(mDisplayContent.mTransitionController.isCollecting(statusBar.mToken));
assertFalse(mDisplayContent.mTransitionController.isCollecting(decorToken));
assertTrue(ime.mToken.inTransition());
assertTrue(task.inTransition());
assertTrue(asyncRotationController.isTargetToken(decorToken));
assertShouldFreezeInsetsPosition(asyncRotationController, statusBar, true);
// Only seamless window syncs its draw transaction with transition.
assertTrue(asyncRotationController.handleFinishDrawing(screenDecor, mMockT));
// Status bar finishes drawing before the start transaction. Its fade-in animation will be
// executed until the transaction is committed, so it is still in target tokens.
assertFalse(asyncRotationController.handleFinishDrawing(statusBar, mMockT));
assertTrue(asyncRotationController.isTargetToken(statusBar.mToken));
final SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class);
final SurfaceControl.TransactionCommittedListener transactionCommittedListener =
onRotationTransactionReady(player, startTransaction);
// The transaction is committed, so fade-in animation for status bar is consumed.
transactionCommittedListener.onTransactionCommitted();
assertFalse(asyncRotationController.isTargetToken(statusBar.mToken));
assertShouldFreezeInsetsPosition(asyncRotationController, navBar, false);
// Navigation bar finishes drawing after the start transaction, so its fade-in animation
// can execute directly.
navBar.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN;
asyncRotationController.updateTargetWindows();
assertFalse(asyncRotationController.isTargetToken(navBar.mToken));
assertNull(mDisplayContent.getAsyncRotationController());
}
@Test
public void testAppTransitionWithRotationChange() {
final TestTransitionPlayer player = registerTestTransitionPlayer();
final boolean useFixedRotation = !player.mController.useShellTransitionsRotation();
if (useFixedRotation) {
testFixedRotationOpen(player);
} else {
testShellRotationOpen(player);
}
}
private void testShellRotationOpen(TestTransitionPlayer player) {
final WindowState statusBar = createWindow(null, TYPE_STATUS_BAR, "statusBar");
makeWindowVisible(statusBar);
mDisplayContent.getDisplayPolicy().addWindowLw(statusBar, statusBar.mAttrs);
final ActivityRecord app = createActivityRecord(mDisplayContent);
final Transition transition = app.mTransitionController.createTransition(TRANSIT_OPEN);
app.mTransitionController.requestStartTransition(transition, app.getTask(),
null /* remoteTransition */, null /* displayChange */);
mDisplayContent.getDisplayRotation().setRotation(mDisplayContent.getRotation() + 1);
final int anyChanges = 1;
mDisplayContent.setLastHasContent();
mDisplayContent.requestChangeTransitionIfNeeded(anyChanges, null /* displayChange */);
transition.setKnownConfigChanges(mDisplayContent, anyChanges);
final AsyncRotationController asyncRotationController =
mDisplayContent.getAsyncRotationController();
assertNotNull(asyncRotationController);
assertShouldFreezeInsetsPosition(asyncRotationController, statusBar, true);
player.startTransition();
// Non-app windows should not be collected.
assertFalse(statusBar.mToken.inTransition());
assertTrue(app.getTask().inTransition());
final SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class);
final SurfaceControl leash = statusBar.mToken.getAnimationLeash();
doReturn(true).when(leash).isValid();
final SurfaceControl.TransactionCommittedListener transactionCommittedListener =
onRotationTransactionReady(player, startTransaction);
// The leash should be unrotated.
verify(startTransaction).setMatrix(eq(leash), any(), any());
// The redrawn window will be faded in when the transition finishes. And because this test
// only use one non-activity window, the fade rotation controller should also be cleared.
statusBar.mWinAnimator.mDrawState = WindowStateAnimator.DRAW_PENDING;
final SurfaceControl.Transaction postDrawTransaction =
mock(SurfaceControl.Transaction.class);
final boolean layoutNeeded = statusBar.finishDrawing(postDrawTransaction,
Integer.MAX_VALUE);
assertFalse(layoutNeeded);
transactionCommittedListener.onTransactionCommitted();
player.finish();
// The controller should capture the draw transaction and merge it when preparing to run
// fade-in animation.
verify(mDisplayContent.getPendingTransaction()).merge(eq(postDrawTransaction));
assertNull(mDisplayContent.getAsyncRotationController());
}
private void testFixedRotationOpen(TestTransitionPlayer player) {
final WindowState statusBar = createWindow(null, TYPE_STATUS_BAR, "statusBar");
makeWindowVisible(statusBar);
mDisplayContent.getDisplayPolicy().addWindowLw(statusBar, statusBar.mAttrs);
final ActivityRecord app = createActivityRecord(mDisplayContent);
final Transition transition = app.mTransitionController.createTransition(TRANSIT_OPEN);
app.mTransitionController.requestStartTransition(transition, app.getTask(),
null /* remoteTransition */, null /* displayChange */);
app.mTransitionController.collectExistenceChange(app.getTask());
mDisplayContent.setFixedRotationLaunchingAppUnchecked(app);
final AsyncRotationController asyncRotationController =
mDisplayContent.getAsyncRotationController();
assertNotNull(asyncRotationController);
assertShouldFreezeInsetsPosition(asyncRotationController, statusBar, true);
assertTrue(app.getTask().inTransition());
player.start();
player.finish();
app.getTask().finishSync(mWm.mTransactionFactory.get(), app.getTask().getSyncGroup(),
false /* cancel */);
// The open transition is finished. Continue to play seamless display change transition,
// so the previous async rotation controller should still exist.
mDisplayContent.getDisplayRotation().setRotation(mDisplayContent.getRotation() + 1);
mDisplayContent.setLastHasContent();
mDisplayContent.requestChangeTransitionIfNeeded(1 /* changes */, null /* displayChange */);
assertTrue(mDisplayContent.hasTopFixedRotationLaunchingApp());
assertNotNull(mDisplayContent.getAsyncRotationController());
// The app is still in transition, so the callback should be no-op.
mDisplayContent.mTransitionController.dispatchLegacyAppTransitionFinished(app);
assertTrue(mDisplayContent.hasTopFixedRotationLaunchingApp());
player.startTransition();
// Non-app windows should not be collected.
assertFalse(mDisplayContent.mTransitionController.isCollecting(statusBar.mToken));
onRotationTransactionReady(player, mWm.mTransactionFactory.get()).onTransactionCommitted();
assertEquals(ROTATION_ANIMATION_SEAMLESS, player.mLastReady.getChange(
mDisplayContent.mRemoteToken.toWindowContainerToken()).getRotationAnimation());
player.finish();
// The controller should be cleared if the target windows are drawn.
statusBar.finishDrawing(mWm.mTransactionFactory.get(), Integer.MAX_VALUE);
assertNull(mDisplayContent.getAsyncRotationController());
}
private static void assertShouldFreezeInsetsPosition(AsyncRotationController controller,
WindowState w, boolean freeze) {
if (TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST) {
// Non blast sync should never freeze insets position.
freeze = false;
}
assertEquals(freeze, controller.shouldFreezeInsetsPosition(w));
}
@Test
public void testDeferRotationForTransientLaunch() {
final TestTransitionPlayer player = registerTestTransitionPlayer();
assumeFalse(mDisplayContent.mTransitionController.useShellTransitionsRotation());
final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
final ActivityRecord home = new ActivityBuilder(mAtm)
.setTask(mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask())
.setScreenOrientation(SCREEN_ORIENTATION_NOSENSOR).setVisible(false).build();
final Transition transition = home.mTransitionController.createTransition(TRANSIT_OPEN);
final int prevRotation = mDisplayContent.getRotation();
transition.setTransientLaunch(home, null /* restoreBelow */);
home.mTransitionController.requestStartTransition(transition, home.getTask(),
null /* remoteTransition */, null /* displayChange */);
transition.collectExistenceChange(home);
home.setVisibleRequested(true);
mDisplayContent.setFixedRotationLaunchingAppUnchecked(home);
doReturn(true).when(home).hasFixedRotationTransform(any());
player.startTransition();
player.onTransactionReady(mDisplayContent.getSyncTransaction());
final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
final RemoteDisplayChangeController displayChangeController = mDisplayContent
.mRemoteDisplayChangeController;
spyOn(displayRotation);
spyOn(displayChangeController);
doReturn(true).when(displayChangeController).isWaitingForRemoteDisplayChange();
doReturn(prevRotation + 1).when(displayRotation).rotationForOrientation(
anyInt() /* orientation */, anyInt() /* lastRotation */);
// Rotation update is skipped while the recents animation is running.
assertFalse(mDisplayContent.updateRotationUnchecked());
assertEquals(SCREEN_ORIENTATION_UNSET, displayRotation.getLastOrientation());
// Return to the app without fixed orientation from recents.
app.moveFocusableActivityToTop("test");
player.finish();
// The display should be updated to the changed orientation after the animation is finish.
assertNotEquals(mDisplayContent.getRotation(), prevRotation);
}
@Test
public void testIntermediateVisibility() {
final TransitionController controller = new TestTransitionController(mAtm);
controller.setSyncEngine(mWm.mSyncEngine);
final ITransitionPlayer player = new ITransitionPlayer.Default();
controller.registerTransitionPlayer(player, null /* playerProc */);
final Transition openTransition = controller.createTransition(TRANSIT_OPEN);
// Start out with task2 visible and set up a transition that closes task2 and opens task1
final Task task1 = createTask(mDisplayContent);
final ActivityRecord activity1 = createActivityRecord(task1);
activity1.setVisibleRequested(false);
activity1.setVisible(false);
final Task task2 = createTask(mDisplayContent);
makeTaskOrganized(task1, task2);
final ActivityRecord activity2 = createActivityRecord(task1);
activity2.setVisibleRequested(true);
activity2.setVisible(true);
openTransition.collectExistenceChange(task1);
openTransition.collectExistenceChange(activity1);
openTransition.collectExistenceChange(task2);
openTransition.collectExistenceChange(activity2);
activity1.setVisibleRequested(true);
activity1.setVisible(true);
activity2.setVisibleRequested(false);
// Using abort to force-finish the sync (since we can't wait for drawing in unit test).
// We didn't call abort on the transition itself, so it will still run onTransactionReady
// normally.
mWm.mSyncEngine.abort(openTransition.getSyncId());
// Before finishing openTransition, we are now going to simulate closing task1 to return
// back to (open) task2.
final Transition closeTransition = controller.createTransition(TRANSIT_CLOSE);
closeTransition.collectExistenceChange(task1);
closeTransition.collectExistenceChange(activity1);
closeTransition.collectExistenceChange(task2);
closeTransition.collectExistenceChange(activity2);
activity1.setVisibleRequested(false);
activity2.setVisibleRequested(true);
openTransition.finishTransition();
// We finished the openTransition. Even though activity1 is visibleRequested=false, since
// the closeTransition animation hasn't played yet, make sure that we didn't commit
// visible=false on activity1 since it needs to remain visible for the animation.
assertTrue(activity1.isVisible());
assertTrue(activity2.isVisible());
// Using abort to force-finish the sync (since we obviously can't wait for drawing).
// We didn't call abort on the actual transition, so it will still run onTransactionReady
// normally.
mWm.mSyncEngine.abort(closeTransition.getSyncId());
closeTransition.finishTransition();
assertFalse(activity1.isVisible());
assertTrue(activity2.isVisible());
// The abort should still commit visible-requested to visible.
final Transition abortTransition = controller.createTransition(TRANSIT_OPEN);
abortTransition.collect(activity1);
activity1.setVisibleRequested(true);
activity1.setVisible(false);
abortTransition.abort();
assertTrue(activity1.isVisible());
// The mLaunchTaskBehind flag of an invisible initializing activity should not be cleared.
final Transition noChangeTransition = controller.createTransition(TRANSIT_OPEN);
noChangeTransition.collect(activity1);
activity1.setVisibleRequested(false);
activity1.setState(ActivityRecord.State.INITIALIZING, "test");
activity1.mLaunchTaskBehind = true;
mWm.mSyncEngine.abort(noChangeTransition.getSyncId());
noChangeTransition.finishTransition();
assertTrue(activity1.mLaunchTaskBehind);
}
@Test
public void testTransientLaunch() {
spyOn(mWm.mSnapshotController.mTaskSnapshotController);
mWm.mSnapshotController.registerTransitionStateConsumer(TASK_CLOSE,
mWm.mSnapshotController.mTaskSnapshotController::handleTaskClose);
final ArrayList<ActivityRecord> enteringAnimReports = new ArrayList<>();
final TransitionController controller = new TestTransitionController(mAtm) {
@Override
protected void dispatchLegacyAppTransitionFinished(ActivityRecord ar) {
if (ar.mEnteringAnimation) {
enteringAnimReports.add(ar);
}
super.dispatchLegacyAppTransitionFinished(ar);
}
};
controller.setSyncEngine(mWm.mSyncEngine);
controller.mSnapshotController = mWm.mSnapshotController;
final TaskSnapshotController taskSnapshotController = controller.mSnapshotController
.mTaskSnapshotController;
final ITransitionPlayer player = new ITransitionPlayer.Default();
controller.registerTransitionPlayer(player, null /* playerProc */);
final Transition openTransition = controller.createTransition(TRANSIT_OPEN);
// Start out with task2 visible and set up a transition that closes task2 and opens task1
final Task task1 = createTask(mDisplayContent);
final ActivityRecord activity1 = createActivityRecord(task1);
activity1.setVisibleRequested(false);
activity1.setVisible(false);
final Task task2 = createTask(mDisplayContent);
makeTaskOrganized(task1, task2);
final ActivityRecord activity2 = createActivityRecord(task2);
activity2.setVisibleRequested(true);
activity2.setVisible(true);
openTransition.collectExistenceChange(task1);
openTransition.collectExistenceChange(activity1);
openTransition.collectExistenceChange(task2);
openTransition.collectExistenceChange(activity2);
activity1.setVisibleRequested(true);
activity1.setVisible(true);
activity2.setVisibleRequested(false);
// Using abort to force-finish the sync (since we can't wait for drawing in unit test).
// We didn't call abort on the transition itself, so it will still run onTransactionReady
// normally.
mWm.mSyncEngine.abort(openTransition.getSyncId());
verify(taskSnapshotController, times(1)).recordSnapshot(eq(task2), eq(false));
controller.finishTransition(openTransition);
// We are now going to simulate closing task1 to return back to (open) task2.
final Transition closeTransition = controller.createTransition(TRANSIT_CLOSE);
closeTransition.collectExistenceChange(task2);
closeTransition.collectExistenceChange(activity2);
closeTransition.setTransientLaunch(activity2, task1);
final Transition.ChangeInfo task1ChangeInfo = closeTransition.mChanges.get(task1);
assertNotNull(task1ChangeInfo);
assertTrue(task1ChangeInfo.hasChanged());
// Make sure the unrelated activity is NOT collected.
final Transition.ChangeInfo activity1ChangeInfo = closeTransition.mChanges.get(activity1);
assertNull(activity1ChangeInfo);
// No need to wait for the activity in transient hide task.
assertEquals(WindowContainer.SYNC_STATE_NONE, activity1.mSyncState);
activity1.setVisibleRequested(false);
activity2.setVisibleRequested(true);
activity2.setVisible(true);
// Using abort to force-finish the sync (since we obviously can't wait for drawing).
// We didn't call abort on the actual transition, so it will still run onTransactionReady
// normally.
mWm.mSyncEngine.abort(closeTransition.getSyncId());
// Make sure we haven't called recordSnapshot (since we are transient, it shouldn't be
// called until finish).
verify(taskSnapshotController, times(0)).recordSnapshot(eq(task1), eq(false));
enteringAnimReports.clear();
doCallRealMethod().when(mWm.mRoot).ensureActivitiesVisible(any(),
anyInt(), anyBoolean(), anyBoolean());
final boolean[] wasInFinishingTransition = { false };
controller.registerLegacyListener(new WindowManagerInternal.AppTransitionListener() {
@Override
public void onAppTransitionFinishedLocked(IBinder token) {
final ActivityRecord r = ActivityRecord.forToken(token);
if (r != null) {
wasInFinishingTransition[0] = controller.inFinishingTransition(r);
}
}
});
assertTrue(activity1.isVisible());
controller.finishTransition(closeTransition);
assertTrue(wasInFinishingTransition[0]);
assertNull(controller.mFinishingTransition);
assertTrue(activity2.isVisible());
assertEquals(ActivityTaskManagerService.APP_SWITCH_DISALLOW, mAtm.getBalAppSwitchesState());
// Because task1 is occluded by task2, finishTransition should make activity1 invisible.
assertFalse(activity1.isVisibleRequested());
// Make sure activity1 visibility was committed
assertFalse(activity1.isVisible());
assertFalse(activity1.app.hasActivityInVisibleTask());
verify(taskSnapshotController, times(1)).recordSnapshot(eq(task1), eq(false));
assertTrue(enteringAnimReports.contains(activity2));
}
@Test
public void testNotReadyPushPop() {
final TransitionController controller = new TestTransitionController(mAtm);
controller.setSyncEngine(mWm.mSyncEngine);
final ITransitionPlayer player = new ITransitionPlayer.Default();
controller.registerTransitionPlayer(player, null /* playerProc */);
final Transition openTransition = controller.createTransition(TRANSIT_OPEN);
// Start out with task2 visible and set up a transition that closes task2 and opens task1
final Task task1 = createTask(mDisplayContent);
openTransition.collectExistenceChange(task1);
assertFalse(openTransition.allReady());
openTransition.setAllReady();
openTransition.deferTransitionReady();
assertFalse(openTransition.allReady());
openTransition.continueTransitionReady();
assertTrue(openTransition.allReady());
}
@Test
public void testIsBehindStartingWindowChange() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
final ArraySet<WindowContainer> participants = transition.mParticipants;
final Task task = createTask(mDisplayContent);
final ActivityRecord activity0 = createActivityRecord(task);
final ActivityRecord activity1 = createActivityRecord(task);
doReturn(true).when(activity1).hasStartingWindow();
// Start states.
changes.put(activity0,
new Transition.ChangeInfo(activity0, true /* vis */, false /* exChg */));
changes.put(activity1,
new Transition.ChangeInfo(activity1, false /* vis */, false /* exChg */));
// End states.
activity0.setVisibleRequested(false);
activity1.setVisibleRequested(true);
participants.add(activity0);
participants.add(activity1);
final ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
participants, changes);
final TransitionInfo info = Transition.calculateTransitionInfo(
transition.mType, 0 /* flags */, targets, mMockT);
// All windows in the Task should have FLAG_IS_BEHIND_STARTING_WINDOW because the starting
// window should cover the whole Task.
assertEquals(2, info.getChanges().size());
assertTrue(info.getChanges().get(0).hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW));
assertTrue(info.getChanges().get(1).hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW));
}
@Test
public void testFlagInTaskWithEmbeddedActivity() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
final ArraySet<WindowContainer> participants = transition.mParticipants;
final Task task = createTask(mDisplayContent);
final ActivityRecord nonEmbeddedActivity = createActivityRecord(task);
assertFalse(nonEmbeddedActivity.isEmbedded());
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
mAtm.mTaskFragmentOrganizerController.registerOrganizer(
ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()));
final TaskFragment embeddedTf = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
.createActivityCount(2)
.setOrganizer(organizer)
.build();
final ActivityRecord closingActivity = embeddedTf.getBottomMostActivity();
final ActivityRecord openingActivity = embeddedTf.getTopMostActivity();
// Start states.
changes.put(embeddedTf,
new Transition.ChangeInfo(embeddedTf, true /* vis */, false /* exChg */));
changes.put(closingActivity,
new Transition.ChangeInfo(closingActivity, true /* vis */, false /* exChg */));
changes.put(openingActivity,
new Transition.ChangeInfo(openingActivity, false /* vis */, true /* exChg */));
changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(nonEmbeddedActivity,
true /* vis */, false /* exChg */));
// End states.
closingActivity.setVisibleRequested(false);
openingActivity.setVisibleRequested(true);
nonEmbeddedActivity.setVisibleRequested(false);
participants.add(closingActivity);
participants.add(openingActivity);
participants.add(nonEmbeddedActivity);
final ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
participants, changes);
final TransitionInfo info = Transition.calculateTransitionInfo(
transition.mType, 0 /* flags */, targets, mMockT);
// All windows in the Task should have FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY because the Task
// contains embedded activity.
assertEquals(3, info.getChanges().size());
assertTrue(info.getChanges().get(0).hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY));
assertTrue(info.getChanges().get(1).hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY));
assertTrue(info.getChanges().get(2).hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY));
}
@Test
public void testFlagFillsTask_embeddingNotFillingTask() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
final ArraySet<WindowContainer> participants = transition.mParticipants;
final Task task = createTask(mDisplayContent);
final Rect taskBounds = new Rect(0, 0, 500, 1000);
task.getConfiguration().windowConfiguration.setBounds(taskBounds);
final ActivityRecord nonEmbeddedActivity = createActivityRecord(task);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
mAtm.mTaskFragmentOrganizerController.registerOrganizer(
ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()));
final TaskFragment embeddedTf = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
.createActivityCount(1)
.setOrganizer(organizer)
.build();
final ActivityRecord embeddedActivity = embeddedTf.getTopMostActivity();
// Start states.
changes.put(task, new Transition.ChangeInfo(task, true /* vis */, false /* exChg */));
changes.put(nonEmbeddedActivity,
new Transition.ChangeInfo(nonEmbeddedActivity, true /* vis */, false /* exChg */));
changes.put(embeddedTf,
new Transition.ChangeInfo(embeddedTf, false /* vis */, true /* exChg */));
// End states.
nonEmbeddedActivity.setVisibleRequested(false);
embeddedActivity.setVisibleRequested(true);
embeddedTf.setBounds(new Rect(0, 0, 500, 500));
participants.add(nonEmbeddedActivity);
participants.add(embeddedTf);
final ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
participants, changes);
final TransitionInfo info = Transition.calculateTransitionInfo(
transition.mType, 0 /* flags */, targets, mMockT);
// The embedded with bounds overridden should not have the flag.
assertEquals(2, info.getChanges().size());
assertFalse(info.getChanges().get(0).hasFlags(FLAG_FILLS_TASK));
assertEquals(embeddedTf.getBounds(), info.getChanges().get(0).getEndAbsBounds());
assertTrue(info.getChanges().get(1).hasFlags(FLAG_FILLS_TASK));
}
@Test
public void testFlagFillsTask_openActivityFillingTask() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
final ArraySet<WindowContainer> participants = transition.mParticipants;
final Task task = createTask(mDisplayContent);
final Rect taskBounds = new Rect(0, 0, 500, 1000);
task.getConfiguration().windowConfiguration.setBounds(taskBounds);
final ActivityRecord activity = createActivityRecord(task);
// Start states: set bounds to make sure the start bounds is ignored if it is not visible.
activity.getConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 250, 500));
activity.setVisibleRequested(false);
changes.put(activity, new Transition.ChangeInfo(activity));
// End states: reset bounds to fill Task.
activity.getConfiguration().windowConfiguration.setBounds(taskBounds);
activity.setVisibleRequested(true);
participants.add(activity);
final ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
participants, changes);
final TransitionInfo info = Transition.calculateTransitionInfo(
transition.mType, 0 /* flags */, targets, mMockT);
// Opening activity that is filling Task after transition should have the flag.
assertEquals(1, info.getChanges().size());
assertTrue(info.getChanges().get(0).hasFlags(FLAG_FILLS_TASK));
}
@Test
public void testFlagFillsTask_closeActivityFillingTask() {
final Transition transition = createTestTransition(TRANSIT_CLOSE);
final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
final ArraySet<WindowContainer> participants = transition.mParticipants;
final Task task = createTask(mDisplayContent);
final Rect taskBounds = new Rect(0, 0, 500, 1000);
task.getConfiguration().windowConfiguration.setBounds(taskBounds);
final ActivityRecord activity = createActivityRecord(task);
// Start states: fills Task without override.
activity.setVisibleRequested(true);
changes.put(activity, new Transition.ChangeInfo(activity));
// End states: set bounds to make sure the start bounds is ignored if it is not visible.
activity.getConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 250, 500));
activity.setVisibleRequested(false);
participants.add(activity);
final ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
participants, changes);
final TransitionInfo info = Transition.calculateTransitionInfo(
transition.mType, 0 /* flags */, targets, mMockT);
// Closing activity that is filling Task before transition should have the flag.
assertEquals(1, info.getChanges().size());
assertTrue(info.getChanges().get(0).hasFlags(FLAG_FILLS_TASK));
}
@Test
public void testReparentChangeLastParent() {
final Transition transition = createTestTransition(TRANSIT_CHANGE);
final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
final ArraySet<WindowContainer> participants = transition.mParticipants;
// Reparent activity in transition.
final Task lastParent = createTask(mDisplayContent);
final Task newParent = createTask(mDisplayContent);
final ActivityRecord activity = createActivityRecord(lastParent);
activity.setVisibleRequested(true);
// Skip manipulate the SurfaceControl.
doNothing().when(activity).setDropInputMode(anyInt());
changes.put(activity, new Transition.ChangeInfo(activity));
activity.reparent(newParent, POSITION_TOP);
activity.setVisibleRequested(false);
participants.add(activity);
final ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
participants, changes);
final TransitionInfo info = Transition.calculateTransitionInfo(
transition.mType, 0 /* flags */, targets, mMockT);
// Change contains last parent info.
assertEquals(1, info.getChanges().size());
assertEquals(lastParent.mRemoteToken.toWindowContainerToken(),
info.getChanges().get(0).getLastParent());
}
@Test
public void testIncludeEmbeddedActivityReparent() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
final Task task = createTask(mDisplayContent);
task.setBounds(new Rect(0, 0, 2000, 1000));
final ActivityRecord activity = createActivityRecord(task);
activity.setVisibleRequested(true);
// Skip manipulate the SurfaceControl.
doNothing().when(activity).setDropInputMode(anyInt());
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
mAtm.mTaskFragmentOrganizerController.registerOrganizer(
ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()));
final TaskFragment embeddedTf = new TaskFragmentBuilder(mAtm)
.setParentTask(task)
.setOrganizer(organizer)
.build();
// TaskFragment with different bounds from Task.
embeddedTf.setBounds(new Rect(0, 0, 1000, 1000));
// Start states.
transition.collect(activity);
transition.collectExistenceChange(embeddedTf);
// End states.
activity.reparent(embeddedTf, POSITION_TOP);
// Verify that both activity and TaskFragment are included.
final ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
assertTrue(Transition.containsChangeFor(embeddedTf, targets));
assertTrue(Transition.containsChangeFor(activity, targets));
}
@Test
public void testChangeSetBackgroundColor() {
final Transition transition = createTestTransition(TRANSIT_CHANGE);
final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
final ArraySet<WindowContainer> participants = transition.mParticipants;
// Test background color for Activity and embedded TaskFragment.
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
mAtm.mTaskFragmentOrganizerController.registerOrganizer(
ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()));
final Task task = createTask(mDisplayContent);
final TaskFragment embeddedTf = createTaskFragmentWithEmbeddedActivity(task, organizer);
final ActivityRecord embeddedActivity = embeddedTf.getTopMostActivity();
final ActivityRecord nonEmbeddedActivity = createActivityRecord(task);
final ActivityManager.TaskDescription taskDescription =
new ActivityManager.TaskDescription.Builder()
.setBackgroundColor(Color.YELLOW)
.build();
task.setTaskDescription(taskDescription);
// Start states:
embeddedActivity.setVisibleRequested(true);
nonEmbeddedActivity.setVisibleRequested(false);
changes.put(embeddedTf, new Transition.ChangeInfo(embeddedTf));
changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(nonEmbeddedActivity));
// End states:
embeddedActivity.setVisibleRequested(false);
nonEmbeddedActivity.setVisibleRequested(true);
participants.add(embeddedTf);
participants.add(nonEmbeddedActivity);
final ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
participants, changes);
final TransitionInfo info = Transition.calculateTransitionInfo(transition.mType,
0 /* flags */, targets, mMockT);
// Background color should be set on both Activity and embedded TaskFragment.
final int expectedBackgroundColor = ColorUtils.setAlphaComponent(
taskDescription.getBackgroundColor(), 255);
assertEquals(2, info.getChanges().size());
assertEquals(expectedBackgroundColor, info.getChanges().get(0).getBackgroundColor());
assertEquals(expectedBackgroundColor, info.getChanges().get(1).getBackgroundColor());
}
@Test
public void testTransitionVisibleChange() {
registerTestTransitionPlayer();
final ActivityRecord app = createActivityRecord(
mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
final Transition transition = new Transition(TRANSIT_OPEN, 0 /* flags */,
app.mTransitionController, mWm.mSyncEngine);
app.mTransitionController.moveToCollecting(transition);
mWm.mSyncEngine.setSyncMethod(transition.getSyncId(), BLASTSyncEngine.METHOD_NONE);
final ArrayList<WindowContainer> freezeCalls = new ArrayList<>();
transition.setContainerFreezer(new Transition.IContainerFreezer() {
@Override
public boolean freeze(@NonNull WindowContainer wc, @NonNull Rect bounds) {
freezeCalls.add(wc);
return true;
}
@Override
public void cleanUp(SurfaceControl.Transaction t) {
}
});
final Task task = app.getTask();
transition.collect(task);
final Rect bounds = new Rect(task.getBounds());
Configuration c = new Configuration(task.getRequestedOverrideConfiguration());
bounds.inset(10, 10);
c.windowConfiguration.setBounds(bounds);
task.onRequestedOverrideConfigurationChanged(c);
assertTrue(freezeCalls.contains(task));
transition.abort();
}
@Test
public void testDeferTransitionReady_deferStartedTransition() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
transition.setAllReady();
transition.start();
assertTrue(mSyncEngine.isReady(transition.getSyncId()));
transition.deferTransitionReady();
// Both transition ready tracker and sync engine should be deferred.
assertFalse(transition.allReady());
assertFalse(mSyncEngine.isReady(transition.getSyncId()));
transition.continueTransitionReady();
assertTrue(transition.allReady());
assertTrue(mSyncEngine.isReady(transition.getSyncId()));
}
@Test
public void testVisibleChange_snapshot() {
registerTestTransitionPlayer();
final ActivityRecord app = createActivityRecord(
mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */,
app.mTransitionController, mWm.mSyncEngine);
app.mTransitionController.moveToCollecting(transition);
mWm.mSyncEngine.setSyncMethod(transition.getSyncId(), BLASTSyncEngine.METHOD_NONE);
final SurfaceControl mockSnapshot = mock(SurfaceControl.class);
transition.setContainerFreezer(new Transition.IContainerFreezer() {
@Override
public boolean freeze(@NonNull WindowContainer wc, @NonNull Rect bounds) {
Objects.requireNonNull(transition.mChanges.get(wc)).mSnapshot = mockSnapshot;
return true;
}
@Override
public void cleanUp(SurfaceControl.Transaction t) {
}
});
final Task task = app.getTask();
transition.collect(task);
final Rect bounds = new Rect(task.getBounds());
Configuration c = new Configuration(task.getRequestedOverrideConfiguration());
bounds.inset(10, 10);
c.windowConfiguration.setBounds(bounds);
task.onRequestedOverrideConfigurationChanged(c);
ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
TransitionInfo info = Transition.calculateTransitionInfo(
TRANSIT_CHANGE, 0, targets, mMockT);
assertEquals(mockSnapshot,
info.getChange(task.mRemoteToken.toWindowContainerToken()).getSnapshot());
transition.abort();
}
@Test
public void testCollectReparentChange() {
registerTestTransitionPlayer();
// Reparent activity in transition.
final Task lastParent = createTask(mDisplayContent);
final Task newParent = createTask(mDisplayContent);
final ActivityRecord activity = createActivityRecord(lastParent);
doReturn(true).when(lastParent).shouldRemoveSelfOnLastChildRemoval();
doNothing().when(activity).setDropInputMode(anyInt());
activity.setVisibleRequested(true);
final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */,
activity.mTransitionController, mWm.mSyncEngine);
activity.mTransitionController.moveToCollecting(transition);
transition.collect(activity);
activity.reparent(newParent, POSITION_TOP);
// ChangeInfo#mCommonAncestor should be set after reparent.
final Transition.ChangeInfo change = transition.mChanges.get(activity);
assertEquals(newParent.getDisplayArea(), change.mCommonAncestor);
}
@Test
public void testMoveToTopWhileVisible() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
final ArraySet<WindowContainer> participants = transition.mParticipants;
// Start with taskB on top and taskA on bottom but both visible.
final Task rootTaskA = createTask(mDisplayContent);
final Task leafTaskA = createTaskInRootTask(rootTaskA, 0 /* userId */);
final Task taskB = createTask(mDisplayContent);
leafTaskA.setVisibleRequested(true);
taskB.setVisibleRequested(true);
// manually collect since this is a test transition and not known by transitionController.
transition.collect(leafTaskA);
rootTaskA.moveToFront("test", leafTaskA);
// All the tasks were already visible, so there shouldn't be any changes
ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
participants, changes);
assertTrue(targets.isEmpty());
// After collecting order changes, it should recognize that a task moved to top.
transition.collectOrderChanges(true);
targets = Transition.calculateTargets(participants, changes);
assertEquals(1, targets.size());
// Make sure the flag is set
final TransitionInfo info = Transition.calculateTransitionInfo(
transition.mType, 0 /* flags */, targets, mMockT);
assertTrue((info.getChanges().get(0).getFlags() & TransitionInfo.FLAG_MOVED_TO_TOP) != 0);
assertEquals(TRANSIT_CHANGE, info.getChanges().get(0).getMode());
}
private class OrderChangeTestSetup {
final TransitionController mController;
final TestTransitionPlayer mPlayer;
final Transition mTransitA;
final Transition mTransitB;
OrderChangeTestSetup() {
mController = mAtm.getTransitionController();
mPlayer = registerTestTransitionPlayer();
mController.setSyncEngine(mWm.mSyncEngine);
mTransitA = createTestTransition(TRANSIT_OPEN, mController);
mTransitA.mParallelCollectType = Transition.PARALLEL_TYPE_MUTUAL;
mTransitB = createTestTransition(TRANSIT_OPEN, mController);
mTransitB.mParallelCollectType = Transition.PARALLEL_TYPE_MUTUAL;
}
void startParallelCollect(boolean activityLevelFirst) {
// Start with taskB on top and taskA on bottom but both visible.
final Task taskA = createTask(mDisplayContent);
taskA.setVisibleRequested(true);
final ActivityRecord actA = createActivityRecord(taskA);
final TestWindowState winA = createWindowState(
new WindowManager.LayoutParams(TYPE_BASE_APPLICATION), actA);
actA.addWindow(winA);
final ActivityRecord actB = createActivityRecord(taskA);
final TestWindowState winB = createWindowState(
new WindowManager.LayoutParams(TYPE_BASE_APPLICATION), actB);
actB.addWindow(winB);
final Task taskB = createTask(mDisplayContent);
actA.setVisibleRequested(true);
actB.setVisibleRequested(false);
taskB.setVisibleRequested(true);
assertTrue(actA.isAttached());
final Consumer<Boolean> startAndCollectA = (doReady) -> {
mController.startCollectOrQueue(mTransitA, (deferred) -> {
});
// Collect activity-level change into A
mTransitA.collect(actA);
actA.setVisibleRequested(false);
winA.onSyncFinishedDrawing();
mTransitA.collect(actB);
actB.setVisibleRequested(true);
winB.onSyncFinishedDrawing();
mTransitA.start();
if (doReady) {
mTransitA.setReady(mDisplayContent, true);
}
};
final Consumer<Boolean> startAndCollectB = (doReady) -> {
mController.startCollectOrQueue(mTransitB, (deferred) -> {
});
mTransitB.collect(taskA);
taskA.moveToFront("test");
mTransitB.start();
if (doReady) {
mTransitB.setReady(mDisplayContent, true);
}
};
if (activityLevelFirst) {
startAndCollectA.accept(true);
startAndCollectB.accept(false);
} else {
startAndCollectB.accept(true);
startAndCollectA.accept(false);
}
}
}
@Test
public void testMoveToTopStartAfterReadyAfterParallel() {
// Start collect activity-only transit A
// Start collect task transit B in parallel
// finish A first -> should not include order change from B.
final OrderChangeTestSetup setup = new OrderChangeTestSetup();
setup.startParallelCollect(true /* activity first */);
mWm.mSyncEngine.tryFinishForTest(setup.mTransitA.getSyncId());
waitUntilHandlersIdle();
for (int i = 0; i < setup.mPlayer.mLastReady.getChanges().size(); ++i) {
assertNull(setup.mPlayer.mLastReady.getChanges().get(i).getTaskInfo());
}
setup.mTransitB.setAllReady();
mWm.mSyncEngine.tryFinishForTest(setup.mTransitB.getSyncId());
waitUntilHandlersIdle();
boolean hasOrderChange = false;
for (int i = 0; i < setup.mPlayer.mLastReady.getChanges().size(); ++i) {
final TransitionInfo.Change chg = setup.mPlayer.mLastReady.getChanges().get(i);
if (chg.getTaskInfo() == null) continue;
hasOrderChange = hasOrderChange || (chg.getFlags() & FLAG_MOVED_TO_TOP) != 0;
}
assertTrue(hasOrderChange);
}
@Test
public void testMoveToTopStartAfterReadyBeforeParallel() {
// Start collect activity-only transit A
// Start collect task transit B in parallel
// finish B first -> should include order change
// then finish A -> should NOT include order change.
final OrderChangeTestSetup setup = new OrderChangeTestSetup();
setup.startParallelCollect(true /* activity first */);
// Make it unready now so that it doesn't get dequeued automatically.
setup.mTransitA.setReady(mDisplayContent, false);
// Make task change ready first
setup.mTransitB.setAllReady();
mWm.mSyncEngine.tryFinishForTest(setup.mTransitB.getSyncId());
waitUntilHandlersIdle();
boolean hasOrderChange = false;
for (int i = 0; i < setup.mPlayer.mLastReady.getChanges().size(); ++i) {
final TransitionInfo.Change chg = setup.mPlayer.mLastReady.getChanges().get(i);
if (chg.getTaskInfo() == null) continue;
hasOrderChange = hasOrderChange || (chg.getFlags() & FLAG_MOVED_TO_TOP) != 0;
}
assertTrue(hasOrderChange);
setup.mTransitA.setAllReady();
mWm.mSyncEngine.tryFinishForTest(setup.mTransitA.getSyncId());
waitUntilHandlersIdle();
for (int i = 0; i < setup.mPlayer.mLastReady.getChanges().size(); ++i) {
assertNull(setup.mPlayer.mLastReady.getChanges().get(i).getTaskInfo());
}
}
@Test
public void testMoveToTopStartBeforeReadyAfterParallel() {
// Start collect task transit B
// Start collect activity-only transit A in parallel
// finish A first -> should not include order change from B.
final OrderChangeTestSetup setup = new OrderChangeTestSetup();
setup.startParallelCollect(false /* activity first */);
// Make B unready now so that it doesn't get dequeued automatically.
setup.mTransitB.setReady(mDisplayContent, false);
setup.mTransitA.setAllReady();
mWm.mSyncEngine.tryFinishForTest(setup.mTransitA.getSyncId());
waitUntilHandlersIdle();
for (int i = 0; i < setup.mPlayer.mLastReady.getChanges().size(); ++i) {
assertNull(setup.mPlayer.mLastReady.getChanges().get(i).getTaskInfo());
}
setup.mTransitB.setAllReady();
mWm.mSyncEngine.tryFinishForTest(setup.mTransitB.getSyncId());
waitUntilHandlersIdle();
boolean hasOrderChange = false;
for (int i = 0; i < setup.mPlayer.mLastReady.getChanges().size(); ++i) {
final TransitionInfo.Change chg = setup.mPlayer.mLastReady.getChanges().get(i);
if (chg.getTaskInfo() == null) continue;
hasOrderChange = hasOrderChange || (chg.getFlags() & FLAG_MOVED_TO_TOP) != 0;
}
assertTrue(hasOrderChange);
}
@Test
public void testMoveToTopStartBeforeReadyBeforeParallel() {
// Start collect task transit B
// Start collect activity-only transit A in parallel
// finish B first -> should include order change
// then finish A -> should NOT include order change.
final OrderChangeTestSetup setup = new OrderChangeTestSetup();
setup.startParallelCollect(false /* activity first */);
mWm.mSyncEngine.tryFinishForTest(setup.mTransitB.getSyncId());
waitUntilHandlersIdle();
boolean hasOrderChange = false;
for (int i = 0; i < setup.mPlayer.mLastReady.getChanges().size(); ++i) {
final TransitionInfo.Change chg = setup.mPlayer.mLastReady.getChanges().get(i);
if (chg.getTaskInfo() == null) continue;
hasOrderChange = hasOrderChange || (chg.getFlags() & FLAG_MOVED_TO_TOP) != 0;
}
assertTrue(hasOrderChange);
setup.mTransitA.setAllReady();
mWm.mSyncEngine.tryFinishForTest(setup.mTransitA.getSyncId());
waitUntilHandlersIdle();
for (int i = 0; i < setup.mPlayer.mLastReady.getChanges().size(); ++i) {
assertNull(setup.mPlayer.mLastReady.getChanges().get(i).getTaskInfo());
}
}
@Test
public void testQueueStartCollect() {
final TransitionController controller = mAtm.getTransitionController();
final TestTransitionPlayer player = registerTestTransitionPlayer();
mSyncEngine = createTestBLASTSyncEngine();
controller.setSyncEngine(mSyncEngine);
final Transition transitA = createTestTransition(TRANSIT_OPEN, controller);
final Transition transitB = createTestTransition(TRANSIT_OPEN, controller);
final Transition transitC = createTestTransition(TRANSIT_OPEN, controller);
final boolean[] onStartA = new boolean[]{false, false};
final boolean[] onStartB = new boolean[]{false, false};
controller.startCollectOrQueue(transitA, (deferred) -> {
onStartA[0] = true;
onStartA[1] = deferred;
});
controller.startCollectOrQueue(transitB, (deferred) -> {
onStartB[0] = true;
onStartB[1] = deferred;
});
waitUntilHandlersIdle();
assertTrue(onStartA[0]);
assertFalse(onStartA[1]);
assertTrue(transitA.isCollecting());
// B should be queued, so no calls yet
assertFalse(onStartB[0]);
assertTrue(transitB.isPending());
// finish collecting A
transitA.start();
transitA.setAllReady();
mSyncEngine.tryFinishForTest(transitA.getSyncId());
waitUntilHandlersIdle();
assertTrue(transitA.isPlaying());
assertTrue(transitB.isCollecting());
assertTrue(onStartB[0]);
// Should receive deferred = true
assertTrue(onStartB[1]);
// finish collecting B
transitB.start();
transitB.setAllReady();
mSyncEngine.tryFinishForTest(transitB.getSyncId());
assertTrue(transitB.isPlaying());
// Now we should be able to start collecting directly a new transition
final boolean[] onStartC = new boolean[]{false, false};
controller.startCollectOrQueue(transitC, (deferred) -> {
onStartC[0] = true;
onStartC[1] = deferred;
});
waitUntilHandlersIdle();
assertTrue(onStartC[0]);
assertFalse(onStartC[1]);
assertTrue(transitC.isCollecting());
}
@Test
public void testQueueWithLegacy() {
final TransitionController controller = mAtm.getTransitionController();
final TestTransitionPlayer player = registerTestTransitionPlayer();
mSyncEngine = createTestBLASTSyncEngine();
controller.setSyncEngine(mSyncEngine);
final Transition transitA = createTestTransition(TRANSIT_OPEN, controller);
final Transition transitB = createTestTransition(TRANSIT_OPEN, controller);
controller.startCollectOrQueue(transitA, (deferred) -> {});
BLASTSyncEngine.SyncGroup legacySync = mSyncEngine.prepareSyncSet(
mock(BLASTSyncEngine.TransactionReadyListener.class), "test");
final boolean[] applyLegacy = new boolean[]{false};
controller.startLegacySyncOrQueue(legacySync, () -> applyLegacy[0] = true);
assertFalse(applyLegacy[0]);
waitUntilHandlersIdle();
controller.startCollectOrQueue(transitB, (deferred) -> {});
assertTrue(transitA.isCollecting());
// finish collecting A
transitA.start();
transitA.setAllReady();
mSyncEngine.tryFinishForTest(transitA.getSyncId());
waitUntilHandlersIdle();
assertTrue(transitA.isPlaying());
// legacy sync should start now
assertTrue(applyLegacy[0]);
// transitB must wait
assertTrue(transitB.isPending());
// finish legacy sync
mSyncEngine.setReady(legacySync.mSyncId);
mSyncEngine.tryFinishForTest(legacySync.mSyncId);
// transitioncontroller should be notified so it can start collecting B
assertTrue(transitB.isCollecting());
}
@Test
public void testQueueParallel() {
final TransitionController controller = mAtm.getTransitionController();
final TestTransitionPlayer player = registerTestTransitionPlayer();
mSyncEngine = createTestBLASTSyncEngine();
controller.setSyncEngine(mSyncEngine);
final Transition transitA = createTestTransition(TRANSIT_OPEN, controller);
transitA.mParallelCollectType = Transition.PARALLEL_TYPE_MUTUAL;
final Transition transitB = createTestTransition(TRANSIT_OPEN, controller);
transitB.mParallelCollectType = Transition.PARALLEL_TYPE_MUTUAL;
final Transition transitC = createTestTransition(TRANSIT_OPEN, controller);
transitC.mParallelCollectType = Transition.PARALLEL_TYPE_MUTUAL;
final Transition transitSync = createTestTransition(TRANSIT_OPEN, controller);
final Transition transitD = createTestTransition(TRANSIT_OPEN, controller);
controller.startCollectOrQueue(transitA, (deferred) -> {});
controller.startCollectOrQueue(transitB, (deferred) -> {});
controller.startCollectOrQueue(transitC, (deferred) -> {});
controller.startCollectOrQueue(transitSync, (deferred) -> {});
controller.startCollectOrQueue(transitD, (deferred) -> {});
assertTrue(transitA.isCollecting() && !transitA.isStarted());
// We still serialize on readiness
assertTrue(transitB.isPending());
assertTrue(transitC.isPending());
transitA.start();
transitA.setAllReady();
transitB.start();
transitB.setAllReady();
// A, B, and C should be collecting in parallel now.
assertTrue(transitA.isStarted());
assertTrue(transitB.isStarted());
assertTrue(transitC.isCollecting() && !transitC.isStarted());
transitC.start();
transitC.setAllReady();
assertTrue(transitA.isStarted());
assertTrue(transitB.isStarted());
assertTrue(transitC.isStarted());
// Not parallel so should remain pending
assertTrue(transitSync.isPending());
// After Sync, so should also remain pending.
assertTrue(transitD.isPending());
// There should always be a collector, since Sync can't collect yet, C should remain.
assertEquals(transitC, controller.getCollectingTransition());
mSyncEngine.tryFinishForTest(transitB.getSyncId());
// The other transitions should remain waiting.
assertTrue(transitA.isStarted());
assertTrue(transitB.isPlaying());
assertTrue(transitC.isStarted());
assertEquals(transitC, controller.getCollectingTransition());
mSyncEngine.tryFinishForTest(transitC.getSyncId());
assertTrue(transitA.isStarted());
assertTrue(transitC.isPlaying());
// The "collecting" one became ready, so the first "waiting" should move back to collecting.
assertEquals(transitA, controller.getCollectingTransition());
assertTrue(transitSync.isPending());
assertTrue(transitD.isPending());
mSyncEngine.tryFinishForTest(transitA.getSyncId());
// Now all collectors are done, so sync can be pulled-off the queue.
assertTrue(transitSync.isCollecting() && !transitSync.isStarted());
transitSync.start();
transitSync.setAllReady();
// Since D can run in parallel, it should be pulled-off the queue.
assertTrue(transitSync.isStarted());
assertTrue(transitD.isPending());
mSyncEngine.tryFinishForTest(transitSync.getSyncId());
assertTrue(transitD.isCollecting());
transitD.start();
transitD.setAllReady();
mSyncEngine.tryFinishForTest(transitD.getSyncId());
// Now nothing should be collecting
assertFalse(controller.isCollecting());
}
private static void makeTaskOrganized(Task... tasks) {
final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
for (Task t : tasks) {
t.mTaskOrganizer = organizer;
}
}
private static void makeDisplayAreaOrganized(WindowContainer<?> from,
WindowContainer<?> end) {
final IDisplayAreaOrganizer organizer = mock(IDisplayAreaOrganizer.class);
while (from != null && from != end) {
if (from.asDisplayArea() != null) {
from.asDisplayArea().mOrganizer = organizer;
}
from = from.getParent();
}
if (end.asDisplayArea() != null) {
end.asDisplayArea().mOrganizer = organizer;
}
}
/** Fill the change map with all the parents of top. Change maps are usually fully populated */
private static void fillChangeMap(ArrayMap<WindowContainer, Transition.ChangeInfo> changes,
WindowContainer top) {
for (WindowContainer curr = top.getParent(); curr != null; curr = curr.getParent()) {
changes.put(curr, new Transition.ChangeInfo(curr, true /* vis */, false /* exChg */));
}
}
private static SurfaceControl.TransactionCommittedListener onRotationTransactionReady(
TestTransitionPlayer player, SurfaceControl.Transaction startTransaction) {
final ArgumentCaptor<SurfaceControl.TransactionCommittedListener> listenerCaptor =
ArgumentCaptor.forClass(SurfaceControl.TransactionCommittedListener.class);
player.onTransactionReady(startTransaction);
verify(startTransaction).addTransactionCommittedListener(any(), listenerCaptor.capture());
return listenerCaptor.getValue();
}
}