From 50a757a486ef7356ac200c892449b0cc5e76ac4d Mon Sep 17 00:00:00 2001 From: Riddle Hsu Date: Mon, 6 Jun 2022 22:50:20 +0800 Subject: [PATCH] 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 a20990ec66bf61f45d36c5b66011f54d85c8095c) --- .../server/wm/ActivityTaskManagerService.java | 34 ++++++++++++++----- .../android/server/wm/RecentsAnimation.java | 28 +++++++++++---- .../server/wm/RecentsAnimationTest.java | 18 ++++++---- .../server/wm/SystemServicesTestRule.java | 30 ++++++++++++++-- .../android/server/wm/WindowTestsBase.java | 19 +++++------ 5 files changed, 96 insertions(+), 33 deletions(-) diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 7dbd8e8047a6b..f830f6fe09267 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -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) { diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index eca201dc2bdad..f840171b29b06 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -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) { diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java index 1b19a28a9790f..28fb0154a3b8a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java @@ -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. diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index dc9a62554fdb0..258625c751a37 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -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 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() { diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 20fbda428381c..7347f8825ff70 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -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();