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

Commit 7dd39bb7 authored by Andrii Kulian's avatar Andrii Kulian
Browse files

Refactor ActivityRecord#finish* methods

Updated ActivityRecord#finish* methods and to separate their purposes
and use better suited names.

ActivityRecord#finishIfPossible - start of activity finishing. In
most cases will first pause activity and wait for it to report back.

ActivityRecord#completeFinishing - second stage of the process, when
activity is no longer resumed. Usually will be called from 'paused'
callback on the server. If the next activity that should be shown in
place of the current one is not visible yet - it will defer the
finishing process and add the current activity to stopping list. If
next is already visible - will finish immediately.

ActivityRecord#destroyIfPossible - called when activity is ready to
be destroyed, or when the caller does not want to wait. Will initiate
the client destruction immediately, unless it's the last activity on
display.

Bug: 137329632
Test: atest CtsWindowManagerDeviceTestCases:android.server.wm.lifecycle
Test: atest WmTests:ActivityRecordTests
Change-Id: Id6e6dae306e2af91e9e00d9e66c416b13810ad93
parent 12f61509
Loading
Loading
Loading
Loading
+0 −4
Original line number Diff line number Diff line
@@ -490,10 +490,6 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack>
        return null;
    }

    ActivityStack getNextFocusableStack() {
        return getNextFocusableStack(null /* currentFocus */, false /* ignoreCurrent */);
    }

    ActivityStack getNextFocusableStack(ActivityStack currentFocus, boolean ignoreCurrent) {
        final int currentWindowingMode = currentFocus != null
                ? currentFocus.getWindowingMode() : WINDOWING_MODE_UNDEFINED;
+119 −91
Original line number Diff line number Diff line
@@ -122,7 +122,6 @@ import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE;
import static com.android.server.wm.ActivityStack.STOP_TIMEOUT_MSG;
import static com.android.server.wm.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONFIGURATION;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONTAINERS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_FOCUS;
@@ -1592,8 +1591,7 @@ final class ActivityRecord extends ConfigurationContainer {
            if (!Objects.equals(cur.taskAffinity, taskAffinity)) {
                break;
            }
            cur.finishActivityLocked(Activity.RESULT_CANCELED, null /* resultData */,
                    "request-affinity", true /* oomAdj */);
            cur.finishIfPossible("request-affinity", true /* oomAdj */);
        }
    }

