[DO NOT MERGE] Animate dialog stack and use in UserSwitcher am: 327d67cb2a
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/16244707
Change-Id: Ic3b4406d1d64029a7062772f0eb841ec612f6f74
diff --git a/packages/SystemUI/animation/res/values/ids.xml b/packages/SystemUI/animation/res/values/ids.xml
index ef60a24..c4cb89f 100644
--- a/packages/SystemUI/animation/res/values/ids.xml
+++ b/packages/SystemUI/animation/res/values/ids.xml
@@ -16,4 +16,5 @@
-->
<resources>
<item type="id" name="launch_animation_running"/>
+ <item type="id" name="dialog_content_parent" />
</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 413612f..9aad278 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -40,6 +40,7 @@
import kotlin.math.roundToInt
private const val TAG = "DialogLaunchAnimator"
+private val DIALOG_CONTENT_PARENT_ID = R.id.dialog_content_parent
/**
* A class that allows dialogs to be started in a seamless way from a view that is transforming
@@ -86,10 +87,10 @@
// If the parent of the view we are launching from is the background of some other animated
// dialog, then this means the caller intent is to launch a dialog from another dialog. In
// this case, we also animate the parent (which is the dialog background).
- val dialogContentParent = openedDialogs
+ val animatedParent = openedDialogs
.firstOrNull { it.dialogContentParent == view.parent }
- ?.dialogContentParent
- val animateFrom = dialogContentParent ?: view
+ val parentHostDialog = animatedParent?.hostDialog
+ val animateFrom = animatedParent?.dialogContentParent ?: view
// Make sure we don't run the launch animation from the same view twice at the same time.
if (animateFrom.getTag(TAG_LAUNCH_ANIMATION_RUNNING) != null) {
@@ -100,12 +101,18 @@
animateFrom.setTag(TAG_LAUNCH_ANIMATION_RUNNING, true)
- val launchAnimation = AnimatedDialog(
- context, launchAnimator, hostDialogProvider, animateFrom,
- onDialogDismissed = { openedDialogs.remove(it) }, originalDialog = dialog,
- animateBackgroundBoundsChange)
- val hostDialog = launchAnimation.hostDialog
- openedDialogs.add(launchAnimation)
+ val animatedDialog = AnimatedDialog(
+ context,
+ launchAnimator,
+ hostDialogProvider,
+ animateFrom,
+ onDialogDismissed = { openedDialogs.remove(it) },
+ originalDialog = dialog,
+ animateBackgroundBoundsChange,
+ openedDialogs.firstOrNull { it.hostDialog == parentHostDialog }
+ )
+ val hostDialog = animatedDialog.hostDialog
+ openedDialogs.add(animatedDialog)
// If the dialog is dismissed/hidden/shown, then we should actually dismiss/hide/show the
// host dialog.
@@ -119,15 +126,15 @@
// If AOD is disabled the screen will directly becomes black and we won't see
// the animation anyways.
if (reason == DialogListener.DismissReason.DEVICE_LOCKED) {
- launchAnimation.exitAnimationDisabled = true
+ animatedDialog.exitAnimationDisabled = true
}
hostDialog.dismiss()
}
override fun onHide() {
- if (launchAnimation.ignoreNextCallToHide) {
- launchAnimation.ignoreNextCallToHide = false
+ if (animatedDialog.ignoreNextCallToHide) {
+ animatedDialog.ignoreNextCallToHide = false
return
}
@@ -138,21 +145,44 @@
hostDialog.show()
// We don't actually want to show the original dialog, so hide it.
- launchAnimation.ignoreNextCallToHide = true
+ animatedDialog.ignoreNextCallToHide = true
dialog.hide()
}
override fun onSizeChanged() {
- launchAnimation.onOriginalDialogSizeChanged()
+ animatedDialog.onOriginalDialogSizeChanged()
+ }
+
+ override fun prepareForStackDismiss() {
+ animatedDialog.touchSurface = animatedDialog.prepareForStackDismiss()
}
})
}
- launchAnimation.start()
+ animatedDialog.start()
return hostDialog
}
/**
+ * Launch [dialog] from a [parentHostDialog] as returned by [showFromView]. This will allow
+ * for dismissing the whole stack.
+ *
+ * This will return a new host dialog, with the same caveat as [showFromView].
+ *
+ * @see DialogListener.prepareForStackDismiss
+ */
+ fun showFromDialog(
+ dialog: Dialog,
+ parentHostDialog: Dialog,
+ animateBackgroundBoundsChange: Boolean = false
+ ): Dialog {
+ val view = parentHostDialog.findViewById<ViewGroup>(DIALOG_CONTENT_PARENT_ID)
+ ?.getChildAt(0)
+ ?: throw IllegalStateException("No dialog content parent found in host dialog")
+ return showFromView(dialog, view, animateBackgroundBoundsChange)
+ }
+
+ /**
* Ensure that all dialogs currently shown won't animate into their touch surface when
* dismissed.
*
@@ -214,6 +244,12 @@
/** Called when this dialog show() is called. */
fun onShow()
+ /**
+ * Call before dismissing a stack of dialogs (dialogs launched from dialogs), so the topmost
+ * can animate directly into the original `touchSurface`.
+ */
+ fun prepareForStackDismiss()
+
/** Called when this dialog size might have changed, e.g. because of configuration changes. */
fun onSizeChanged()
}
@@ -224,7 +260,7 @@
hostDialogProvider: HostDialogProvider,
/** The view that triggered the dialog after being tapped. */
- private val touchSurface: View,
+ var touchSurface: View,
/**
* A callback that will be called with this [AnimatedDialog] after the dialog was
@@ -236,7 +272,10 @@
private val originalDialog: Dialog,
/** Whether we should animate the dialog background when its bounds change. */
- private val animateBackgroundBoundsChange: Boolean
+ private val animateBackgroundBoundsChange: Boolean,
+
+ /** Launch animation corresponding to the parent [hostDialog]. */
+ private val parentAnimatedDialog: AnimatedDialog? = null
) {
/**
* The fullscreen dialog to which we will add the content view [originalDialogView] of
@@ -253,7 +292,9 @@
* the same size as the original dialog window and to which we will set the original dialog
* window background.
*/
- val dialogContentParent = FrameLayout(context)
+ val dialogContentParent = FrameLayout(context).apply {
+ id = DIALOG_CONTENT_PARENT_ID
+ }
/**
* The background color of [originalDialogView], taking into consideration the [originalDialog]
@@ -359,9 +400,7 @@
// Make the touch surface invisible and make sure that it stays invisible as long as the
// dialog is shown or animating.
touchSurface.visibility = View.INVISIBLE
- if (touchSurface is LaunchableView) {
- touchSurface.setShouldBlockVisibilityChanges(true)
- }
+ (touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
// Add a pre draw listener to (maybe) start the animation once the touch surface is
// actually invisible.
@@ -576,9 +615,7 @@
Log.i(TAG, "Skipping animation of dialog into the touch surface")
// Make sure we allow the touch surface to change its visibility again.
- if (touchSurface is LaunchableView) {
- touchSurface.setShouldBlockVisibilityChanges(false)
- }
+ (touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
// If the view is invisible it's probably because of us, so we make it visible again.
if (touchSurface.visibility == View.INVISIBLE) {
@@ -598,9 +635,7 @@
},
onLaunchAnimationEnd = {
// Make sure we allow the touch surface to change its visibility again.
- if (touchSurface is LaunchableView) {
- touchSurface.setShouldBlockVisibilityChanges(false)
- }
+ (touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
touchSurface.visibility = View.VISIBLE
dialogContentParent.visibility = View.INVISIBLE
@@ -796,4 +831,18 @@
animator.start()
}
}
+
+ fun prepareForStackDismiss(): View {
+ if (parentAnimatedDialog == null) {
+ return touchSurface
+ }
+ parentAnimatedDialog.exitAnimationDisabled = true
+ parentAnimatedDialog.originalDialog.hide()
+ val view = parentAnimatedDialog.prepareForStackDismiss()
+ parentAnimatedDialog.originalDialog.dismiss()
+ // Make the touch surface invisible, so we end up animating to it when we actually
+ // dismiss the stack
+ view.visibility = View.INVISIBLE
+ return view
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index 821bd51..be9aa0e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -28,6 +28,8 @@
import android.view.View;
import android.view.ViewGroup;
+import androidx.annotation.Nullable;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -37,10 +39,10 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.qs.PseudoGridView;
import com.android.systemui.qs.QSUserSwitcherEvent;
+import com.android.systemui.qs.user.UserSwitchDialogController;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.UserSwitcherController;
-import java.util.function.Consumer;
-
import javax.inject.Inject;
/**
@@ -77,7 +79,7 @@
private View mCurrentUserView;
private final UiEventLogger mUiEventLogger;
private final FalsingManager mFalsingManager;
- private Consumer<UserSwitcherController.UserRecord> mClickCallback;
+ private @Nullable UserSwitchDialogController.DialogShower mDialogShower;
@Inject
public Adapter(Context context, UserSwitcherController controller,
@@ -95,8 +97,17 @@
return createUserDetailItemView(convertView, parent, item);
}
- public void injectCallback(Consumer<UserSwitcherController.UserRecord> clickCallback) {
- mClickCallback = clickCallback;
+ /**
+ * If this adapter is inside a dialog, passing a
+ * {@link UserSwitchDialogController.DialogShower} will help animate to and from the parent
+ * dialog. This will also allow for dismissing the whole stack of dialogs in a single
+ * animation.
+ *
+ * @param shower
+ * @see SystemUIDialog#dismissStack()
+ */
+ public void injectDialogShower(UserSwitchDialogController.DialogShower shower) {
+ mDialogShower = shower;
}
public UserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent,
@@ -172,10 +183,7 @@
}
view.setActivated(true);
}
- onUserListItemClicked(tag);
- }
- if (mClickCallback != null) {
- mClickCallback.accept(tag);
+ onUserListItemClicked(tag, mDialogShower);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
index bae7996..d74a50e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
@@ -16,7 +16,9 @@
package com.android.systemui.qs.user
+import android.app.Dialog
import android.content.Context
+import android.content.DialogInterface
import android.content.Intent
import android.provider.Settings
import android.view.View
@@ -84,12 +86,26 @@
doneButton.setOnClickListener { dismiss() }
val adapter = userDetailViewAdapterProvider.get()
- adapter.injectCallback {
- dismiss()
- }
adapter.linkToViewGroup(grid)
- dialogLaunchAnimator.showFromView(this, view)
+ val hostDialog = dialogLaunchAnimator.showFromView(this, view)
+ adapter.injectDialogShower(DialogShowerImpl(hostDialog, dialogLaunchAnimator))
}
}
+
+ private class DialogShowerImpl(
+ private val hostDialog: Dialog,
+ private val dialogLaunchAnimator: DialogLaunchAnimator
+ ) : DialogInterface by hostDialog, DialogShower {
+ override fun showDialog(dialog: Dialog): Dialog {
+ return dialogLaunchAnimator.showFromDialog(
+ dialog,
+ parentHostDialog = hostDialog
+ )
+ }
+ }
+
+ interface DialogShower : DialogInterface {
+ fun showDialog(dialog: Dialog): Dialog
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index cf4aaba..1130ec2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -218,6 +218,19 @@
}
}
+ /**
+ * Dismiss this dialog. If it was launched from another dialog using
+ * {@link com.android.systemui.animation.DialogLaunchAnimator#showFromView} with a
+ * non-{@code null} {@code parentHostDialog} parameter, also dismisses the stack of dialogs,
+ * animating back to the original touchSurface.
+ */
+ public void dismissStack() {
+ for (DialogListener listener : new LinkedHashSet<>(mDialogListeners)) {
+ listener.prepareForStackDismiss();
+ }
+ dismiss();
+ }
+
@Override
public void hide() {
super.hide();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index b630689..fd387ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -55,6 +55,8 @@
import android.view.WindowManagerGlobal;
import android.widget.BaseAdapter;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
@@ -77,6 +79,7 @@
import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.qs.QSUserSwitcherEvent;
import com.android.systemui.qs.tiles.UserDetailView;
+import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -460,7 +463,7 @@
}
@VisibleForTesting
- void onUserListItemClicked(UserRecord record) {
+ void onUserListItemClicked(UserRecord record, DialogShower dialogShower) {
int id;
if (record.isGuest && record.info == null) {
// No guest user. Create one.
@@ -472,7 +475,7 @@
mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_ADD);
id = guestId;
} else if (record.isAddUser) {
- showAddUserDialog();
+ showAddUserDialog(dialogShower);
return;
} else {
id = record.info.id;
@@ -481,7 +484,7 @@
int currUserId = mUserTracker.getUserId();
if (currUserId == id) {
if (record.isGuest) {
- showExitGuestDialog(id);
+ showExitGuestDialog(id, dialogShower);
}
return;
}
@@ -490,11 +493,15 @@
// If switching from guest, we want to bring up the guest exit dialog instead of switching
UserInfo currUserInfo = mUserManager.getUserInfo(currUserId);
if (currUserInfo != null && currUserInfo.isGuest()) {
- showExitGuestDialog(currUserId, record.resolveId());
+ showExitGuestDialog(currUserId, record.resolveId(), dialogShower);
return;
}
}
-
+ if (dialogShower != null) {
+ // If we haven't morphed into another dialog, it means we have just switched users.
+ // Then, dismiss the dialog.
+ dialogShower.dismiss();
+ }
switchToUserId(id);
}
@@ -511,7 +518,7 @@
}
}
- protected void showExitGuestDialog(int id) {
+ private void showExitGuestDialog(int id, DialogShower dialogShower) {
int newId = UserHandle.USER_SYSTEM;
if (mResumeUserOnGuestLogout && mLastNonGuestUser != UserHandle.USER_SYSTEM) {
UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
@@ -519,23 +526,31 @@
newId = info.id;
}
}
- showExitGuestDialog(id, newId);
+ showExitGuestDialog(id, newId, dialogShower);
}
- protected void showExitGuestDialog(int id, int targetId) {
+ private void showExitGuestDialog(int id, int targetId, DialogShower dialogShower) {
if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
mExitGuestDialog.cancel();
}
mExitGuestDialog = new ExitGuestDialog(mContext, id, targetId);
- mExitGuestDialog.show();
+ if (dialogShower != null) {
+ dialogShower.showDialog(mExitGuestDialog);
+ } else {
+ mExitGuestDialog.show();
+ }
}
- public void showAddUserDialog() {
+ private void showAddUserDialog(DialogShower dialogShower) {
if (mAddUserDialog != null && mAddUserDialog.isShowing()) {
mAddUserDialog.cancel();
}
mAddUserDialog = new AddUserDialog(mContext);
- mAddUserDialog.show();
+ if (dialogShower != null) {
+ dialogShower.showDialog(mAddUserDialog);
+ } else {
+ mAddUserDialog.show();
+ }
}
private void listenForCallState() {
@@ -868,9 +883,17 @@
/**
* It handles click events on user list items.
+ *
+ * If the user switcher is hosted in a dialog, passing a non-null {@link DialogShower}
+ * will allow animation to and from the parent dialog.
+ *
*/
+ public void onUserListItemClicked(UserRecord record, @Nullable DialogShower dialogShower) {
+ mController.onUserListItemClicked(record, dialogShower);
+ }
+
public void onUserListItemClicked(UserRecord record) {
- mController.onUserListItemClicked(record);
+ onUserListItemClicked(record, null);
}
public String getName(Context context, UserRecord item) {
@@ -1156,7 +1179,7 @@
cancel();
} else {
mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
- dismiss();
+ dismissStack();
removeGuestUser(mGuestId, mTargetId);
}
}
@@ -1187,7 +1210,7 @@
if (which == BUTTON_NEGATIVE) {
cancel();
} else {
- dismiss();
+ dismissStack();
if (ActivityManager.isUserAMonkey()) {
return;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
index d4c3840..9bd33eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
@@ -16,6 +16,7 @@
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
+import org.junit.After
import org.junit.Test
import org.junit.runner.RunWith
@@ -28,24 +29,22 @@
private val dialogLaunchAnimator =
DialogLaunchAnimator(context, launchAnimator, hostDialogprovider)
+ private val attachedViews = mutableSetOf<View>()
+
+ @After
+ fun tearDown() {
+ runOnMainThreadAndWaitForIdleSync {
+ attachedViews.forEach {
+ ViewUtils.detachView(it)
+ }
+ }
+ }
+
@Test
fun testShowDialogFromView() {
// Show the dialog. showFromView() must be called on the main thread with a dialog created
// on the main thread too.
- val (dialog, hostDialog) = runOnMainThreadAndWaitForIdleSync {
- val touchSurfaceRoot = LinearLayout(context)
- val touchSurface = View(context)
- touchSurfaceRoot.addView(touchSurface)
-
- // We need to attach the root to the window manager otherwise the exit animation will
- // be skipped
- ViewUtils.attachView(touchSurfaceRoot)
-
- val dialog = TestDialog(context)
- val hostDialog =
- dialogLaunchAnimator.showFromView(dialog, touchSurface) as TestHostDialog
- dialog to hostDialog
- }
+ val (dialog, hostDialog) = createDialogAndHostDialog()
// Only the host dialog is actually showing.
assertTrue(hostDialog.isShowing)
@@ -100,6 +99,51 @@
assertTrue(dialog.onStopCalled)
}
+ @Test
+ fun testStackedDialogsDismissesAll() {
+ val (_, hostDialogFirst) = createDialogAndHostDialog()
+ val (dialogSecond, hostDialogSecond) = createDialogAndHostDialogFromDialog(hostDialogFirst)
+
+ runOnMainThreadAndWaitForIdleSync {
+ dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations()
+ dialogSecond.dismissStack()
+ }
+
+ assertTrue(hostDialogSecond.wasDismissed)
+ assertTrue(hostDialogFirst.wasDismissed)
+ }
+
+ private fun createDialogAndHostDialog(): Pair<TestDialog, TestHostDialog> {
+ return runOnMainThreadAndWaitForIdleSync {
+ val touchSurfaceRoot = LinearLayout(context)
+ val touchSurface = View(context)
+ touchSurfaceRoot.addView(touchSurface)
+
+ // We need to attach the root to the window manager otherwise the exit animation will
+ // be skipped
+ ViewUtils.attachView(touchSurfaceRoot)
+ attachedViews.add(touchSurfaceRoot)
+
+ val dialog = TestDialog(context)
+ val hostDialog =
+ dialogLaunchAnimator.showFromView(dialog, touchSurface) as TestHostDialog
+ dialog to hostDialog
+ }
+ }
+
+ private fun createDialogAndHostDialogFromDialog(
+ hostParent: Dialog
+ ): Pair<TestDialog, TestHostDialog> {
+ return runOnMainThreadAndWaitForIdleSync {
+ val dialog = TestDialog(context)
+ val hostDialog = dialogLaunchAnimator.showFromDialog(
+ dialog,
+ hostParent
+ ) as TestHostDialog
+ dialog to hostDialog
+ }
+ }
+
private fun <T : Any> runOnMainThreadAndWaitForIdleSync(f: () -> T): T {
lateinit var result: T
context.mainExecutor.execute {
@@ -198,6 +242,11 @@
notifyListeners { onShow() }
}
+ fun dismissStack() {
+ notifyListeners { prepareForStackDismiss() }
+ dismiss()
+ }
+
private fun notifyListeners(notify: DialogListener.() -> Unit) {
for (listener in HashSet(listeners)) {
listener.notify()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
index 7e900c8..ea3a42c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.user
+import android.app.Dialog
import android.content.Intent
import android.provider.Settings
import android.testing.AndroidTestingRunner
@@ -27,7 +28,7 @@
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PseudoGridView
import com.android.systemui.qs.tiles.UserDetailView
-import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
@@ -39,15 +40,13 @@
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.`when`
-import org.mockito.Mockito.any
+import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.argThat
import org.mockito.Mockito.inOrder
-import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-import java.util.function.Consumer
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -71,6 +70,8 @@
private lateinit var gridView: PseudoGridView
@Mock
private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+ @Mock
+ private lateinit var hostDialog: Dialog
@Captor
private lateinit var clickCaptor: ArgumentCaptor<View.OnClickListener>
@@ -85,6 +86,8 @@
`when`(dialog.grid).thenReturn(gridView)
`when`(launchView.context).thenReturn(mContext)
+ `when`(dialogLaunchAnimator.showFromView(any(), any(), anyBoolean()))
+ .thenReturn(hostDialog)
controller = UserSwitchDialogController(
{ userDetailViewAdapter },
@@ -188,15 +191,15 @@
}
@Test
- fun callbackFromDetailView_dismissesDialog() {
- val captor = argumentCaptor<Consumer<UserSwitcherController.UserRecord>>()
+ fun callbackFromDialogShower_dismissesDialog() {
+ val captor = argumentCaptor<UserSwitchDialogController.DialogShower>()
controller.showDialog(launchView)
- verify(userDetailViewAdapter).injectCallback(capture(captor))
+ verify(userDetailViewAdapter).injectDialogShower(capture(captor))
- captor.value.accept(mock(UserSwitcherController.UserRecord::class.java))
+ captor.value.dismiss()
- verify(dialog).dismiss()
+ verify(hostDialog).dismiss()
}
private class IntentMatcher(private val action: String) : ArgumentMatcher<Intent> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
index 69ab9c5..bdd189a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
@@ -44,13 +44,16 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.QSUserSwitcherEvent
+import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.phone.NotificationShadeWindowView
import com.android.systemui.telephony.TelephonyListenerManager
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.time.FakeSystemClock
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertFalse
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -85,6 +88,8 @@
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var interactionJankMonitor: InteractionJankMonitor
@Mock private lateinit var latencyTracker: LatencyTracker
+ @Mock private lateinit var dialogShower: UserSwitchDialogController.DialogShower
+ @Mock private lateinit var notificationShadeWindowView: NotificationShadeWindowView
private lateinit var testableLooper: TestableLooper
private lateinit var uiBgExecutor: FakeExecutor
private lateinit var uiEventLogger: UiEventLoggerFake
@@ -98,6 +103,8 @@
private val guestId = 1234
private val guestInfo = UserInfo(guestId, "Guest", null,
UserInfo.FLAG_FULL or UserInfo.FLAG_GUEST, UserManager.USER_TYPE_FULL_GUEST)
+ private val secondaryUser =
+ UserInfo(10, "Secondary", null, 0, UserManager.USER_TYPE_FULL_SECONDARY)
@Before
fun setUp() {
@@ -114,6 +121,7 @@
mock(FingerprintManager::class.java))
`when`(userManager.canAddMoreUsers()).thenReturn(true)
+ `when`(notificationShadeWindowView.context).thenReturn(context)
userSwitcherController = UserSwitcherController(
context,
@@ -139,6 +147,26 @@
userSwitcherController.mPauseRefreshUsers = true
picture = UserIcons.convertToBitmap(context.getDrawable(R.drawable.ic_avatar_user))
+ userSwitcherController.init(notificationShadeWindowView)
+ }
+
+ @Test
+ fun testSwitchUser_parentDialogDismissed() {
+ val otherUserRecord = UserSwitcherController.UserRecord(
+ secondaryUser,
+ picture,
+ false /* guest */,
+ false /* current */,
+ false /* isAddUser */,
+ false /* isRestricted */,
+ true /* isSwitchToEnabled */)
+ `when`(userTracker.userId).thenReturn(ownerId)
+ `when`(userTracker.userInfo).thenReturn(ownerInfo)
+
+ userSwitcherController.onUserListItemClicked(otherUserRecord, dialogShower)
+ testableLooper.processAllMessages()
+
+ verify(dialogShower).dismiss()
}
@Test
@@ -156,7 +184,7 @@
`when`(userManager.createGuest(any(), anyString())).thenReturn(guestInfo)
- userSwitcherController.onUserListItemClicked(emptyGuestUserRecord)
+ userSwitcherController.onUserListItemClicked(emptyGuestUserRecord, null)
testableLooper.processAllMessages()
verify(interactionJankMonitor).begin(any())
verify(latencyTracker).onActionStart(LatencyTracker.ACTION_USER_SWITCH)
@@ -166,6 +194,26 @@
}
@Test
+ fun testAddGuest_parentDialogDismissed() {
+ val emptyGuestUserRecord = UserSwitcherController.UserRecord(
+ null,
+ null,
+ true /* guest */,
+ false /* current */,
+ false /* isAddUser */,
+ false /* isRestricted */,
+ true /* isSwitchToEnabled */)
+ `when`(userTracker.userId).thenReturn(ownerId)
+ `when`(userTracker.userInfo).thenReturn(ownerInfo)
+
+ `when`(userManager.createGuest(any(), anyString())).thenReturn(guestInfo)
+
+ userSwitcherController.onUserListItemClicked(emptyGuestUserRecord, dialogShower)
+ testableLooper.processAllMessages()
+ verify(dialogShower).dismiss()
+ }
+
+ @Test
fun testRemoveGuest_removeButtonPressed_isLogged() {
val currentGuestUserRecord = UserSwitcherController.UserRecord(
guestInfo,
@@ -178,7 +226,7 @@
`when`(userTracker.userId).thenReturn(guestInfo.id)
`when`(userTracker.userInfo).thenReturn(guestInfo)
- userSwitcherController.onUserListItemClicked(currentGuestUserRecord)
+ userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
assertNotNull(userSwitcherController.mExitGuestDialog)
userSwitcherController.mExitGuestDialog
.getButton(DialogInterface.BUTTON_POSITIVE).performClick()
@@ -188,6 +236,46 @@
}
@Test
+ fun testRemoveGuest_removeButtonPressed_dialogDismissed() {
+ val currentGuestUserRecord = UserSwitcherController.UserRecord(
+ guestInfo,
+ picture,
+ true /* guest */,
+ true /* current */,
+ false /* isAddUser */,
+ false /* isRestricted */,
+ true /* isSwitchToEnabled */)
+ `when`(userTracker.userId).thenReturn(guestInfo.id)
+ `when`(userTracker.userInfo).thenReturn(guestInfo)
+
+ userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
+ assertNotNull(userSwitcherController.mExitGuestDialog)
+ userSwitcherController.mExitGuestDialog
+ .getButton(DialogInterface.BUTTON_POSITIVE).performClick()
+ testableLooper.processAllMessages()
+ assertFalse(userSwitcherController.mExitGuestDialog.isShowing)
+ }
+
+ @Test
+ fun testRemoveGuest_dialogShowerUsed() {
+ val currentGuestUserRecord = UserSwitcherController.UserRecord(
+ guestInfo,
+ picture,
+ true /* guest */,
+ true /* current */,
+ false /* isAddUser */,
+ false /* isRestricted */,
+ true /* isSwitchToEnabled */)
+ `when`(userTracker.userId).thenReturn(guestInfo.id)
+ `when`(userTracker.userInfo).thenReturn(guestInfo)
+
+ userSwitcherController.onUserListItemClicked(currentGuestUserRecord, dialogShower)
+ assertNotNull(userSwitcherController.mExitGuestDialog)
+ testableLooper.processAllMessages()
+ verify(dialogShower).showDialog(userSwitcherController.mExitGuestDialog)
+ }
+
+ @Test
fun testRemoveGuest_cancelButtonPressed_isNotLogged() {
val currentGuestUserRecord = UserSwitcherController.UserRecord(
guestInfo,
@@ -200,7 +288,7 @@
`when`(userTracker.userId).thenReturn(guestId)
`when`(userTracker.userInfo).thenReturn(guestInfo)
- userSwitcherController.onUserListItemClicked(currentGuestUserRecord)
+ userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
assertNotNull(userSwitcherController.mExitGuestDialog)
userSwitcherController.mExitGuestDialog
.getButton(DialogInterface.BUTTON_NEGATIVE).performClick()
@@ -226,7 +314,7 @@
eq(GuestResumeSessionReceiver.SETTING_GUEST_HAS_LOGGED_IN), anyInt(), anyInt()))
.thenReturn(1)
- userSwitcherController.onUserListItemClicked(currentGuestUserRecord)
+ userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
// Simulate a user switch event
val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, guestId)
@@ -260,7 +348,7 @@
eq(GuestResumeSessionReceiver.SETTING_GUEST_HAS_LOGGED_IN), anyInt(), anyInt()))
.thenReturn(1)
- userSwitcherController.onUserListItemClicked(currentGuestUserRecord)
+ userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
// Simulate a user switch event
val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, guestId)