Allow Recents transitions to collect in parallel

We can't fully support parallel transition collection,
but we can for specific cases (like recents). This CL
adds some hard-coded checks for recents specifically and
allows it to collect in parallel with activity-only
transitions. This is basically way of assigning "priority"
to recents since it is a very user-forward operation
and an integral part of how users percieve device
performance.

Bug: 277838915
Bug: 264536014
Test: In the running task, launch a slow-to-draw activity,
      then swipe to recents or home. It should not wait
      anymore.
Change-Id: Ie09e31bf290847b108578b688f57360536fe7f4e
This commit is contained in:
Evan Rosky
2023-04-10 14:01:30 -07:00
parent d373da3cba
commit b4963b6da5
5 changed files with 134 additions and 11 deletions

View File

@@ -403,8 +403,11 @@ public class ActivityOptions extends ComponentOptions {
private static final String KEY_SPLASH_SCREEN_STYLE =
"android.activity.splashScreenStyle";
/** See {@link #setTransientLaunch()}. */
private static final String KEY_TRANSIENT_LAUNCH = "android.activity.transientLaunch";
/**
* See {@link #setTransientLaunch()}.
* @hide
*/
public static final String KEY_TRANSIENT_LAUNCH = "android.activity.transientLaunch";
/** see {@link #makeLaunchIntoPip(PictureInPictureParams)}. */
private static final String KEY_LAUNCH_INTO_PIP_PARAMS =

View File

@@ -1183,6 +1183,12 @@
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/AppTransitionController.java"
},
"-1005167552": {
"message": "Playing #%d in parallel on track #%d",
"level": "VERBOSE",
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/TransitionController.java"
},
"-1003678883": {
"message": "Cleaning splash screen token=%s",
"level": "VERBOSE",
@@ -1447,6 +1453,12 @@
"group": "WM_DEBUG_TASKS",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
"-774908272": {
"message": "Marking #%d animation as SYNC.",
"level": "VERBOSE",
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/TransitionController.java"
},
"-771177730": {
"message": "Removing focused app token:%s displayId=%d",
"level": "VERBOSE",
@@ -1495,12 +1507,6 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
"-741766551": {
"message": "Content Recording: Ignoring session on invalid virtual display",
"level": "VERBOSE",
"group": "WM_DEBUG_CONTENT_RECORDING",
"at": "com\/android\/server\/wm\/ContentRecordingController.java"
},
"-732715767": {
"message": "Unable to retrieve window container to start recording for display %d",
"level": "VERBOSE",
@@ -2017,6 +2023,12 @@
"group": "WM_DEBUG_WALLPAPER",
"at": "com\/android\/server\/wm\/WallpaperController.java"
},
"-266707683": {
"message": "Moving #%d from collecting to waiting.",
"level": "VERBOSE",
"group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
"at": "com\/android\/server\/wm\/TransitionController.java"
},
"-262984451": {
"message": "Relaunch failed %s",
"level": "INFO",
@@ -2089,6 +2101,12 @@
"group": "WM_DEBUG_WINDOW_MOVEMENT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
"-186693085": {
"message": "Starting a Recents transition which can be parallel.",
"level": "VERBOSE",
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/Transition.java"
},
"-182877285": {
"message": "Wallpaper layer changed: assigning layers + relayout",
"level": "VERBOSE",

View File

@@ -55,6 +55,7 @@ import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
import static android.window.TransitionInfo.FLAG_TASK_LAUNCHING_BEHIND;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static android.window.TransitionInfo.FLAG_WILL_IME_SHOWN;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM;
@@ -65,12 +66,14 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.IApplicationThread;
import android.content.pm.ActivityInfo;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.RemoteException;
@@ -85,6 +88,7 @@ import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.ScreenCapture;
import android.window.TransitionInfo;
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
@@ -185,7 +189,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
final ArraySet<WindowContainer> mParticipants = new ArraySet<>();
/** The final animation targets derived from participants after promotion. */
private ArrayList<ChangeInfo> mTargets;
ArrayList<ChangeInfo> mTargets;
/** The displays that this transition is running on. */
private final ArrayList<DisplayContent> mTargetDisplays = new ArrayList<>();
@@ -271,9 +275,14 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
/** Any 2 transitions of this type can run in parallel with each other. Used for testing. */
static final int PARALLEL_TYPE_MUTUAL = 1;
/** This is a recents transition. */
static final int PARALLEL_TYPE_RECENTS = 2;
@IntDef(prefix = { "PARALLEL_TYPE_" }, value = {
PARALLEL_TYPE_NONE,
PARALLEL_TYPE_MUTUAL
PARALLEL_TYPE_MUTUAL,
PARALLEL_TYPE_RECENTS
})
@Retention(RetentionPolicy.SOURCE)
@interface ParallelType {}
@@ -328,6 +337,21 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
mFlags |= flag;
}
void calcParallelCollectType(WindowContainerTransaction wct) {
for (int i = 0; i < wct.getHierarchyOps().size(); ++i) {
final WindowContainerTransaction.HierarchyOp hop = wct.getHierarchyOps().get(i);
if (hop.getType() != HIERARCHY_OP_TYPE_PENDING_INTENT) continue;
final Bundle b = hop.getLaunchOptions();
if (b == null || b.isEmpty()) continue;
final boolean transientLaunch = b.getBoolean(ActivityOptions.KEY_TRANSIENT_LAUNCH);
if (transientLaunch) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Starting a Recents transition which can be parallel.");
mParallelCollectType = PARALLEL_TYPE_RECENTS;
}
}
}
/** Records an activity as transient-launch. This activity must be already collected. */
void setTransientLaunch(@NonNull ActivityRecord activity, @Nullable Task restoreBelow) {
if (mTransientLaunches == null) {
@@ -380,6 +404,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
return false;
}
boolean hasTransientLaunch() {
return mTransientLaunches != null && !mTransientLaunches.isEmpty();
}
boolean isTransientLaunch(@NonNull ActivityRecord activity) {
return mTransientLaunches != null && mTransientLaunches.containsKey(activity);
}

View File

@@ -879,6 +879,8 @@ class TransitionController {
// If it's a legacy sync, then it needs to wait until there is no collecting transition.
if (queued.mTransition == null) return;
if (!canStartCollectingNow(queued.mTransition)) return;
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Moving #%d from collecting"
+ " to waiting.", mCollectingTransition.getSyncId());
mWaitingTransitions.add(mCollectingTransition);
mCollectingTransition = null;
} else if (mSyncEngine.hasActiveSync()) {
@@ -930,10 +932,37 @@ class TransitionController {
* in {@link #getIsIndependent} later.
*/
boolean getCanBeIndependent(Transition collecting, Transition queued) {
// For tests
if (queued.mParallelCollectType == Transition.PARALLEL_TYPE_MUTUAL
&& collecting.mParallelCollectType == Transition.PARALLEL_TYPE_MUTUAL) {
return true;
}
// For recents
if (queued.mParallelCollectType == Transition.PARALLEL_TYPE_RECENTS) {
if (collecting.mParallelCollectType == Transition.PARALLEL_TYPE_RECENTS) {
// Must serialize with itself.
return false;
}
// allow this if `collecting` only has activities
for (int i = 0; i < collecting.mParticipants.size(); ++i) {
final WindowContainer wc = collecting.mParticipants.valueAt(i);
final ActivityRecord ar = wc.asActivityRecord();
if (ar == null && wc.asWindowState() == null && wc.asWindowToken() == null) {
// Is task or above, so can't be independent
return false;
}
if (ar != null && ar.isActivityTypeHomeOrRecents()) {
// It's a recents or home type, so it conflicts.
return false;
}
}
return true;
} else if (collecting.mParallelCollectType == Transition.PARALLEL_TYPE_RECENTS) {
// We can collect simultaneously with recents if it is populated. This is because
// we know that recents will not collect/trampoline any more stuff. If anything in the
// queued transition overlaps, it will end up just waiting in sync-queue anyways.
return true;
}
return false;
}
@@ -942,11 +971,47 @@ class TransitionController {
* `running` is playing based on its current state.
*/
static boolean getIsIndependent(Transition running, Transition incoming) {
// For tests
if (running.mParallelCollectType == Transition.PARALLEL_TYPE_MUTUAL
&& incoming.mParallelCollectType == Transition.PARALLEL_TYPE_MUTUAL) {
return true;
}
return false;
// For now there's only one mutually-independent pair: an all activity-level transition and
// a transient-launch where none of the activities are part of the transient-launch task,
// so the following logic is hard-coded specifically for this.
// Also, we currently restrict valid transient-launches to just recents.
final Transition recents;
final Transition other;
if (running.mParallelCollectType == Transition.PARALLEL_TYPE_RECENTS
&& running.hasTransientLaunch()) {
if (incoming.mParallelCollectType == Transition.PARALLEL_TYPE_RECENTS) {
// Recents can't be independent from itself.
return false;
}
recents = running;
other = incoming;
} else if (incoming.mParallelCollectType == Transition.PARALLEL_TYPE_RECENTS
&& incoming.hasTransientLaunch()) {
recents = incoming;
other = running;
} else {
return false;
}
// Check against *targets* because that is the post-promotion set of containers that are
// actually animating.
for (int i = 0; i < other.mTargets.size(); ++i) {
final WindowContainer wc = other.mTargets.get(i).mContainer;
final ActivityRecord ar = wc.asActivityRecord();
if (ar == null && wc.asWindowState() == null && wc.asWindowToken() == null) {
// Is task or above, so for now don't let them be independent.
return false;
}
if (ar != null && recents.isTransientLaunch(ar)) {
// Change overlaps with recents, so serialize.
return false;
}
}
return true;
}
void assignTrack(Transition transition, TransitionInfo info) {
@@ -970,9 +1035,15 @@ class TransitionController {
if (track < 0) {
// Didn't overlap with anything, so give it its own track
track = mTrackCount;
if (track > 0) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Playing #%d in parallel on "
+ "track #%d", transition.getSyncId(), track);
}
}
if (sync) {
info.setFlags(info.getFlags() | TransitionInfo.FLAG_SYNC);
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Marking #%d animation as SYNC.",
transition.getSyncId());
}
transition.mAnimationTrack = track;
info.setTrack(track);
@@ -1145,6 +1216,8 @@ class TransitionController {
// Check if we can run in parallel here.
if (canStartCollectingNow(transit)) {
// start running in parallel.
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Moving #%d from"
+ " collecting to waiting.", mCollectingTransition.getSyncId());
mWaitingTransitions.add(mCollectingTransition);
mCollectingTransition = null;
moveToCollecting(transit);

View File

@@ -299,6 +299,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
final boolean needsSetReady = t != null;
final Transition nextTransition = new Transition(type, 0 /* flags */,
mTransitionController, mService.mWindowManager.mSyncEngine);
nextTransition.calcParallelCollectType(wct);
mTransitionController.startCollectOrQueue(nextTransition,
(deferred) -> {
nextTransition.start();