Trigger onTaskAppeared when a task started from recents becomes ready.

In quick switch flows, launcher will first swipe task snapshot
through recents animation, and then start new task with custom animation
options through startActivityFromRecents after gesture finish detected,
and then finish recents animation finally.

But that way user may experience flickering before the new task launch
and recents animation finish.

To improve quick switch flickering, we ignore the new task's custom
animation from recents and generate task remote animation target,
and then trigger a callback for launcher to control/animate this
task surface, more like a part of RecentsAnimation,

Also, adding removeTask method for launcher can flexibility remove the
new task animation target once no need to animate, so that launcher
can decide when to finish recents animation.

Bug: 152480470
Test: manual as below steps:
  1) Doing quick switch task.
  2) Make sure launcher can receive onTaskAppeared callback.
  3) Make sure launcher calls removeTask successfully.
  4) Make sure launcher can finish recents animation after 3).
Change-Id: I0692a280a49719229fa8871509bad37a1343a00f
This commit is contained in:
lumark
2020-03-07 00:03:33 +08:00
committed by Ming-Shin Lu
parent edb826c298
commit 04bceb99f1
14 changed files with 186 additions and 31 deletions

View File

@@ -192,6 +192,11 @@ public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase {
Assume.assumeNoException(
new AssertionError("onAnimationCanceled should not be called"));
}
@Override
public void onTaskAppeared(RemoteAnimationTarget app) throws RemoteException {
/* no-op */
}
};
recentsSemaphore.tryAcquire();

View File

@@ -114,4 +114,16 @@ interface IRecentsAnimationController {
* animation is cancelled through fail safe mechanism.
*/
void setWillFinishToHome(boolean willFinishToHome);
/**
* Stops controlling a task that is currently controlled by this recents animation.
*
* This method should be called when a task that has been received via {@link #onAnimationStart}
* or {@link #onTaskAppeared} is no longer needed. After calling this method, the task will
* either disappear from the screen, or jump to its final position in case it was the top task.
*
* @param taskId Id of the Task target to remove
* @return {@code true} when target removed successfully, {@code false} otherwise.
*/
boolean removeTask(int taskId);
}

View File

@@ -56,4 +56,10 @@ oneway interface IRecentsAnimationRunner {
void onAnimationStart(in IRecentsAnimationController controller,
in RemoteAnimationTarget[] apps, in RemoteAnimationTarget[] wallpapers,
in Rect homeContentInsets, in Rect minimizedHomeBounds) = 2;
/**
* Called when the task of an activity that has been started while the recents animation
* was running becomes ready for control.
*/
void onTaskAppeared(in RemoteAnimationTarget app) = 3;
}

View File

@@ -883,6 +883,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
"-242787066": {
"message": "addTaskToRecentsAnimationIfNeeded, control: %s, task: %s, transit: %s",
"level": "DEBUG",
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/WindowContainer.java"
},
"-198463978": {
"message": "updateRotationUnchecked: alwaysSendConfiguration=%b forceRelayout=%b",
"level": "VERBOSE",
@@ -901,6 +907,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/ScreenRotationAnimation.java"
},
"-172900257": {
"message": "addTaskToTargets, target: %s",
"level": "DEBUG",
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimationController.java"
},
"-167822951": {
"message": "Attempted to add starting window to token with already existing starting window",
"level": "WARN",
@@ -1513,6 +1525,12 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimation.java"
},
"854237232": {
"message": "addTaskToRecentsAnimationIfNeeded, control: %s, task: %s, transit: %s",
"level": "DEBUG",
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/Task.java"
},
"873914452": {
"message": "goodToGo()",
"level": "DEBUG",

View File

@@ -261,6 +261,11 @@ public class ActivityManagerWrapper {
animationHandler.onAnimationCanceled(
taskSnapshot != null ? new ThumbnailData(taskSnapshot) : null);
}
@Override
public void onTaskAppeared(RemoteAnimationTarget app) {
animationHandler.onTaskAppeared(new RemoteAnimationTargetCompat(app));
}
};
}
ActivityTaskManager.getService().startRecentsActivity(intent, receiver, runner);

