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

Commit 50a757a4 authored by Riddle Hsu's avatar Riddle Hsu
Browse files

Demote top app scheduling group when recents is animating

With legacy transition, the recents activity doesn't move to top
when it is animating. So both the real top app and the recents
process will have top-app scheduling group. Then there may be
a CPU contention which causes animation jank.

Because the recents animation is usually more noticeable (mainly
swipe-up to home), demote the current top app to foreground
group for reducing the potential contention.

Bug: 240356132
Bug: 233992724
Test: atest RecentsAnimationTest
Test: adb shell dumpsys activity o | grep -e "A.*calc"
  Launch calculator:
    T/A/TOP  LCMN  t: 0 22249:calc/u0a158 (top-activity)
  Swipe-up to enter recents:
    F/A/TOP  LCMN  t: 0 22249:calc/u0a158 (intermediate-top-activity)
  Return to calculator:
    T/A/TOP  LCMN  t: 0 22249:calc/u0a158 (top-activity)
  Swipe to home:
    b/ /LAST ----  t: 0 22249:calc/u0a158 (previous)

Change-Id: I0d002ea46de4474fcf863f142f2ee17fe3a5c742
Merged-In: I0d002ea46de4474fcf863f142f2ee17fe3a5c742
(cherry picked from commit a20990ec)
parent bea0e67d
Loading
Loading
Loading
Loading
+25 −9
Original line number Diff line number Diff line
@@ -400,12 +400,25 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
    /** The time at which the previous process was last visible. */
    private long mPreviousProcessVisibleTime;

    /** It is set from keyguard-going-away to set-keyguard-shown. */
    static final int DEMOTE_TOP_REASON_DURING_UNLOCKING = 1;
    /** It is set if legacy recents animation is running. */
    static final int DEMOTE_TOP_REASON_ANIMATING_RECENTS = 1 << 1;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({
            DEMOTE_TOP_REASON_DURING_UNLOCKING,
            DEMOTE_TOP_REASON_ANIMATING_RECENTS,
    })
    @interface DemoteTopReason {}

    /**
     * It can be true from keyguard-going-away to set-keyguard-shown. And getTopProcessState() will
     * If non-zero, getTopProcessState() will
     * return {@link ActivityManager#PROCESS_STATE_IMPORTANT_FOREGROUND} to avoid top app from
     * preempting CPU while keyguard is animating.
     * preempting CPU while another process is running an important animation.
     */
    private volatile boolean mDemoteTopAppDuringUnlocking;
    @DemoteTopReason
    volatile int mDemoteTopAppReasons;

    /** List of intents that were used to start the most recent tasks. */
    private RecentTasks mRecentTasks;
