Keep visible requested for transient hide activities
If a process doesn't have other active component and its activity is not visible requested, it will become background state and may lose the access of various system services. The invisible requested will also cause other animation issues such as incorrect dim layer, window surface position. Because isGoneForLayout will return false by the visible state. By checking isTransientHide in TaskFragment#getVisibility, the task can keep visible even if it is occluded by the transient launch target. And then add FLAG_ABOVE_TRANSIENT_LAUNCH to the tree of transient-hide task so the ChangeInfo will always report as TRANSIT_TO_BACK. When the transient transition is finished, if the transient launch target is visible, then update the visibility to make the transient-hide activities to be invisible requested and add the activities to stopping list. So when checking the visibility participants, it can continue to commit the state. Also add a condition for mAvoidMoveToFront for the case that the moving-to-back task may be moved to front again after transient launch if the app uses a trampoline activity. - Previously during the transient transition, the trampoline launch will be blocked by BAL because invisible. - In legacy transition, the task is already on top (recent is launch-behind) and finish-recents-animation will move home to front. Now it will launch the new activity into its task without moving the task to front, e.g. during the swipe-return-to-home animation, the going-to-invisible activity launches next activity, it will still stay at home. And then launch the app again, the latest new activity on its task can be resumed directly. Fix: 269577449 Fix: 271282193 Test: atest TransitionTests#testTransientLaunch Change-Id: I887f63a4985804a59e6602817dce6aec0e6f2c69
This commit is contained in:
@@ -1680,6 +1680,11 @@ class ActivityStarter {
|
||||
targetTask.removeImmediately("bulky-task");
|
||||
return START_ABORTED;
|
||||
}
|
||||
// When running transient transition, the transient launch target should keep on top.
|
||||
// So disallow the transient hide activity to move itself to front, e.g. trampoline.
|
||||
if (!mAvoidMoveToFront && r.mTransitionController.isTransientHide(targetTask)) {
|
||||
mAvoidMoveToFront = true;
|
||||
}
|
||||
mPriorAboveTask = TaskDisplayArea.getRootTaskAbove(targetTask.getRootTask());
|
||||
}
|
||||
|
||||
@@ -1796,7 +1801,7 @@ class ActivityStarter {
|
||||
// root-task to the will not update the focused root-task. If starting the new
|
||||
// activity now allows the task root-task to be focusable, then ensure that we
|
||||
// now update the focused root-task accordingly.
|
||||
if (mTargetRootTask.isTopActivityFocusable()
|
||||
if (!mAvoidMoveToFront && mTargetRootTask.isTopActivityFocusable()
|
||||
&& !mRootWindowContainer.isTopDisplayFocusedRootTask(mTargetRootTask)) {
|
||||
mTargetRootTask.moveToFront("startActivityInner");
|
||||
}
|
||||
|
||||
@@ -1012,6 +1012,10 @@ class TaskFragment extends WindowContainer<WindowContainer> {
|
||||
if (isTopActivityLaunchedBehind()) {
|
||||
return TASK_FRAGMENT_VISIBILITY_VISIBLE;
|
||||
}
|
||||
final Task thisTask = asTask();
|
||||
if (thisTask != null && mTransitionController.isTransientHide(thisTask)) {
|
||||
return TASK_FRAGMENT_VISIBILITY_VISIBLE;
|
||||
}
|
||||
|
||||
boolean gotTranslucentFullscreen = false;
|
||||
boolean gotTranslucentAdjacent = false;
|
||||
|
||||
@@ -194,6 +194,13 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
|
||||
*/
|
||||
private ArrayMap<ActivityRecord, Task> mTransientLaunches = null;
|
||||
|
||||
/**
|
||||
* The tasks that may be occluded by the transient activity. Assume the task stack is
|
||||
* [Home, A(opaque), B(opaque), C(translucent)] (bottom to top), then A is the restore-below
|
||||
* task, and [B, C] are the transient-hide tasks.
|
||||
*/
|
||||
private ArrayList<Task> mTransientHideTasks;
|
||||
|
||||
/** Custom activity-level animation options and callbacks. */
|
||||
private TransitionInfo.AnimationOptions mOverrideOptions;
|
||||
private IRemoteCallback mClientAnimationStartCallback = null;
|
||||
@@ -265,35 +272,51 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
|
||||
void setTransientLaunch(@NonNull ActivityRecord activity, @Nullable Task restoreBelow) {
|
||||
if (mTransientLaunches == null) {
|
||||
mTransientLaunches = new ArrayMap<>();
|
||||
mTransientHideTasks = new ArrayList<>();
|
||||
}
|
||||
mTransientLaunches.put(activity, restoreBelow);
|
||||
setTransientLaunchToChanges(activity);
|
||||
|
||||
if (restoreBelow != null) {
|
||||
final ChangeInfo info = mChanges.get(restoreBelow);
|
||||
if (info != null) {
|
||||
info.mFlags |= ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH;
|
||||
// Collect all visible activities which can be occluded by the transient activity to
|
||||
// make sure they are in the participants so their visibilities can be updated when
|
||||
// finishing transition.
|
||||
((WindowContainer<?>) restoreBelow.getParent()).forAllTasks(t -> {
|
||||
if (t.isVisibleRequested() && !t.isAlwaysOnTop()
|
||||
&& !t.getWindowConfiguration().tasksAreFloating()) {
|
||||
if (t.isRootTask()) {
|
||||
mTransientHideTasks.add(t);
|
||||
}
|
||||
if (t.isLeafTask()) {
|
||||
t.forAllActivities(r -> {
|
||||
if (r.isVisibleRequested()) {
|
||||
collect(r);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return t == restoreBelow;
|
||||
});
|
||||
// Add FLAG_ABOVE_TRANSIENT_LAUNCH to the tree of transient-hide tasks,
|
||||
// so ChangeInfo#hasChanged() can return true to report the transition info.
|
||||
for (int i = mChanges.size() - 1; i >= 0; --i) {
|
||||
final WindowContainer<?> wc = mChanges.keyAt(i);
|
||||
if (wc.asTaskFragment() == null && wc.asActivityRecord() == null) continue;
|
||||
if (isInTransientHide(wc)) {
|
||||
mChanges.valueAt(i).mFlags |= ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH;
|
||||
}
|
||||
}
|
||||
}
|
||||
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as "
|
||||
+ "transient-launch", mSyncId, activity);
|
||||
}
|
||||
|
||||
boolean isTransientHide(@NonNull Task task) {
|
||||
if (mTransientLaunches == null) return false;
|
||||
for (int i = 0; i < mTransientLaunches.size(); ++i) {
|
||||
if (mTransientLaunches.valueAt(i) == task) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @return whether `wc` is a descendent of a transient-hide window. */
|
||||
boolean isInTransientHide(@NonNull WindowContainer wc) {
|
||||
if (mTransientLaunches == null) return false;
|
||||
for (int i = 0; i < mTransientLaunches.size(); ++i) {
|
||||
if (wc.isDescendantOf(mTransientLaunches.valueAt(i))) {
|
||||
if (mTransientHideTasks == null) return false;
|
||||
for (int i = mTransientHideTasks.size() - 1; i >= 0; --i) {
|
||||
final Task task = mTransientHideTasks.get(i);
|
||||
if (wc == task || wc.isDescendantOf(task)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -816,6 +839,14 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
|
||||
}
|
||||
mController.mFinishingTransition = this;
|
||||
|
||||
if (mTransientHideTasks != null && !mTransientHideTasks.isEmpty()) {
|
||||
// The transient hide tasks could be occluded now, e.g. returning to home. So trigger
|
||||
// the update to make the activities in the tasks invisible-requested, then the next
|
||||
// step can continue to commit the visibility.
|
||||
mController.mAtm.mRootWindowContainer.ensureActivitiesVisible(null /* starting */,
|
||||
0 /* configChanges */, true /* preserveWindows */);
|
||||
}
|
||||
|
||||
boolean hasParticipatedDisplay = false;
|
||||
boolean hasVisibleTransientLaunch = false;
|
||||
// Commit all going-invisible containers
|
||||
@@ -1175,14 +1206,13 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
|
||||
|
||||
// Record windowtokens (activity/wallpaper) that are expected to be visible after the
|
||||
// transition animation. This will be used in finishTransition to prevent prematurely
|
||||
// committing visibility.
|
||||
for (int i = mParticipants.size() - 1; i >= 0; --i) {
|
||||
final WindowContainer wc = mParticipants.valueAt(i);
|
||||
if (wc.asWindowToken() == null || !wc.isVisibleRequested()) continue;
|
||||
// don't include transient launches, though, since those are only temporarily visible.
|
||||
if (mTransientLaunches != null && wc.asActivityRecord() != null
|
||||
&& mTransientLaunches.containsKey(wc.asActivityRecord())) continue;
|
||||
mVisibleAtTransitionEndTokens.add(wc.asWindowToken());
|
||||
// committing visibility. Skip transient launches since those are only temporarily visible.
|
||||
if (mTransientLaunches == null) {
|
||||
for (int i = mParticipants.size() - 1; i >= 0; --i) {
|
||||
final WindowContainer wc = mParticipants.valueAt(i);
|
||||
if (wc.asWindowToken() == null || !wc.isVisibleRequested()) continue;
|
||||
mVisibleAtTransitionEndTokens.add(wc.asWindowToken());
|
||||
}
|
||||
}
|
||||
|
||||
// Take task snapshots before the animation so that we can capture IME before it gets
|
||||
|
||||
@@ -366,11 +366,11 @@ class TransitionController {
|
||||
}
|
||||
|
||||
boolean isTransientHide(@NonNull Task task) {
|
||||
if (mCollectingTransition != null && mCollectingTransition.isTransientHide(task)) {
|
||||
if (mCollectingTransition != null && mCollectingTransition.isInTransientHide(task)) {
|
||||
return true;
|
||||
}
|
||||
for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
|
||||
if (mPlayingTransitions.get(i).isTransientHide(task)) return true;
|
||||
if (mPlayingTransitions.get(i).isInTransientHide(task)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ 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;
|
||||
@@ -54,6 +55,7 @@ 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;
|
||||
@@ -1400,7 +1402,13 @@ public class TransitionTests extends WindowTestsBase {
|
||||
closeTransition.collectExistenceChange(activity1);
|
||||
closeTransition.collectExistenceChange(task2);
|
||||
closeTransition.collectExistenceChange(activity2);
|
||||
closeTransition.setTransientLaunch(activity2, null /* restoreBelow */);
|
||||
closeTransition.setTransientLaunch(activity2, task1);
|
||||
final Transition.ChangeInfo task1ChangeInfo = closeTransition.mChanges.get(task1);
|
||||
assertNotNull(task1ChangeInfo);
|
||||
assertTrue(task1ChangeInfo.hasChanged());
|
||||
final Transition.ChangeInfo activity1ChangeInfo = closeTransition.mChanges.get(activity1);
|
||||
assertNotNull(activity1ChangeInfo);
|
||||
assertTrue(activity1ChangeInfo.hasChanged());
|
||||
|
||||
activity1.setVisibleRequested(false);
|
||||
activity2.setVisibleRequested(true);
|
||||
@@ -1416,6 +1424,8 @@ public class TransitionTests extends WindowTestsBase {
|
||||
verify(snapshotController, 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
|
||||
@@ -1430,7 +1440,11 @@ public class TransitionTests extends WindowTestsBase {
|
||||
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());
|
||||
assertFalse(activity1.isVisible());
|
||||
assertFalse(activity1.app.hasActivityInVisibleTask());
|
||||
|
||||
verify(snapshotController, times(1)).recordSnapshot(eq(task1), eq(false));
|
||||
|
||||
Reference in New Issue
Block a user