@@ -1650,14 +1648,17 @@ final class ActivityRecord extends ConfigurationContainer {
    @interface FinishRequest {}

    /**
     * See {@link #finishActivityLocked(int, Intent, String, boolean, boolean)}
     * See {@link #finishIfPossible(int, Intent, String, boolean, boolean)}
     */
    @FinishRequest int finishActivityLocked(int resultCode, Intent resultData, String reason,
            boolean oomAdj) {
        return finishActivityLocked(resultCode, resultData, reason, oomAdj, !PAUSE_IMMEDIATELY);
    @FinishRequest int finishIfPossible(String reason, boolean oomAdj) {
        return finishIfPossible(Activity.RESULT_CANCELED, null /* resultData */, reason,
                oomAdj, !PAUSE_IMMEDIATELY);
    }

    /**
     * Finish activity if possible. If activity was resumed - we must first pause it to make the
     * activity below resumed. Otherwise we will try to complete the request immediately by calling
     * {@link #completeFinishing(String)}.
     * @return One of {@link FinishRequest} values:
     * {@link #FINISH_RESULT_REMOVED} if this activity has been removed from the history list.
     * {@link #FINISH_RESULT_REQUESTED} if removal process was started, but it is still in the list
@@ -1665,7 +1666,7 @@ final class ActivityRecord extends ConfigurationContainer {
     * {@link #FINISH_RESULT_CANCELLED} if activity is already finishing or in invalid state and the
     * request to finish it was not ignored.
     */
    @FinishRequest int finishActivityLocked(int resultCode, Intent resultData, String reason,
    @FinishRequest int finishIfPossible(int resultCode, Intent resultData, String reason,
            boolean oomAdj, boolean pauseImmediately) {
        if (DEBUG_RESULTS || DEBUG_STATES) {
            Slog.v(TAG_STATES, "Finishing activity r=" + this + ", result=" + resultCode
@@ -1704,20 +1705,24 @@ final class ActivityRecord extends ConfigurationContainer {
            pauseKeyDispatchingLocked();

            final ActivityStack stack = getActivityStack();
            stack.adjustFocusedActivityStack(this, "finishActivity");
            stack.adjustFocusedActivityStack(this, "finishIfPossible");

            finishActivityResults(resultCode, resultData);

            final boolean endTask = index <= 0 && !task.isClearingToReuseTask();
            final int transit = endTask ? TRANSIT_TASK_CLOSE : TRANSIT_ACTIVITY_CLOSE;
            if (stack.getResumedActivity() == this) {
                if (DEBUG_VISIBILITY || DEBUG_TRANSITION) {
                    Slog.v(TAG_TRANSITION, "Prepare close transition: finishing " + this);
                }
            if (isState(RESUMED)) {
                if (endTask) {
                    mAtmService.getTaskChangeNotificationController().notifyTaskRemovalStarted(
                            task.getTaskInfo());
                }
                // Prepare app close transition, but don't execute just yet. It is possible that
                // an activity that will be made resumed in place of this one will immediately
                // launch another new activity. In this case current closing transition will be
                // combined with open transition for the new activity.
                if (DEBUG_VISIBILITY || DEBUG_TRANSITION) {
                    Slog.v(TAG_TRANSITION, "Prepare close transition: finishing " + this);
                }
                getDisplay().mDisplayContent.prepareAppTransition(transit, false);

                // When finishing the activity preemptively take the snapshot before the app window
@@ -1744,17 +1749,17 @@ final class ActivityRecord extends ConfigurationContainer {
                    mAtmService.getLockTaskController().clearLockedTask(task);
                }
            } else if (!isState(PAUSING)) {
                // If the activity is PAUSING, we will complete the finish once
                // it is done pausing; else we can just directly finish it here.
                if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Finish not pausing: " + this);
                if (visible) {
                    // Prepare and execute close transition.
                    prepareActivityHideTransitionAnimation(transit);
                }

                final int finishMode = (visible || nowVisible) ? FINISH_AFTER_VISIBLE
                        : FINISH_AFTER_PAUSE;
                final boolean removedActivity = finishCurrentActivityLocked(finishMode, oomAdj,
                        "finishActivityLocked") == null;
                final boolean removedActivity = completeFinishing("finishIfPossible") == null;
                // Performance optimization - only invoke OOM adjustment if the state changed to
                // 'STOPPING'. Otherwise it will not change the OOM scores.
                if (oomAdj && isState(STOPPING)) {
                    mAtmService.updateOomAdj();
                }

                // The following code is an optimization. When the last non-task overlay activity
                // is removed from the task, we remove the entire task from the stack. However,
@@ -1771,6 +1776,7 @@ final class ActivityRecord extends ConfigurationContainer {
                        taskOverlay.prepareActivityHideTransitionAnimation(transit);
                    }
                }

                return removedActivity ? FINISH_RESULT_REMOVED : FINISH_RESULT_REQUESTED;
            } else {
                if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Finish waiting for pause of: " + this);
@@ -1789,101 +1795,123 @@ final class ActivityRecord extends ConfigurationContainer {
        dc.executeAppTransition();
    }

    static final int FINISH_IMMEDIATELY = 0;
    private static final int FINISH_AFTER_PAUSE = 1;
    static final int FINISH_AFTER_VISIBLE = 2;
    /**
     * 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 reason Reason for finishing the activity.
     * @return Flag indicating whether the activity was removed from history.
     */
    ActivityRecord completeFinishing(String reason) {
        if (!finishing || isState(RESUMED)) {
            throw new IllegalArgumentException(
                    "Activity must be finishing and not resumed to complete, r=" + this
                            + ", finishing=" + finishing + ", state=" + mState);
        }

    ActivityRecord finishCurrentActivityLocked(int mode, boolean oomAdj, String reason) {
        // First things first: if this activity is currently visible,
        // and the resumed activity is not yet visible, then hold off on
        // finishing until the resumed one becomes visible.
        if (isState(PAUSING)) {
            // Activity is marked as finishing and will be processed once it completes.
            return this;
        }

        boolean activityRemoved = false;

        // If this activity is currently visible, and the resumed activity is not yet visible, then
        // hold off on finishing until the resumed one becomes visible.
        // The activity that we are finishing may be over the lock screen. In this case, we do not
        // want to consider activities that cannot be shown on the lock screen as running and should
        // proceed with finishing the activity if there is no valid next top running activity.
        // Note that if this finishing activity is floating task, we don't need to wait the
        // next activity resume and can destroy it directly.
        // TODO(b/137329632): find the next activity directly underneath this one, not just anywhere
        final ActivityRecord next = getDisplay().topRunningActivity(
                true /* considerKeyguardState */);
        final boolean isVisible = visible || nowVisible;
        final ActivityStack stack = getActivityStack();
        final ActivityDisplay display = getDisplay();
        final ActivityRecord next = display.topRunningActivity(true /* considerKeyguardState */);
        final boolean isFloating = getConfiguration().windowConfiguration.tasksAreFloating();

        if (mode == FINISH_AFTER_VISIBLE && (visible || nowVisible)
                && next != null && !next.nowVisible && !isFloating) {
        final boolean notFocusedStack = stack != mRootActivityContainer.getTopDisplayFocusedStack();
        if (isVisible && next != null && !next.nowVisible) {
            if (!mStackSupervisor.mStoppingActivities.contains(this)) {
                stack.addToStopping(this, false /* scheduleIdle */, false /* idleDelayed */,
                        "finishCurrentActivityLocked");
                getActivityStack().addToStopping(this, false /* scheduleIdle */,
                        false /* idleDelayed */, "finishCurrentActivityLocked");
            }
            if (DEBUG_STATES) {
                Slog.v(TAG_STATES, "Moving to STOPPING: " + this + " (finish requested)");
            }
            setState(STOPPING, "finishCurrentActivityLocked");
            if (oomAdj) {
                mAtmService.updateOomAdj();
            if (notFocusedStack) {
                mRootActivityContainer.ensureVisibilityAndConfig(next, getDisplayId(),
                        false /* markFrozenIfConfigChanged */, true /* deferResume */);
            }
            return this;
        } else if (isVisible && isState(PAUSED) && getActivityStack().isFocusedStackOnDisplay()
                && !inPinnedWindowingMode()) {
            // TODO(b/137329632): Currently non-focused stack is handled differently.
            addToFinishingAndWaitForIdle();
        } else {
            // Not waiting for the next one to become visible - finish right away.
            activityRemoved = destroyIfPossible(reason);
        }

        // make sure the record is cleaned out of other places.
        return activityRemoved ? null : this;
    }

    /**
     * Destroy and cleanup the activity both on client and server if possible. If activity is the
     * last one left on display with home stack and there is no other running activity - delay
     * destroying it until the next one starts.
     */
    boolean destroyIfPossible(String reason) {
        setState(FINISHING, "destroyIfPossible");

        // Make sure the record is cleaned out of other places.
        mStackSupervisor.mStoppingActivities.remove(this);
        mStackSupervisor.mGoingToSleepActivities.remove(this);
        final ActivityState prevState = getState();
        if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to FINISHING: " + this);

        setState(FINISHING, "finishCurrentActivityLocked");

        // Don't destroy activity immediately if the display contains home stack, although there is
        // no next activity at the moment but another home activity should be started later. Keep
        // this activity alive until next home activity is resumed then user won't see a temporary
        // black screen.
        final boolean noRunningStack = next == null && display.topRunningActivity() == null
                && display.getHomeStack() == null;
        final boolean noFocusedStack = stack != display.getFocusedStack();
        final boolean finishingInNonFocusedStackOrNoRunning = mode == FINISH_AFTER_VISIBLE
                && prevState == PAUSED && (noFocusedStack || noRunningStack);

        if (mode == FINISH_IMMEDIATELY
                || (prevState == PAUSED && (mode == FINISH_AFTER_PAUSE || inPinnedWindowingMode()))
                || finishingInNonFocusedStackOrNoRunning
                || prevState == STARTED
                || prevState == STOPPING
                || prevState == STOPPED
                || prevState == ActivityState.INITIALIZING) {

        final ActivityStack stack = getActivityStack();
        final ActivityDisplay display = getDisplay();
        // TODO(b/137329632): Exclude current activity when looking for the next one with
        //  ActivityDisplay#topRunningActivity().
        final ActivityRecord next = display.topRunningActivity();
        final boolean isLastStackOverEmptyHome =
                next == null && stack.isFocusedStackOnDisplay() && display.getHomeStack() != null;
        if (isLastStackOverEmptyHome) {
            // Don't destroy activity immediately if this is the last activity on the display and
            // the display contains home stack. Although there is no next activity at the moment,
            // another home activity should be started later. Keep this activity alive until next
            // home activity is resumed. This way the user won't see a temporary black screen.
            addToFinishingAndWaitForIdle();
            return false;
        }
        makeFinishingLocked();
            boolean activityRemoved = stack.destroyActivityLocked(this, true /* removeFromApp */,
                    "finish-imm:" + reason);

            if (finishingInNonFocusedStackOrNoRunning) {
                // Finishing activity that was in paused state and it was in not currently focused
                // stack, need to make something visible in its place. Also if the display does not
                // have running activity, the configuration may need to be updated for restoring
                // original orientation of the display.

        boolean activityRemoved = getActivityStack().destroyActivityLocked(this,
                true /* removeFromApp */, "finish-imm:" + reason);

        // If the display does not have running activity, the configuration may need to be
        // updated for restoring original orientation of the display.
        if (next == null) {
            mRootActivityContainer.ensureVisibilityAndConfig(next, getDisplayId(),
                    false /* markFrozenIfConfigChanged */, true /* deferResume */);
        }
        if (activityRemoved) {
            mRootActivityContainer.resumeFocusedStacksTopActivities();
        }

        if (DEBUG_CONTAINERS) {
                Slog.d(TAG_CONTAINERS, "destroyActivityLocked: finishCurrentActivityLocked r="
                        + this + " destroy returned removed=" + activityRemoved);
            Slog.d(TAG_CONTAINERS, "destroyIfPossible: r=" + this + " destroy returned removed="
                    + activityRemoved);
        }
            return activityRemoved ? null : this;

        return activityRemoved;
    }

        // Need to go through the full pause cycle to get this
        // activity into the stopped state and then finish it.
        if (DEBUG_ALL) Slog.v(TAG, "Enqueueing pending finish: " + this);
    @VisibleForTesting
    void addToFinishingAndWaitForIdle() {
        if (DEBUG_STATES) Slog.v(TAG, "Enqueueing pending finish: " + this);
        setState(FINISHING, "addToFinishingAndWaitForIdle");
        mStackSupervisor.mFinishingActivities.add(this);
        resumeKeyDispatchingLocked();
        mRootActivityContainer.resumeFocusedStacksTopActivities();
        // If activity was not paused at this point - explicitly pause it to start finishing
        // process. Finishing will be completed once it reports pause back.
        if (isState(RESUMED) && stack.mPausingActivity != null) {
            stack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */,
                    next /* resuming */, false /* dontWait */);
        }
        return this;
    }

    void makeFinishingLocked() {
+30 −41

File changed.

Preview size limit exceeded, changes collapsed.

+3 −4
Original line number Diff line number Diff line
@@ -897,8 +897,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
                    Slog.e(TAG, "Second failure launching "
                            + r.intent.getComponent().flattenToShortString() + ", giving up", e);
                    proc.appDied();
                    r.finishActivityLocked(Activity.RESULT_CANCELED, null /* resultData */,
                            "2nd-crash", false /* oomAdj */);
                    r.finishIfPossible("2nd-crash", false /* oomAdj */);
                    return false;
                }

@@ -1347,8 +1346,8 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
            final ActivityStack stack = r.getActivityStack();
            if (stack != null) {
                if (r.finishing) {
                    r.finishCurrentActivityLocked(ActivityRecord.FINISH_IMMEDIATELY,
                            false /* oomAdj */, "activityIdleInternalLocked");
                    // TODO(b/137329632): Wait for idle of the right activity, not just any.
                    r.destroyIfPossible("activityIdleInternalLocked");
                } else {
                    stack.stopActivityLocked(r);
                }
+1 −2
Original line number Diff line number Diff line
@@ -1426,8 +1426,7 @@ class ActivityStarter {
                // performing operations without a window container.
                final ActivityStack stack = mStartActivity.getActivityStack();
                if (stack != null) {
                    mStartActivity.finishActivityLocked(RESULT_CANCELED,
                            null /* intentResultData */, "startActivity", true /* oomAdj */);
                    mStartActivity.finishIfPossible("startActivity", true /* oomAdj */);
                }

                // Stack should also be detached from display and be removed if it's empty.
Loading