[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:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user