Merge "Add an observer class for transition lifecycles" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
cbb61207bd
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user