[18/n] Letterbox Education: start dialog enter animation after all shell transitions have finished

Fix: 219499529
Test: atest WMShellUnitTests:LetterboxEduWindowManagerTest
Test: atest WMShellUnitTests:CompatUIControllerTest
Test: atest WMShellUnitTests:ShellTransitionTests
Change-Id: Ifa7ab82c6fa076fbb054b8d370a244e7e8738b0b
This commit is contained in:
tomnatan
2022-03-09 11:42:49 +00:00
parent 0e76941f06
commit dc3dac34bd
9 changed files with 176 additions and 14 deletions

View File

@@ -42,6 +42,7 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
import com.android.wm.shell.transition.Transitions;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -50,6 +51,8 @@ import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import dagger.Lazy;
/**
* Controller to show/update compat UI components on Tasks based on whether the foreground
* activities are in compatibility mode.
@@ -102,6 +105,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
private final DisplayImeController mImeController;
private final SyncTransactionQueue mSyncQueue;
private final ShellExecutor mMainExecutor;
private final Lazy<Transitions> mTransitionsLazy;
private final CompatUIImpl mImpl = new CompatUIImpl();
private CompatUICallback mCallback;
@@ -118,13 +122,15 @@ public class CompatUIController implements OnDisplaysChangedListener,
DisplayInsetsController displayInsetsController,
DisplayImeController imeController,
SyncTransactionQueue syncQueue,
ShellExecutor mainExecutor) {
ShellExecutor mainExecutor,
Lazy<Transitions> transitionsLazy) {
mContext = context;
mDisplayController = displayController;
mDisplayInsetsController = displayInsetsController;
mImeController = imeController;
mSyncQueue = syncQueue;
mMainExecutor = mainExecutor;
mTransitionsLazy = transitionsLazy;
mDisplayController.addDisplayWindowListener(this);
mImeController.addPositionProcessor(this);
mCompatUIHintsState = new CompatUIHintsState();
@@ -302,6 +308,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
ShellTaskOrganizer.TaskListener taskListener) {
return new LetterboxEduWindowManager(context, taskInfo,
mSyncQueue, taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
mTransitionsLazy.get(),
this::onLetterboxEduDismissed);
}

View File

@@ -18,6 +18,7 @@ package com.android.wm.shell.compatui.letterboxedu;
import static com.android.internal.R.styleable.WindowAnimation_windowEnterAnimation;
import static com.android.internal.R.styleable.WindowAnimation_windowExitAnimation;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -42,7 +43,9 @@ import com.android.internal.policy.TransitionAnimation;
class LetterboxEduAnimationController {
private static final String TAG = "LetterboxEduAnimation";
private static final int ENTER_ANIM_START_DELAY_MILLIS = 500;
// If shell transitions are enabled, startEnterAnimation will be called after all transitions
// have finished, and therefore the start delay should be shorter.
private static final int ENTER_ANIM_START_DELAY_MILLIS = ENABLE_SHELL_TRANSITIONS ? 300 : 500;
private final TransitionAnimation mTransitionAnimation;
private final String mPackageName;

View File

@@ -38,6 +38,7 @@ class LetterboxEduDialogLayout extends ConstraintLayout {
// The alpha of a background is a number between 0 (fully transparent) to 255 (fully opaque).
// 204 is simply 255 * 0.8.
static final int BACKGROUND_DIM_ALPHA = 204;
private View mDialogContainer;
private TextView mDialogTitle;
private Drawable mBackgroundDim;

View File

@@ -36,6 +36,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIWindowManagerAbstract;
import com.android.wm.shell.transition.Transitions;
/**
* Window manager for the Letterbox Education.
@@ -63,6 +64,8 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
private final LetterboxEduAnimationController mAnimationController;
private final Transitions mTransitions;
// Remember the last reported state in case visibility changes due to keyguard or IME updates.
private boolean mEligibleForLetterboxEducation;
@@ -80,17 +83,19 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
public LetterboxEduWindowManager(Context context, TaskInfo taskInfo,
SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
DisplayLayout displayLayout, Runnable onDismissCallback) {
this(context, taskInfo, syncQueue, taskListener, displayLayout, onDismissCallback,
new LetterboxEduAnimationController(context));
DisplayLayout displayLayout, Transitions transitions,
Runnable onDismissCallback) {
this(context, taskInfo, syncQueue, taskListener, displayLayout, transitions,
onDismissCallback, new LetterboxEduAnimationController(context));
}
@VisibleForTesting
LetterboxEduWindowManager(Context context, TaskInfo taskInfo,
SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
DisplayLayout displayLayout, Runnable onDismissCallback,
DisplayLayout displayLayout, Transitions transitions, Runnable onDismissCallback,
LetterboxEduAnimationController animationController) {
super(context, taskInfo, syncQueue, taskListener, displayLayout);
mTransitions = transitions;
mOnDismissCallback = onDismissCallback;
mAnimationController = animationController;
mEligibleForLetterboxEducation = taskInfo.topActivityEligibleForLetterboxEducation;
@@ -132,8 +137,8 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
mLayout = inflateLayout();
updateDialogMargins();
mAnimationController.startEnterAnimation(mLayout, /* endCallback= */
this::onDialogEnterAnimationEnded);
// startEnterAnimation will be called immediately if shell-transitions are disabled.
mTransitions.runOnIdle(this::startEnterAnimation);
return mLayout;
}
@@ -158,8 +163,18 @@ public class LetterboxEduWindowManager extends CompatUIWindowManagerAbstract {
R.layout.letterbox_education_dialog_layout, null);
}
private void startEnterAnimation() {
if (mLayout == null) {
// Dialog has already been released.
return;
}
mAnimationController.startEnterAnimation(mLayout, /* endCallback= */
this::onDialogEnterAnimationEnded);
}
private void onDialogEnterAnimationEnded() {
if (mLayout == null) {
// Dialog has already been released.
return;
}
mLayout.setDismissOnClickListener(this::onDismiss);

View File

@@ -96,6 +96,7 @@ import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
import java.util.Optional;
import dagger.BindsOptionalOf;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
@@ -210,9 +211,9 @@ public abstract class WMShellBaseModule {
static CompatUIController provideCompatUIController(Context context,
DisplayController displayController, DisplayInsetsController displayInsetsController,
DisplayImeController imeController, SyncTransactionQueue syncQueue,
@ShellMainThread ShellExecutor mainExecutor) {
@ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy) {
return new CompatUIController(context, displayController, displayInsetsController,
imeController, syncQueue, mainExecutor);
imeController, syncQueue, mainExecutor, transitionsLazy);
}
@WMSingleton

View File

@@ -109,6 +109,9 @@ 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<>();
/** List of {@link Runnable} instances to run when the last active transition has finished. */
private final ArrayList<Runnable> mRunWhenIdleQueue = new ArrayList<>();
private float mTransitionAnimationScaleSetting = 1.0f;
private static final class ActiveTransition {
@@ -224,6 +227,21 @@ public class Transitions implements RemoteCallable<Transitions> {
mRemoteTransitionHandler.removeFiltered(remoteTransition);
}
/**
* Runs the given {@code runnable} when the last active transition has finished, or immediately
* if there are currently no active transitions.
*
* <p>This method should be called on the Shell main-thread, where the given {@code runnable}
* will be executed when the last active transition is finished.
*/
public void runOnIdle(Runnable runnable) {
if (mActiveTransitions.isEmpty()) {
runnable.run();
} else {
mRunWhenIdleQueue.add(runnable);
}
}
/** @return true if the transition was triggered by opening something vs closing something */
public static boolean isOpeningType(@WindowManager.TransitionType int type) {
return type == TRANSIT_OPEN
@@ -520,6 +538,11 @@ public class Transitions implements RemoteCallable<Transitions> {
if (mActiveTransitions.size() <= activeIdx) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations "
+ "finished");
// Run all runnables from the run-when-idle queue.
for (int i = 0; i < mRunWhenIdleQueue.size(); i++) {
mRunWhenIdleQueue.get(i).run();
}
mRunWhenIdleQueue.clear();
return;
}
// Start animating the next active transition

View File

@@ -53,6 +53,7 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
import org.junit.Test;
@@ -62,6 +63,8 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import dagger.Lazy;
/**
* Tests for {@link CompatUIController}.
*
@@ -82,6 +85,7 @@ public class CompatUIControllerTest extends ShellTestCase {
private @Mock ShellTaskOrganizer.TaskListener mMockTaskListener;
private @Mock SyncTransactionQueue mMockSyncQueue;
private @Mock ShellExecutor mMockExecutor;
private @Mock Lazy<Transitions> mMockTransitionsLazy;
private @Mock CompatUIWindowManager mMockCompatLayout;
private @Mock LetterboxEduWindowManager mMockLetterboxEduLayout;
@@ -102,7 +106,8 @@ public class CompatUIControllerTest extends ShellTestCase {
doReturn(true).when(mMockLetterboxEduLayout).createLayout(anyBoolean());
doReturn(true).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean());
mController = new CompatUIController(mContext, mMockDisplayController,
mMockDisplayInsetsController, mMockImeController, mMockSyncQueue, mMockExecutor) {
mMockDisplayInsetsController, mMockImeController, mMockSyncQueue, mMockExecutor,
mMockTransitionsLazy) {
@Override
CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
ShellTaskOrganizer.TaskListener taskListener) {

View File

@@ -54,6 +54,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.transition.Transitions;
import org.junit.After;
import org.junit.Before;
@@ -86,11 +87,14 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
private ArgumentCaptor<WindowManager.LayoutParams> mWindowAttrsCaptor;
@Captor
private ArgumentCaptor<Runnable> mEndCallbackCaptor;
@Captor
private ArgumentCaptor<Runnable> mRunOnIdleCaptor;
@Mock private LetterboxEduAnimationController mAnimationController;
@Mock private SyncTransactionQueue mSyncTransactionQueue;
@Mock private ShellTaskOrganizer.TaskListener mTaskListener;
@Mock private SurfaceControlViewHost mViewHost;
@Mock private Transitions mTransitions;
@Mock private Runnable mOnDismissCallback;
private SharedPreferences mSharedPreferences;
@@ -203,6 +207,23 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
verify(mOnDismissCallback).run();
}
@Test
public void testCreateLayout_windowManagerReleasedBeforeTransitionsIsIdle_doesNotStartAnim() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
assertTrue(windowManager.createLayout(/* canShow= */ true));
assertTrue(mSharedPreferences.getBoolean(mPrefKey, /* default= */ false));
verify(mTransitions).runOnIdle(mRunOnIdleCaptor.capture());
windowManager.release();
mRunOnIdleCaptor.getValue().run();
verify(mAnimationController, never()).startEnterAnimation(any(), any());
}
@Test
public void testUpdateCompatInfo_updatesLayoutCorrectly() {
LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true);
@@ -303,6 +324,13 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
}
private void verifyAndFinishEnterAnimation(LetterboxEduDialogLayout layout) {
verify(mTransitions).runOnIdle(mRunOnIdleCaptor.capture());
// startEnterAnimation isn't called until run-on-idle runnable is called.
verify(mAnimationController, never()).startEnterAnimation(any(), any());
mRunOnIdleCaptor.getValue().run();
verify(mAnimationController).startEnterAnimation(eq(layout), mEndCallbackCaptor.capture());
mEndCallbackCaptor.getValue().run();
}
@@ -320,7 +348,8 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase {
boolean isTaskbarEduShowing) {
LetterboxEduWindowManager windowManager = new LetterboxEduWindowManager(mContext,
createTaskInfo(eligible), mSyncTransactionQueue, mTaskListener,
createDisplayLayout(), mOnDismissCallback, mAnimationController);
createDisplayLayout(), mTransitions, mOnDismissCallback,
mAnimationController);
spyOn(windowManager);
doReturn(mViewHost).when(windowManager).createSurfaceViewHost();

