Update visibility when finishing activity

Consider the situation where there is a translucent or non-fullscreen
activity in foreground and a middle activity that is going to be
finished. The black background will show below the foreground activity
if the middle activity is in STARTED state because the activity in
STARTED state was not treated as visible.

Another case is that the task of non-fullscreen activity is different to
the middle activity. We can't find any resumed activity from the task of
the middle activity.

This CL treat both PAUSED and STARTED activities as visible and update
the visibility if current activity is opaque.

Also clean up some dead code in Task and ActivityRecord.

Bug: 172837736
Test: atest WmTests:ActivityRecordTests RootTaskTests
Test: atest ActivityVisibilityTests
Test: atest ActivityLifecycleTopResumedStateTests
Test: atest CtsWindowManagerDeviceTestCases:ActivityLifecycleTests
Test: atest MultiDisplayPolicyTests
Change-Id: I43618e34383409b4805942b1923f1281f4226b1c
This commit is contained in:
Chilun
2021-04-01 11:58:56 +08:00
parent 5b6127dffb
commit 2cf6e53096
4 changed files with 53 additions and 28 deletions

View File

@@ -114,8 +114,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_UNSET;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
@@ -324,7 +322,6 @@ import com.android.internal.policy.AttributeCache;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ToBooleanFunction;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.pooled.PooledConsumer;
import com.android.internal.util.function.pooled.PooledFunction;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
@@ -2384,7 +2381,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return occludesParent(false /* includingFinishing */);
}
private boolean occludesParent(boolean includingFinishing) {
@VisibleForTesting
boolean occludesParent(boolean includingFinishing) {
if (!includingFinishing && finishing) {
return false;
}
@@ -2822,7 +2820,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
final boolean endTask = task.getTopNonFinishingActivity() == null
&& !task.isClearingToReuseTask();
final int transit = endTask ? TRANSIT_OLD_TASK_CLOSE : TRANSIT_OLD_ACTIVITY_CLOSE;
if (newTransition != null) {
mAtmService.getTransitionController().requestStartTransition(newTransition,
endTask ? task : null, null /* remote */);
@@ -2879,7 +2876,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
} else if (!isState(PAUSING)) {
if (mVisibleRequested) {
// Prepare and execute close transition.
prepareActivityHideTransitionAnimation(transit);
prepareActivityHideTransitionAnimation();
}
final boolean removedActivity = completeFinishing("finishIfPossible") == null;
@@ -2897,11 +2894,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// In this case, we can set the visibility of all the task overlay activities when
// we detect the last one is finishing to keep them in sync.
if (task.onlyHasTaskOverlayActivities(false /* includeFinishing */)) {
final PooledConsumer c = PooledLambda.obtainConsumer(
ActivityRecord::prepareActivityHideTransitionAnimationIfOvarlay,
PooledLambda.__(ActivityRecord.class), transit);
task.forAllActivities(c);
c.recycle();
task.forAllActivities((r) -> {
r.prepareActivityHideTransitionAnimationIfOvarlay();
});
}
return removedActivity ? FINISH_RESULT_REMOVED : FINISH_RESULT_REQUESTED;
} else {
@@ -2914,28 +2909,33 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
}
private void prepareActivityHideTransitionAnimationIfOvarlay(@TransitionOldType int transit) {
private void prepareActivityHideTransitionAnimationIfOvarlay() {
if (mTaskOverlay) {
prepareActivityHideTransitionAnimation(transit);
prepareActivityHideTransitionAnimation();
}
}
private void prepareActivityHideTransitionAnimation(@TransitionOldType int transit) {
private void prepareActivityHideTransitionAnimation() {
final DisplayContent dc = mDisplayContent;
dc.prepareAppTransition(TRANSIT_CLOSE);
setVisibility(false);
dc.executeAppTransition();
}
ActivityRecord completeFinishing(String reason) {
return completeFinishing(true /* updateVisibility */, reason);
}
/**
* Complete activity finish request that was initiated earlier. If the activity is still
* pausing we will wait for it to complete its transition. If the activity that should appear in
* place of this one is not visible yet - we'll wait for it first. Otherwise - activity can be
* destroyed right away.
* @param updateVisibility Indicate if need to update activity visibility.
* @param reason Reason for finishing the activity.
* @return Flag indicating whether the activity was removed from history.
*/
ActivityRecord completeFinishing(String reason) {
ActivityRecord completeFinishing(boolean updateVisibility, String reason) {
if (!finishing || isState(RESUMED)) {
throw new IllegalArgumentException(
"Activity must be finishing and not resumed to complete, r=" + this
@@ -2947,13 +2947,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return this;
}
final boolean isCurrentVisible = mVisibleRequested || isState(PAUSED);
if (isCurrentVisible) {
final Task rootTask = getRootTask();
final ActivityRecord activity = rootTask.getResumedActivity();
final boolean isCurrentVisible = mVisibleRequested || isState(PAUSED, STARTED);
if (updateVisibility && isCurrentVisible) {
boolean ensureVisibility = false;
if (activity != null && !activity.occludesParent()) {
// If the resume activity is not opaque, we need to make sure the visibilities of
if (occludesParent(true /* includingFinishing */)) {
// If the current activity is not opaque, we need to make sure the visibilities of
// activities be updated, they may be seen by users.
ensureVisibility = true;
} else if (mTaskSupervisor.getKeyguardController().isKeyguardLocked()

View File

@@ -5828,8 +5828,13 @@ class Task extends WindowContainer<WindowContainer> {
final boolean wasStopping = prev.isState(STOPPING);
prev.setState(PAUSED, "completePausedLocked");
if (prev.finishing) {
// We will update the activity visibility later, no need to do in
// completeFinishing(). Updating visibility here might also making the next
// activities to be resumed, and could result in wrong app transition due to
// lack of previous activity information.
ProtoLog.v(WM_DEBUG_STATES, "Executing finish of activity: %s", prev);
prev = prev.completeFinishing("completePausedLocked");
prev = prev.completeFinishing(false /* updateVisibility */,
"completePausedLocked");
} else if (prev.hasProcess()) {
ProtoLog.v(WM_DEBUG_STATES, "Enqueue pending stop if needed: %s "
+ "wasStopping=%b visibleRequested=%b", prev, wasStopping,

View File

@@ -1351,25 +1351,39 @@ public class ActivityRecordTests extends WindowTestsBase {
* must ensure the visibilities of activities being updated.
*/
@Test
public void testCompleteFinishing_ensureActivitiesVisible() {
public void testCompleteFinishing_ensureActivitiesVisible_withConditions() {
testCompleteFinishing_ensureActivitiesVisible(false, PAUSED);
testCompleteFinishing_ensureActivitiesVisible(false, STARTED);
testCompleteFinishing_ensureActivitiesVisible(true, PAUSED);
testCompleteFinishing_ensureActivitiesVisible(true, STARTED);
}
private void testCompleteFinishing_ensureActivitiesVisible(boolean diffTask,
ActivityState secondActivityState) {
final ActivityRecord activity = createActivityWithTask();
final Task task = activity.getTask();
final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(task).build();
firstActivity.mVisibleRequested = false;
firstActivity.nowVisible = false;
firstActivity.setState(STOPPED, "true");
firstActivity.setState(STOPPED, "test");
final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(task).build();
secondActivity.mVisibleRequested = true;
secondActivity.nowVisible = true;
secondActivity.setState(PAUSED, "true");
secondActivity.setState(secondActivityState, "test");
final ActivityRecord translucentActivity = new ActivityBuilder(mAtm).setTask(task).build();
ActivityRecord translucentActivity;
if (diffTask) {
translucentActivity = new ActivityBuilder(mAtm).setCreateTask(true).build();
} else {
translucentActivity = new ActivityBuilder(mAtm).setTask(task).build();
}
translucentActivity.mVisibleRequested = true;
translucentActivity.nowVisible = true;
translucentActivity.setState(RESUMED, "true");
translucentActivity.setState(RESUMED, "test");
doReturn(false).when(translucentActivity).occludesParent();
doReturn(true).when(firstActivity).occludesParent(true);
doReturn(true).when(secondActivity).occludesParent(true);
// Finish the second activity
secondActivity.finishing = true;
@@ -1385,6 +1399,10 @@ public class ActivityRecordTests extends WindowTestsBase {
verify(firstActivity.mDisplayContent, times(2)).ensureActivitiesVisible(null /* starting */,
0 /* configChanges */ , false /* preserveWindows */,
true /* notifyClients */);
// Remove the translucent activity and clear invocations for next test
translucentActivity.getTask().removeImmediately("test");
clearInvocations(mDefaultDisplay);
}
/**

View File

@@ -38,6 +38,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
import static com.android.server.wm.Task.ActivityState.DESTROYED;
import static com.android.server.wm.Task.ActivityState.DESTROYING;
import static com.android.server.wm.Task.ActivityState.FINISHING;
import static com.android.server.wm.Task.ActivityState.PAUSING;
@@ -1204,6 +1205,9 @@ public class RootTaskTests extends WindowTestsBase {
// See {@link ActivityStack#destroyActivityLocked}.
activity.app = null;
overlayActivity.app = null;
// Simulate the process is dead
activity.mVisibleRequested = false;
activity.setState(DESTROYED, "Test");
assertEquals(2, task.getChildCount());