diff options
7 files changed, 208 insertions, 5 deletions
diff --git a/core/java/android/window/IBackAnimationHandoffHandler.aidl b/core/java/android/window/IBackAnimationHandoffHandler.aidl new file mode 100644 index 000000000000..577948dd0abc --- /dev/null +++ b/core/java/android/window/IBackAnimationHandoffHandler.aidl @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 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.Bundle; +import android.view.RemoteAnimationTarget; +import android.window.BackMotionEvent; +import android.window.WindowAnimationState; + + +/** + * Interface that allows an {@link OnBackInvokedCallback} object to hand off an animation to another + * handler. + * + * @hide + */ +oneway interface IBackAnimationHandoffHandler { + /** + * Triggers a handoff of the animation of the given targets and their associated states. + * Important: since this is a one-way method, the caller must first make sure that the animation + * can indeed be taken over. + */ + oneway void handOffAnimation(in RemoteAnimationTarget[] targets, + in WindowAnimationState[] states); +} diff --git a/core/java/android/window/IOnBackInvokedCallback.aidl b/core/java/android/window/IOnBackInvokedCallback.aidl index e07d4a9fc61b..81ad4b7bd19e 100644 --- a/core/java/android/window/IOnBackInvokedCallback.aidl +++ b/core/java/android/window/IOnBackInvokedCallback.aidl @@ -18,6 +18,7 @@ package android.window; import android.window.BackMotionEvent; +import android.window.IBackAnimationHandoffHandler; /** * Interface that wraps a {@link OnBackInvokedCallback} object, to be stored in window manager @@ -61,4 +62,9 @@ oneway interface IOnBackInvokedCallback { * Sets whether the back gesture is past the trigger threshold. */ void setTriggerBack(in boolean triggerBack); + + /** + * Sets a {@link IBackAnimationHandoffHandler} that can be used to hand off the back animation. + */ + void setHandoffHandler(in IBackAnimationHandoffHandler handoffHandler); } diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java index c67b9cac250b..d478108d928a 100644 --- a/core/java/android/window/ImeOnBackInvokedDispatcher.java +++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java @@ -392,6 +392,11 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc // no-op } + @Override + public void setHandoffHandler(IBackAnimationHandoffHandler handoffHandler) { + // no-op + } + private void maybeRunOnAnimationCallback(Consumer<OnBackAnimationCallback> block) { if (mCallback instanceof OnBackAnimationCallback) { mHandler.post(() -> block.accept((OnBackAnimationCallback) mCallback)); diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index 0ea4bb41d3a4..20e3f6b93bd0 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -546,6 +546,11 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } @Override + public void setHandoffHandler(IBackAnimationHandoffHandler handoffHandler) { + // no-op + } + + @Override public void onBackProgressed(BackMotionEvent backEvent) { // This is only called in some special cases such as when activity embedding is active // or when the activity is letterboxed. Otherwise mProgressAnimator#onBackProgressed is 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 ce7a97703f44..e9cfd9bc2209 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 @@ -37,6 +37,7 @@ import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_B import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; +import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.IActivityTaskManager; import android.app.TaskInfo; @@ -73,10 +74,12 @@ import android.window.BackMotionEvent; import android.window.BackNavigationInfo; import android.window.BackTouchTracker; import android.window.IBackAnimationFinishedCallback; +import android.window.IBackAnimationHandoffHandler; import android.window.IBackAnimationRunner; import android.window.IOnBackInvokedCallback; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; +import android.window.WindowAnimationState; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; @@ -84,6 +87,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLog; import com.android.internal.util.LatencyTracker; import com.android.internal.view.AppearanceRegion; +import com.android.systemui.animation.TransitionAnimator; +import com.android.window.flags.Flags; import com.android.wm.shell.R; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.RemoteCallable; @@ -227,6 +232,15 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private Runnable mPilferPointerCallback; private BackAnimation.TopUiRequest mRequestTopUiCallback; + private final IBackAnimationHandoffHandler mHandoffHandler = + new IBackAnimationHandoffHandler.Stub() { + @Override + public void handOffAnimation( + RemoteAnimationTarget[] targets, WindowAnimationState[] states) { + mBackTransitionHandler.handOffAnimation(targets, states); + } + }; + public BackAnimationController( @NonNull ShellInit shellInit, @NonNull ShellController shellController, @@ -282,7 +296,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mShellCommandHandler = shellCommandHandler; mWindowManager = context.getSystemService(WindowManager.class); mTransitions = transitions; - mBackTransitionHandler = new BackTransitionHandler(); + mBackTransitionHandler = new BackTransitionHandler(mTransitions); mTransitions.addHandler(mBackTransitionHandler); mHandler = handler; mTransitions.registerObserver(mBackTransitionObserver); @@ -715,6 +729,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } try { callback.onBackStarted(backEvent); + if (mBackTransitionHandler.canHandOffAnimation()) { + callback.setHandoffHandler(mHandoffHandler); + } mOnBackStartDispatched = true; } catch (RemoteException e) { Log.e(TAG, "dispatchOnBackStarted error: ", e); @@ -1192,6 +1209,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } class BackTransitionHandler implements Transitions.TransitionHandler { + private final Transitions mTransitions; Runnable mOnAnimationFinishCallback; boolean mCloseTransitionRequested; @@ -1203,6 +1221,12 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont // animation is canceled, start a close prepare transition to finish the whole transition. IBinder mClosePrepareTransition; TransitionInfo mOpenTransitionInfo; + Transitions.TransitionHandler mTakeoverHandler; + + BackTransitionHandler(Transitions transitions) { + mTransitions = transitions; + } + void onAnimationFinished() { if (!mCloseTransitionRequested && mPrepareOpenTransition != null) { createClosePrepareTransition(); @@ -1214,18 +1238,23 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } private void applyFinishOpenTransition() { - mOpenTransitionInfo = null; - mPrepareOpenTransition = null; if (mFinishOpenTransaction != null) { final SurfaceControl.Transaction t = mFinishOpenTransaction; - mFinishOpenTransaction = null; t.apply(); } if (mFinishOpenTransitionCallback != null) { final Transitions.TransitionFinishCallback callback = mFinishOpenTransitionCallback; - mFinishOpenTransitionCallback = null; callback.onTransitionFinished(null); } + cleanUpInternalState(); + } + + private void cleanUpInternalState() { + mOpenTransitionInfo = null; + mPrepareOpenTransition = null; + mFinishOpenTransaction = null; + mFinishOpenTransitionCallback = null; + mTakeoverHandler = null; } private void applyAndFinish(@NonNull SurfaceControl.Transaction st, @@ -1237,6 +1266,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont finishCallback.onTransitionFinished(null); mCloseTransitionRequested = false; } + @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @@ -1246,6 +1276,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont final boolean isPrepareTransition = info.getType() == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION; if (isPrepareTransition) { + if (checkTakeoverFlags()) { + mTakeoverHandler = mTransitions.getHandlerForTakeover(transition, info); + } kickStartAnimation(); } // Both mShellExecutor and Transitions#mMainExecutor are ShellMainThread, so we don't @@ -1288,6 +1321,57 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont return handleCloseTransition(info, st, ft, finishCallback); } + private boolean canHandOffAnimation() { + if (!checkTakeoverFlags()) { + return false; + } + + return mTakeoverHandler != null; + } + + private void handOffAnimation( + RemoteAnimationTarget[] targets, WindowAnimationState[] states) { + if (!checkTakeoverFlags()) { + ProtoLog.e(WM_SHELL_BACK_PREVIEW, + "Trying to hand off the animation, but the required flags are disabled."); + return; + } else if (mTakeoverHandler == null) { + ProtoLog.e(WM_SHELL_BACK_PREVIEW, + "Missing takeover handler when trying to hand off animation."); + return; + } else if (targets.length != states.length) { + ProtoLog.e(WM_SHELL_BACK_PREVIEW, + "Targets passed for takeover don't match the window states."); + return; + } + + // The states passed to this method are paired with the targets, but they need to be + // paired with the changes inside the TransitionInfo. So for each change we find its + // matching target, and leave the state for any change missing a matching target blank. + WindowAnimationState[] updatedStates = + new WindowAnimationState[mOpenTransitionInfo.getChanges().size()]; + for (int i = 0; i < mOpenTransitionInfo.getChanges().size(); i++) { + ActivityManager.RunningTaskInfo taskInfo = + mOpenTransitionInfo.getChanges().get(i).getTaskInfo(); + if (taskInfo == null) { + continue; + } + + for (int j = 0; j < targets.length; j++) { + if (taskInfo.taskId == targets[j].taskId) { + updatedStates[i] = states[j]; + break; + } + } + } + + mTakeoverHandler.takeOverAnimation( + mPrepareOpenTransition, mOpenTransitionInfo, new SurfaceControl.Transaction(), + mFinishOpenTransitionCallback, updatedStates); + + cleanUpInternalState(); + } + @Override public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishTransaction) { @@ -1673,6 +1757,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } return null; } + + private static boolean checkTakeoverFlags() { + return TransitionAnimator.Companion.longLivedReturnAnimationsEnabled() + && Flags.unifyBackNavigationTransition(); + } } private static boolean isNotGestureBackTransition(@NonNull TransitionInfo info) { 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 13a8518ae8ed..c3e396524da1 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 @@ -57,6 +57,8 @@ import android.os.Handler; import android.os.IBinder; import android.os.RemoteCallback; import android.os.RemoteException; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableContentResolver; @@ -91,6 +93,7 @@ import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -140,6 +143,8 @@ public class BackAnimationControllerTest extends ShellTestCase { private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; @Mock private Handler mHandler; + @Mock + private Transitions.TransitionHandler mTakeoverHandler; private BackAnimationController mController; private TestableContentResolver mContentResolver; @@ -152,6 +157,9 @@ public class BackAnimationControllerTest extends ShellTestCase { private BackAnimationController.BackTransitionHandler mBackTransitionHandler; + @Rule + public SetFlagsRule mSetflagsRule = new SetFlagsRule(); + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -605,6 +613,51 @@ public class BackAnimationControllerTest extends ShellTestCase { verify(mAnimatorCallback, never()).onBackInvoked(); } + @EnableFlags({com.android.systemui.shared.Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY, + com.android.systemui.shared.Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED, + com.android.window.flags.Flags.FLAG_UNIFY_BACK_NAVIGATION_TRANSITION}) + @Test + public void appCallback_receivesTakeoverHandler_whenAvailable() throws RemoteException { + registerAnimation(BackNavigationInfo.TYPE_CROSS_TASK); + mBackTransitionHandler.mTakeoverHandler = mTakeoverHandler; + + final int type = BackNavigationInfo.TYPE_CALLBACK; + final ResultListener result = new ResultListener(); + createNavigationInfo(new BackNavigationInfo.Builder() + .setType(type) + .setOnBackInvokedCallback(mAppCallback) + .setOnBackNavigationDone(new RemoteCallback(result)) + .setTouchableRegion(mTouchableRegion) + .setAppProgressAllowed(true)); + + triggerBackGesture(); + mShellExecutor.flushAll(); + releaseBackGesture(); + mShellExecutor.flushAll(); + + verify(mAppCallback).setHandoffHandler(any()); + } + + @Test + public void appCallback_doesNotReceiveTakeoverHandler_whenUnavailable() throws RemoteException { + registerAnimation(BackNavigationInfo.TYPE_CROSS_TASK); + + final int type = BackNavigationInfo.TYPE_CALLBACK; + final ResultListener result = new ResultListener(); + createNavigationInfo(new BackNavigationInfo.Builder() + .setType(type) + .setOnBackInvokedCallback(mAppCallback) + .setOnBackNavigationDone(new RemoteCallback(result)) + .setTouchableRegion(mTouchableRegion) + .setAppProgressAllowed(true)); + triggerBackGesture(); + mShellExecutor.flushAll(); + releaseBackGesture(); + mShellExecutor.flushAll(); + + verify(mAppCallback, never()).setHandoffHandler(any()); + } + @Test public void skipsCancelWithoutStart() throws RemoteException { final int type = BackNavigationInfo.TYPE_CALLBACK; 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 d6be9159694b..40da9ea2d718 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -66,6 +66,7 @@ import android.view.WindowManager; import android.window.BackAnimationAdapter; import android.window.BackMotionEvent; import android.window.BackNavigationInfo; +import android.window.IBackAnimationHandoffHandler; import android.window.IOnBackInvokedCallback; import android.window.OnBackInvokedCallback; import android.window.OnBackInvokedCallbackInfo; @@ -780,6 +781,10 @@ public class BackNavigationControllerTests extends WindowTestsBase { @Override public void setTriggerBack(boolean triggerBack) { } + + @Override + public void setHandoffHandler(IBackAnimationHandoffHandler unused) { + } }; } |