From 710ff7116980863a615a78d80563349cd32c492b Mon Sep 17 00:00:00 2001 From: Evan Rosky Date: Wed, 9 Dec 2020 13:34:02 -0800 Subject: [PATCH] Add support/adapter for recents animation via shell transition This does the basics to adapt recents animation to shell transition. Replaces startRecentsActivity with a normal startActivity. Once we make a full switch to shell transitions, we can hopefully remove it. Updates the TransitionHandlers in shell and RemoteTransitions to support attaching a WindowContainerTransaction to a transition's finish. For recents, this is used to move the "pausing" app (the one getting animated into the recents carousel) back to the front if the gesture doesn't go all-the-way into recents. Bug: 162503077 Test: enable shell transit. launch an app, use gesture to open recents. use gesture to start going to recents, but then release early to return to the app. atest ShellTransitionTests Change-Id: Ied54f1b2c70c5c129b2f5798117d6d8a85360998 --- core/java/android/app/ActivityOptions.java | 12 ++ .../android/window/IRemoteTransition.aidl | 4 +- .../IRemoteTransitionFinishedCallback.aidl | 29 ++++ .../SplitScreenTransitions.java | 7 +- .../transition/DefaultTransitionHandler.java | 5 +- .../transition/RemoteTransitionHandler.java | 23 ++- .../wm/shell/transition/Transitions.java | 34 +++- .../transition/ShellTransitionTests.java | 23 +-- .../shared/system/ActivityOptionsCompat.java | 8 + .../system/RemoteAnimationAdapterCompat.java | 5 +- .../system/RemoteAnimationTargetCompat.java | 1 + .../shared/system/RemoteTransitionCompat.java | 124 ++++++++++++++ .../shared/system/RemoteTransitionTest.java | 152 ++++++++++++++++++ .../server/wm/WindowOrganizerController.java | 4 +- 14 files changed, 396 insertions(+), 35 deletions(-) create mode 100644 core/java/android/window/IRemoteTransitionFinishedCallback.aidl create mode 100644 packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index baf21eda67253..2b5e18d3feece 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -987,6 +987,18 @@ public class ActivityOptions { return opts; } + /** + * Create an {@link ActivityOptions} instance that lets the application control the entire + * transition using a {@link IRemoteTransition}. + * @hide + */ + @RequiresPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS) + public static ActivityOptions makeRemoteTransition(IRemoteTransition remoteTransition) { + final ActivityOptions opts = new ActivityOptions(); + opts.mRemoteTransition = remoteTransition; + return opts; + } + /** @hide */ public boolean getLaunchTaskBehind() { return mAnimationType == ANIM_LAUNCH_TASK_BEHIND; diff --git a/core/java/android/window/IRemoteTransition.aidl b/core/java/android/window/IRemoteTransition.aidl index e0ddf056653f4..e71de702334ae 100644 --- a/core/java/android/window/IRemoteTransition.aidl +++ b/core/java/android/window/IRemoteTransition.aidl @@ -16,8 +16,8 @@ package android.window; -import android.view.IRemoteAnimationFinishedCallback; import android.view.SurfaceControl; +import android.window.IRemoteTransitionFinishedCallback; import android.window.TransitionInfo; /** @@ -42,5 +42,5 @@ oneway interface IRemoteTransition { * `finishCallback`. */ void startAnimation(in TransitionInfo info, in SurfaceControl.Transaction t, - in IRemoteAnimationFinishedCallback finishCallback); + in IRemoteTransitionFinishedCallback finishCallback); } diff --git a/core/java/android/window/IRemoteTransitionFinishedCallback.aidl b/core/java/android/window/IRemoteTransitionFinishedCallback.aidl new file mode 100644 index 0000000000000..02aa1a93a35fc --- /dev/null +++ b/core/java/android/window/IRemoteTransitionFinishedCallback.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2020 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.window.WindowContainerTransaction; + +/** + * Interface to be invoked by the controlling process when a remote transition has finished. + * + * @see IRemoteTransition + * {@hide} + */ +interface IRemoteTransitionFinishedCallback { + void onTransitionFinished(in WindowContainerTransaction wct); +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java index d066cf92650cb..eea5c08818cc9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/SplitScreenTransitions.java @@ -64,7 +64,7 @@ public class SplitScreenTransitions implements Transitions.TransitionHandler { /** Keeps track of currently running animations */ private final ArrayList mAnimations = new ArrayList<>(); - private Runnable mFinishCallback = null; + private Transitions.TransitionFinishCallback mFinishCallback = null; private SurfaceControl.Transaction mFinishTransaction; SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions, @@ -203,7 +203,8 @@ public class SplitScreenTransitions implements Transitions.TransitionHandler { @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction t, @NonNull Runnable finishCallback) { + @NonNull SurfaceControl.Transaction t, + @NonNull Transitions.TransitionFinishCallback finishCallback) { if (transition != mPendingDismiss && transition != mPendingEnter) { // If we're not in split-mode, just abort if (!mSplitScreen.isDividerVisible()) return false; @@ -330,7 +331,7 @@ public class SplitScreenTransitions implements Transitions.TransitionHandler { mFinishTransaction.apply(); mTransactionPool.release(mFinishTransaction); mFinishTransaction = null; - mFinishCallback.run(); + mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); mFinishCallback = null; if (mAnimatingTransition == mPendingEnter) { mPendingEnter = null; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index f1e06f7f57249..59f8c1df12132 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -56,7 +56,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction t, @NonNull Runnable finishCallback) { + @NonNull SurfaceControl.Transaction t, + @NonNull Transitions.TransitionFinishCallback finishCallback) { if (mAnimations.containsKey(transition)) { throw new IllegalStateException("Got a duplicate startAnimation call for " + transition); @@ -68,7 +69,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { final Runnable onAnimFinish = () -> { if (!animations.isEmpty()) return; mAnimations.remove(transition); - finishCallback.run(); + finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); }; for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java index cf141c6a48269..8271b06890534 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java @@ -23,9 +23,9 @@ import android.os.RemoteException; import android.util.ArrayMap; import android.util.Log; import android.util.Pair; -import android.view.IRemoteAnimationFinishedCallback; import android.view.SurfaceControl; import android.window.IRemoteTransition; +import android.window.IRemoteTransitionFinishedCallback; import android.window.TransitionFilter; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; @@ -54,7 +54,7 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { } void addFiltered(TransitionFilter filter, IRemoteTransition remote) { - mFilters.add(new Pair(filter, remote)); + mFilters.add(new Pair<>(filter, remote)); } void removeFiltered(IRemoteTransition remote) { @@ -67,7 +67,8 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction t, @NonNull Runnable finishCallback) { + @NonNull SurfaceControl.Transaction t, + @NonNull Transitions.TransitionFinishCallback finishCallback) { IRemoteTransition pendingRemote = mPendingRemotes.remove(transition); if (pendingRemote == null) { // If no explicit remote, search filters until one matches @@ -84,15 +85,17 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { final IRemoteTransition remote = pendingRemote; final IBinder.DeathRecipient remoteDied = () -> { Log.e(Transitions.TAG, "Remote transition died, finishing"); - mMainExecutor.execute(finishCallback); + mMainExecutor.execute( + () -> finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */)); }; - IRemoteAnimationFinishedCallback cb = new IRemoteAnimationFinishedCallback.Stub() { + IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() { @Override - public void onAnimationFinished() throws RemoteException { + public void onTransitionFinished(WindowContainerTransaction wct) { if (remote.asBinder() != null) { remote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */); } - mMainExecutor.execute(finishCallback); + mMainExecutor.execute( + () -> finishCallback.onTransitionFinished(wct, null /* wctCB */)); } }; try { @@ -101,8 +104,12 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { } remote.startAnimation(info, t, cb); } catch (RemoteException e) { + if (remote.asBinder() != null) { + remote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */); + } Log.e(Transitions.TAG, "Error running remote transition.", e); - mMainExecutor.execute(finishCallback); + mMainExecutor.execute( + () -> finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */)); } return true; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index c085168a33170..2ab4e0bdd76f1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -38,6 +38,7 @@ import android.window.TransitionFilter; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; +import android.window.WindowContainerTransactionCallback; import android.window.WindowOrganizer; import androidx.annotation.BinderThread; @@ -232,22 +233,22 @@ public class Transitions { // Invalid root-leash implies that the transition is empty/no-op, so just do // housekeeping and return. t.apply(); - onFinish(transitionToken); + onFinish(transitionToken, null /* wct */, null /* wctCB */); return; } setupStartState(info, t); - final Runnable finishRunnable = () -> onFinish(transitionToken); + final TransitionFinishCallback finishCb = (wct, cb) -> onFinish(transitionToken, wct, cb); // If a handler chose to uniquely run this animation, try delegating to it. if (active.mFirstHandler != null && active.mFirstHandler.startAnimation( - transitionToken, info, t, finishRunnable)) { + transitionToken, info, t, finishCb)) { return; } // Otherwise give every other handler a chance (in order) for (int i = mHandlers.size() - 1; i >= 0; --i) { if (mHandlers.get(i) == active.mFirstHandler) continue; - if (mHandlers.get(i).startAnimation(transitionToken, info, t, finishRunnable)) { + if (mHandlers.get(i).startAnimation(transitionToken, info, t, finishCb)) { return; } } @@ -255,7 +256,8 @@ public class Transitions { "This shouldn't happen, maybe the default handler is broken."); } - private void onFinish(IBinder transition) { + private void onFinish(IBinder transition, @Nullable WindowContainerTransaction wct, + @Nullable WindowContainerTransactionCallback wctCB) { if (!mActiveTransitions.containsKey(transition)) { Log.e(TAG, "Trying to finish a non-running transition. Maybe remote crashed?"); return; @@ -263,7 +265,7 @@ public class Transitions { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition animations finished, notifying core %s", transition); mActiveTransitions.remove(transition); - mOrganizer.finishTransition(transition, null, null); + mOrganizer.finishTransition(transition, wct, wctCB); } void requestStartTransition(@NonNull IBinder transitionToken, @@ -297,6 +299,23 @@ public class Transitions { return transition; } + /** + * Interface for a callback that must be called after a TransitionHandler finishes playing an + * animation. + */ + public interface TransitionFinishCallback { + /** + * This must be called on the main thread when a transition finishes playing an animation. + * The transition must not touch the surfaces after this has been called. + * + * @param wct A WindowContainerTransaction to run along with the transition clean-up. + * @param wctCB A sync callback that will be run when the transition clean-up is done and + * wct has been applied. + */ + void onTransitionFinished(@Nullable WindowContainerTransaction wct, + @Nullable WindowContainerTransactionCallback wctCB); + } + /** * Interface for something which can handle a subset of transitions. */ @@ -310,7 +329,8 @@ public class Transitions { * @return true if transition was handled, false if not (falls-back to default). */ boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction t, @NonNull Runnable finishCallback); + @NonNull SurfaceControl.Transaction t, + @NonNull TransitionFinishCallback finishCallback); /** * Potentially handles a startTransition request. diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index f3bee4ba27e83..5eca3e75a7b82 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -44,10 +44,10 @@ import android.app.ActivityManager.RunningTaskInfo; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; -import android.view.IRemoteAnimationFinishedCallback; import android.view.SurfaceControl; import android.view.WindowManager; import android.window.IRemoteTransition; +import android.window.IRemoteTransitionFinishedCallback; import android.window.TransitionFilter; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; @@ -119,7 +119,8 @@ public class ShellTransitionTests { TestTransitionHandler testHandler = new TestTransitionHandler() { @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction t, @NonNull Runnable finishCallback) { + @NonNull SurfaceControl.Transaction t, + @NonNull Transitions.TransitionFinishCallback finishCallback) { for (TransitionInfo.Change chg : info.getChanges()) { if (chg.getMode() == TRANSIT_CHANGE) { return super.startAnimation(transition, info, t, finishCallback); @@ -192,12 +193,13 @@ public class ShellTransitionTests { transitions.replaceDefaultHandlerForTest(mDefaultHandler); final boolean[] remoteCalled = new boolean[]{false}; + final WindowContainerTransaction remoteFinishWCT = new WindowContainerTransaction(); IRemoteTransition testRemote = new IRemoteTransition.Stub() { @Override public void startAnimation(TransitionInfo info, SurfaceControl.Transaction t, - IRemoteAnimationFinishedCallback finishCallback) throws RemoteException { + IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { remoteCalled[0] = true; - finishCallback.onAnimationFinished(); + finishCallback.onTransitionFinished(remoteFinishWCT); } }; IBinder transitToken = new Binder(); @@ -211,7 +213,7 @@ public class ShellTransitionTests { assertTrue(remoteCalled[0]); mDefaultHandler.finishAll(); mMainExecutor.flushAll(); - verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any(), any()); + verify(mOrganizer, times(1)).finishTransition(eq(transitToken), eq(remoteFinishWCT), any()); } @Test @@ -261,9 +263,9 @@ public class ShellTransitionTests { IRemoteTransition testRemote = new IRemoteTransition.Stub() { @Override public void startAnimation(TransitionInfo info, SurfaceControl.Transaction t, - IRemoteAnimationFinishedCallback finishCallback) throws RemoteException { + IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { remoteCalled[0] = true; - finishCallback.onAnimationFinished(); + finishCallback.onTransitionFinished(null /* wct */); } }; @@ -317,11 +319,12 @@ public class ShellTransitionTests { } class TestTransitionHandler implements Transitions.TransitionHandler { - final ArrayList mFinishes = new ArrayList<>(); + final ArrayList mFinishes = new ArrayList<>(); @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction t, @NonNull Runnable finishCallback) { + @NonNull SurfaceControl.Transaction t, + @NonNull Transitions.TransitionFinishCallback finishCallback) { mFinishes.add(finishCallback); return true; } @@ -335,7 +338,7 @@ public class ShellTransitionTests { void finishAll() { for (int i = mFinishes.size() - 1; i >= 0; --i) { - mFinishes.get(i).run(); + mFinishes.get(i).onTransitionFinished(null /* wct */, null /* wctCB */); } mFinishes.clear(); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java index 325e268a63eaa..3584c82bc57df 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java @@ -64,6 +64,14 @@ public abstract class ActivityOptionsCompat { remoteAnimationAdapter.getRemoteTransition().getTransition()); } + /** + * Constructs an ActivityOptions object that will delegate its transition handling to a + * `remoteTransition`. + */ + public static ActivityOptions makeRemoteTransition(RemoteTransitionCompat remoteTransition) { + return ActivityOptions.makeRemoteTransition(remoteTransition.getTransition()); + } + public static ActivityOptions makeCustomAnimation(Context context, int enterResId, int exitResId, final Runnable callback, final Handler callbackHandler) { return ActivityOptions.makeCustomAnimation(context, enterResId, exitResId, callbackHandler, diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java index f105fcec8e9fd..a56c6a1f084e5 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java @@ -30,6 +30,7 @@ import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import android.window.IRemoteTransition; +import android.window.IRemoteTransitionFinishedCallback; import android.window.TransitionInfo; /** @@ -98,7 +99,7 @@ public class RemoteAnimationAdapterCompat { return new IRemoteTransition.Stub() { @Override public void startAnimation(TransitionInfo info, SurfaceControl.Transaction t, - IRemoteAnimationFinishedCallback finishCallback) { + IRemoteTransitionFinishedCallback finishCallback) { final RemoteAnimationTargetCompat[] appsCompat = RemoteAnimationTargetCompat.wrap(info, false /* wallpapers */); final RemoteAnimationTargetCompat[] wallpapersCompat = @@ -107,7 +108,7 @@ public class RemoteAnimationAdapterCompat { @Override public void run() { try { - finishCallback.onAnimationFinished(); + finishCallback.onTransitionFinished(null /* wct */); } catch (RemoteException e) { Log.e("ActivityOptionsCompat", "Failed to call app controlled animation" + " finished callback", e); diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java index f88e38a5923c4..2a0715ed764cd 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java @@ -34,6 +34,7 @@ public class RemoteAnimationTargetCompat { public static final int MODE_OPENING = RemoteAnimationTarget.MODE_OPENING; public static final int MODE_CLOSING = RemoteAnimationTarget.MODE_CLOSING; + public static final int MODE_CHANGING = RemoteAnimationTarget.MODE_CHANGING; public final int mode; public static final int ACTIVITY_TYPE_UNDEFINED = WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java index 5c27b89c03de9..9df1230d76565 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java @@ -17,16 +17,29 @@ package com.android.systemui.shared.system; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; +import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import android.annotation.NonNull; import android.annotation.Nullable; +import android.graphics.Rect; import android.os.Parcelable; +import android.os.RemoteException; +import android.util.Log; +import android.view.IRecentsAnimationController; +import android.view.SurfaceControl; import android.window.IRemoteTransition; +import android.window.IRemoteTransitionFinishedCallback; import android.window.TransitionFilter; +import android.window.TransitionInfo; +import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DataClass; +import com.android.systemui.shared.recents.model.ThumbnailData; /** * Wrapper to expose RemoteTransition (shell transitions) to Launcher. @@ -43,6 +56,43 @@ public class RemoteTransitionCompat implements Parcelable { mTransition = transition; } + /** Constructor specifically for recents animation */ + public RemoteTransitionCompat(RecentsAnimationListener recents, + RecentsAnimationControllerCompat controller) { + mTransition = new IRemoteTransition.Stub() { + @Override + public void startAnimation(TransitionInfo info, SurfaceControl.Transaction t, + IRemoteTransitionFinishedCallback finishedCallback) { + final RemoteAnimationTargetCompat[] apps = + RemoteAnimationTargetCompat.wrap(info, false /* wallpapers */); + final RemoteAnimationTargetCompat[] wallpapers = + RemoteAnimationTargetCompat.wrap(info, true /* wallpapers */); + // TODO(b/177438007): Move this set-up logic into launcher's animation impl. + // This transition is for opening recents, so recents is on-top. We want to draw + // the current going-away task on top of recents, though, so move it to front + WindowContainerToken pausingTask = null; + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (change.getMode() == TRANSIT_CLOSE || change.getMode() == TRANSIT_TO_BACK) { + t.setLayer(change.getLeash(), info.getChanges().size() * 3 - i); + if (change.getTaskInfo() != null) { + pausingTask = change.getTaskInfo().token; + } + } + } + // Also make all the wallpapers opaque since we want the visible from the start + for (int i = wallpapers.length - 1; i >= 0; --i) { + t.setAlpha(wallpapers[i].leash.mSurfaceControl, 1); + } + t.apply(); + final RecentsAnimationControllerCompat wrapControl = + new RecentsControllerWrap(controller, finishedCallback, pausingTask); + recents.onAnimationStart(wrapControl, apps, wallpapers, new Rect(0, 0, 0, 0), + new Rect()); + } + }; + } + /** Adds a filter check that restricts this remote transition to home open transitions. */ public void addHomeOpenCheck() { if (mFilter == null) { @@ -54,6 +104,80 @@ public class RemoteTransitionCompat implements Parcelable { mFilter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT}; } + /** + * Wrapper to hook up parts of recents animation to shell transition. + * TODO(b/177438007): Remove this once Launcher handles shell transitions directly. + */ + @VisibleForTesting + static class RecentsControllerWrap extends RecentsAnimationControllerCompat { + private final RecentsAnimationControllerCompat mWrapped; + private final IRemoteTransitionFinishedCallback mFinishCB; + private final WindowContainerToken mPausingTask; + + RecentsControllerWrap(RecentsAnimationControllerCompat wrapped, + IRemoteTransitionFinishedCallback finishCB, WindowContainerToken pausingTask) { + mWrapped = wrapped; + mFinishCB = finishCB; + mPausingTask = pausingTask; + } + + @Override public ThumbnailData screenshotTask(int taskId) { + return mWrapped != null ? mWrapped.screenshotTask(taskId) : null; + } + + @Override public void setInputConsumerEnabled(boolean enabled) { + if (mWrapped != null) mWrapped.setInputConsumerEnabled(enabled); + } + + @Override public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars) { + if (mWrapped != null) mWrapped.setAnimationTargetsBehindSystemBars(behindSystemBars); + } + + @Override public void hideCurrentInputMethod() { + mWrapped.hideCurrentInputMethod(); + } + + @Override public void setFinishTaskBounds(int taskId, Rect destinationBounds) { + if (mWrapped != null) mWrapped.setFinishTaskBounds(taskId, destinationBounds); + } + + @Override public void finish(boolean toHome, boolean sendUserLeaveHint) { + try { + if (!toHome && mPausingTask != null) { + // The gesture went back to opening the app rather than continuing with + // recents, so end the transition by moving the app back to the top. + final WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.reorder(mPausingTask, true /* onTop */); + mFinishCB.onTransitionFinished(wct); + } else { + mFinishCB.onTransitionFinished(null /* wct */); + } + } catch (RemoteException e) { + Log.e("RemoteTransitionCompat", "Failed to call animation finish callback", e); + } + if (mWrapped != null) mWrapped.finish(toHome, sendUserLeaveHint); + } + + @Override public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) { + if (mWrapped != null) mWrapped.setDeferCancelUntilNextTransition(defer, screenshot); + } + + @Override public void cleanupScreenshot() { + if (mWrapped != null) mWrapped.cleanupScreenshot(); + } + + @Override public void setWillFinishToHome(boolean willFinishToHome) { + if (mWrapped != null) mWrapped.setWillFinishToHome(willFinishToHome); + } + + /** + * @see IRecentsAnimationController#removeTask + */ + @Override public boolean removeTask(int taskId) { + return mWrapped != null ? mWrapped.removeTask(taskId) : false; + } + } + // Code below generated by codegen v1.0.21. diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java new file mode 100644 index 0000000000000..d1259d359d222 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2021 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.systemui.shared.system; + +import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_OPEN; +import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; +import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; +import static android.window.TransitionInfo.FLAG_TRANSLUCENT; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME; +import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CHANGING; +import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING; +import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; + +import android.app.ActivityManager; +import android.graphics.Rect; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.SurfaceControl; +import android.view.WindowManager; +import android.window.TransitionInfo; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class RemoteTransitionTest extends SysuiTestCase { + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testLegacyTargetExtract() { + TransitionInfo combined = new TransitionInfoBuilder(TRANSIT_CLOSE) + .addChange(TRANSIT_CHANGE, FLAG_SHOW_WALLPAPER) + .addChange(TRANSIT_CLOSE, 0 /* flags */) + .addChange(TRANSIT_OPEN, FLAG_IS_WALLPAPER).build(); + // Check non-wallpaper extraction + RemoteAnimationTargetCompat[] wrapped = + RemoteAnimationTargetCompat.wrap(combined, false /* wallpapers */); + assertEquals(2, wrapped.length); + int changeLayer = -1; + int closeLayer = -1; + for (RemoteAnimationTargetCompat t : wrapped) { + if (t.mode == MODE_CHANGING) { + changeLayer = t.prefixOrderIndex; + } else if (t.mode == MODE_CLOSING) { + closeLayer = t.prefixOrderIndex; + } else { + fail(); + } + } + // verify ordering + assertTrue(closeLayer < changeLayer); + + // Check wallpaper extraction + RemoteAnimationTargetCompat[] wallps = + RemoteAnimationTargetCompat.wrap(combined, true /* wallpapers */); + assertEquals(1, wallps.length); + assertTrue(wallps[0].prefixOrderIndex < closeLayer); + assertEquals(MODE_OPENING, wallps[0].mode); + } + + @Test + public void testLegacyTargetWrapper() { + final Rect endBounds = new Rect(40, 60, 140, 200); + final TransitionInfo.Change change = + new TransitionInfo.Change(null /* token */, null /* leash */); + change.setTaskInfo(createTaskInfo(1 /* taskId */, ACTIVITY_TYPE_HOME)); + change.setMode(TRANSIT_CHANGE); + change.setEndAbsBounds(endBounds); + change.setEndRelOffset(0, 0); + change.setFlags(FLAG_TRANSLUCENT); + final RemoteAnimationTargetCompat wrapped = + new RemoteAnimationTargetCompat(change, 0 /* order */); + assertEquals(ACTIVITY_TYPE_HOME, wrapped.activityType); + assertEquals(new Rect(0, 0, 100, 140), wrapped.localBounds); + assertEquals(endBounds, wrapped.screenSpaceBounds); + assertTrue(wrapped.isTranslucent); + } + + class TransitionInfoBuilder { + final TransitionInfo mInfo; + + TransitionInfoBuilder(@WindowManager.TransitionType int type) { + mInfo = new TransitionInfo(type, 0 /* flags */); + mInfo.setRootLeash(createMockSurface(true /* valid */), 0, 0); + } + + TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode, + @TransitionInfo.ChangeFlags int flags) { + final TransitionInfo.Change change = + new TransitionInfo.Change(null /* token */, null /* leash */); + change.setMode(mode); + change.setFlags(flags); + mInfo.addChange(change); + return this; + } + + TransitionInfo build() { + return mInfo; + } + } + + private static SurfaceControl createMockSurface(boolean valid) { + SurfaceControl sc = mock(SurfaceControl.class); + if (valid) { + doReturn(true).when(sc).isValid(); + } + return sc; + } + + private static ActivityManager.RunningTaskInfo createTaskInfo(int taskId, int activityType) { + ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); + taskInfo.taskId = taskId; + taskInfo.configuration.windowConfiguration.setActivityType(activityType); + return taskInfo; + } + +} diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index dcab478ad7a8d..bef4c68afd350 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -209,10 +209,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub if (t != null && callback != null) { syncId = startSyncWithOrganizer(callback); } - getTransitionController().finishTransition(transitionToken); + // apply the incoming transaction before finish in case it alters the visibility + // of the participants. if (t != null) { applyTransaction(t, syncId, null /*transition*/); } + getTransitionController().finishTransition(transitionToken); if (syncId >= 0) { setSyncReady(syncId); }