blob: 45d8e226c64f26422731e6f1603ea022510b3e50 [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_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_SHOW_WALLPAPER;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static android.window.TransitionInfo.isIndependent;
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.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.mockito.ArgumentMatchers.any;
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.content.res.Configuration;
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.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 org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
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) {
TransitionTracer tracer = mock(TransitionTracer.class);
final TransitionController controller = new TransitionController(
mock(ActivityTaskManagerService.class), mock(TaskSnapshotController.class),
mock(TransitionTracer.class));
mSyncEngine = createTestBLASTSyncEngine();
final Transition t = new Transition(transitType, 0 /* flags */, controller, mSyncEngine);
t.startCollecting(0 /* timeoutMs */);
return t;
}
@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(false /* vis */, true /* exChg */));
changes.put(oldTask, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
changes.put(opening, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
fillChangeMap(changes, newTask);
// End states.
closing.mVisibleRequested = false;
opening.mVisibleRequested = true;
final int transit = transition.mType;
int flags = 0;
// Check basic both tasks participating
participants.add(oldTask);
participants.add(newTask);
ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes,
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, changes, 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, changes, 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, changes, 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(false /* vis */, true /* exChg */));
changes.put(newNestedTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
changes.put(newNestedTask2, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
changes.put(oldTask, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
changes.put(opening, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
changes.put(opening2, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
fillChangeMap(changes, newTask);
// End states.
closing.mVisibleRequested = false;
opening.mVisibleRequested = true;
opening2.mVisibleRequested = true;
final int transit = transition.mType;
int flags = 0;
// Check full promotion from leaf
participants.add(oldTask);
participants.add(opening);
participants.add(opening2);
ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes,
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, changes, mMockT);
assertEquals(2, info.getChanges().size());
assertNotNull(info.getChange(newNestedTask.mRemoteToken.toWindowContainerToken()));
assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
}
@Test
public void testCreateInfo_DisplayArea() {
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(false /* vis */, true /* exChg */));
changes.put(showNestedTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
changes.put(showTask2, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
changes.put(tda, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
changes.put(showing, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
changes.put(showing2, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
fillChangeMap(changes, tda);
// End states.
showing.mVisibleRequested = true;
showing2.mVisibleRequested = true;
final int transit = transition.mType;
int flags = 0;
// Check promotion to DisplayArea
participants.add(showing);
participants.add(showing2);
ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes,
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, changes, 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, changes, 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.mVisibleRequested = false; // starts invisible
final Task closeTask = createTask(mDisplayContent);
final ActivityRecord closing = createActivityRecord(closeTask);
closing.mVisibleRequested = true; // starts visible
transition.collectExistenceChange(openTask);
transition.collect(opening);
transition.collect(closing);
opening.mVisibleRequested = true;
closing.mVisibleRequested = false;
ArrayList<WindowContainer> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
TransitionInfo info = Transition.calculateTransitionInfo(
0, 0, targets, transition.mChanges, 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.mVisibleRequested = (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().mVisibleRequested = (i % 2) != 0;
}
ArrayList<WindowContainer> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
TransitionInfo info = Transition.calculateTransitionInfo(
0, 0, targets, transition.mChanges, 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.mVisibleRequested = (i % 2) == 0; // starts invisible
act.visibleIgnoringKeyguard = (i % 2) == 0;
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().mVisibleRequested = (i % 2) != 0;
}
ArrayList<WindowContainer> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
TransitionInfo info = Transition.calculateTransitionInfo(
0, 0, targets, transition.mChanges, 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 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);
mDisplayContent.getWindowConfiguration().setRotation(
(mDisplayContent.getWindowConfiguration().getRotation() + 1) % 4);
ArrayList<WindowContainer> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
TransitionInfo info = Transition.calculateTransitionInfo(
0, 0, targets, transition.mChanges, 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).build();
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.collectExistenceChange(app.getTask());
controller.requestStartTransition(transition, app.getTask(), remoteTransition,
null /* displayChange */);
testPlayer.startTransition();
testPlayer.onTransactionReady(app.getSyncTransaction());
assertTrue(playerProc.isRunningRemoteTransition());
assertTrue(delegateProc.isRunningRemoteTransition());
assertTrue(controller.mRemotePlayer.reportRunning(delegateProc.getThread()));
testPlayer.finish();
assertFalse(playerProc.isRunningRemoteTransition());
assertFalse(delegateProc.isRunningRemoteTransition());
assertFalse(controller.mRemotePlayer.reportRunning(delegateProc.getThread()));
}
@Test
public void testOpenActivityInTheSameTaskWithDisplayChange() {
final ActivityRecord closing = createActivityRecord(mDisplayContent);
closing.mVisibleRequested = true;
final Task task = closing.getTask();
makeTaskOrganized(task);
final ActivityRecord opening = createActivityRecord(task);
opening.mVisibleRequested = 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.mVisibleRequested = false;
opening.mVisibleRequested = true;
final int newRotation = mDisplayContent.getWindowConfiguration().getRotation() + 1;
for (WindowContainer<?> wc : wcs) {
wc.getWindowConfiguration().setRotation(newRotation);
}
final ArrayList<WindowContainer> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
// Especially the activities must be in the targets.
assertTrue(targets.containsAll(Arrays.asList(wcs)));
}
@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(false /* vis */, true /* exChg */));
changes.put(changeTask, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
changes.put(openInOpenTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
changes.put(openInChangeTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
changes.put(changeInChangeTask,
new Transition.ChangeInfo(true /* vis */, false /* exChg */));
changes.put(openInOpen, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
changes.put(openInChange, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
changes.put(changeInChange, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
fillChangeMap(changes, openTask);
// End states.
changeInChange.mVisibleRequested = true;
openInOpen.mVisibleRequested = true;
openInChange.mVisibleRequested = true;
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<WindowContainer> targets =
Transition.calculateTargets(participants, changes);
TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes,
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 newTask = createTask(mDisplayContent);
doReturn(false).when(newTask).isTranslucent(any());
final Task oldTask = createTask(mDisplayContent);
doReturn(false).when(oldTask).isTranslucent(any());
final ActivityRecord closing = createActivityRecord(oldTask);
closing.setOccludesParent(true);
final ActivityRecord opening = createActivityRecord(newTask);
opening.setOccludesParent(false);
// Start states.
changes.put(newTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
changes.put(oldTask, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
changes.put(opening, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
changes.put(closing, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
fillChangeMap(changes, newTask);
// End states.
closing.mVisibleRequested = true;
opening.mVisibleRequested = true;
final int transit = transition.mType;
int flags = 0;
// Check basic both tasks participating
participants.add(oldTask);
participants.add(newTask);
ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes,
mMockT);
assertEquals(2, info.getChanges().size());
assertEquals(transit, info.getType());
assertTrue((info.getChanges().get(0).getFlags() & FLAG_TRANSLUCENT) == 0);
assertTrue((info.getChanges().get(1).getFlags() & FLAG_TRANSLUCENT) == 0);
}
@Test
public void testOpenTranslucentTask() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> participants = transition.mParticipants;
final Task newTask = createTask(mDisplayContent);
doReturn(true).when(newTask).isTranslucent(any());
final Task oldTask = createTask(mDisplayContent);
doReturn(false).when(oldTask).isTranslucent(any());
final ActivityRecord closing = createActivityRecord(oldTask);
closing.setOccludesParent(true);
final ActivityRecord opening = createActivityRecord(newTask);
opening.setOccludesParent(false);
// Start states.
changes.put(newTask, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
changes.put(oldTask, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
changes.put(opening, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
changes.put(closing, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
fillChangeMap(changes, newTask);
// End states.
closing.mVisibleRequested = true;
opening.mVisibleRequested = true;
final int transit = transition.mType;
int flags = 0;
// Check basic both tasks participating
participants.add(oldTask);
participants.add(newTask);
ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes,
mMockT);
assertEquals(2, info.getChanges().size());
assertEquals(transit, info.getType());
assertTrue((info.getChanges().get(0).getFlags() & FLAG_TRANSLUCENT) != 0);
assertTrue((info.getChanges().get(1).getFlags() & FLAG_TRANSLUCENT) == 0);
}
@Test
public void testTimeout() {
final TransitionController controller = new TransitionController(mAtm,
mock(TaskSnapshotController.class), mock(TransitionTracer.class));
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),
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 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);
for (WindowState w : windows) {
w.setOrientationChanging(true);
}
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);
screenDecor.setOrientationChanging(false);
// 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.
statusBar.setOrientationChanging(false);
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.setOrientationChanging(false);
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);
statusBar.setOrientationChanging(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(), 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());
statusBar.setOrientationChanging(true);
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);
statusBar.setOrientationChanging(false);
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.mVisibleRequested = 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 TaskSnapshotController snapshotController = mock(TaskSnapshotController.class);
final TransitionController controller = new TransitionController(mAtm, snapshotController,
mock(TransitionTracer.class));
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.mVisibleRequested = false;
activity1.setVisible(false);
final Task task2 = createTask(mDisplayContent);
makeTaskOrganized(task1, task2);
final ActivityRecord activity2 = createActivityRecord(task1);
activity2.mVisibleRequested = true;
activity2.setVisible(true);
openTransition.collectExistenceChange(task1);
openTransition.collectExistenceChange(activity1);
openTransition.collectExistenceChange(task2);
openTransition.collectExistenceChange(activity2);
activity1.mVisibleRequested = true;
activity1.setVisible(true);
activity2.mVisibleRequested = 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.mVisibleRequested = false;
activity2.mVisibleRequested = 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());
}
@Test
public void testTransientLaunch() {
final TaskSnapshotController snapshotController = mock(TaskSnapshotController.class);
final ArrayList<ActivityRecord> enteringAnimReports = new ArrayList<>();
final TransitionController controller = new TransitionController(mAtm, snapshotController,
mock(TransitionTracer.class)) {
@Override
protected void dispatchLegacyAppTransitionFinished(ActivityRecord ar) {
if (ar.mEnteringAnimation) {
enteringAnimReports.add(ar);
}
super.dispatchLegacyAppTransitionFinished(ar);
}
};
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.mVisibleRequested = false;
activity1.setVisible(false);
final Task task2 = createTask(mDisplayContent);
makeTaskOrganized(task1, task2);
final ActivityRecord activity2 = createActivityRecord(task2);
activity2.mVisibleRequested = true;
activity2.setVisible(true);
openTransition.collectExistenceChange(task1);
openTransition.collectExistenceChange(activity1);
openTransition.collectExistenceChange(task2);
openTransition.collectExistenceChange(activity2);
activity1.mVisibleRequested = true;
activity1.setVisible(true);
activity2.mVisibleRequested = 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(snapshotController, times(1)).recordTaskSnapshot(eq(task2), eq(false));
openTransition.finishTransition();
// 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);
closeTransition.setTransientLaunch(activity2, null /* restoreBelow */);
activity1.mVisibleRequested = false;
activity2.mVisibleRequested = 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(snapshotController, times(0)).recordTaskSnapshot(eq(task1), eq(false));
enteringAnimReports.clear();
closeTransition.finishTransition();
verify(snapshotController, times(1)).recordTaskSnapshot(eq(task1), eq(false));
assertTrue(enteringAnimReports.contains(activity2));
}
@Test
public void testNotReadyPushPop() {
final TaskSnapshotController snapshotController = mock(TaskSnapshotController.class);
final TransitionController controller = new TransitionController(mAtm, snapshotController,
mock(TransitionTracer.class));
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(true /* vis */, false /* exChg */));
changes.put(activity1, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
// End states.
activity0.mVisibleRequested = false;
activity1.mVisibleRequested = true;
participants.add(activity0);
participants.add(activity1);
final ArrayList<WindowContainer> targets = Transition.calculateTargets(
participants, changes);
final TransitionInfo info = Transition.calculateTransitionInfo(
transition.mType, 0 /* flags */, targets, changes, 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(true /* vis */, false /* exChg */));
changes.put(closingActivity, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
changes.put(openingActivity, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(true /* vis */,
false /* exChg */));
// End states.
closingActivity.mVisibleRequested = false;
openingActivity.mVisibleRequested = true;
nonEmbeddedActivity.mVisibleRequested = false;
participants.add(closingActivity);
participants.add(openingActivity);
participants.add(nonEmbeddedActivity);
final ArrayList<WindowContainer> targets = Transition.calculateTargets(
participants, changes);
final TransitionInfo info = Transition.calculateTransitionInfo(
transition.mType, 0 /* flags */, targets, changes, 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);
// Set to multi-windowing mode in order to set bounds.
task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
final Rect taskBounds = new Rect(0, 0, 500, 1000);
task.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(true /* vis */, false /* exChg */));
changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(true /* vis */,
false /* exChg */));
changes.put(embeddedTf, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
// End states.
nonEmbeddedActivity.mVisibleRequested = false;
embeddedActivity.mVisibleRequested = true;
embeddedTf.setBounds(new Rect(0, 0, 500, 500));
participants.add(nonEmbeddedActivity);
participants.add(embeddedTf);
final ArrayList<WindowContainer> targets = Transition.calculateTargets(
participants, changes);
final TransitionInfo info = Transition.calculateTransitionInfo(
transition.mType, 0 /* flags */, targets, changes, 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());
assertFalse(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);
// Set to multi-windowing mode in order to set bounds.
task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
final Rect taskBounds = new Rect(0, 0, 500, 1000);
task.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.mVisibleRequested = false;
changes.put(activity, new Transition.ChangeInfo(activity));
// End states: reset bounds to fill Task.
activity.getConfiguration().windowConfiguration.setBounds(taskBounds);
activity.mVisibleRequested = true;
participants.add(activity);
final ArrayList<WindowContainer> targets = Transition.calculateTargets(
participants, changes);
final TransitionInfo info = Transition.calculateTransitionInfo(
transition.mType, 0 /* flags */, targets, changes, 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);
// Set to multi-windowing mode in order to set bounds.
task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
final Rect taskBounds = new Rect(0, 0, 500, 1000);
task.setBounds(taskBounds);
final ActivityRecord activity = createActivityRecord(task);
// Start states: fills Task without override.
activity.mVisibleRequested = 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.mVisibleRequested = false;
participants.add(activity);
final ArrayList<WindowContainer> targets = Transition.calculateTargets(
participants, changes);
final TransitionInfo info = Transition.calculateTransitionInfo(
transition.mType, 0 /* flags */, targets, changes, 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 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.mVisibleRequested = 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<WindowContainer> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
assertTrue(targets.contains(embeddedTf));
assertTrue(targets.contains(activity));
}
@Test
public void testTransitionVisibleChange() {
registerTestTransitionPlayer();
final ActivityRecord app = createActivityRecord(mDisplayContent);
final Transition transition = new Transition(TRANSIT_OPEN, 0 /* flags */,
app.mTransitionController, mWm.mSyncEngine);
app.mTransitionController.moveToCollecting(transition, 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);
final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */,
app.mTransitionController, mWm.mSyncEngine);
app.mTransitionController.moveToCollecting(transition, 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<WindowContainer> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
TransitionInfo info = Transition.calculateTransitionInfo(
TRANSIT_CHANGE, 0, targets, transition.mChanges, mMockT);
assertEquals(mockSnapshot,
info.getChange(task.mRemoteToken.toWindowContainerToken()).getSnapshot());
transition.abort();
}
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(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();
}
}