View File

@@ -109,4 +109,16 @@ public class RecentsAnimationControllerCompat {
Log.e(TAG, "Failed to set overview reached state", e);
}
}
/**
* @see IRecentsAnimationController#removeTask
*/
public boolean removeTask(int taskId) {
try {
return mAnimationController.removeTask(taskId);
} catch (RemoteException e) {
Log.e(TAG, "Failed to remove remote animation target", e);
return false;
}
}
}

View File

@@ -32,4 +32,10 @@ public interface RecentsAnimationListener {
* Called when the animation into Recents was canceled. This call is made on the binder thread.
*/
void onAnimationCanceled(ThumbnailData thumbnailData);
/**
* Called when the task of an activity that has been started while the recents animation
* was running becomes ready for control.
*/
void onTaskAppeared(RemoteAnimationTargetCompat app);
}

View File

@@ -403,11 +403,18 @@ public class AppTransition implements Dump {
mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
}
boolean isNextAppTransitionOpenCrossProfileApps() {
return mNextAppTransitionType == NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS;
}
boolean isNextAppTransitionCustomFromRecents() {
final RecentTasks recentTasks = mService.mAtmService.getRecentTasks();
final String recentsPackageName =
(recentTasks != null) ? recentTasks.getRecentsComponent().getPackageName() : null;
return mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM
&& mNextAppTransitionPackage.equals(recentsPackageName);
}
/**
* @return true if and only if we are currently fetching app transition specs from the future
* passed into {@link #overridePendingAppTransitionMultiThumbFuture}

View File

@@ -442,10 +442,6 @@ class RecentsAnimation implements RecentsAnimationCallbacks,
// Always prepare an app transition since we rely on the transition callbacks to cleanup
mWindowManager.prepareAppTransition(TRANSIT_NONE, false);
controller.setCancelOnNextTransitionStart();
} else {
// Just cancel directly to unleash from launcher when the next launching task is the
// current top task.
mWindowManager.cancelRecentsAnimation(REORDER_KEEP_IN_PLACE, "stackOrderChanged");
}
}

View File

@@ -43,6 +43,7 @@ import android.os.RemoteException;
import android.os.SystemClock;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.proto.ProtoOutputStream;
@@ -99,6 +100,8 @@ public class RecentsAnimationController implements DeathRecipient {
private IRecentsAnimationRunner mRunner;
private final RecentsAnimationCallbacks mCallbacks;
private final ArrayList<TaskAnimationAdapter> mPendingAnimations = new ArrayList<>();
private final IntArray mPendingNewTaskTargets = new IntArray(0);
private final ArrayList<WallpaperAnimationAdapter> mPendingWallpaperAnimations =
new ArrayList<>();
private final int mDisplayId;
@@ -220,6 +223,10 @@ public class RecentsAnimationController implements DeathRecipient {
if (mCanceled) {
return;
}
// Remove all new task targets.
for (int i = mPendingNewTaskTargets.size() - 1; i >= 0; i--) {
removeTaskInternal(mPendingNewTaskTargets.get(i));
}
}
// Note, the callback will handle its own synchronization, do not lock on WM lock
@@ -310,6 +317,18 @@ public class RecentsAnimationController implements DeathRecipient {
mWillFinishToHome = willFinishToHome;
}
}
@Override
public boolean removeTask(int taskId) {
final long token = Binder.clearCallingIdentity();
try {
synchronized (mService.getWindowManagerLock()) {
return removeTaskInternal(taskId);
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
};
/**
@@ -405,11 +424,17 @@ public class RecentsAnimationController implements DeathRecipient {
@VisibleForTesting
AnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible) {
return addAnimation(task, isRecentTaskInvisible, null /* finishedCallback */);
}
@VisibleForTesting
AnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible,
OnAnimationFinishedCallback finishedCallback) {
ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "addAnimation(%s)", task.getName());
final TaskAnimationAdapter taskAdapter = new TaskAnimationAdapter(task,
isRecentTaskInvisible);
task.startAnimation(task.getPendingTransaction(), taskAdapter, false /* hidden */,
ANIMATION_TYPE_RECENTS);
ANIMATION_TYPE_RECENTS, finishedCallback);
task.commitPendingTransaction();
mPendingAnimations.add(taskAdapter);
return taskAdapter;
@@ -489,6 +514,49 @@ public class RecentsAnimationController implements DeathRecipient {
}
}
void addTaskToTargets(Task task, OnAnimationFinishedCallback finishedCallback) {
if (mRunner != null) {
final RemoteAnimationTarget target = createTaskRemoteAnimation(task, finishedCallback);
if (target == null) return;
ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "addTaskToTargets, target: %s", target);
try {
mRunner.onTaskAppeared(target);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to report task appeared", e);
}
}
}
private RemoteAnimationTarget createTaskRemoteAnimation(Task task,
OnAnimationFinishedCallback finishedCallback) {
final SparseBooleanArray recentTaskIds =
mService.mAtmService.getRecentTasks().getRecentTaskIds();
TaskAnimationAdapter adapter = (TaskAnimationAdapter) addAnimation(task,
!recentTaskIds.get(task.mTaskId), finishedCallback);
mPendingNewTaskTargets.add(task.mTaskId);
return adapter.createRemoteAnimationTarget();
}
private boolean removeTaskInternal(int taskId) {
boolean result = false;
for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
// Only allows when task target has became visible to user, to prevent
// the flickering during remove animation and task visible.
final TaskAnimationAdapter target = mPendingAnimations.get(i);
if (target.mTask.mTaskId == taskId && target.mTask.isOnTop()) {
removeAnimation(target);
final int taskIndex = mPendingNewTaskTargets.indexOf(taskId);
if (taskIndex != -1) {
mPendingNewTaskTargets.remove(taskIndex);
}
result = true;
break;
}
}
return result;
}
private RemoteAnimationTarget[] createAppAnimations() {
final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {

View File

@@ -66,6 +66,7 @@ import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_KEEP_SCREEN_ON;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.server.wm.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
import static com.android.server.wm.RootWindowContainerProto.IS_HOME_RECENTS_COMPONENT;
import static com.android.server.wm.RootWindowContainerProto.KEYGUARD_CONTROLLER;
import static com.android.server.wm.RootWindowContainerProto.PENDING_ACTIVITIES;
@@ -1515,6 +1516,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
// Updates the extra information of the intent.
if (fromHomeKey) {
homeIntent.putExtra(WindowManagerPolicy.EXTRA_FROM_HOME_KEY, true);
mWindowManager.cancelRecentsAnimation(REORDER_KEEP_IN_PLACE, "startHomeActivity");
}
// Update the reason for ANR debugging to verify if the user activity is the one that
// actually launched.

View File

@@ -86,6 +86,8 @@ import static com.android.server.wm.IdentifierProto.HASH_CODE;
import static com.android.server.wm.IdentifierProto.TITLE;
import static com.android.server.wm.IdentifierProto.USER_ID;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
import static com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowContainerChildProto.TASK;
@@ -135,6 +137,7 @@ import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.ITaskOrganizer;
import com.android.internal.annotations.VisibleForTesting;
@@ -3403,6 +3406,24 @@ class Task extends WindowContainer<WindowContainer> {
}
}
@Override
protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter,
int transit, boolean isVoiceInteraction,
@Nullable OnAnimationFinishedCallback finishedCallback) {
final RecentsAnimationController control = mWmService.getRecentsAnimationController();
if (control != null && enter
&& getDisplayContent().mAppTransition.isNextAppTransitionCustomFromRecents()) {
ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
"addTaskToRecentsAnimationIfNeeded, control: %s, task: %s, transit: %s",
control, asTask(), AppTransition.appTransitionToString(transit));
// We let the transition to be controlled by RecentsAnimation, and callback task's
// RemoteAnimationTarget for remote runner to animate.
control.addTaskToTargets(getRootTask(), finishedCallback);
} else {
super.applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, finishedCallback);
}
}
@Override
void dump(PrintWriter pw, String prefix, boolean dumpAll) {
super.dump(pw, prefix, dumpAll);

View File

@@ -2076,8 +2076,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
* @see #getAnimationAdapter
*/
boolean applyAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
boolean isVoiceInteraction,
@Nullable OnAnimationFinishedCallback animationFinishedCallback) {
boolean isVoiceInteraction, @Nullable OnAnimationFinishedCallback finishedCallback) {
if (mWmService.mDisableTransitionAnimation) {
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
"applyAnimation: transition animation is disabled or skipped. "
@@ -2092,22 +2091,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WC#applyAnimation");
if (okToAnimate()) {
final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp,
transit, enter, isVoiceInteraction);
AnimationAdapter adapter = adapters.first;
AnimationAdapter thumbnailAdapter = adapters.second;
if (adapter != null) {
startAnimation(getPendingTransaction(), adapter, !isVisible(),
ANIMATION_TYPE_APP_TRANSITION, animationFinishedCallback);
if (adapter.getShowWallpaper()) {
getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
}
if (thumbnailAdapter != null) {
mSurfaceFreezer.mSnapshot.startAnimation(getPendingTransaction(),
thumbnailAdapter, ANIMATION_TYPE_APP_TRANSITION,
(type, anim) -> { });
}
}
applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, finishedCallback);
} else {
cancelAnimation();
}
@@ -2201,6 +2185,26 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return resultAdapters;
}
protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter,
int transit, boolean isVoiceInteraction,
@Nullable OnAnimationFinishedCallback finishedCallback) {
final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp,
transit, enter, isVoiceInteraction);
AnimationAdapter adapter = adapters.first;
AnimationAdapter thumbnailAdapter = adapters.second;
if (adapter != null) {
startAnimation(getPendingTransaction(), adapter, !isVisible(),
ANIMATION_TYPE_APP_TRANSITION, finishedCallback);
if (adapter.getShowWallpaper()) {
getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
}
if (thumbnailAdapter != null) {
mSurfaceFreezer.mSnapshot.startAnimation(getPendingTransaction(),
thumbnailAdapter, ANIMATION_TYPE_APP_TRANSITION, (type, anim) -> { });
}
}
}
final SurfaceAnimationRunner getSurfaceAnimationRunner() {
return mWmService.mSurfaceAnimationRunner;
}

View File

@@ -238,9 +238,6 @@ public class RecentsAnimationTest extends ActivityTestsBase {
assertTrue(targetActivity.mLaunchTaskBehind);
anotherHomeActivity.moveFocusableActivityToTop("launchAnotherHome");
// The current top activity is not the recents so the animation should be canceled.
verify(mService.mWindowManager, times(1)).cancelRecentsAnimation(
eq(REORDER_KEEP_IN_PLACE), any() /* reason */);
// The test uses mocked RecentsAnimationController so we have to invoke the callback
// manually to simulate the flow.
@@ -279,10 +276,6 @@ public class RecentsAnimationTest extends ActivityTestsBase {
fullscreenStack.moveToFront("Activity start");
// Ensure that the recents animation was canceled by cancelAnimationSynchronously().
verify(mService.mWindowManager, times(1)).cancelRecentsAnimation(
eq(REORDER_KEEP_IN_PLACE), any());
// Assume recents animation already started, set a state that cancel recents animation
// with screenshot.
doReturn(true).when(mRecentsAnimationController).shouldDeferCancelUntilNextTransition();