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

Commit 86e70fcc authored by Andrii Kulian's avatar Andrii Kulian
Browse files

Report top resumed state only after previous activity lost it

This provides a guarantee that onTopResumedActivityChanged(true) will
always be called only after completing handling
onTopResumedActivityChanged(false) in previous top activity. This
allows for safely acquiring singleton resources in the system,
because the previous app should've already released those.

Bug: 117135575
Test: atest CtsActivityManagerDeviceTestCases:ActivityLifecycleTopResumedStateTests
Change-Id: Ica3e54fae1202db7a7af241b73b9988642fdb85b
parent 311af9ea
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -129,6 +129,7 @@ interface IActivityTaskManager {
    oneway void activityIdle(in IBinder token, in Configuration config,
            in boolean stopProfiling);
    void activityResumed(in IBinder token);
    void activityTopResumedStateLost();
    void activityPaused(in IBinder token);
    void activityStopped(in IBinder token, in Bundle state,
            in PersistableBundle persistentState, in CharSequence description);
+22 −0
Original line number Diff line number Diff line
@@ -17,9 +17,11 @@ package android.app.servertransaction;

import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;

import android.app.ActivityTaskManager;
import android.app.ClientTransactionHandler;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.Trace;

/**
@@ -38,6 +40,26 @@ public class TopResumedActivityChangeItem extends ClientTransactionItem {
        Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
    }

    @Override
    public void postExecute(ClientTransactionHandler client, IBinder token,
            PendingTransactionActions pendingActions) {
        if (mOnTop) {
            return;
        }

        // The loss of top resumed state can always be reported immediately in postExecute
        // because only three cases are possible:
        // 1. Activity is in RESUMED state now and it just handled the callback in #execute().
        // 2. Activity wasn't RESUMED yet, which means that it didn't receive the top state yet.
        // 3. Activity is PAUSED or in other lifecycle state after PAUSED. In this case top resumed
        // state loss was already called right before pausing.
        try {
            ActivityTaskManager.getService().activityTopResumedStateLost();
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }


    // ObjectPoolItem implementation

+8 −7
Original line number Diff line number Diff line
@@ -293,6 +293,7 @@ final class ActivityRecord extends ConfigurationContainer {
    long cpuTimeAtResume;         // the cpu time of host process at the time of resuming activity
    long pauseTime;               // last time we started pausing the activity
    long launchTickTime;          // base time for launch tick messages
    long topResumedStateLossTime; // last time we reported top resumed state loss to an activity
    // Last configuration reported to the activity in the client process.
    private MergedConfiguration mLastReportedConfiguration;
    private int mLastReportedDisplayId;
@@ -694,14 +695,14 @@ final class ActivityRecord extends ConfigurationContainer {

    void scheduleTopResumedActivityChanged(boolean onTop) {
        if (!attachedToProcess()) {
            if (DEBUG_CONFIGURATION) {
            if (DEBUG_STATES) {
                Slog.w(TAG, "Can't report activity position update - client not running"
                                + ", activityRecord=" + this);
            }
            return;
        }
        try {
            if (DEBUG_CONFIGURATION) {
            if (DEBUG_STATES) {
                Slog.v(TAG, "Sending position change to " + this + ", onTop: " + onTop);
            }

@@ -3283,7 +3284,7 @@ final class ActivityRecord extends ConfigurationContainer {
            transaction.addCallback(callbackItem);
            transaction.setLifecycleStateRequest(lifecycleItem);
            mAtmService.getLifecycleManager().scheduleTransaction(transaction);
            mRootActivityContainer.updateTopResumedActivityIfNeeded();
            mStackSupervisor.updateTopResumedActivityIfNeeded();
            // Note: don't need to call pauseIfSleepingLocked() here, because the caller will only
            // request resume if this activity is currently resumed, which implies we aren't
            // sleeping.
+2 −1
Original line number Diff line number Diff line
@@ -1491,6 +1491,7 @@ class ActivityStack extends ConfigurationContainer {
                + " callers=" + Debug.getCallers(5));
        r.setState(RESUMED, "minimalResumeActivityLocked");
        r.completeResumeLocked();
        mStackSupervisor.updateTopResumedActivityIfNeeded();
        if (DEBUG_SAVED_STATE) Slog.i(TAG_SAVED_STATE,
                "Launch completed; removing icicle of " + r.icicle);
    }
@@ -2575,7 +2576,7 @@ class ActivityStack extends ConfigurationContainer {
            // Protect against recursion.
            mInResumeTopActivity = true;
            result = resumeTopActivityInnerLocked(prev, options);
            mRootActivityContainer.updateTopResumedActivityIfNeeded();
            mStackSupervisor.updateTopResumedActivityIfNeeded();

            // When resuming the top activity, it may be necessary to pause the top activity (for
            // example, returning to the lock screen. We suppress the normal pause logic in
+96 −3
Original line number Diff line number Diff line
@@ -170,6 +170,9 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
    // How long we can hold the launch wake lock before giving up.
    static final int LAUNCH_TIMEOUT = 10 * 1000;

    /** How long we wait until giving up on the activity telling us it released the top state. */
    static final int TOP_RESUMED_STATE_LOSS_TIMEOUT = 500;

    static final int IDLE_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG;
    static final int IDLE_NOW_MSG = FIRST_SUPERVISOR_STACK_MSG + 1;
    static final int RESUME_TOP_ACTIVITY_MSG = FIRST_SUPERVISOR_STACK_MSG + 2;
@@ -179,6 +182,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
    static final int REPORT_MULTI_WINDOW_MODE_CHANGED_MSG = FIRST_SUPERVISOR_STACK_MSG + 14;
    static final int REPORT_PIP_MODE_CHANGED_MSG = FIRST_SUPERVISOR_STACK_MSG + 15;
    static final int REPORT_HOME_CHANGED_MSG = FIRST_SUPERVISOR_STACK_MSG + 16;
    static final int TOP_RESUMED_STATE_LOSS_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG + 17;

    // Used to indicate that windows of activities should be preserved during the resize.
    static final boolean PRESERVE_WINDOWS = true;
@@ -300,6 +304,18 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
     */
    final ArrayList<ActivityRecord> mNoAnimActivities = new ArrayList<>();

    /**
     * Cached value of the topmost resumed activity in the system. Updated when new activity is
     * resumed.
     */
    private ActivityRecord mTopResumedActivity;

    /**
     * Flag indicating whether we're currently waiting for the previous top activity to handle the
     * loss of the state and report back before making new activity top resumed.
     */
    private boolean mTopResumedActivityWaitingForPrev;

    /** The target stack bounds for the picture-in-picture mode changed that we need to report to
     * the application */
    Rect mPipModeChangedTargetStackBounds;
@@ -844,7 +860,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {

                // Schedule transaction.
                mService.getLifecycleManager().scheduleTransaction(clientTransaction);
                mRootActivityContainer.updateTopResumedActivityIfNeeded();
                updateTopResumedActivityIfNeeded();

                if ((proc.mInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0
                        && mService.mHasHeavyWeightFeature) {
@@ -2288,6 +2304,73 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
        mHandler.sendEmptyMessage(IDLE_NOW_MSG);
    }

    /**
     * Updates the record of top resumed activity when it changes and handles reporting of the
     * state changes to previous and new top activities. It will immediately dispatch top resumed
     * state loss message to previous top activity (if haven't done it already). After the previous
     * activity releases the top state and reports back, message about acquiring top state will be
     * sent to the new top resumed activity.
     */
    void updateTopResumedActivityIfNeeded() {
        final ActivityRecord prevTopActivity = mTopResumedActivity;
        final ActivityStack topStack = mRootActivityContainer.getTopDisplayFocusedStack();
        if (topStack == null || topStack.mResumedActivity == prevTopActivity) {
            return;
        }

        // Ask previous activity to release the top state.
        final boolean prevActivityReceivedTopState =
                prevTopActivity != null && !mTopResumedActivityWaitingForPrev;
        // mTopResumedActivityWaitingForPrev == true at this point would mean that an activity
        // before the prevTopActivity one hasn't reported back yet. So server never sent the top
        // resumed state change message to prevTopActivity.
        if (prevActivityReceivedTopState) {
            prevTopActivity.scheduleTopResumedActivityChanged(false /* onTop */);
            scheduleTopResumedStateLossTimeout(prevTopActivity);
            mTopResumedActivityWaitingForPrev = true;
        }

        // Update the current top activity.
        mTopResumedActivity = topStack.mResumedActivity;
        scheduleTopResumedActivityStateIfNeeded();
    }

    /** Schedule top resumed state change if previous top activity already reported back. */
    private void scheduleTopResumedActivityStateIfNeeded() {
        if (mTopResumedActivity != null && !mTopResumedActivityWaitingForPrev) {
            mTopResumedActivity.scheduleTopResumedActivityChanged(true /* onTop */);
        }
    }

    /**
     * Limit the time given to the app to report handling of the state loss.
     */
    private void scheduleTopResumedStateLossTimeout(ActivityRecord r) {
        final Message msg = mHandler.obtainMessage(TOP_RESUMED_STATE_LOSS_TIMEOUT_MSG);
        msg.obj = r;
        r.topResumedStateLossTime = SystemClock.uptimeMillis();
        mHandler.sendMessageDelayed(msg, TOP_RESUMED_STATE_LOSS_TIMEOUT);
        if (DEBUG_STATES) Slog.v(TAG_STATES, "Waiting for top state to be released by " + r);
    }

    /**
     * Handle a loss of top resumed state by an activity - update internal state and inform next top
     * activity if needed.
     */
    void handleTopResumedStateReleased(boolean timeout) {
        if (DEBUG_STATES) {
            Slog.v(TAG_STATES, "Top resumed state released "
                    + (timeout ? " (due to timeout)" : " (transition complete)"));
        }
        mHandler.removeMessages(TOP_RESUMED_STATE_LOSS_TIMEOUT_MSG);
        if (!mTopResumedActivityWaitingForPrev) {
            // Top resumed activity state loss already handled.
            return;
        }
        mTopResumedActivityWaitingForPrev = false;
        scheduleTopResumedActivityStateIfNeeded();
    }

    void removeTimeoutsForActivityLocked(ActivityRecord r) {
        if (DEBUG_IDLE) Slog.d(TAG_IDLE, "removeTimeoutsForActivity: Callers="
                + Debug.getCallers(4));
@@ -2578,8 +2661,18 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
                        // Start home activities on displays with no activities.
                        mRootActivityContainer.startHomeOnEmptyDisplays("homeChanged");
                    }
                } break;
                case TOP_RESUMED_STATE_LOSS_TIMEOUT_MSG: {
                    ActivityRecord r = (ActivityRecord) msg.obj;
                    Slog.w(TAG, "Activity top resumed state loss timeout for " + r);
                    synchronized (mService.mGlobalLock) {
                        if (r.hasProcess()) {
                            mService.logAppTooSlow(r.app, r.topResumedStateLossTime,
                                    "top state loss for " + r);
                        }
                    }
                break;
                    handleTopResumedStateReleased(true /* timeout */);
                } break;
            }
        }
    }
Loading