diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java index 87cfbb2a57055..9b91cf2e9db62 100644 --- a/core/java/android/window/BackNavigationInfo.java +++ b/core/java/android/window/BackNavigationInfo.java @@ -89,6 +89,8 @@ public final class BackNavigationInfo implements Parcelable { @Nullable private final IOnBackInvokedCallback mOnBackInvokedCallback; private final boolean mPrepareRemoteAnimation; + @Nullable + private WindowContainerToken mDepartingWindowContainerToken; /** * Create a new {@link BackNavigationInfo} instance. @@ -97,6 +99,7 @@ public final class BackNavigationInfo implements Parcelable { * @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 departingWindowContainerToken The {@link WindowContainerToken} of departing 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. @@ -104,11 +107,13 @@ public final class BackNavigationInfo implements Parcelable { private BackNavigationInfo(@BackTargetType int type, @Nullable RemoteCallback onBackNavigationDone, @Nullable IOnBackInvokedCallback onBackInvokedCallback, - boolean isPrepareRemoteAnimation) { + boolean isPrepareRemoteAnimation, + @Nullable WindowContainerToken departingWindowContainerToken) { mType = type; mOnBackNavigationDone = onBackNavigationDone; mOnBackInvokedCallback = onBackInvokedCallback; mPrepareRemoteAnimation = isPrepareRemoteAnimation; + mDepartingWindowContainerToken = departingWindowContainerToken; } private BackNavigationInfo(@NonNull Parcel in) { @@ -116,6 +121,7 @@ public final class BackNavigationInfo implements Parcelable { mOnBackNavigationDone = in.readTypedObject(RemoteCallback.CREATOR); mOnBackInvokedCallback = IOnBackInvokedCallback.Stub.asInterface(in.readStrongBinder()); mPrepareRemoteAnimation = in.readBoolean(); + mDepartingWindowContainerToken = in.readTypedObject(WindowContainerToken.CREATOR); } @Override @@ -124,6 +130,7 @@ public final class BackNavigationInfo implements Parcelable { dest.writeTypedObject(mOnBackNavigationDone, flags); dest.writeStrongInterface(mOnBackInvokedCallback); dest.writeBoolean(mPrepareRemoteAnimation); + dest.writeTypedObject(mDepartingWindowContainerToken, flags); } /** @@ -156,6 +163,18 @@ public final class BackNavigationInfo implements Parcelable { return mPrepareRemoteAnimation; } + /** + * Returns the {@link WindowContainerToken} of the highest container in the hierarchy being + * removed. + *

+ * For example, if an Activity is the last one of its Task, the Task's token will be given. + * Otherwise, it will be the Activity's token. + */ + @Nullable + public WindowContainerToken getDepartingWindowContainerToken() { + return mDepartingWindowContainerToken; + } + /** * 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. @@ -193,6 +212,7 @@ public final class BackNavigationInfo implements Parcelable { + "mType=" + typeToString(mType) + " (" + mType + ")" + ", mOnBackNavigationDone=" + mOnBackNavigationDone + ", mOnBackInvokedCallback=" + mOnBackInvokedCallback + + ", mWindowContainerToken=" + mDepartingWindowContainerToken + '}'; } @@ -228,6 +248,9 @@ public final class BackNavigationInfo implements Parcelable { @Nullable private IOnBackInvokedCallback mOnBackInvokedCallback = null; private boolean mPrepareRemoteAnimation; + @Nullable + private WindowContainerToken mDepartingWindowContainerToken = null; + /** * @see BackNavigationInfo#getType() */ @@ -261,12 +284,21 @@ public final class BackNavigationInfo implements Parcelable { return this; } + /** + * @see BackNavigationInfo#getDepartingWindowContainerToken() + */ + public void setDepartingWCT(@NonNull WindowContainerToken windowContainerToken) { + mDepartingWindowContainerToken = windowContainerToken; + } + /** * Builds and returns an instance of {@link BackNavigationInfo} */ public BackNavigationInfo build() { return new BackNavigationInfo(mType, mOnBackNavigationDone, - mOnBackInvokedCallback, mPrepareRemoteAnimation); + mOnBackInvokedCallback, + mPrepareRemoteAnimation, + mDepartingWindowContainerToken); } } } 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 cd77802fa10bb..6f9c8b18625f9 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 @@ -58,6 +58,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.annotations.ShellBackgroundThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.Transitions; import java.util.concurrent.atomic.AtomicBoolean; @@ -75,6 +76,9 @@ public class BackAnimationController implements RemoteCallable mAnimationDefinition = new SparseArray<>(); + private final Transitions mTransitions; + private BackTransitionHandler mBackTransitionHandler; @VisibleForTesting final IWindowFocusObserver mFocusObserver = new IWindowFocusObserver.Stub() { @@ -184,9 +190,11 @@ public class BackAnimationController implements RemoteCallable { + finishBackNavigation(); + finishCallback.onTransitionFinished(null, null); + }); + } + private void createAdapter() { IBackAnimationRunner runner = new IBackAnimationRunner.Stub() { @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackTransitionHandler.java new file mode 100644 index 0000000000000..6d72d9c1f6375 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackTransitionHandler.java @@ -0,0 +1,78 @@ +/* + * 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 android.os.IBinder; +import android.view.SurfaceControl; +import android.window.TransitionInfo; +import android.window.TransitionRequestInfo; +import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.wm.shell.transition.Transitions; + +class BackTransitionHandler implements Transitions.TransitionHandler { + private BackAnimationController mBackAnimationController; + private WindowContainerToken mDepartingWindowContainerToken; + + BackTransitionHandler(@NonNull BackAnimationController backAnimationController) { + mBackAnimationController = backAnimationController; + } + + @Override + public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + if (mDepartingWindowContainerToken != null) { + final TransitionInfo.Change change = info.getChange(mDepartingWindowContainerToken); + if (change == null) { + return false; + } + + startTransaction.hide(change.getLeash()); + startTransaction.apply(); + mDepartingWindowContainerToken = null; + mBackAnimationController.finishTransition(finishCallback); + return true; + } + + return false; + } + + @Nullable + @Override + public WindowContainerTransaction handleRequest(@NonNull IBinder transition, + @NonNull TransitionRequestInfo request) { + return null; + } + + @Override + public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + } + + void setDepartingWindowContainerToken( + @Nullable WindowContainerToken departingWindowContainerToken) { + mDepartingWindowContainerToken = departingWindowContainerToken; + } +} + diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 80cdd1f79cb5d..2932bf1de4f61 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -273,12 +273,13 @@ public abstract class WMShellBaseModule { Context context, ShellInit shellInit, @ShellMainThread ShellExecutor shellExecutor, - @ShellBackgroundThread Handler backgroundHandler + @ShellBackgroundThread Handler backgroundHandler, + Transitions transitions ) { if (BackAnimationController.IS_ENABLED) { return Optional.of( new BackAnimationController(shellInit, shellExecutor, backgroundHandler, - context)); + context, transitions)); } return Optional.empty(); } 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 9f39598e70a52..ac5236f95040d 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 @@ -61,6 +61,7 @@ import com.android.internal.util.test.FakeSettingsProvider; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.Transitions; import org.junit.Before; import org.junit.Rule; @@ -98,6 +99,9 @@ public class BackAnimationControllerTest extends ShellTestCase { @Mock private IRemoteAnimationRunner mBackAnimationRunner; + @Mock + private Transitions mTransitions; + private BackAnimationController mController; private int mEventTime = 0; @@ -117,7 +121,7 @@ public class BackAnimationControllerTest extends ShellTestCase { mController = new BackAnimationController(mShellInit, mShellExecutor, new Handler(mTestableLooper.getLooper()), mActivityTaskManager, mContext, - mContentResolver); + mContentResolver, mTransitions); mShellInit.init(); mEventTime = 0; mShellExecutor.flushAll(); @@ -209,7 +213,7 @@ public class BackAnimationControllerTest extends ShellTestCase { mController = new BackAnimationController(shellInit, mShellExecutor, new Handler(mTestableLooper.getLooper()), mActivityTaskManager, mContext, - mContentResolver); + mContentResolver, mTransitions); shellInit.init(); mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner); @@ -250,6 +254,8 @@ public class BackAnimationControllerTest extends ShellTestCase { doMotionEvent(MotionEvent.ACTION_DOWN, 0); verifyNoMoreInteractions(mIOnBackInvokedCallback); mController.onBackAnimationFinished(); + // Pretend the transition handler called finishAnimation. + mController.finishBackNavigation(); // Verify that more events from a rejected swipe cannot start animation. doMotionEvent(MotionEvent.ACTION_MOVE, 100); @@ -278,6 +284,8 @@ public class BackAnimationControllerTest extends ShellTestCase { // Simulate transition timeout. mShellExecutor.flushAll(); mController.onBackAnimationFinished(); + // Pretend the transition handler called finishAnimation. + mController.finishBackNavigation(); doMotionEvent(MotionEvent.ACTION_DOWN, 0); doMotionEvent(MotionEvent.ACTION_MOVE, 100); diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index d42ad58f6f217..e9774479233dd 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -43,6 +43,7 @@ import android.window.IBackAnimationFinishedCallback; import android.window.OnBackInvokedCallbackInfo; import android.window.ScreenCapture; import android.window.TaskSnapshot; +import android.window.WindowContainerToken; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; @@ -61,6 +62,9 @@ class BackNavigationController { private boolean mShowWallpaper; private Runnable mPendingAnimation; + // TODO (b/241808055) Find a appropriate time to remove during refactor + // Execute back animation with legacy transition system. Temporary flag for easier debugging. + static final boolean ENABLE_SHELL_TRANSITIONS = WindowManagerService.sEnableShellTransitions; /** * Returns true if the back predictability feature is enabled */ @@ -263,12 +267,14 @@ class BackNavigationController { // 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()) { + && (removedWindowContainer.hasCommittedReparentToAnimationLeash() + || removedWindowContainer.mTransitionController.inTransition())) { Slog.w(TAG, "Can't prepare back animation due to another animation is running."); prepareAnimation = false; } if (prepareAnimation) { + infoBuilder.setDepartingWCT(toWindowContainerToken(currentTask)); prepareAnimationIfNeeded(currentTask, prevTask, prevActivity, removedWindowContainer, backType, adapter); } @@ -287,6 +293,13 @@ class BackNavigationController { return infoBuilder.build(); } + private static WindowContainerToken toWindowContainerToken(WindowContainer windowContainer) { + if (windowContainer == null || windowContainer.mRemoteToken == null) { + return null; + } + return windowContainer.mRemoteToken.toWindowContainerToken(); + } + private void prepareAnimationIfNeeded(Task currentTask, Task prevTask, ActivityRecord prevActivity, WindowContainer removedWindowContainer, int backType, BackAnimationAdapter adapter) { @@ -352,8 +365,10 @@ class BackNavigationController { leashes.add(screenshotSurface); } } else if (prevTask != null) { - // Special handling for preventing next transition. - currentTask.mBackGestureStarted = true; + if (!ENABLE_SHELL_TRANSITIONS) { + // 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 @@ -396,16 +411,27 @@ class BackNavigationController { } 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); + if (ENABLE_SHELL_TRANSITIONS) { + if (!triggerBack) { + if (!needsScreenshot(backType)) { + restoreLaunchBehind(finalPrevActivity); + } + } + } else { + if (triggerBack) { + final SurfaceControl surfaceControl = + removedWindowContainer.getSurfaceControl(); + if (surfaceControl != null && surfaceControl.isValid()) { + // When going back to home, hide the task surface before it + // re-parented to avoid flicker. + finishedTransaction.hide(surfaceControl); + } + } else { + currentTask.mBackGestureStarted = false; + if (!needsScreenshot(backType)) { + restoreLaunchBehind(finalPrevActivity); + } } - } else if (!needsScreenshot(backType)) { - restoreLaunchBehind(finalPrevActivity); } } finishedTransaction.apply();