Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2cf6e530 authored by Chilun's avatar Chilun
Browse files

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
parent 5b6127df
Loading
Loading
Loading
Loading
+19 −21
Original line number Diff line number Diff line
@@ -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()
+6 −1
Original line number Diff line number Diff line
@@ -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,
+24 −6
Original line number Diff line number Diff line
@@ -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);
    }

    /**
+4 −0
Original line number Diff line number Diff line
@@ -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());