Add an observer class for transition lifecycles

This will be used to transfer window decorations over transitions,
regardless of the actual handler of these transitions.

Bug: 241975249
Test: atest ShellTransitionTests
Change-Id: I86486ec5af5057c8b22ccd7e4929627667672049
This commit is contained in:
Garfield Tan
2022-08-10 14:18:28 -07:00
parent fcfe5eeccf
commit 243c74b1b9
2 changed files with 278 additions and 0 deletions

View File

@@ -119,6 +119,8 @@ public class Transitions implements RemoteCallable<Transitions> {
/** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();
private final ArrayList<TransitionObserver> mObservers = new ArrayList<>();
/** List of {@link Runnable} instances to run when the last active transition has finished. */
private final ArrayList<Runnable> mRunWhenIdleQueue = new ArrayList<>();
@@ -242,6 +244,16 @@ public class Transitions implements RemoteCallable<Transitions> {
mRemoteTransitionHandler.removeFiltered(remoteTransition);
}
/** Registers an observer on the lifecycle of transitions. */
public void registerObserver(@NonNull TransitionObserver observer) {
mObservers.add(observer);
}
/** Unregisters the observer. */
public void unregisterObserver(@NonNull TransitionObserver observer) {
mObservers.remove(observer);
}
/** Boosts the process priority of remote animation player. */
public static void setRunningRemoteTransitionDelegate(IApplicationThread appThread) {
if (appThread == null) return;
@@ -407,6 +419,11 @@ public class Transitions implements RemoteCallable<Transitions> {
+ Arrays.toString(mActiveTransitions.stream().map(
activeTransition -> activeTransition.mToken).toArray()));
}
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionReady(transitionToken, info, t, finishT);
}
if (!info.getRootLeash().isValid()) {
// Invalid root-leash implies that the transition is empty/no-op, so just do
// housekeeping and return.
@@ -474,6 +491,10 @@ public class Transitions implements RemoteCallable<Transitions> {
}
private void playTransition(@NonNull ActiveTransition active) {
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionStarting(active.mToken);
}
setupAnimHierarchy(active.mInfo, active.mStartT, active.mFinishT);
// If a handler already chose to run this animation, try delegating to it first.
@@ -546,6 +567,10 @@ public class Transitions implements RemoteCallable<Transitions> {
active.mHandler.onTransitionConsumed(
active.mToken, abort, abort ? null : active.mFinishT);
}
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionMerged(
active.mToken, mActiveTransitions.get(0).mToken);
}
return;
}
final ActiveTransition active = mActiveTransitions.get(activeIdx);
@@ -555,6 +580,9 @@ public class Transitions implements RemoteCallable<Transitions> {
active.mHandler.onTransitionConsumed(
transition, true /* aborted */, null /* finishTransaction */);
}
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionFinished(active.mToken, active.mAborted);
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"Transition animation finished (abort=%b), notifying core %s", abort, transition);
// Merge all relevant transactions together
@@ -593,6 +621,9 @@ public class Transitions implements RemoteCallable<Transitions> {
transition, true /* aborted */, null /* finishTransaction */);
}
mOrganizer.finishTransition(aborted.mToken, null /* wct */, null /* wctCB */);
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionFinished(active.mToken, true);
}
}
if (mActiveTransitions.size() <= activeIdx) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations "
@@ -792,6 +823,52 @@ public class Transitions implements RemoteCallable<Transitions> {
default void setAnimScaleSetting(float scale) {}
}
/**
* Interface for something that needs to know the lifecycle of some transitions, but never
* handles any transition by itself.
*/
public interface TransitionObserver {
/**
* Called when the transition is ready to play. It may later be merged into other
* transitions. Note this doesn't mean this transition will be played anytime soon.
*
* @param transition the unique token of this transition
* @param startTransaction the transaction given to the handler to be applied before the
* transition animation. This will be applied when the transition
* handler that handles this transition starts the transition.
* @param finishTransaction the transaction given to the handler to be applied after the
* transition animation. The Transition system will apply it when
* finishCallback is called by the transition handler.
*/
void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction);
/**
* Called when the transition is starting to play. It isn't called for merged transitions.
*
* @param transition the unique token of this transition
*/
void onTransitionStarting(@NonNull IBinder transition);
/**
* Called when a transition is merged into another transition. There won't be any following
* lifecycle calls for the merged transition.
*
* @param merged the unique token of the transition that's merged to another one
* @param playing the unique token of the transition that accepts the merge
*/
void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing);
/**
* Called when the transition is finished. This isn't called for merged transitions.
*
* @param transition the unique token of this transition
* @param aborted {@code true} if this transition is aborted; {@code false} otherwise.
*/
void onTransitionFinished(@NonNull IBinder transition, boolean aborted);
}
@BinderThread
private class TransitionPlayerImpl extends ITransitionPlayer.Stub {
@Override

View File

@@ -43,11 +43,13 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -89,6 +91,7 @@ import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import java.util.ArrayList;
@@ -688,6 +691,204 @@ public class ShellTransitionTests extends ShellTestCase {
verify(runnable4, times(1)).run();
}
@Test
public void testObserverLifecycle_basicTransitionFlow() {
Transitions transitions = createTestTransitions();
Transitions.TransitionObserver observer = mock(Transitions.TransitionObserver.class);
transitions.registerObserver(observer);
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
IBinder transitToken = new Binder();
transitions.requestStartTransition(transitToken,
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
transitions.onTransitionReady(transitToken, info, startT, finishT);
InOrder observerOrder = inOrder(observer);
observerOrder.verify(observer).onTransitionReady(transitToken, info, startT, finishT);
observerOrder.verify(observer).onTransitionStarting(transitToken);
verify(observer, times(0)).onTransitionFinished(eq(transitToken), anyBoolean());
mDefaultHandler.finishAll();
mMainExecutor.flushAll();
verify(observer).onTransitionFinished(transitToken, false);
}
@Test
public void testObserverLifecycle_queueing() {
Transitions transitions = createTestTransitions();
Transitions.TransitionObserver observer = mock(Transitions.TransitionObserver.class);
transitions.registerObserver(observer);
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
IBinder transitToken1 = new Binder();
transitions.requestStartTransition(transitToken1,
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
TransitionInfo info1 = new TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
SurfaceControl.Transaction finishT1 = mock(SurfaceControl.Transaction.class);
transitions.onTransitionReady(transitToken1, info1, startT1, finishT1);
verify(observer).onTransitionReady(transitToken1, info1, startT1, finishT1);
IBinder transitToken2 = new Binder();
transitions.requestStartTransition(transitToken2,
new TransitionRequestInfo(TRANSIT_CLOSE, null /* trigger */, null /* remote */));
TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_CLOSE)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
transitions.onTransitionReady(transitToken2, info2, startT2, finishT2);
verify(observer, times(1)).onTransitionReady(transitToken2, info2, startT2, finishT2);
verify(observer, times(0)).onTransitionStarting(transitToken2);
verify(observer, times(0)).onTransitionFinished(eq(transitToken1), anyBoolean());
verify(observer, times(0)).onTransitionFinished(eq(transitToken2), anyBoolean());
mDefaultHandler.finishAll();
mMainExecutor.flushAll();
// first transition finished
verify(observer, times(1)).onTransitionFinished(transitToken1, false);
verify(observer, times(1)).onTransitionStarting(transitToken2);
verify(observer, times(0)).onTransitionFinished(eq(transitToken2), anyBoolean());
mDefaultHandler.finishAll();
mMainExecutor.flushAll();
verify(observer, times(1)).onTransitionFinished(transitToken2, false);
}
@Test
public void testObserverLifecycle_merging() {
Transitions transitions = createTestTransitions();
Transitions.TransitionObserver observer = mock(Transitions.TransitionObserver.class);
transitions.registerObserver(observer);
mDefaultHandler.setSimulateMerge(true);
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
IBinder transitToken1 = new Binder();
transitions.requestStartTransition(transitToken1,
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
TransitionInfo info1 = new TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
SurfaceControl.Transaction finishT1 = mock(SurfaceControl.Transaction.class);
transitions.onTransitionReady(transitToken1, info1, startT1, finishT1);
IBinder transitToken2 = new Binder();
transitions.requestStartTransition(transitToken2,
new TransitionRequestInfo(TRANSIT_CLOSE, null /* trigger */, null /* remote */));
TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_CLOSE)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
transitions.onTransitionReady(transitToken2, info2, startT2, finishT2);
InOrder observerOrder = inOrder(observer);
observerOrder.verify(observer).onTransitionReady(transitToken2, info2, startT2, finishT2);
observerOrder.verify(observer).onTransitionMerged(transitToken2, transitToken1);
verify(observer, times(0)).onTransitionFinished(eq(transitToken1), anyBoolean());
mDefaultHandler.finishAll();
mMainExecutor.flushAll();
// transition + merged all finished.
verify(observer, times(1)).onTransitionFinished(transitToken1, false);
// Merged transition won't receive any lifecycle calls beyond ready
verify(observer, times(0)).onTransitionStarting(transitToken2);
verify(observer, times(0)).onTransitionFinished(eq(transitToken2), anyBoolean());
}
@Test
public void testObserverLifecycle_mergingAfterQueueing() {
Transitions transitions = createTestTransitions();
Transitions.TransitionObserver observer = mock(Transitions.TransitionObserver.class);
transitions.registerObserver(observer);
mDefaultHandler.setSimulateMerge(true);
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
// Make a test handler that only responds to multi-window triggers AND only animates
// Change transitions.
final WindowContainerTransaction handlerWCT = new WindowContainerTransaction();
TestTransitionHandler testHandler = new TestTransitionHandler() {
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
for (TransitionInfo.Change chg : info.getChanges()) {
if (chg.getMode() == TRANSIT_CHANGE) {
return super.startAnimation(transition, info, startTransaction,
finishTransaction, finishCallback);
}
}
return false;
}
@Nullable
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) {
final RunningTaskInfo task = request.getTriggerTask();
return (task != null && task.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW)
? handlerWCT : null;
}
};
transitions.addHandler(testHandler);
// Use test handler to play an animation
IBinder transitToken1 = new Binder();
RunningTaskInfo mwTaskInfo =
createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
transitions.requestStartTransition(transitToken1,
new TransitionRequestInfo(TRANSIT_CHANGE, mwTaskInfo, null /* remote */));
TransitionInfo change = new TransitionInfoBuilder(TRANSIT_CHANGE)
.addChange(TRANSIT_CHANGE).build();
SurfaceControl.Transaction startT1 = mock(SurfaceControl.Transaction.class);
SurfaceControl.Transaction finishT1 = mock(SurfaceControl.Transaction.class);
transitions.onTransitionReady(transitToken1, change, startT1, finishT1);
// Request the second transition that should be handled by the default handler
IBinder transitToken2 = new Binder();
TransitionInfo open = new TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
transitions.requestStartTransition(transitToken2,
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
SurfaceControl.Transaction startT2 = mock(SurfaceControl.Transaction.class);
SurfaceControl.Transaction finishT2 = mock(SurfaceControl.Transaction.class);
transitions.onTransitionReady(transitToken2, open, startT2, finishT2);
verify(observer).onTransitionReady(transitToken2, open, startT2, finishT2);
verify(observer, times(0)).onTransitionStarting(transitToken2);
// Request the third transition that should be merged into the second one
IBinder transitToken3 = new Binder();
transitions.requestStartTransition(transitToken3,
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
SurfaceControl.Transaction startT3 = mock(SurfaceControl.Transaction.class);
SurfaceControl.Transaction finishT3 = mock(SurfaceControl.Transaction.class);
transitions.onTransitionReady(transitToken3, open, startT3, finishT3);
verify(observer, times(0)).onTransitionStarting(transitToken2);
verify(observer).onTransitionReady(transitToken3, open, startT3, finishT3);
verify(observer, times(0)).onTransitionStarting(transitToken3);
testHandler.finishAll();
mMainExecutor.flushAll();
verify(observer).onTransitionFinished(transitToken1, false);
mDefaultHandler.finishAll();
mMainExecutor.flushAll();
InOrder observerOrder = inOrder(observer);
observerOrder.verify(observer).onTransitionStarting(transitToken2);
observerOrder.verify(observer).onTransitionMerged(transitToken3, transitToken2);
observerOrder.verify(observer).onTransitionFinished(transitToken2, false);
// Merged transition won't receive any lifecycle calls beyond ready
verify(observer, times(0)).onTransitionStarting(transitToken3);
verify(observer, times(0)).onTransitionFinished(eq(transitToken3), anyBoolean());
}
class TransitionInfoBuilder {
final TransitionInfo mInfo;