Merge "Add support/adapter for recents animation via shell transition"
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -64,7 +64,7 @@ public class SplitScreenTransitions implements Transitions.TransitionHandler {
|
||||
/** Keeps track of currently running animations */
|
||||
private final ArrayList<Animator> 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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<TransitionFilter, IRemoteTransition>(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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<Runnable> mFinishes = new ArrayList<>();
|
||||
final ArrayList<Transitions.TransitionFinishCallback> 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();
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user