View File

@@ -46,6 +46,7 @@ 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.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@@ -93,7 +94,7 @@ import java.util.ArrayList;
* Tests for the shell transitions.
*
* Build/Install/Run:
* atest WMShellUnitTests:ShellTransitionTests
* atest WMShellUnitTests:ShellTransitionTests
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -600,6 +601,83 @@ public class ShellTransitionTests {
assertTrue(DefaultTransitionHandler.isRotationSeamless(seamlessDisplay, displays));
}
@Test
public void testRunWhenIdle() {
Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
Runnable runnable1 = mock(Runnable.class);
Runnable runnable2 = mock(Runnable.class);
Runnable runnable3 = mock(Runnable.class);
Runnable runnable4 = mock(Runnable.class);
transitions.runOnIdle(runnable1);
// runnable1 is executed immediately because there are no active transitions.
verify(runnable1, times(1)).run();
clearInvocations(runnable1);
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();
transitions.onTransitionReady(transitToken1, info1, mock(SurfaceControl.Transaction.class),
mock(SurfaceControl.Transaction.class));
assertEquals(1, mDefaultHandler.activeCount());
transitions.runOnIdle(runnable2);
transitions.runOnIdle(runnable3);
// runnable2 and runnable3 aren't executed immediately because there is an active
// transaction.
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();
transitions.onTransitionReady(transitToken2, info2, mock(SurfaceControl.Transaction.class),
mock(SurfaceControl.Transaction.class));
assertEquals(1, mDefaultHandler.activeCount());
mDefaultHandler.finishAll();
mMainExecutor.flushAll();
// first transition finished
verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any(), any());
verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any(), any());
// But now the "queued" transition is running
assertEquals(1, mDefaultHandler.activeCount());
// runnable2 and runnable3 are still not executed because the second transition is still
// active.
verify(runnable2, times(0)).run();
verify(runnable3, times(0)).run();
mDefaultHandler.finishAll();
mMainExecutor.flushAll();
verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any(), any());
// runnable2 and runnable3 are executed after the second transition finishes because there
// are no other active transitions, runnable1 isn't executed again.
verify(runnable1, times(0)).run();
verify(runnable2, times(1)).run();
verify(runnable3, times(1)).run();
clearInvocations(runnable2);
clearInvocations(runnable3);
transitions.runOnIdle(runnable4);
// runnable4 is executed immediately because there are no active transitions, all other
// runnables aren't executed again.
verify(runnable1, times(0)).run();
verify(runnable2, times(0)).run();
verify(runnable3, times(0)).run();
verify(runnable4, times(1)).run();
}
class TransitionInfoBuilder {
final TransitionInfo mInfo;
@@ -749,7 +827,7 @@ public class ShellTransitionTests {
IWindowManager mockWM = mock(IWindowManager.class);
final IDisplayWindowListener[] displayListener = new IDisplayWindowListener[1];
try {
doReturn(new int[] {DEFAULT_DISPLAY}).when(mockWM).registerDisplayWindowListener(any());
doReturn(new int[]{DEFAULT_DISPLAY}).when(mockWM).registerDisplayWindowListener(any());
} catch (RemoteException e) {
// No remote stuff happening, so this can't be hit
}