Merge "Refactor default transit animations into handler for testing"
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* 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.wm.shell.transition;
|
||||
|
||||
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 static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.ActivityManager;
|
||||
import android.os.IBinder;
|
||||
import android.util.ArrayMap;
|
||||
import android.view.SurfaceControl;
|
||||
import android.window.TransitionInfo;
|
||||
import android.window.WindowContainerTransaction;
|
||||
|
||||
import com.android.wm.shell.common.ShellExecutor;
|
||||
import com.android.wm.shell.common.TransactionPool;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/** The default handler that handles anything not already handled. */
|
||||
public class DefaultTransitionHandler implements Transitions.TransitionHandler {
|
||||
private final TransactionPool mTransactionPool;
|
||||
private final ShellExecutor mMainExecutor;
|
||||
private final ShellExecutor mAnimExecutor;
|
||||
|
||||
/** Keeps track of the currently-running animations associated with each transition. */
|
||||
private final ArrayMap<IBinder, ArrayList<Animator>> mAnimations = new ArrayMap<>();
|
||||
|
||||
DefaultTransitionHandler(@NonNull TransactionPool transactionPool,
|
||||
@NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
|
||||
mTransactionPool = transactionPool;
|
||||
mMainExecutor = mainExecutor;
|
||||
mAnimExecutor = animExecutor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
|
||||
@NonNull SurfaceControl.Transaction t, @NonNull Runnable finishCallback) {
|
||||
if (mAnimations.containsKey(transition)) {
|
||||
throw new IllegalStateException("Got a duplicate startAnimation call for "
|
||||
+ transition);
|
||||
}
|
||||
final ArrayList<Animator> animations = new ArrayList<>();
|
||||
mAnimations.put(transition, animations);
|
||||
final boolean isOpening = Transitions.isOpeningType(info.getType());
|
||||
|
||||
final Runnable onAnimFinish = () -> {
|
||||
if (!animations.isEmpty()) return;
|
||||
mAnimations.remove(transition);
|
||||
finishCallback.run();
|
||||
};
|
||||
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
|
||||
final TransitionInfo.Change change = info.getChanges().get(i);
|
||||
|
||||
// Don't animate anything with an animating parent
|
||||
if (change.getParent() != null) continue;
|
||||
|
||||
final int mode = change.getMode();
|
||||
if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
|
||||
if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
|
||||
// This received a transferred starting window, so don't animate
|
||||
continue;
|
||||
}
|
||||
// fade in
|
||||
startExampleAnimation(
|
||||
animations, change.getLeash(), true /* show */, onAnimFinish);
|
||||
} else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) {
|
||||
// fade out
|
||||
startExampleAnimation(
|
||||
animations, change.getLeash(), false /* show */, onAnimFinish);
|
||||
}
|
||||
}
|
||||
t.apply();
|
||||
// run finish now in-case there are no animations
|
||||
onAnimFinish.run();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public WindowContainerTransaction handleRequest(int type, @NonNull IBinder transition,
|
||||
@Nullable ActivityManager.RunningTaskInfo triggerTask) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO(shell-transitions): real animations
|
||||
private void startExampleAnimation(@NonNull ArrayList<Animator> animations,
|
||||
@NonNull SurfaceControl leash, boolean show, @NonNull Runnable finishCallback) {
|
||||
final float end = show ? 1.f : 0.f;
|
||||
final float start = 1.f - end;
|
||||
final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
|
||||
final ValueAnimator va = ValueAnimator.ofFloat(start, end);
|
||||
va.setDuration(500);
|
||||
va.addUpdateListener(animation -> {
|
||||
float fraction = animation.getAnimatedFraction();
|
||||
transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
|
||||
transaction.apply();
|
||||
});
|
||||
final Runnable finisher = () -> {
|
||||
transaction.setAlpha(leash, end);
|
||||
transaction.apply();
|
||||
mTransactionPool.release(transaction);
|
||||
mMainExecutor.execute(() -> {
|
||||
animations.remove(va);
|
||||
finishCallback.run();
|
||||
});
|
||||
};
|
||||
va.addListener(new Animator.AnimatorListener() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) { }
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
finisher.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
finisher.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animator animation) { }
|
||||
});
|
||||
animations.add(va);
|
||||
mAnimExecutor.execute(va::start);
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,6 @@ import static android.view.WindowManager.TRANSIT_TO_BACK;
|
||||
import static android.view.WindowManager.TRANSIT_TO_FRONT;
|
||||
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.ActivityManager;
|
||||
@@ -41,6 +39,7 @@ import android.window.WindowOrganizer;
|
||||
|
||||
import androidx.annotation.BinderThread;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.protolog.common.ProtoLog;
|
||||
import com.android.wm.shell.ShellTaskOrganizer;
|
||||
import com.android.wm.shell.common.ShellExecutor;
|
||||
@@ -48,6 +47,7 @@ import com.android.wm.shell.common.TransactionPool;
|
||||
import com.android.wm.shell.protolog.ShellProtoLogGroup;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
/** Plays transition animations */
|
||||
public class Transitions {
|
||||
@@ -58,7 +58,6 @@ public class Transitions {
|
||||
SystemProperties.getBoolean("persist.debug.shell_transit", false);
|
||||
|
||||
private final WindowOrganizer mOrganizer;
|
||||
private final TransactionPool mTransactionPool;
|
||||
private final ShellExecutor mMainExecutor;
|
||||
private final ShellExecutor mAnimExecutor;
|
||||
private final TransitionPlayerImpl mPlayerImpl;
|
||||
@@ -67,7 +66,6 @@ public class Transitions {
|
||||
private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();
|
||||
|
||||
private static final class ActiveTransition {
|
||||
ArrayList<Animator> mAnimations = null;
|
||||
TransitionHandler mFirstHandler = null;
|
||||
}
|
||||
|
||||
@@ -77,10 +75,11 @@ public class Transitions {
|
||||
public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
|
||||
@NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
|
||||
mOrganizer = organizer;
|
||||
mTransactionPool = pool;
|
||||
mMainExecutor = mainExecutor;
|
||||
mAnimExecutor = animExecutor;
|
||||
mPlayerImpl = new TransitionPlayerImpl();
|
||||
// The very last handler (0 in the list) should be the default one.
|
||||
mHandlers.add(new DefaultTransitionHandler(pool, mainExecutor, animExecutor));
|
||||
}
|
||||
|
||||
/** Register this transition handler with Core */
|
||||
@@ -104,47 +103,10 @@ public class Transitions {
|
||||
return mAnimExecutor;
|
||||
}
|
||||
|
||||
// TODO(shell-transitions): real animations
|
||||
private void startExampleAnimation(@NonNull IBinder transition, @NonNull SurfaceControl leash,
|
||||
boolean show) {
|
||||
final float end = show ? 1.f : 0.f;
|
||||
final float start = 1.f - end;
|
||||
final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
|
||||
final ValueAnimator va = ValueAnimator.ofFloat(start, end);
|
||||
va.setDuration(500);
|
||||
va.addUpdateListener(animation -> {
|
||||
float fraction = animation.getAnimatedFraction();
|
||||
transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
|
||||
transaction.apply();
|
||||
});
|
||||
final Runnable finisher = () -> {
|
||||
transaction.setAlpha(leash, end);
|
||||
transaction.apply();
|
||||
mTransactionPool.release(transaction);
|
||||
mMainExecutor.execute(() -> {
|
||||
mActiveTransitions.get(transition).mAnimations.remove(va);
|
||||
onFinish(transition);
|
||||
});
|
||||
};
|
||||
va.addListener(new Animator.AnimatorListener() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) { }
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
finisher.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
finisher.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animator animation) { }
|
||||
});
|
||||
mActiveTransitions.get(transition).mAnimations.add(va);
|
||||
mAnimExecutor.execute(va::start);
|
||||
/** Only use this in tests. This is used to avoid running animations during tests. */
|
||||
@VisibleForTesting
|
||||
void replaceDefaultHandlerForTest(TransitionHandler handler) {
|
||||
mHandlers.set(0, handler);
|
||||
}
|
||||
|
||||
/** @return true if the transition was triggered by opening something vs closing something */
|
||||
@@ -217,18 +179,16 @@ public class Transitions {
|
||||
}
|
||||
}
|
||||
|
||||
private void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
|
||||
@VisibleForTesting
|
||||
void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
|
||||
@NonNull SurfaceControl.Transaction t) {
|
||||
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
|
||||
transitionToken, info);
|
||||
final ActiveTransition active = mActiveTransitions.get(transitionToken);
|
||||
if (active == null) {
|
||||
throw new IllegalStateException("Got transitionReady for non-active transition "
|
||||
+ transitionToken + ". expecting one of " + mActiveTransitions.keySet());
|
||||
}
|
||||
if (active.mAnimations != null) {
|
||||
throw new IllegalStateException("Got a duplicate onTransitionReady call for "
|
||||
+ transitionToken);
|
||||
+ transitionToken + ". expecting one of "
|
||||
+ Arrays.toString(mActiveTransitions.keySet().toArray()));
|
||||
}
|
||||
if (!info.getRootLeash().isValid()) {
|
||||
// Invalid root-leash implies that the transition is empty/no-op, so just do
|
||||
@@ -253,44 +213,21 @@ public class Transitions {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// No handler chose to perform this animation, so fall-back to the
|
||||
// default animation handling.
|
||||
final boolean isOpening = isOpeningType(info.getType());
|
||||
active.mAnimations = new ArrayList<>(); // Play fade animations
|
||||
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
|
||||
final TransitionInfo.Change change = info.getChanges().get(i);
|
||||
|
||||
// Don't animate anything with an animating parent
|
||||
if (change.getParent() != null) continue;
|
||||
|
||||
final int mode = info.getChanges().get(i).getMode();
|
||||
if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
|
||||
if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
|
||||
// This received a transferred starting window, so don't animate
|
||||
continue;
|
||||
}
|
||||
// fade in
|
||||
startExampleAnimation(transitionToken, change.getLeash(), true /* show */);
|
||||
} else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) {
|
||||
// fade out
|
||||
startExampleAnimation(transitionToken, change.getLeash(), false /* show */);
|
||||
}
|
||||
}
|
||||
t.apply();
|
||||
onFinish(transitionToken);
|
||||
throw new IllegalStateException(
|
||||
"This shouldn't happen, maybe the default handler is broken.");
|
||||
}
|
||||
|
||||
private void onFinish(IBinder transition) {
|
||||
final ActiveTransition active = mActiveTransitions.get(transition);
|
||||
if (active.mAnimations != null && !active.mAnimations.isEmpty()) return;
|
||||
if (!mActiveTransitions.containsKey(transition)) {
|
||||
throw new IllegalStateException("Trying to finish an already-finished transition.");
|
||||
}
|
||||
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
|
||||
"Transition animations finished, notifying core %s", transition);
|
||||
mActiveTransitions.remove(transition);
|
||||
mOrganizer.finishTransition(transition, null, null);
|
||||
}
|
||||
|
||||
private void requestStartTransition(int type, @NonNull IBinder transitionToken,
|
||||
void requestStartTransition(int type, @NonNull IBinder transitionToken,
|
||||
@Nullable ActivityManager.RunningTaskInfo triggerTask) {
|
||||
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: type=%d %s",
|
||||
type, transitionToken);
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.wm.shell;
|
||||
|
||||
import com.android.wm.shell.common.ShellExecutor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Really basic test executor. It just gathers all events in a blob. The only option is to
|
||||
* execute everything at once. If better control over delayed execution is needed, please add it.
|
||||
*/
|
||||
public class TestShellExecutor implements ShellExecutor {
|
||||
final ArrayList<Runnable> mRunnables = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void execute(Runnable runnable) {
|
||||
mRunnables.add(runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeDelayed(Runnable r, long delayMillis) {
|
||||
mRunnables.add(r);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCallbacks(Runnable r) {
|
||||
mRunnables.remove(r);
|
||||
}
|
||||
|
||||
public void flushAll() {
|
||||
for (int i = mRunnables.size() - 1; i >= 0; --i) {
|
||||
mRunnables.get(i).run();
|
||||
}
|
||||
mRunnables.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* 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.wm.shell.transition;
|
||||
|
||||
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
|
||||
import static android.view.WindowManager.TRANSIT_CHANGE;
|
||||
import static android.view.WindowManager.TRANSIT_CLOSE;
|
||||
import static android.view.WindowManager.TRANSIT_OPEN;
|
||||
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.app.ActivityManager.RunningTaskInfo;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.WindowManager;
|
||||
import android.window.TransitionInfo;
|
||||
import android.window.WindowContainerTransaction;
|
||||
import android.window.WindowOrganizer;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.wm.shell.TestShellExecutor;
|
||||
import com.android.wm.shell.common.ShellExecutor;
|
||||
import com.android.wm.shell.common.TransactionPool;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Tests for the shell transitions.
|
||||
*/
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ShellTransitionTests {
|
||||
|
||||
private final WindowOrganizer mOrganizer = mock(WindowOrganizer.class);
|
||||
private final TransactionPool mTransactionPool = mock(TransactionPool.class);
|
||||
private final TestShellExecutor mMainExecutor = new TestShellExecutor();
|
||||
private final ShellExecutor mAnimExecutor = new TestShellExecutor();
|
||||
private final TestTransitionHandler mDefaultHandler = new TestTransitionHandler();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
doAnswer(invocation -> invocation.getArguments()[1])
|
||||
.when(mOrganizer).startTransition(anyInt(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicTransitionFlow() {
|
||||
Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mMainExecutor,
|
||||
mAnimExecutor);
|
||||
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
|
||||
|
||||
IBinder transitToken = new Binder();
|
||||
transitions.requestStartTransition(TRANSIT_OPEN, transitToken, null /* trigger */);
|
||||
verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), any());
|
||||
TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
|
||||
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
|
||||
transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class));
|
||||
assertEquals(1, mDefaultHandler.activeCount());
|
||||
mDefaultHandler.finishAll();
|
||||
mMainExecutor.flushAll();
|
||||
verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonDefaultHandler() {
|
||||
Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mMainExecutor,
|
||||
mAnimExecutor);
|
||||
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
|
||||
|
||||
final WindowContainerTransaction handlerWCT = new WindowContainerTransaction();
|
||||
// Make a test handler that only responds to multi-window triggers AND only animates
|
||||
// Change transitions.
|
||||
TestTransitionHandler testHandler = new TestTransitionHandler() {
|
||||
@Override
|
||||
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
|
||||
@NonNull SurfaceControl.Transaction t, @NonNull Runnable finishCallback) {
|
||||
for (TransitionInfo.Change chg : info.getChanges()) {
|
||||
if (chg.getMode() == TRANSIT_CHANGE) {
|
||||
return super.startAnimation(transition, info, t, finishCallback);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public WindowContainerTransaction handleRequest(int type, @NonNull IBinder transition,
|
||||
@Nullable RunningTaskInfo triggerTask) {
|
||||
return (triggerTask != null
|
||||
&& triggerTask.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW)
|
||||
? handlerWCT : null;
|
||||
}
|
||||
};
|
||||
transitions.addHandler(testHandler);
|
||||
|
||||
IBinder transitToken = new Binder();
|
||||
TransitionInfo open = new TransitionInfoBuilder(TRANSIT_OPEN)
|
||||
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
|
||||
|
||||
// Make a request that will be rejected by the testhandler.
|
||||
transitions.requestStartTransition(TRANSIT_OPEN, transitToken, null /* trigger */);
|
||||
verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), isNull());
|
||||
transitions.onTransitionReady(transitToken, open, mock(SurfaceControl.Transaction.class));
|
||||
assertEquals(1, mDefaultHandler.activeCount());
|
||||
assertEquals(0, testHandler.activeCount());
|
||||
mDefaultHandler.finishAll();
|
||||
mMainExecutor.flushAll();
|
||||
|
||||
// Make a request that will be handled by testhandler but not animated by it.
|
||||
RunningTaskInfo mwTaskInfo =
|
||||
createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
|
||||
transitions.requestStartTransition(TRANSIT_OPEN, transitToken, mwTaskInfo);
|
||||
verify(mOrganizer, times(1)).startTransition(
|
||||
eq(TRANSIT_OPEN), eq(transitToken), eq(handlerWCT));
|
||||
transitions.onTransitionReady(transitToken, open, mock(SurfaceControl.Transaction.class));
|
||||
assertEquals(1, mDefaultHandler.activeCount());
|
||||
assertEquals(0, testHandler.activeCount());
|
||||
mDefaultHandler.finishAll();
|
||||
mMainExecutor.flushAll();
|
||||
|
||||
// Make a request that will be handled AND animated by testhandler.
|
||||
// Add an aggressive handler (doesn't handle but always animates) on top to make sure that
|
||||
// the test handler gets first shot at animating since it claimed to handle it.
|
||||
TestTransitionHandler topHandler = new TestTransitionHandler();
|
||||
transitions.addHandler(topHandler);
|
||||
transitions.requestStartTransition(TRANSIT_CHANGE, transitToken, mwTaskInfo);
|
||||
verify(mOrganizer, times(1)).startTransition(
|
||||
eq(TRANSIT_OPEN), eq(transitToken), eq(handlerWCT));
|
||||
TransitionInfo change = new TransitionInfoBuilder(TRANSIT_CHANGE)
|
||||
.addChange(TRANSIT_CHANGE).build();
|
||||
transitions.onTransitionReady(transitToken, change, mock(SurfaceControl.Transaction.class));
|
||||
assertEquals(0, mDefaultHandler.activeCount());
|
||||
assertEquals(1, testHandler.activeCount());
|
||||
assertEquals(0, topHandler.activeCount());
|
||||
testHandler.finishAll();
|
||||
mMainExecutor.flushAll();
|
||||
}
|
||||
|
||||
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,
|
||||
RunningTaskInfo taskInfo) {
|
||||
final TransitionInfo.Change change =
|
||||
new TransitionInfo.Change(null /* token */, null /* leash */);
|
||||
change.setMode(mode);
|
||||
change.setTaskInfo(taskInfo);
|
||||
mInfo.addChange(change);
|
||||
return this;
|
||||
}
|
||||
|
||||
TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode) {
|
||||
return addChange(mode, null /* taskInfo */);
|
||||
}
|
||||
|
||||
TransitionInfo build() {
|
||||
return mInfo;
|
||||
}
|
||||
}
|
||||
|
||||
class TestTransitionHandler implements Transitions.TransitionHandler {
|
||||
final ArrayList<Runnable> mFinishes = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
|
||||
@NonNull SurfaceControl.Transaction t, @NonNull Runnable finishCallback) {
|
||||
mFinishes.add(finishCallback);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public WindowContainerTransaction handleRequest(int type, @NonNull IBinder transition,
|
||||
@Nullable RunningTaskInfo triggerTask) {
|
||||
return null;
|
||||
}
|
||||
|
||||
void finishAll() {
|
||||
for (int i = mFinishes.size() - 1; i >= 0; --i) {
|
||||
mFinishes.get(i).run();
|
||||
}
|
||||
mFinishes.clear();
|
||||
}
|
||||
|
||||
int activeCount() {
|
||||
return mFinishes.size();
|
||||
}
|
||||
}
|
||||
|
||||
private static SurfaceControl createMockSurface(boolean valid) {
|
||||
SurfaceControl sc = mock(SurfaceControl.class);
|
||||
doReturn(valid).when(sc).isValid();
|
||||
return sc;
|
||||
}
|
||||
|
||||
private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode, int activityType) {
|
||||
RunningTaskInfo taskInfo = new RunningTaskInfo();
|
||||
taskInfo.taskId = taskId;
|
||||
taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
|
||||
taskInfo.configuration.windowConfiguration.setActivityType(activityType);
|
||||
return taskInfo;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user