Merge "Add support/adapter for recents animation via shell transition"

This commit is contained in:
Evan Rosky
2021-01-26 00:29:40 +00:00
committed by Android (Google) Code Review
14 changed files with 396 additions and 35 deletions

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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.

View File

@@ -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();
}

View File

@@ -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,

View File

@@ -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);

View File

@@ -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;

View File

@@ -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.

View File

@@ -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;
}
}

View File

@@ -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);
}