diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index 6576a1a5d3c2a..9bad0a0544b13 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -73,6 +73,7 @@ import android.view.IWindowFocusObserver; import android.view.RemoteAnimationDefinition; import android.view.RemoteAnimationAdapter; import android.window.IWindowOrganizerController; +import android.window.BackAnimationAdapter; import android.window.BackNavigationInfo; import android.window.SplashScreenView; import com.android.internal.app.IVoiceInteractor; @@ -352,9 +353,10 @@ interface IActivityTaskManager { /** * Prepare the back navigation in the server. This setups the leashed for sysui to animate * the back gesture and returns the data needed for the animation. - * @param requestAnimation true if the caller wishes to animate the back navigation * @param focusObserver a remote callback to nofify shell when the focused window lost focus. + * @param adaptor a remote animation to be run for the back navigation plays the animation. + * @return Returns the back navigation info. */ - android.window.BackNavigationInfo startBackNavigation(in boolean requestAnimation, - in IWindowFocusObserver focusObserver); + android.window.BackNavigationInfo startBackNavigation( + in IWindowFocusObserver focusObserver, in BackAnimationAdapter adaptor); } diff --git a/core/java/android/window/BackAnimationAdapter.aidl b/core/java/android/window/BackAnimationAdapter.aidl new file mode 100644 index 0000000000000..2d7126c02a0d8 --- /dev/null +++ b/core/java/android/window/BackAnimationAdapter.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +/** + * @hide + */ +parcelable BackAnimationAdapter; \ No newline at end of file diff --git a/core/java/android/window/BackAnimationAdapter.java b/core/java/android/window/BackAnimationAdapter.java new file mode 100644 index 0000000000000..5eb34e694a572 --- /dev/null +++ b/core/java/android/window/BackAnimationAdapter.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Object that describes how to run a remote back animation. + * + * @hide + */ +public class BackAnimationAdapter implements Parcelable { + private final IBackAnimationRunner mRunner; + + public BackAnimationAdapter(IBackAnimationRunner runner) { + mRunner = runner; + } + + public BackAnimationAdapter(Parcel in) { + mRunner = IBackAnimationRunner.Stub.asInterface(in.readStrongBinder()); + } + + public IBackAnimationRunner getRunner() { + return mRunner; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeStrongInterface(mRunner); + } + + public static final @android.annotation.NonNull Creator CREATOR = + new Creator() { + public BackAnimationAdapter createFromParcel(Parcel in) { + return new BackAnimationAdapter(in); + } + + public BackAnimationAdapter[] newArray(int size) { + return new BackAnimationAdapter[size]; + } + }; +} diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java index 1024e2e50c3e2..4a4f561c71ede 100644 --- a/core/java/android/window/BackEvent.java +++ b/core/java/android/window/BackEvent.java @@ -18,10 +18,8 @@ package android.window; import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; -import android.view.RemoteAnimationTarget; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -52,8 +50,6 @@ public class BackEvent implements Parcelable { @SwipeEdge private final int mSwipeEdge; - @Nullable - private final RemoteAnimationTarget mDepartingAnimationTarget; /** * Creates a new {@link BackEvent} instance. @@ -62,16 +58,12 @@ public class BackEvent implements Parcelable { * @param touchY Absolute Y location of the touch point of this event. * @param progress Value between 0 and 1 on how far along the back gesture is. * @param swipeEdge Indicates which edge the swipe starts from. - * @param departingAnimationTarget The remote animation target of the departing application - * window. */ - public BackEvent(float touchX, float touchY, float progress, @SwipeEdge int swipeEdge, - @Nullable RemoteAnimationTarget departingAnimationTarget) { + public BackEvent(float touchX, float touchY, float progress, @SwipeEdge int swipeEdge) { mTouchX = touchX; mTouchY = touchY; mProgress = progress; mSwipeEdge = swipeEdge; - mDepartingAnimationTarget = departingAnimationTarget; } private BackEvent(@NonNull Parcel in) { @@ -79,7 +71,6 @@ public class BackEvent implements Parcelable { mTouchY = in.readFloat(); mProgress = in.readFloat(); mSwipeEdge = in.readInt(); - mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR); } public static final Creator CREATOR = new Creator() { @@ -105,7 +96,6 @@ public class BackEvent implements Parcelable { dest.writeFloat(mTouchY); dest.writeFloat(mProgress); dest.writeInt(mSwipeEdge); - dest.writeTypedObject(mDepartingAnimationTarget, flags); } /** @@ -136,16 +126,6 @@ public class BackEvent implements Parcelable { return mSwipeEdge; } - /** - * Returns the {@link RemoteAnimationTarget} of the top departing application window, - * or {@code null} if the top window should not be moved for the current type of back - * destination. - */ - @Nullable - public RemoteAnimationTarget getDepartingAnimationTarget() { - return mDepartingAnimationTarget; - } - @Override public String toString() { return "BackEvent{" @@ -153,7 +133,6 @@ public class BackEvent implements Parcelable { + ", mTouchY=" + mTouchY + ", mProgress=" + mProgress + ", mSwipeEdge" + mSwipeEdge - + ", mDepartingAnimationTarget" + mDepartingAnimationTarget + "}"; } } diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java index dd49014176711..87cfbb2a57055 100644 --- a/core/java/android/window/BackNavigationInfo.java +++ b/core/java/android/window/BackNavigationInfo.java @@ -19,14 +19,10 @@ package android.window; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.WindowConfiguration; -import android.hardware.HardwareBuffer; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteCallback; -import android.view.RemoteAnimationTarget; -import android.view.SurfaceControl; /** * Information to be sent to SysUI about a back event. @@ -84,75 +80,50 @@ public final class BackNavigationInfo implements Parcelable { TYPE_CROSS_TASK, TYPE_CALLBACK }) - @interface BackTargetType { + public @interface BackTargetType { } private final int mType; @Nullable - private final RemoteAnimationTarget mDepartingAnimationTarget; - @Nullable - private final SurfaceControl mScreenshotSurface; - @Nullable - private final HardwareBuffer mScreenshotBuffer; - @Nullable private final RemoteCallback mOnBackNavigationDone; @Nullable - private final WindowConfiguration mTaskWindowConfiguration; - @Nullable private final IOnBackInvokedCallback mOnBackInvokedCallback; + private final boolean mPrepareRemoteAnimation; /** * Create a new {@link BackNavigationInfo} instance. * * @param type The {@link BackTargetType} of the destination (what will be - * displayed after the back action). - * @param departingAnimationTarget The remote animation target, containing a leash to animate - * away the departing window. The consumer of the leash is - * responsible for removing it. - * @param screenshotSurface The screenshot of the previous activity to be displayed. - * @param screenshotBuffer A buffer containing a screenshot used to display the activity. - * See {@link #getScreenshotHardwareBuffer()} for information - * about nullity. - * @param taskWindowConfiguration The window configuration of the Task being animated beneath. * @param onBackNavigationDone The callback to be called once the client is done with the * back preview. * @param onBackInvokedCallback The back callback registered by the current top level window. + * @param isPrepareRemoteAnimation Return whether the core is preparing a back gesture + * animation, if true, the caller of startBackNavigation should + * be expected to receive an animation start callback. */ private BackNavigationInfo(@BackTargetType int type, - @Nullable RemoteAnimationTarget departingAnimationTarget, - @Nullable SurfaceControl screenshotSurface, - @Nullable HardwareBuffer screenshotBuffer, - @Nullable WindowConfiguration taskWindowConfiguration, @Nullable RemoteCallback onBackNavigationDone, - @Nullable IOnBackInvokedCallback onBackInvokedCallback) { + @Nullable IOnBackInvokedCallback onBackInvokedCallback, + boolean isPrepareRemoteAnimation) { mType = type; - mDepartingAnimationTarget = departingAnimationTarget; - mScreenshotSurface = screenshotSurface; - mScreenshotBuffer = screenshotBuffer; - mTaskWindowConfiguration = taskWindowConfiguration; mOnBackNavigationDone = onBackNavigationDone; mOnBackInvokedCallback = onBackInvokedCallback; + mPrepareRemoteAnimation = isPrepareRemoteAnimation; } private BackNavigationInfo(@NonNull Parcel in) { mType = in.readInt(); - mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR); - mScreenshotSurface = in.readTypedObject(SurfaceControl.CREATOR); - mScreenshotBuffer = in.readTypedObject(HardwareBuffer.CREATOR); - mTaskWindowConfiguration = in.readTypedObject(WindowConfiguration.CREATOR); mOnBackNavigationDone = in.readTypedObject(RemoteCallback.CREATOR); mOnBackInvokedCallback = IOnBackInvokedCallback.Stub.asInterface(in.readStrongBinder()); + mPrepareRemoteAnimation = in.readBoolean(); } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(mType); - dest.writeTypedObject(mDepartingAnimationTarget, flags); - dest.writeTypedObject(mScreenshotSurface, flags); - dest.writeTypedObject(mScreenshotBuffer, flags); - dest.writeTypedObject(mTaskWindowConfiguration, flags); dest.writeTypedObject(mOnBackNavigationDone, flags); dest.writeStrongInterface(mOnBackInvokedCallback); + dest.writeBoolean(mPrepareRemoteAnimation); } /** @@ -164,49 +135,6 @@ public final class BackNavigationInfo implements Parcelable { return mType; } - /** - * Returns a {@link RemoteAnimationTarget}, containing a leash to the top window container - * that needs to be animated. This can be null if the back animation is controlled by - * the application. - */ - @Nullable - public RemoteAnimationTarget getDepartingAnimationTarget() { - return mDepartingAnimationTarget; - } - - /** - * Returns the {@link SurfaceControl} that should be used to display a screenshot of the - * previous activity. - */ - @Nullable - public SurfaceControl getScreenshotSurface() { - return mScreenshotSurface; - } - - /** - * Returns the {@link HardwareBuffer} containing the screenshot the activity about to be - * shown. This can be null if one of the following conditions is met: - *
    - *
  • The screenshot is not available - *
  • The previous activity is the home screen ( {@link #TYPE_RETURN_TO_HOME} - *
  • The current window is a dialog ({@link #TYPE_DIALOG_CLOSE} - *
  • The back animation is controlled by the application - *
- */ - @Nullable - public HardwareBuffer getScreenshotHardwareBuffer() { - return mScreenshotBuffer; - } - - /** - * Returns the {@link WindowConfiguration} of the current task. This is null when the top - * application is controlling the back animation. - */ - @Nullable - public WindowConfiguration getTaskWindowConfiguration() { - return mTaskWindowConfiguration; - } - /** * Returns the {@link OnBackInvokedCallback} of the top level window or null if * the client didn't register a callback. @@ -221,6 +149,13 @@ public final class BackNavigationInfo implements Parcelable { return mOnBackInvokedCallback; } + /** + * Return true if the core is preparing a back gesture nimation. + */ + public boolean isPrepareRemoteAnimation() { + return mPrepareRemoteAnimation; + } + /** * Callback to be called when the back preview is finished in order to notify the server that * it can clean up the resources created for the animation. @@ -256,10 +191,6 @@ public final class BackNavigationInfo implements Parcelable { public String toString() { return "BackNavigationInfo{" + "mType=" + typeToString(mType) + " (" + mType + ")" - + ", mDepartingAnimationTarget=" + mDepartingAnimationTarget - + ", mScreenshotSurface=" + mScreenshotSurface - + ", mTaskWindowConfiguration= " + mTaskWindowConfiguration - + ", mScreenshotBuffer=" + mScreenshotBuffer + ", mOnBackNavigationDone=" + mOnBackNavigationDone + ", mOnBackInvokedCallback=" + mOnBackInvokedCallback + '}'; @@ -291,21 +222,12 @@ public final class BackNavigationInfo implements Parcelable { */ @SuppressWarnings("UnusedReturnValue") // Builder pattern public static class Builder { - private int mType = TYPE_UNDEFINED; @Nullable - private RemoteAnimationTarget mDepartingAnimationTarget = null; - @Nullable - private SurfaceControl mScreenshotSurface = null; - @Nullable - private HardwareBuffer mScreenshotBuffer = null; - @Nullable - private WindowConfiguration mTaskWindowConfiguration = null; - @Nullable private RemoteCallback mOnBackNavigationDone = null; @Nullable private IOnBackInvokedCallback mOnBackInvokedCallback = null; - + private boolean mPrepareRemoteAnimation; /** * @see BackNavigationInfo#getType() */ @@ -314,40 +236,6 @@ public final class BackNavigationInfo implements Parcelable { return this; } - /** - * @see BackNavigationInfo#getDepartingAnimationTarget - */ - public Builder setDepartingAnimationTarget( - @Nullable RemoteAnimationTarget departingAnimationTarget) { - mDepartingAnimationTarget = departingAnimationTarget; - return this; - } - - /** - * @see BackNavigationInfo#getScreenshotSurface - */ - public Builder setScreenshotSurface(@Nullable SurfaceControl screenshotSurface) { - mScreenshotSurface = screenshotSurface; - return this; - } - - /** - * @see BackNavigationInfo#getScreenshotHardwareBuffer() - */ - public Builder setScreenshotBuffer(@Nullable HardwareBuffer screenshotBuffer) { - mScreenshotBuffer = screenshotBuffer; - return this; - } - - /** - * @see BackNavigationInfo#getTaskWindowConfiguration - */ - public Builder setTaskWindowConfiguration( - @Nullable WindowConfiguration taskWindowConfiguration) { - mTaskWindowConfiguration = taskWindowConfiguration; - return this; - } - /** * @see BackNavigationInfo#onBackNavigationFinished(boolean) */ @@ -365,13 +253,20 @@ public final class BackNavigationInfo implements Parcelable { return this; } + /** + * @param prepareRemoteAnimation Whether core prepare animation for shell. + */ + public Builder setPrepareRemoteAnimation(boolean prepareRemoteAnimation) { + mPrepareRemoteAnimation = prepareRemoteAnimation; + return this; + } + /** * Builds and returns an instance of {@link BackNavigationInfo} */ public BackNavigationInfo build() { - return new BackNavigationInfo(mType, mDepartingAnimationTarget, mScreenshotSurface, - mScreenshotBuffer, mTaskWindowConfiguration, mOnBackNavigationDone, - mOnBackInvokedCallback); + return new BackNavigationInfo(mType, mOnBackNavigationDone, + mOnBackInvokedCallback, mPrepareRemoteAnimation); } } } diff --git a/core/java/android/window/IBackAnimationFinishedCallback.aidl b/core/java/android/window/IBackAnimationFinishedCallback.aidl new file mode 100644 index 0000000000000..8afc003256edd --- /dev/null +++ b/core/java/android/window/IBackAnimationFinishedCallback.aidl @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.window; + +/** + * Interface to be invoked by the controlling process when a back animation has finished. + * + * @param trigger Whether the back gesture has passed the triggering threshold. + * {@hide} + */ +oneway interface IBackAnimationFinishedCallback { + void onAnimationFinished(in boolean triggerBack); +} \ No newline at end of file diff --git a/core/java/android/window/IBackAnimationRunner.aidl b/core/java/android/window/IBackAnimationRunner.aidl new file mode 100644 index 0000000000000..1c677896dbd91 --- /dev/null +++ b/core/java/android/window/IBackAnimationRunner.aidl @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import android.view.RemoteAnimationTarget; +import android.window.IBackAnimationFinishedCallback; + +/** + * Interface that is used to callback from window manager to the process that runs a back gesture + * animation to start or cancel it. + * + * {@hide} + */ +oneway interface IBackAnimationRunner { + + /** + * Called when the system needs to cancel the current animation. This can be due to the + * wallpaper not drawing in time, or the handler not finishing the animation within a predefined + * amount of time. + * + */ + void onAnimationCancelled() = 1; + + /** + * Called when the system is ready for the handler to start animating all the visible tasks. + * @param type The back navigation type. + * @param apps The list of departing (type=MODE_CLOSING) and entering (type=MODE_OPENING) + windows to animate, + * @param wallpapers The list of wallpapers to animate. + * @param nonApps The list of non-app windows such as Bubbles to animate. + * @param finishedCallback The callback to invoke when the animation is finished. + */ + void onAnimationStart(in int type, + in RemoteAnimationTarget[] apps, + in RemoteAnimationTarget[] wallpapers, + in RemoteAnimationTarget[] nonApps, + in IBackAnimationFinishedCallback finishedCallback) = 2; +} \ No newline at end of file diff --git a/core/tests/coretests/src/android/window/BackNavigationTest.java b/core/tests/coretests/src/android/window/BackNavigationTest.java index bbbc4230903ab..d6145ebbbdb00 100644 --- a/core/tests/coretests/src/android/window/BackNavigationTest.java +++ b/core/tests/coretests/src/android/window/BackNavigationTest.java @@ -92,7 +92,7 @@ public class BackNavigationTest { try { mInstrumentation.getUiAutomation().waitForIdle(500, 1000); BackNavigationInfo info = ActivityTaskManager.getService() - .startBackNavigation(true, null); + .startBackNavigation(null, null); assertNotNull("BackNavigationInfo is null", info); assertNotNull("OnBackInvokedCallback is null", info.getOnBackInvokedCallback()); info.getOnBackInvokedCallback().onBackInvoked(); diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index cbdef846ceaf5..f9f2906316f40 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -1111,6 +1111,12 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-1033630971": { + "message": "onBackNavigationDone backType=%s, triggerBack=%b", + "level": "DEBUG", + "group": "WM_DEBUG_BACK_PREVIEW", + "at": "com\/android\/server\/wm\/BackNavigationController.java" + }, "-1022146708": { "message": "Skipping %s: mismatch activity type", "level": "DEBUG", @@ -3991,12 +3997,6 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, - "1778919449": { - "message": "onBackNavigationDone backType=%s, task=%s, prevActivity=%s", - "level": "DEBUG", - "group": "WM_DEBUG_BACK_PREVIEW", - "at": "com\/android\/server\/wm\/BackNavigationController.java" - }, "1781673113": { "message": "onAnimationFinished(): targetRootTask=%s targetActivity=%s mRestoreTargetBehindRootTask=%s", "level": "DEBUG", diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index ebf8c0354c1bb..cd77802fa10bb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -23,13 +23,9 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityTaskManager; import android.app.IActivityTaskManager; -import android.app.WindowConfiguration; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; -import android.graphics.Point; -import android.graphics.PointF; -import android.hardware.HardwareBuffer; import android.hardware.input.InputManager; import android.net.Uri; import android.os.Handler; @@ -40,15 +36,19 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings.Global; import android.util.Log; +import android.util.SparseArray; +import android.view.IRemoteAnimationRunner; import android.view.IWindowFocusObserver; import android.view.InputDevice; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.RemoteAnimationTarget; -import android.view.SurfaceControl; +import android.window.BackAnimationAdapter; import android.window.BackEvent; import android.window.BackNavigationInfo; +import android.window.IBackAnimationFinishedCallback; +import android.window.IBackAnimationRunner; import android.window.IOnBackInvokedCallback; import com.android.internal.annotations.VisibleForTesting; @@ -83,16 +83,6 @@ public class BackAnimationController implements RemoteCallable { - finishAnimation(); - mTransitionInProgress = false; ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Transition didn't finish in %d ms. Resetting...", MAX_TRANSITION_DURATION); + onBackAnimationFinished(); }; + private IBackAnimationFinishedCallback mBackAnimationFinishedCallback; + @VisibleForTesting + BackAnimationAdapter mBackAnimationAdapter; + + private final TouchTracker mTouchTracker = new TouchTracker(); + + private final SparseArray mAnimationDefinition = new SparseArray<>(); + @VisibleForTesting final IWindowFocusObserver mFocusObserver = new IWindowFocusObserver.Stub() { @Override @@ -134,18 +126,66 @@ public class BackAnimationController implements RemoteCallable= 0 + ? PROGRESS_THRESHOLD : mProgressThreshold; + return Math.min(Math.max(Math.abs(deltaX) / progressThreshold, 0), 1); + } + + void reset() { + mInitTouchX = 0; + mInitTouchY = 0; + mSwipeEdge = -1; + } + } + public BackAnimationController( @NonNull ShellInit shellInit, @NonNull @ShellMainThread ShellExecutor shellExecutor, @NonNull @ShellBackgroundThread Handler backgroundHandler, Context context) { - this(shellInit, shellExecutor, backgroundHandler, new SurfaceControl.Transaction(), + this(shellInit, shellExecutor, backgroundHandler, ActivityTaskManager.getService(), context, context.getContentResolver()); } @@ -154,11 +194,9 @@ public class BackAnimationController implements RemoteCallable controller.setBackToLauncherCallback(callback)); + (controller) -> controller.setBackToLauncherCallback(callback, runner)); } @Override @@ -261,28 +301,30 @@ public class BackAnimationController implements RemoteCallable controller.clearBackToLauncherCallback()); } - @Override - public void onBackToLauncherAnimationFinished() { - executeRemoteCallWithTaskPermission(mController, "onBackToLauncherAnimationFinished", - (controller) -> controller.onBackToLauncherAnimationFinished()); - } - void invalidate() { mController = null; } } @VisibleForTesting - void setBackToLauncherCallback(IOnBackInvokedCallback callback) { - mBackToLauncherCallback = callback; + void setBackToLauncherCallback(IOnBackInvokedCallback callback, IRemoteAnimationRunner runner) { + mAnimationDefinition.set(BackNavigationInfo.TYPE_RETURN_TO_HOME, + new BackAnimationRunner(callback, runner)); } private void clearBackToLauncherCallback() { - mBackToLauncherCallback = null; + mAnimationDefinition.remove(BackNavigationInfo.TYPE_RETURN_TO_HOME); } @VisibleForTesting - void onBackToLauncherAnimationFinished() { + void onBackAnimationFinished() { + if (!mTransitionInProgress) { + return; + } + + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: onBackAnimationFinished()"); + + // Trigger real back. if (mBackNavigationInfo != null) { IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback(); if (mTriggerBack) { @@ -303,6 +345,7 @@ public class BackAnimationController implements RemoteCallable= 0 ? PROGRESS_THRESHOLD : mProgressThreshold; - float progress = Math.min(Math.max(Math.abs(deltaX) / progressThreshold, 0), 1); + mTouchTracker.update(touchX, touchY, swipeEdge); + float progress = mTouchTracker.getProgress(touchX); int backType = mBackNavigationInfo.getType(); - RemoteAnimationTarget animationTarget = mBackNavigationInfo.getDepartingAnimationTarget(); - BackEvent backEvent = new BackEvent( - touchX, touchY, progress, swipeEdge, animationTarget); + BackEvent backEvent = new BackEvent(touchX, touchY, progress, swipeEdge); IOnBackInvokedCallback targetCallback = null; - if (shouldDispatchToLauncher(backType)) { - targetCallback = mBackToLauncherCallback; - } else if (backType == BackNavigationInfo.TYPE_CROSS_TASK - || backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY) { - // TODO(208427216) Run the actual animation - } else if (backType == BackNavigationInfo.TYPE_CALLBACK) { + if (shouldDispatchToAnimator(backType)) { + targetCallback = mAnimationDefinition.get(backType).getCallback(); + } else { targetCallback = mBackNavigationInfo.getOnBackInvokedCallback(); } dispatchOnBackProgressed(targetCallback, backEvent); @@ -473,11 +473,16 @@ public class BackAnimationController implements RemoteCallable { + final BackAnimationRunner runner = mAnimationDefinition.get(type); + if (runner == null) { + Log.e(TAG, "Animation didn't be defined for type " + + BackNavigationInfo.typeToString(type)); + if (finishedCallback != null) { + try { + finishedCallback.onAnimationFinished(false); + } catch (RemoteException e) { + Log.w(TAG, "Failed call IBackNaviAnimationController", e); + } + } + return; + } + mBackAnimationFinishedCallback = finishedCallback; + + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startAnimation()"); + runner.startAnimation(apps, wallpapers, nonApps, + BackAnimationController.this::onBackAnimationFinished); + + if (!mBackGestureStarted) { + // if the down -> up gesture happened before animation start, we have to + // trigger the uninterruptible transition to finish the back animation. + final BackEvent backFinish = new BackEvent( + mTouchTracker.mLatestTouchX, mTouchTracker.mLatestTouchY, 1, + mTouchTracker.mSwipeEdge); + startTransition(); + runner.consumeIfGestureFinished(backFinish); + } + }); + } + + @Override + public void onAnimationCancelled() { } + }; + mBackAnimationAdapter = new BackAnimationAdapter(runner); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java new file mode 100644 index 0000000000000..12bbf73af5616 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.back; + +import static android.view.WindowManager.TRANSIT_OLD_UNSET; + +import android.os.RemoteException; +import android.util.Log; +import android.view.IRemoteAnimationFinishedCallback; +import android.view.IRemoteAnimationRunner; +import android.view.RemoteAnimationTarget; +import android.window.BackEvent; +import android.window.IBackAnimationRunner; +import android.window.IOnBackInvokedCallback; + +/** + * Used to register the animation callback and runner, it will trigger result if gesture was finish + * before it received IBackAnimationRunner#onAnimationStart, so the controller could continue + * trigger the real back behavior. + */ +class BackAnimationRunner { + private static final String TAG = "ShellBackPreview"; + + private final IOnBackInvokedCallback mCallback; + private final IRemoteAnimationRunner mRunner; + + private boolean mTriggerBack; + // Whether we are waiting to receive onAnimationStart + private boolean mWaitingAnimation; + + BackAnimationRunner(IOnBackInvokedCallback callback, IRemoteAnimationRunner runner) { + mCallback = callback; + mRunner = runner; + } + + /** Returns the registered animation runner */ + IRemoteAnimationRunner getRunner() { + return mRunner; + } + + /** Returns the registered animation callback */ + IOnBackInvokedCallback getCallback() { + return mCallback; + } + + /** + * Called from {@link IBackAnimationRunner}, it will deliver these + * {@link RemoteAnimationTarget}s to the corresponding runner. + */ + void startAnimation(RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, + RemoteAnimationTarget[] nonApps, Runnable finishedCallback) { + final IRemoteAnimationFinishedCallback callback = + new IRemoteAnimationFinishedCallback.Stub() { + @Override + public void onAnimationFinished() { + finishedCallback.run(); + } + }; + mWaitingAnimation = false; + try { + mRunner.onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers, + nonApps, callback); + } catch (RemoteException e) { + Log.w(TAG, "Failed call onAnimationStart", e); + } + } + + IOnBackInvokedCallback getGestureStartedCallback() { + mWaitingAnimation = true; + return mCallback; + } + + boolean onGestureFinished(boolean triggerBack) { + if (mWaitingAnimation) { + mTriggerBack = triggerBack; + return true; + } + return false; + } + + void consumeIfGestureFinished(final BackEvent backFinish) { + Log.d(TAG, "Start transition due to gesture is finished"); + try { + mCallback.onBackProgressed(backFinish); + if (mTriggerBack) { + mCallback.onBackInvoked(); + } else { + mCallback.onBackCancelled(); + } + } catch (RemoteException e) { + Log.e(TAG, "dispatch error: ", e); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/IBackAnimation.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/back/IBackAnimation.aidl index 6311f879fd459..2b2a0e3977925 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/IBackAnimation.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/IBackAnimation.aidl @@ -17,29 +17,21 @@ package com.android.wm.shell.back; import android.window.IOnBackInvokedCallback; +import android.view.IRemoteAnimationRunner; /** * Interface for Launcher process to register back invocation callbacks. */ interface IBackAnimation { - /** - * Sets a {@link IOnBackInvokedCallback} to be invoked when + * Sets a {@link IOnBackInvokedCallback} and a {@link IRemoteAnimationRunner} to be invoked when * back navigation has type {@link BackNavigationInfo#TYPE_RETURN_TO_HOME}. */ - void setBackToLauncherCallback(in IOnBackInvokedCallback callback); + void setBackToLauncherCallback(in IOnBackInvokedCallback callback, + in IRemoteAnimationRunner runner); /** * Clears the previously registered {@link IOnBackInvokedCallback}. */ void clearBackToLauncherCallback(); - - /** - * Notifies Shell that the back to launcher animation has fully finished - * (including the transition animation that runs after the finger is lifted). - * - * At this point the top window leash (if one was created) should be ready to be released. - * //TODO: Remove once we play the transition animation through shell transitions. - */ - void onBackToLauncherAnimationFinished(); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index 5b3b8fd7ad712..9f39598e70a52 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -18,12 +18,9 @@ package com.android.wm.shell.back; import static android.window.BackNavigationInfo.KEY_TRIGGER_BACK; -import static org.junit.Assert.assertEquals; 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 static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -39,7 +36,6 @@ import android.app.WindowConfiguration; import android.content.pm.ApplicationInfo; import android.graphics.Point; import android.graphics.Rect; -import android.hardware.HardwareBuffer; import android.os.Handler; import android.os.IBinder; import android.os.RemoteCallback; @@ -49,11 +45,13 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableContentResolver; import android.testing.TestableContext; import android.testing.TestableLooper; +import android.view.IRemoteAnimationRunner; import android.view.MotionEvent; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import android.window.BackEvent; import android.window.BackNavigationInfo; +import android.window.IBackAnimationFinishedCallback; import android.window.IOnBackInvokedCallback; import androidx.test.filters.SmallTest; @@ -65,7 +63,6 @@ import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.sysui.ShellInit; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -89,15 +86,18 @@ public class BackAnimationControllerTest extends ShellTestCase { public TestableContext mContext = new TestableContext(InstrumentationRegistry.getInstrumentation().getContext()); - @Mock - private SurfaceControl.Transaction mTransaction; - @Mock private IActivityTaskManager mActivityTaskManager; @Mock private IOnBackInvokedCallback mIOnBackInvokedCallback; + @Mock + private IBackAnimationFinishedCallback mBackAnimationFinishedCallback; + + @Mock + private IRemoteAnimationRunner mBackAnimationRunner; + private BackAnimationController mController; private int mEventTime = 0; @@ -115,7 +115,7 @@ public class BackAnimationControllerTest extends ShellTestCase { mTestableLooper = TestableLooper.get(this); mShellInit = spy(new ShellInit(mShellExecutor)); mController = new BackAnimationController(mShellInit, - mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction, + mShellExecutor, new Handler(mTestableLooper.getLooper()), mActivityTaskManager, mContext, mContentResolver); mShellInit.init(); @@ -123,19 +123,12 @@ public class BackAnimationControllerTest extends ShellTestCase { mShellExecutor.flushAll(); } - private void createNavigationInfo(RemoteAnimationTarget topAnimationTarget, - SurfaceControl screenshotSurface, - HardwareBuffer hardwareBuffer, - int backType, - IOnBackInvokedCallback onBackInvokedCallback) { + private void createNavigationInfo(int backType, IOnBackInvokedCallback onBackInvokedCallback) { BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder() .setType(backType) - .setDepartingAnimationTarget(topAnimationTarget) - .setScreenshotSurface(screenshotSurface) - .setScreenshotBuffer(hardwareBuffer) - .setTaskWindowConfiguration(new WindowConfiguration()) .setOnBackNavigationDone(new RemoteCallback((bundle) -> {})) - .setOnBackInvokedCallback(onBackInvokedCallback); + .setOnBackInvokedCallback(onBackInvokedCallback) + .setPrepareRemoteAnimation(true); createNavigationInfo(builder); } @@ -143,7 +136,7 @@ public class BackAnimationControllerTest extends ShellTestCase { private void createNavigationInfo(BackNavigationInfo.Builder builder) { try { doReturn(builder.build()).when(mActivityTaskManager) - .startBackNavigation(anyBoolean(), any()); + .startBackNavigation(any(), any()); } catch (RemoteException ex) { ex.rethrowFromSystemServer(); } @@ -169,43 +162,12 @@ public class BackAnimationControllerTest extends ShellTestCase { verify(mShellInit, times(1)).addInitCallback(any(), any()); } - @Test - @Ignore("b/207481538") - public void crossActivity_screenshotAttachedAndVisible() { - SurfaceControl screenshotSurface = new SurfaceControl(); - HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class); - createNavigationInfo(createAnimationTarget(), screenshotSurface, hardwareBuffer, - BackNavigationInfo.TYPE_CROSS_ACTIVITY, null); - doMotionEvent(MotionEvent.ACTION_DOWN, 0); - verify(mTransaction).setBuffer(screenshotSurface, hardwareBuffer); - verify(mTransaction).setVisibility(screenshotSurface, true); - verify(mTransaction).apply(); - } - - @Test - public void crossActivity_surfaceMovesWithGesture() { - SurfaceControl screenshotSurface = new SurfaceControl(); - HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class); - RemoteAnimationTarget animationTarget = createAnimationTarget(); - createNavigationInfo(animationTarget, screenshotSurface, hardwareBuffer, - BackNavigationInfo.TYPE_CROSS_ACTIVITY, null); - doMotionEvent(MotionEvent.ACTION_DOWN, 0); - doMotionEvent(MotionEvent.ACTION_MOVE, 100); - // b/207481538, we check that the surface is not moved for now, we can re-enable this once - // we implement the animation - verify(mTransaction, never()).setScale(eq(screenshotSurface), anyInt(), anyInt()); - verify(mTransaction, never()).setPosition( - animationTarget.leash, 100, 100); - verify(mTransaction, atLeastOnce()).apply(); - } - @Test public void verifyAnimationFinishes() { RemoteAnimationTarget animationTarget = createAnimationTarget(); boolean[] backNavigationDone = new boolean[]{false}; boolean[] triggerBack = new boolean[]{false}; createNavigationInfo(new BackNavigationInfo.Builder() - .setDepartingAnimationTarget(animationTarget) .setType(BackNavigationInfo.TYPE_CROSS_ACTIVITY) .setOnBackNavigationDone( new RemoteCallback(result -> { @@ -219,19 +181,19 @@ public class BackAnimationControllerTest extends ShellTestCase { @Test public void backToHome_dispatchesEvents() throws RemoteException { - mController.setBackToLauncherCallback(mIOnBackInvokedCallback); - RemoteAnimationTarget animationTarget = createAnimationTarget(); - createNavigationInfo(animationTarget, null, null, - BackNavigationInfo.TYPE_RETURN_TO_HOME, null); + mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner); + createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, mIOnBackInvokedCallback); doMotionEvent(MotionEvent.ACTION_DOWN, 0); // Check that back start and progress is dispatched when first move. doMotionEvent(MotionEvent.ACTION_MOVE, 100); + + simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME); verify(mIOnBackInvokedCallback).onBackStarted(); + verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any()); ArgumentCaptor backEventCaptor = ArgumentCaptor.forClass(BackEvent.class); - verify(mIOnBackInvokedCallback).onBackProgressed(backEventCaptor.capture()); - assertEquals(animationTarget, backEventCaptor.getValue().getDepartingAnimationTarget()); + verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(backEventCaptor.capture()); // Check that back invocation is dispatched. mController.setTriggerBack(true); // Fake trigger back @@ -245,17 +207,16 @@ public class BackAnimationControllerTest extends ShellTestCase { Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, "0"); ShellInit shellInit = new ShellInit(mShellExecutor); mController = new BackAnimationController(shellInit, - mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction, + mShellExecutor, new Handler(mTestableLooper.getLooper()), mActivityTaskManager, mContext, mContentResolver); shellInit.init(); - mController.setBackToLauncherCallback(mIOnBackInvokedCallback); + mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner); - RemoteAnimationTarget animationTarget = createAnimationTarget(); IOnBackInvokedCallback appCallback = mock(IOnBackInvokedCallback.class); ArgumentCaptor backEventCaptor = ArgumentCaptor.forClass(BackEvent.class); - createNavigationInfo(animationTarget, null, null, - BackNavigationInfo.TYPE_RETURN_TO_HOME, appCallback); + + createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, appCallback); triggerBackGesture(); @@ -266,25 +227,29 @@ public class BackAnimationControllerTest extends ShellTestCase { verify(mIOnBackInvokedCallback, never()).onBackStarted(); verify(mIOnBackInvokedCallback, never()).onBackProgressed(backEventCaptor.capture()); verify(mIOnBackInvokedCallback, never()).onBackInvoked(); + verify(mBackAnimationRunner, never()).onAnimationStart( + anyInt(), any(), any(), any(), any()); } @Test public void ignoresGesture_transitionInProgress() throws RemoteException { - mController.setBackToLauncherCallback(mIOnBackInvokedCallback); - RemoteAnimationTarget animationTarget = createAnimationTarget(); - createNavigationInfo(animationTarget, null, null, - BackNavigationInfo.TYPE_RETURN_TO_HOME, null); + mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner); + createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, null); triggerBackGesture(); + simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME); // Check that back invocation is dispatched. verify(mIOnBackInvokedCallback).onBackInvoked(); + verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any()); reset(mIOnBackInvokedCallback); + reset(mBackAnimationRunner); + // Verify that we prevent animation from restarting if another gestures happens before // the previous transition is finished. doMotionEvent(MotionEvent.ACTION_DOWN, 0); verifyNoMoreInteractions(mIOnBackInvokedCallback); - mController.onBackToLauncherAnimationFinished(); + mController.onBackAnimationFinished(); // Verify that more events from a rejected swipe cannot start animation. doMotionEvent(MotionEvent.ACTION_MOVE, 100); @@ -294,39 +259,47 @@ public class BackAnimationControllerTest extends ShellTestCase { // Verify that we start accepting gestures again once transition finishes. doMotionEvent(MotionEvent.ACTION_DOWN, 0); doMotionEvent(MotionEvent.ACTION_MOVE, 100); + + simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME); verify(mIOnBackInvokedCallback).onBackStarted(); + verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any()); } @Test public void acceptsGesture_transitionTimeout() throws RemoteException { - mController.setBackToLauncherCallback(mIOnBackInvokedCallback); - RemoteAnimationTarget animationTarget = createAnimationTarget(); - createNavigationInfo(animationTarget, null, null, - BackNavigationInfo.TYPE_RETURN_TO_HOME, null); + mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner); + createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, null); triggerBackGesture(); + simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME); + reset(mIOnBackInvokedCallback); // Simulate transition timeout. mShellExecutor.flushAll(); + mController.onBackAnimationFinished(); + doMotionEvent(MotionEvent.ACTION_DOWN, 0); doMotionEvent(MotionEvent.ACTION_MOVE, 100); + + simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME); verify(mIOnBackInvokedCallback).onBackStarted(); } @Test public void cancelBackInvokeWhenLostFocus() throws RemoteException { - mController.setBackToLauncherCallback(mIOnBackInvokedCallback); - RemoteAnimationTarget animationTarget = createAnimationTarget(); + mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner); - createNavigationInfo(animationTarget, null, null, - BackNavigationInfo.TYPE_RETURN_TO_HOME, null); + createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, null); doMotionEvent(MotionEvent.ACTION_DOWN, 0); // Check that back start and progress is dispatched when first move. doMotionEvent(MotionEvent.ACTION_MOVE, 100); + + simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME); verify(mIOnBackInvokedCallback).onBackStarted(); + verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any()); // Check that back invocation is dispatched. mController.setTriggerBack(true); // Fake trigger back @@ -349,4 +322,14 @@ public class BackAnimationControllerTest extends ShellTestCase { BackEvent.EDGE_LEFT); mEventTime += 10; } + + private void simulateRemoteAnimationStart(int type) throws RemoteException { + RemoteAnimationTarget animationTarget = createAnimationTarget(); + RemoteAnimationTarget[] targets = new RemoteAnimationTarget[]{animationTarget}; + if (mController.mBackAnimationAdapter != null) { + mController.mBackAnimationAdapter.getRunner().onAnimationStart(type, + targets, null, null, mBackAnimationFinishedCallback); + mShellExecutor.flushAll(); + } + } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 83953022dd72d..6acd9b5be0c42 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -227,6 +227,7 @@ import android.view.IWindowFocusObserver; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationDefinition; import android.view.WindowManager; +import android.window.BackAnimationAdapter; import android.window.BackNavigationInfo; import android.window.IWindowOrganizerController; import android.window.SplashScreenView.SplashScreenViewParcelable; @@ -1846,14 +1847,15 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override - public BackNavigationInfo startBackNavigation(boolean requestAnimation, - IWindowFocusObserver observer) { + public BackNavigationInfo startBackNavigation( + IWindowFocusObserver observer, BackAnimationAdapter adapter) { mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS, "startBackNavigation()"); if (mBackNavigationController == null) { return null; } - return mBackNavigationController.startBackNavigation(requestAnimation, observer); + + return mBackNavigationController.startBackNavigation(observer, adapter); } /** diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index a3f5401fd375b..d15f954333231 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -16,11 +16,14 @@ package com.android.server.wm; +import static android.view.RemoteAnimationTarget.MODE_CLOSING; +import static android.view.RemoteAnimationTarget.MODE_OPENING; +import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; + import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.WindowConfiguration; import android.content.ComponentName; import android.graphics.Point; import android.graphics.Rect; @@ -34,7 +37,9 @@ import android.util.Slog; import android.view.IWindowFocusObserver; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; +import android.window.BackAnimationAdapter; import android.window.BackNavigationInfo; +import android.window.IBackAnimationFinishedCallback; import android.window.OnBackInvokedCallbackInfo; import android.window.ScreenCapture; import android.window.TaskSnapshot; @@ -43,6 +48,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.server.LocalServices; +import java.util.ArrayList; + /** * Controller to handle actions related to the back gesture on the server side. */ @@ -72,10 +79,9 @@ class BackNavigationController { */ @VisibleForTesting @Nullable - BackNavigationInfo startBackNavigation(boolean requestAnimation, - IWindowFocusObserver observer) { + BackNavigationInfo startBackNavigation( + IWindowFocusObserver observer, BackAnimationAdapter adapter) { final WindowManagerService wmService = mWindowManagerService; - final SurfaceControl.Transaction tx = wmService.mTransactionFactory.get(); mFocusObserver = observer; int backType = BackNavigationInfo.TYPE_UNDEFINED; @@ -95,18 +101,10 @@ class BackNavigationController { // currentActivity is the last child of currentTask. ActivityRecord prevActivity; WindowContainer removedWindowContainer = null; - SurfaceControl animationLeashParent = null; - HardwareBuffer screenshotBuffer = null; - RemoteAnimationTarget topAppTarget = null; WindowState window; - int prevTaskId; - int prevUserId; - boolean prepareAnimation; - BackNavigationInfo.Builder infoBuilder = new BackNavigationInfo.Builder(); synchronized (wmService.mGlobalLock) { - WindowConfiguration taskWindowConfiguration; WindowManagerInternal windowManagerInternal = LocalServices.getService(WindowManagerInternal.class); IBinder focusedWindowToken = windowManagerInternal.getFocusedWindowToken(); @@ -204,8 +202,8 @@ class BackNavigationController { infoBuilder.setType(BackNavigationInfo.TYPE_CALLBACK); final WindowState finalFocusedWindow = window; infoBuilder.setOnBackNavigationDone(new RemoteCallback(result -> - onBackNavigationDone(result, finalFocusedWindow, finalFocusedWindow, - BackNavigationInfo.TYPE_CALLBACK, null, null, false))); + onBackNavigationDone(result, finalFocusedWindow, + BackNavigationInfo.TYPE_CALLBACK))); return infoBuilder.setType(backType).build(); } @@ -245,9 +243,6 @@ class BackNavigationController { } infoBuilder.setType(backType); - prevTaskId = prevTask != null ? prevTask.mTaskId : 0; - prevUserId = prevTask != null ? prevTask.mUserId : 0; - ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Previous Destination is Activity:%s Task:%s " + "removedContainer:%s, backType=%s", prevActivity != null ? prevActivity.mActivityComponent : null, @@ -256,162 +251,218 @@ class BackNavigationController { BackNavigationInfo.typeToString(backType)); // For now, we only animate when going home. - prepareAnimation = backType == BackNavigationInfo.TYPE_RETURN_TO_HOME - && requestAnimation - // Only create a new leash if no leash has been created. - // Otherwise return null for animation target to avoid conflict. - && !removedWindowContainer.hasCommittedReparentToAnimationLeash(); + boolean prepareAnimation = backType == BackNavigationInfo.TYPE_RETURN_TO_HOME + && adapter != null; + + // Only prepare animation if no leash has been created (no animation is running). + // TODO(b/241808055): Cancel animation when preparing back animation. + if (prepareAnimation + && removedWindowContainer.hasCommittedReparentToAnimationLeash()) { + Slog.w(TAG, "Can't prepare back animation due to another animation is running."); + prepareAnimation = false; + } if (prepareAnimation) { - taskWindowConfiguration = - currentTask.getTaskInfo().configuration.windowConfiguration; - - infoBuilder.setTaskWindowConfiguration(taskWindowConfiguration); - // Prepare a leash to animate the current top window - // TODO(b/220934562): Use surface animator to better manage animation conflicts. - SurfaceControl animLeash = removedWindowContainer.makeAnimationLeash() - .setName("BackPreview Leash for " + removedWindowContainer) - .setHidden(false) - .setBLASTLayer() - .build(); - removedWindowContainer.reparentSurfaceControl(tx, animLeash); - animationLeashParent = removedWindowContainer.getAnimationLeashParent(); - topAppTarget = createRemoteAnimationTargetLocked(removedWindowContainer, - currentActivity, - currentTask, animLeash); - infoBuilder.setDepartingAnimationTarget(topAppTarget); - } - - //TODO(207481538) Remove once the infrastructure to support per-activity screenshot is - // implemented. For now we simply have the mBackScreenshots hash map that dumbly - // saves the screenshots. - if (needsScreenshot(backType) && prevActivity != null - && prevActivity.mActivityComponent != null) { - screenshotBuffer = - getActivitySnapshot(currentTask, prevActivity.mActivityComponent); - } - - // Special handling for back to home animation - if (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME && prepareAnimation - && prevTask != null) { - currentTask.mBackGestureStarted = true; - // Make launcher show from behind by marking its top activity as visible and - // launch-behind to bump its visibility for the duration of the back gesture. - prevActivity = prevTask.getTopNonFinishingActivity(); - if (prevActivity != null) { - if (!prevActivity.mVisibleRequested) { - prevActivity.setVisibility(true); - } - prevActivity.mLaunchTaskBehind = true; - ProtoLog.d(WM_DEBUG_BACK_PREVIEW, - "Setting Activity.mLauncherTaskBehind to true. Activity=%s", - prevActivity); - prevActivity.mRootWindowContainer.ensureActivitiesVisible( - null /* starting */, 0 /* configChanges */, - false /* preserveWindows */); - } + prepareAnimationIfNeeded(currentTask, prevTask, prevActivity, + removedWindowContainer, backType, adapter); } + infoBuilder.setPrepareRemoteAnimation(prepareAnimation); } // Release wm Lock - // Find a screenshot of the previous activity if we actually have an animation - if (topAppTarget != null && needsScreenshot(backType) && prevTask != null - && screenshotBuffer == null) { - SurfaceControl.Builder builder = new SurfaceControl.Builder() - .setName("BackPreview Screenshot for " + prevActivity) - .setParent(animationLeashParent) - .setHidden(false) - .setBLASTLayer(); - infoBuilder.setScreenshotSurface(builder.build()); - screenshotBuffer = getTaskSnapshot(prevTaskId, prevUserId); - infoBuilder.setScreenshotBuffer(screenshotBuffer); - - - // The Animation leash needs to be above the screenshot surface, but the animation leash - // needs to be added before to be in the synchronized block. - tx.setLayer(topAppTarget.leash, 1); - } - WindowContainer finalRemovedWindowContainer = removedWindowContainer; if (finalRemovedWindowContainer != null) { - try { - currentActivity.token.linkToDeath( - () -> resetSurfaces(finalRemovedWindowContainer), 0); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to link to death", e); - resetSurfaces(removedWindowContainer); - return null; - } - - int finalBackType = backType; - final ActivityRecord finalprevActivity = prevActivity; - final Task finalTask = currentTask; + final int finalBackType = backType; final WindowState finalFocusedWindow = window; RemoteCallback onBackNavigationDone = new RemoteCallback(result -> onBackNavigationDone( - result, finalFocusedWindow, finalRemovedWindowContainer, finalBackType, - finalTask, finalprevActivity, prepareAnimation)); + result, finalFocusedWindow, finalBackType)); infoBuilder.setOnBackNavigationDone(onBackNavigationDone); } - tx.apply(); return infoBuilder.build(); } + private void prepareAnimationIfNeeded(Task currentTask, + Task prevTask, ActivityRecord prevActivity, WindowContainer removedWindowContainer, + int backType, BackAnimationAdapter adapter) { + final ArrayList leashes = new ArrayList<>(); + final SurfaceControl.Transaction startedTransaction = currentTask.getPendingTransaction(); + final SurfaceControl.Transaction finishedTransaction = new SurfaceControl.Transaction(); + // Prepare a leash to animate for the departing window + final SurfaceControl animLeash = currentTask.makeAnimationLeash() + .setName("BackPreview Leash for " + currentTask) + .setHidden(false) + .build(); + removedWindowContainer.reparentSurfaceControl(startedTransaction, animLeash); + + final RemoteAnimationTarget topAppTarget = createRemoteAnimationTargetLocked( + currentTask, animLeash, MODE_CLOSING); + + // reset leash after animation finished. + leashes.add(animLeash); + removedWindowContainer.reparentSurfaceControl(finishedTransaction, + removedWindowContainer.getParentSurfaceControl()); + + // Prepare a leash to animate for the entering window. + RemoteAnimationTarget behindAppTarget = null; + if (needsScreenshot(backType)) { + HardwareBuffer screenshotBuffer = null; + switch(backType) { + case BackNavigationInfo.TYPE_CROSS_TASK: + int prevTaskId = prevTask != null ? prevTask.mTaskId : 0; + int prevUserId = prevTask != null ? prevTask.mUserId : 0; + screenshotBuffer = getTaskSnapshot(prevTaskId, prevUserId); + break; + case BackNavigationInfo.TYPE_CROSS_ACTIVITY: + //TODO(207481538) Remove once the infrastructure to support per-activity + // screenshot is implemented. For now we simply have the mBackScreenshots hash + // map that dumbly saves the screenshots. + if (prevActivity != null + && prevActivity.mActivityComponent != null) { + screenshotBuffer = + getActivitySnapshot(currentTask, prevActivity.mActivityComponent); + } + break; + } + + // Find a screenshot of the previous activity if we actually have an animation + SurfaceControl animationLeashParent = removedWindowContainer.getAnimationLeashParent(); + if (screenshotBuffer != null) { + final SurfaceControl screenshotSurface = new SurfaceControl.Builder() + .setName("BackPreview Screenshot for " + prevActivity) + .setHidden(false) + .setParent(animationLeashParent) + .setBLASTLayer() + .build(); + startedTransaction.setBuffer(screenshotSurface, screenshotBuffer); + + // The Animation leash needs to be above the screenshot surface, but the animation + // leash needs to be added before to be in the synchronized block. + startedTransaction.setLayer(topAppTarget.leash, 1); + + behindAppTarget = createRemoteAnimationTargetLocked( + prevTask, screenshotSurface, MODE_OPENING); + + // reset leash after animation finished. + leashes.add(screenshotSurface); + } + } else if (prevTask != null) { + // Special handling for preventing next transition. + currentTask.mBackGestureStarted = true; + prevActivity = prevTask.getTopNonFinishingActivity(); + if (prevActivity != null) { + // Make previous task show from behind by marking its top activity as visible + // and launch-behind to bump its visibility for the duration of the back gesture. + setLaunchBehind(prevActivity); + + final SurfaceControl leash = prevActivity.makeAnimationLeash() + .setName("BackPreview Leash for " + prevActivity) + .setHidden(false) + .build(); + prevActivity.reparentSurfaceControl(startedTransaction, leash); + behindAppTarget = createRemoteAnimationTargetLocked( + prevTask, leash, MODE_OPENING); + + // reset leash after animation finished. + leashes.add(leash); + prevActivity.reparentSurfaceControl(finishedTransaction, + prevActivity.getParentSurfaceControl()); + } + } + + final RemoteAnimationTarget[] targets = (behindAppTarget == null) + ? new RemoteAnimationTarget[] {topAppTarget} + : new RemoteAnimationTarget[] {topAppTarget, behindAppTarget}; + + final ActivityRecord finalPrevActivity = prevActivity; + final IBackAnimationFinishedCallback callback = + new IBackAnimationFinishedCallback.Stub() { + @Override + public void onAnimationFinished(boolean triggerBack) { + for (SurfaceControl sc: leashes) { + finishedTransaction.remove(sc); + } + + synchronized (mWindowManagerService.mGlobalLock) { + if (triggerBack) { + final SurfaceControl surfaceControl = + removedWindowContainer.getSurfaceControl(); + if (surfaceControl != null && surfaceControl.isValid()) { + // When going back to home, hide the task surface before it is + // re-parented to avoid flicker. + finishedTransaction.hide(surfaceControl); + } + } else if (!needsScreenshot(backType)) { + restoreLaunchBehind(finalPrevActivity); + } + } + finishedTransaction.apply(); + } + }; + + startAnimation(backType, targets, adapter, callback); + } + @NonNull private static RemoteAnimationTarget createRemoteAnimationTargetLocked( - WindowContainer removedWindowContainer, - ActivityRecord activityRecord, Task task, SurfaceControl animLeash) { + Task task, SurfaceControl animLeash, int mode) { + ActivityRecord topApp = task.getTopRealVisibleActivity(); + if (topApp == null) { + topApp = task.getTopNonFinishingActivity(); + } + + final WindowState mainWindow = topApp != null + ? topApp.findMainWindow() + : null; + int windowType = INVALID_WINDOW_TYPE; + if (mainWindow != null) { + windowType = mainWindow.getWindowType(); + } + + Rect bounds = new Rect(task.getBounds()); + Rect localBounds = new Rect(bounds); + Point tmpPos = new Point(); + task.getRelativePosition(tmpPos); + localBounds.offsetTo(tmpPos.x, tmpPos.y); + return new RemoteAnimationTarget( task.mTaskId, - RemoteAnimationTarget.MODE_CLOSING, + mode, animLeash, false /* isTransluscent */, new Rect() /* clipRect */, new Rect() /* contentInsets */, - activityRecord.getPrefixOrderIndex(), - new Point(0, 0) /* position */, - new Rect() /* localBounds */, - new Rect() /* screenSpaceBounds */, - removedWindowContainer.getWindowConfiguration(), + task.getPrefixOrderIndex(), + tmpPos /* position */, + localBounds /* localBounds */, + bounds /* screenSpaceBounds */, + task.getWindowConfiguration(), true /* isNotInRecent */, null, null, task.getTaskInfo(), false, - activityRecord.windowType); + windowType); } - private void onBackNavigationDone( - Bundle result, WindowState focusedWindow, WindowContainer windowContainer, - int backType, @Nullable Task task, @Nullable ActivityRecord prevActivity, - boolean prepareAnimation) { - SurfaceControl surfaceControl = windowContainer.getSurfaceControl(); + @VisibleForTesting + void startAnimation(@BackNavigationInfo.BackTargetType int type, + RemoteAnimationTarget[] targets, BackAnimationAdapter backAnimationAdapter, + IBackAnimationFinishedCallback callback) { + mWindowManagerService.mAnimator.addAfterPrepareSurfacesRunnable(() -> { + try { + backAnimationAdapter.getRunner().onAnimationStart(type, + targets, null, null, callback); + } catch (RemoteException e) { + e.printStackTrace(); + } + }); + } + + private void onBackNavigationDone(Bundle result, WindowState focusedWindow, int backType) { boolean triggerBack = result != null && result.getBoolean( BackNavigationInfo.KEY_TRIGGER_BACK); ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "onBackNavigationDone backType=%s, " - + "task=%s, prevActivity=%s", backType, task, prevActivity); - - if (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME && prepareAnimation) { - if (triggerBack) { - if (surfaceControl != null && surfaceControl.isValid()) { - // When going back to home, hide the task surface before it is re-parented to - // avoid flicker. - SurfaceControl.Transaction t = windowContainer.getSyncTransaction(); - t.hide(surfaceControl); - t.apply(); - } - } - if (prevActivity != null && !triggerBack) { - // Restore the launch-behind state. - task.mTaskSupervisor.scheduleLaunchTaskBehindComplete(prevActivity.token); - prevActivity.mLaunchTaskBehind = false; - ProtoLog.d(WM_DEBUG_BACK_PREVIEW, - "Setting Activity.mLauncherTaskBehind to false. Activity=%s", - prevActivity); - } - } else if (task != null) { - task.mBackGestureStarted = false; - } - resetSurfaces(windowContainer); + + "triggerBack=%b", backType, triggerBack); if (mFocusObserver != null) { focusedWindow.unregisterFocusObserver(mFocusObserver); @@ -450,20 +501,47 @@ class BackNavigationController { return true; } - private void resetSurfaces(@NonNull WindowContainer windowContainer) { - synchronized (windowContainer.mWmService.mGlobalLock) { - ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Back: Reset surfaces"); - SurfaceControl.Transaction tx = windowContainer.getSyncTransaction(); - SurfaceControl surfaceControl = windowContainer.getSurfaceControl(); - if (surfaceControl != null) { - tx.reparent(surfaceControl, - windowContainer.getParent().getSurfaceControl()); - tx.apply(); - } - } - } - void setWindowManager(WindowManagerService wm) { mWindowManagerService = wm; } + + private void setLaunchBehind(ActivityRecord activity) { + if (activity == null) { + return; + } + if (!activity.mVisibleRequested) { + activity.setVisibility(true); + } + activity.mLaunchTaskBehind = true; + + // Handle fixed rotation launching app. + final DisplayContent dc = activity.mDisplayContent; + dc.rotateInDifferentOrientationIfNeeded(activity); + if (activity.hasFixedRotationTransform()) { + // Set the record so we can recognize it to continue to update display orientation + // if the previous activity becomes the top later. + dc.setFixedRotationLaunchingApp(activity, + activity.getWindowConfiguration().getRotation()); + } + + ProtoLog.d(WM_DEBUG_BACK_PREVIEW, + "Setting Activity.mLauncherTaskBehind to true. Activity=%s", activity); + activity.getDisplayContent().ensureActivitiesVisible(null /* starting */, + 0 /* configChanges */, false /* preserveWindows */, true); + } + + private void restoreLaunchBehind(ActivityRecord activity) { + if (activity == null) { + return; + } + + activity.mDisplayContent.continueUpdateOrientationForDiffOrienLaunchingApp(); + + // Restore the launch-behind state. + activity.mTaskSupervisor.scheduleLaunchTaskBehindComplete(activity.token); + activity.mLaunchTaskBehind = false; + ProtoLog.d(WM_DEBUG_BACK_PREVIEW, + "Setting Activity.mLauncherTaskBehind to false. Activity=%s", + activity); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java index c2ca0a227f26c..7847e7a4dd13f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -32,6 +32,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.annotation.NonNull; @@ -40,6 +41,7 @@ import android.hardware.HardwareBuffer; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.view.WindowManager; +import android.window.BackAnimationAdapter; import android.window.BackEvent; import android.window.BackNavigationInfo; import android.window.IOnBackInvokedCallback; @@ -54,6 +56,7 @@ import com.android.server.LocalServices; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mockito; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -61,17 +64,18 @@ import java.util.concurrent.TimeUnit; @Presubmit @RunWith(WindowTestRunner.class) public class BackNavigationControllerTests extends WindowTestsBase { - private BackNavigationController mBackNavigationController; private WindowManagerInternal mWindowManagerInternal; + private BackAnimationAdapter mBackAnimationAdapter; @Before public void setUp() throws Exception { - mBackNavigationController = new BackNavigationController(); + mBackNavigationController = Mockito.spy(new BackNavigationController()); LocalServices.removeServiceForTest(WindowManagerInternal.class); mWindowManagerInternal = mock(WindowManagerInternal.class); LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal); mBackNavigationController.setWindowManager(mWm); + mBackAnimationAdapter = mock(BackAnimationAdapter.class); } @Test @@ -79,14 +83,16 @@ public class BackNavigationControllerTests extends WindowTestsBase { Task task = createTopTaskWithActivity(); IOnBackInvokedCallback callback = withSystemCallback(task); - BackNavigationInfo backNavigationInfo = - mBackNavigationController.startBackNavigation(true, null); + BackNavigationInfo backNavigationInfo = startBackNavigation(); assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull(); - assertThat(backNavigationInfo.getDepartingAnimationTarget()).isNotNull(); - assertThat(backNavigationInfo.getTaskWindowConfiguration()).isNotNull(); assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback); assertThat(typeToString(backNavigationInfo.getType())) .isEqualTo(typeToString(BackNavigationInfo.TYPE_RETURN_TO_HOME)); + + // verify if back animation would start. + verify(mBackNavigationController).startAnimation( + eq(BackNavigationInfo.TYPE_RETURN_TO_HOME), any(), eq(mBackAnimationAdapter), + any()); } @Test @@ -114,10 +120,6 @@ public class BackNavigationControllerTests extends WindowTestsBase { .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY)); assertWithMessage("Activity callback").that( backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback); - - // Until b/207481538 is implemented, this should be null - assertThat(backNavigationInfo.getScreenshotSurface()).isNull(); - assertThat(backNavigationInfo.getScreenshotHardwareBuffer()).isNull(); } @Test @@ -233,7 +235,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { @Nullable private BackNavigationInfo startBackNavigation() { - return mBackNavigationController.startBackNavigation(true, null); + return mBackNavigationController.startBackNavigation(null, mBackAnimationAdapter); } @NonNull @@ -287,6 +289,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK; WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, record, "window"); when(record.mSurfaceControl.isValid()).thenReturn(true); + Mockito.doNothing().when(task).reparentSurfaceControl(any(), any()); mAtm.setFocusedTask(task.mTaskId, record); addToWindowMap(window, true); return task;