summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Riddle Hsu <riddlehsu@google.com> 2022-06-06 22:50:20 +0800
committer Riddle Hsu <riddlehsu@google.com> 2022-06-08 17:48:53 +0800
commita20990ec66bf61f45d36c5b66011f54d85c8095c (patch)
treee0fcacbc2fc474c2983594b555f4082a023f584b
parent4f677e9d79085a4fa33048a8a932761d8b1cee5d (diff)
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: 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
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java34
-rw-r--r--services/core/java/com/android/server/wm/RecentsAnimation.java28
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java18
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java30
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java19
5 files changed, 98 insertions, 31 deletions
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 6ae8887a7912..c7e1b8bbf020 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -402,12 +402,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;
@@ -2841,8 +2854,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) {
@@ -2883,7 +2896,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
@@ -3425,7 +3438,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 -> {
@@ -4035,6 +4048,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) {
@@ -5663,8 +5679,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 eca201dc2bda..f840171b29b0 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 1b19a28a9790..a1d6a5006fef 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.
+ WindowManagerService.MY_PID, WindowManagerService.MY_UID);
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 18c4eb964a3a..6c100d76f5fb 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;
@@ -430,6 +432,34 @@ public class SystemServicesTestRule implements TestRule {
.spiedInstance(sWakeLock).stubOnly());
}
+ 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() {
final WindowManagerService wm = getWindowManagerService();
if (wm == null) {
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 50fa4cc9eb7c..8a539cd3ddff 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;
@@ -933,13 +932,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;
@@ -1127,6 +1128,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();
@@ -1191,16 +1195,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();