@@ -2839,8 +2852,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
            }
            // Always reset the state regardless of keyguard-showing change, because that means the
            // unlock is either completed or canceled.
            if (mDemoteTopAppDuringUnlocking) {
                mDemoteTopAppDuringUnlocking = false;
            if ((mDemoteTopAppReasons & DEMOTE_TOP_REASON_DURING_UNLOCKING) != 0) {
                mDemoteTopAppReasons &= ~DEMOTE_TOP_REASON_DURING_UNLOCKING;
                // The scheduling group of top process was demoted by unlocking, so recompute
                // to restore its real top priority if possible.
                if (mTopApp != null) {
@@ -2881,7 +2894,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
        // animation of system UI. Even if AOD is not enabled, it should be no harm.
        final WindowProcessController proc;
        synchronized (mGlobalLockWithoutBoost) {
            mDemoteTopAppDuringUnlocking = false;
            mDemoteTopAppReasons &= ~DEMOTE_TOP_REASON_DURING_UNLOCKING;
            final WindowState notificationShade = mRootWindowContainer.getDefaultDisplay()
                    .getDisplayPolicy().getNotificationShade();
            proc = notificationShade != null
@@ -3423,7 +3436,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
                    mActivityClientController.invalidateHomeTaskSnapshot(null /* token */);
                } else if (mKeyguardShown) {
                    // Only set if it is not unlocking to launcher which may also animate.
                    mDemoteTopAppDuringUnlocking = true;
                    mDemoteTopAppReasons |= DEMOTE_TOP_REASON_DURING_UNLOCKING;
                }

                mRootWindowContainer.forAllDisplays(displayContent -> {
@@ -3991,6 +4004,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
            mTaskOrganizerController.dump(pw, "  ");
            mVisibleActivityProcessTracker.dump(pw, "  ");
            mActiveUids.dump(pw, "  ");
            if (mDemoteTopAppReasons != 0) {
                pw.println("  mDemoteTopAppReasons=" + mDemoteTopAppReasons);
            }
        }

        if (!printedAnything) {
@@ -5619,8 +5635,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
        @Override
        public int getTopProcessState() {
            final int topState = mTopProcessState;
            if (mDemoteTopAppDuringUnlocking && topState == ActivityManager.PROCESS_STATE_TOP) {
                // The unlocking UI is more important, so defer the top state of app.
            if (mDemoteTopAppReasons != 0 && topState == ActivityManager.PROCESS_STATE_TOP) {
                // There may be a more important UI/animation than the top app.
                return ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
            }
            if (mRetainPowerModeAndTopProcessState) {
+22 −6
Original line number Diff line number Diff line
@@ -203,9 +203,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan
        final LaunchingState launchingState =
                mTaskSupervisor.getActivityMetricsLogger().notifyActivityLaunching(mTargetIntent);

        if (mCaller != null) {
            mCaller.setRunningRecentsAnimation(true);
        }
        setProcessAnimating(true);

        mService.deferWindowLayout();
        try {
@@ -409,15 +407,33 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan
                    if (mWindowManager.mRoot.isLayoutNeeded()) {
                        mWindowManager.mRoot.performSurfacePlacement();
                    }
                    if (mCaller != null) {
                        mCaller.setRunningRecentsAnimation(false);
                    }
                    setProcessAnimating(false);
                    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                }
            });
        }
    }

    /** Gives the owner of recents animation higher priority. */
    private void setProcessAnimating(boolean animating) {
        if (mCaller == null) return;
        // Apply the top-app scheduling group to who runs the animation.
        mCaller.setRunningRecentsAnimation(animating);
        int demoteReasons = mService.mDemoteTopAppReasons;
        if (animating) {
            demoteReasons |= ActivityTaskManagerService.DEMOTE_TOP_REASON_ANIMATING_RECENTS;
        } else {
            demoteReasons &= ~ActivityTaskManagerService.DEMOTE_TOP_REASON_ANIMATING_RECENTS;
        }
        mService.mDemoteTopAppReasons = demoteReasons;
        // Make the demotion of the real top app take effect. No need to restore top app state for
        // finishing recents because addToStopping -> scheduleIdle -> activityIdleInternal ->
        // trimApplications will have a full update.
        if (animating && mService.mTopApp != null) {
            mService.mTopApp.scheduleUpdateOomAdj();
        }
    }

    @Override
    public void onAnimationFinished(@RecentsAnimationController.ReorderMode int reorderMode,
            boolean sendUserLeaveHint) {
+12 −6
Original line number Diff line number Diff line
@@ -37,13 +37,13 @@ import static com.android.server.wm.WindowContainer.POSITION_TOP;

import static com.google.common.truth.Truth.assertThat;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;

import android.app.IApplicationThread;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -91,9 +91,15 @@ public class RecentsAnimationTest extends WindowTestsBase {
        TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
        Task recentsStack = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
                ACTIVITY_TYPE_RECENTS, true /* onTop */);
        final WindowProcessController wpc = mSystemServicesTestRule.addProcess(
                mRecentsComponent.getPackageName(), mRecentsComponent.getPackageName(),
                // Use real pid/uid of the test so the corresponding process can be mapped by
                // Binder.getCallingPid/Uid.
                android.os.Process.myPid(), android.os.Process.myUid());
        ActivityRecord recentActivity = new ActivityBuilder(mAtm)
                .setComponent(mRecentsComponent)
                .setTask(recentsStack)
                .setUseProcess(wpc)
                .build();
        ActivityRecord topActivity = new ActivityBuilder(mAtm).setCreateTask(true).build();
        topActivity.getRootTask().moveToFront("testRecentsActivityVisiblility");
@@ -106,11 +112,14 @@ public class RecentsAnimationTest extends WindowTestsBase {
                mRecentsComponent, true /* getRecentsAnimation */);
        // The launch-behind state should make the recents activity visible.
        assertTrue(recentActivity.mVisibleRequested);
        assertEquals(ActivityTaskManagerService.DEMOTE_TOP_REASON_ANIMATING_RECENTS,
                mAtm.mDemoteTopAppReasons);

        // Simulate the animation is cancelled without changing the stack order.
        recentsAnimation.onAnimationFinished(REORDER_KEEP_IN_PLACE, false /* sendUserLeaveHint */);
        // The non-top recents activity should be invisible by the restored launch-behind state.
        assertFalse(recentActivity.mVisibleRequested);
        assertEquals(0, mAtm.mDemoteTopAppReasons);
    }

    @Test
@@ -138,11 +147,8 @@ public class RecentsAnimationTest extends WindowTestsBase {
                anyInt() /* startFlags */, any() /* profilerInfo */);

        // Assume its process is alive because the caller should be the recents service.
        WindowProcessController wpc = new WindowProcessController(mAtm, aInfo.applicationInfo,
                aInfo.processName, aInfo.applicationInfo.uid, 0 /* userId */,
                mock(Object.class) /* owner */, mock(WindowProcessListener.class));
        wpc.setThread(mock(IApplicationThread.class));
        doReturn(wpc).when(mAtm).getProcessController(eq(wpc.mName), eq(wpc.mUid));
        mSystemServicesTestRule.addProcess(aInfo.packageName, aInfo.processName, 12345 /* pid */,
                aInfo.applicationInfo.uid);

        Intent recentsIntent = new Intent().setComponent(mRecentsComponent);
        // Null animation indicates to preload.
+28 −2
Original line number Diff line number Diff line
@@ -43,12 +43,14 @@ import static org.mockito.Mockito.withSettings;

import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.IApplicationThread;
import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManagerInternal;
import android.database.ContentObserver;
@@ -454,8 +456,32 @@ public class SystemServicesTestRule implements TestRule {
                .spiedInstance(sWakeLock).stubOnly());
    }

    void setSurfaceFactory(Supplier<Surface> factory) {
        mSurfaceFactory = factory;
    WindowProcessController addProcess(String pkgName, String procName, int pid, int uid) {
        return addProcess(mAtmService, pkgName, procName, pid, uid);
    }

    static WindowProcessController addProcess(ActivityTaskManagerService atmService, String pkgName,
            String procName, int pid, int uid) {
        final ApplicationInfo info = new ApplicationInfo();
        info.uid = uid;
        info.packageName = pkgName;
        return addProcess(atmService, info, procName, pid);
    }

    static WindowProcessController addProcess(ActivityTaskManagerService atmService,
            ApplicationInfo info, String procName, int pid) {
        final WindowProcessListener mockListener = mock(WindowProcessListener.class,
                withSettings().stubOnly());
        final int uid = info.uid;
        final WindowProcessController proc = new WindowProcessController(atmService,
                info, procName, uid, UserHandle.getUserId(uid), mockListener, mockListener);
        proc.setThread(mock(IApplicationThread.class, withSettings().stubOnly()));
        atmService.mProcessNames.put(procName, uid, proc);
        if (pid > 0) {
            proc.setPid(pid);
            atmService.mProcessMap.put(pid, proc);
        }
        return proc;
    }

    void cleanupWindowManagerHandlers() {
+9 −10
Original line number Diff line number Diff line
@@ -64,7 +64,6 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityOptions;
import android.app.IApplicationThread;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -915,13 +914,15 @@ class WindowTestsBase extends SystemServiceTestsBase {
     */
    protected static class ActivityBuilder {
        static final int DEFAULT_FAKE_UID = 12345;
        static final String DEFAULT_PROCESS_NAME = "procName";
        static int sProcNameSeq;

        private final ActivityTaskManagerService mService;

        private ComponentName mComponent;
        private String mTargetActivity;
        private Task mTask;
        private String mProcessName = "name";
        private String mProcessName = DEFAULT_PROCESS_NAME;
        private String mAffinity;
        private int mUid = DEFAULT_FAKE_UID;
        private boolean mCreateTask = false;
@@ -1109,6 +1110,9 @@ class WindowTestsBase extends SystemServiceTestsBase {
            aInfo.applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
            aInfo.applicationInfo.packageName = mComponent.getPackageName();
            aInfo.applicationInfo.uid = mUid;
            if (DEFAULT_PROCESS_NAME.equals(mProcessName)) {
                mProcessName += ++sProcNameSeq;
            }
            aInfo.processName = mProcessName;
            aInfo.packageName = mComponent.getPackageName();
            aInfo.name = mComponent.getClassName();
@@ -1173,16 +1177,11 @@ class WindowTestsBase extends SystemServiceTestsBase {
            if (mWpc != null) {
                wpc = mWpc;
            } else {
                wpc = new WindowProcessController(mService,
                        aInfo.applicationInfo, mProcessName, mUid,
                        UserHandle.getUserId(mUid), mock(Object.class),
                        mock(WindowProcessListener.class));
                wpc.setThread(mock(IApplicationThread.class));
                final WindowProcessController p = mService.getProcessController(mProcessName, mUid);
                wpc = p != null ? p : SystemServicesTestRule.addProcess(
                        mService, aInfo.applicationInfo, mProcessName, 0 /* pid */);
            }
            wpc.setThread(mock(IApplicationThread.class));
            activity.setProcess(wpc);
            doReturn(wpc).when(mService).getProcessController(
                    activity.processName, activity.info.applicationInfo.uid);

            // Resume top activities to make sure all other signals in the system are connected.
            mService.mRootWindowContainer.resumeFocusedTasksTopActivities();