diff --git a/packages/SystemUI/res/color/prv_color_surface.xml b/packages/SystemUI/res/color/prv_color_surface.xml
new file mode 100644
index 0000000000000..b9d016c46ba0c
--- /dev/null
+++ b/packages/SystemUI/res/color/prv_color_surface.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/prv_text_color_on_accent.xml b/packages/SystemUI/res/color/prv_text_color_on_accent.xml
new file mode 100644
index 0000000000000..9f44acadb420c
--- /dev/null
+++ b/packages/SystemUI/res/color/prv_text_color_on_accent.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml
new file mode 100644
index 0000000000000..1a128dfe8b109
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml
@@ -0,0 +1,39 @@
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
new file mode 100644
index 0000000000000..467c20f3ffcd0
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
@@ -0,0 +1,42 @@
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/layout/qs_user_dialog_content.xml b/packages/SystemUI/res/layout/qs_user_dialog_content.xml
new file mode 100644
index 0000000000000..321fe68e2f131
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_user_dialog_content.xml
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 3121ce37490a1..db699242a0618 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -77,6 +77,7 @@
+
+ 6dp
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index 9a438df5b7fc1..d6d98b9397ac2 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -53,4 +53,6 @@
true
false
+
+ true
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c26de37517d46..ec63df5e0fe2b 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3026,4 +3026,7 @@
See all
To switch networks, disconnect ethernet
+
+
+ Select user
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 6594216e4290f..6e51ec715da54 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -929,6 +929,39 @@
- 32dp
+
+
+
+
+
+
+
+
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
index b17041bc83f2e..f81811ac0ffbb 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
@@ -192,6 +192,13 @@ public class FeatureFlags {
return FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL);
}
+ /**
+ * Use the new version of the user switcher
+ */
+ public boolean useNewUserSwitcher() {
+ return mFlagReader.isEnabled(R.bool.flag_new_user_switcher);
+ }
+
/** static method for the system setting */
public static boolean isProviderModelSettingEnabled(Context context) {
return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java b/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java
index 87c64c78edc84..2f189beb7780b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PseudoGridView.java
@@ -38,6 +38,7 @@ public class PseudoGridView extends ViewGroup {
private int mNumColumns = 3;
private int mVerticalSpacing;
private int mHorizontalSpacing;
+ private int mFixedChildWidth = -1;
public PseudoGridView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -53,6 +54,8 @@ public class PseudoGridView extends ViewGroup {
mVerticalSpacing = a.getDimensionPixelSize(attr, 0);
} else if (attr == R.styleable.PseudoGridView_horizontalSpacing) {
mHorizontalSpacing = a.getDimensionPixelSize(attr, 0);
+ } else if (attr == R.styleable.PseudoGridView_fixedChildWidth) {
+ mFixedChildWidth = a.getDimensionPixelSize(attr, -1);
}
}
@@ -65,8 +68,15 @@ public class PseudoGridView extends ViewGroup {
throw new UnsupportedOperationException("Needs a maximum width");
}
int width = MeasureSpec.getSize(widthMeasureSpec);
-
- int childWidth = (width - (mNumColumns - 1) * mHorizontalSpacing) / mNumColumns;
+ int childWidth;
+ int necessarySpaceForChildWidth =
+ mFixedChildWidth * mNumColumns + mHorizontalSpacing * (mNumColumns - 1);
+ if (mFixedChildWidth != -1 && necessarySpaceForChildWidth <= width) {
+ childWidth = mFixedChildWidth;
+ width = mFixedChildWidth * mNumColumns + mHorizontalSpacing * (mNumColumns - 1);
+ } else {
+ childWidth = (width - (mNumColumns - 1) * mHorizontalSpacing) / mNumColumns;
+ }
int childWidthSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
int childHeightSpec = MeasureSpec.UNSPECIFIED;
int totalHeight = 0;
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 04437ea14bb3a..821bd5117d185 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -39,6 +39,8 @@ import com.android.systemui.qs.PseudoGridView;
import com.android.systemui.qs.QSUserSwitcherEvent;
import com.android.systemui.statusbar.policy.UserSwitcherController;
+import java.util.function.Consumer;
+
import javax.inject.Inject;
/**
@@ -75,6 +77,7 @@ public class UserDetailView extends PseudoGridView {
private View mCurrentUserView;
private final UiEventLogger mUiEventLogger;
private final FalsingManager mFalsingManager;
+ private Consumer mClickCallback;
@Inject
public Adapter(Context context, UserSwitcherController controller,
@@ -92,6 +95,10 @@ public class UserDetailView extends PseudoGridView {
return createUserDetailItemView(convertView, parent, item);
}
+ public void injectCallback(Consumer clickCallback) {
+ mClickCallback = clickCallback;
+ }
+
public UserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent,
UserSwitcherController.UserRecord item) {
UserDetailItemView v = UserDetailItemView.convertOrInflate(
@@ -167,6 +174,13 @@ public class UserDetailView extends PseudoGridView {
}
onUserListItemClicked(tag);
}
+ if (mClickCallback != null) {
+ mClickCallback.accept(tag);
+ }
+ }
+
+ public void linkToViewGroup(ViewGroup viewGroup) {
+ PseudoGridView.ViewGroupAdapterBridge.link(viewGroup, this);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt
new file mode 100644
index 0000000000000..2ad06c1c172c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserDialog.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.user
+
+import android.content.Context
+import android.os.Bundle
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowInsets
+import android.view.WindowManager
+import com.android.systemui.qs.PseudoGridView
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.R
+
+/**
+ * Dialog for switching users or creating new ones.
+ */
+class UserDialog(
+ context: Context
+) : SystemUIDialog(context, R.style.Theme_SystemUI_Dialog_QSDialog) {
+
+ // create() is no-op after creation
+ private lateinit var _doneButton: View
+ /**
+ * Button with text "Done" in dialog.
+ */
+ val doneButton: View
+ get() {
+ create()
+ return _doneButton
+ }
+
+ private lateinit var _settingsButton: View
+ /**
+ * Button with text "User Settings" in dialog.
+ */
+ val settingsButton: View
+ get() {
+ create()
+ return _settingsButton
+ }
+
+ private lateinit var _grid: PseudoGridView
+ /**
+ * Grid to populate with user avatar from adapter
+ */
+ val grid: ViewGroup
+ get() {
+ create()
+ return _grid
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ window?.apply {
+ setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
+ attributes.fitInsetsTypes = attributes.fitInsetsTypes or WindowInsets.Type.statusBars()
+ attributes.receiveInsetsIgnoringZOrder = true
+ setLayout(
+ context.resources.getDimensionPixelSize(R.dimen.qs_panel_width),
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ setGravity(Gravity.CENTER)
+ }
+ setContentView(R.layout.qs_user_dialog_content)
+
+ _doneButton = requireViewById(R.id.done)
+ _settingsButton = requireViewById(R.id.settings)
+ _grid = requireViewById(R.id.grid)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
new file mode 100644
index 0000000000000..a5e4ba1b5b4bf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.user
+
+import android.content.Context
+import android.content.Intent
+import android.provider.Settings
+import android.view.View
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.tiles.UserDetailView
+import javax.inject.Inject
+import javax.inject.Provider
+
+/**
+ * Controller for [UserDialog].
+ */
+@SysUISingleton
+class UserSwitchDialogController @VisibleForTesting constructor(
+ private val userDetailViewAdapterProvider: Provider,
+ private val activityStarter: ActivityStarter,
+ private val falsingManager: FalsingManager,
+ private val dialogFactory: (Context) -> UserDialog
+) {
+
+ @Inject
+ constructor(
+ userDetailViewAdapterProvider: Provider,
+ activityStarter: ActivityStarter,
+ falsingManager: FalsingManager
+ ) : this(
+ userDetailViewAdapterProvider,
+ activityStarter,
+ falsingManager,
+ { UserDialog(it) }
+ )
+
+ companion object {
+ private val USER_SETTINGS_INTENT = Intent(Settings.ACTION_USER_SETTINGS)
+ }
+
+ /**
+ * Show a [UserDialog].
+ *
+ * Populate the dialog with information from and adapter obtained from
+ * [userDetailViewAdapterProvider] and show it as launched from [view].
+ */
+ fun showDialog(view: View) {
+ with(dialogFactory(view.context)) {
+ setShowForAllUsers(true)
+ setCanceledOnTouchOutside(true)
+ create() // Needs to be called before we can retrieve views
+
+ settingsButton.setOnClickListener {
+ if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ activityStarter.postStartActivityDismissingKeyguard(USER_SETTINGS_INTENT, 0)
+ }
+ dismiss()
+ }
+ doneButton.setOnClickListener { dismiss() }
+
+ val adapter = userDetailViewAdapterProvider.get()
+ adapter.injectCallback {
+ dismiss()
+ }
+ adapter.linkToViewGroup(grid)
+
+ show()
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
index 3fdf1ceaa2d9d..dd21c8af37e07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
@@ -23,11 +23,13 @@ import android.view.View;
import android.view.ViewGroup;
import com.android.systemui.R;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.qs.FooterActionsView;
import com.android.systemui.qs.QSDetailDisplayer;
import com.android.systemui.qs.dagger.QSScope;
+import com.android.systemui.qs.user.UserSwitchDialogController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.ViewController;
@@ -39,6 +41,8 @@ public class MultiUserSwitchController extends ViewController {
private final UserSwitcherController mUserSwitcherController;
private final QSDetailDisplayer mQsDetailDisplayer;
private final FalsingManager mFalsingManager;
+ private final UserSwitchDialogController mUserSwitchDialogController;
+ private final FeatureFlags mFeatureFlags;
private UserSwitcherController.BaseUserAdapter mUserListener;
@@ -49,14 +53,18 @@ public class MultiUserSwitchController extends ViewController {
return;
}
- View center = mView.getChildCount() > 0 ? mView.getChildAt(0) : mView;
+ if (mFeatureFlags.useNewUserSwitcher()) {
+ mUserSwitchDialogController.showDialog(v);
+ } else {
+ View center = mView.getChildCount() > 0 ? mView.getChildAt(0) : mView;
- int[] tmpInt = new int[2];
- center.getLocationInWindow(tmpInt);
- tmpInt[0] += center.getWidth() / 2;
- tmpInt[1] += center.getHeight() / 2;
+ int[] tmpInt = new int[2];
+ center.getLocationInWindow(tmpInt);
+ tmpInt[0] += center.getWidth() / 2;
+ tmpInt[1] += center.getHeight() / 2;
- mQsDetailDisplayer.showDetailAdapter(getUserDetailAdapter(), tmpInt[0], tmpInt[1]);
+ mQsDetailDisplayer.showDetailAdapter(getUserDetailAdapter(), tmpInt[0], tmpInt[1]);
+ }
}
};
@@ -66,31 +74,40 @@ public class MultiUserSwitchController extends ViewController {
private final UserSwitcherController mUserSwitcherController;
private final QSDetailDisplayer mQsDetailDisplayer;
private final FalsingManager mFalsingManager;
+ private final UserSwitchDialogController mUserSwitchDialogController;
+ private final FeatureFlags mFeatureFlags;
@Inject
public Factory(UserManager userManager, UserSwitcherController userSwitcherController,
- QSDetailDisplayer qsDetailDisplayer, FalsingManager falsingManager) {
+ QSDetailDisplayer qsDetailDisplayer, FalsingManager falsingManager,
+ UserSwitchDialogController userSwitchDialogController,
+ FeatureFlags featureFlags) {
mUserManager = userManager;
mUserSwitcherController = userSwitcherController;
mQsDetailDisplayer = qsDetailDisplayer;
mFalsingManager = falsingManager;
+ mUserSwitchDialogController = userSwitchDialogController;
+ mFeatureFlags = featureFlags;
}
public MultiUserSwitchController create(FooterActionsView view) {
return new MultiUserSwitchController(view.findViewById(R.id.multi_user_switch),
mUserManager, mUserSwitcherController, mQsDetailDisplayer,
- mFalsingManager);
+ mFalsingManager, mUserSwitchDialogController, mFeatureFlags);
}
}
private MultiUserSwitchController(MultiUserSwitch view, UserManager userManager,
UserSwitcherController userSwitcherController, QSDetailDisplayer qsDetailDisplayer,
- FalsingManager falsingManager) {
+ FalsingManager falsingManager, UserSwitchDialogController userSwitchDialogController,
+ FeatureFlags featureFlags) {
super(view);
mUserManager = userManager;
mUserSwitcherController = userSwitcherController;
mQsDetailDisplayer = qsDetailDisplayer;
mFalsingManager = falsingManager;
+ mUserSwitchDialogController = userSwitchDialogController;
+ mFeatureFlags = featureFlags;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index d838a05135e7f..b4aba380d971e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -35,10 +35,12 @@ import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
import com.android.settingslib.drawable.CircleFramedDrawable;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.tiles.UserDetailView;
+import com.android.systemui.qs.user.UserSwitchDialogController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -76,6 +78,8 @@ public class KeyguardQsUserSwitchController extends ViewController
private final ConfigurationController mConfigurationController;
private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
private final KeyguardUserDetailAdapter mUserDetailAdapter;
+ private final FeatureFlags mFeatureFlags;
+ private final UserSwitchDialogController mUserSwitchDialogController;
private NotificationPanelViewController mNotificationPanelViewController;
private UserAvatarView mUserAvatarView;
UserSwitcherController.UserRecord mCurrentUser;
@@ -124,7 +128,9 @@ public class KeyguardQsUserSwitchController extends ViewController
SysuiStatusBarStateController statusBarStateController,
DozeParameters dozeParameters,
Provider userDetailViewAdapterProvider,
- UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
+ UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
+ FeatureFlags featureFlags,
+ UserSwitchDialogController userSwitchDialogController) {
super(view);
if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController");
mContext = context;
@@ -139,6 +145,8 @@ public class KeyguardQsUserSwitchController extends ViewController
keyguardStateController, dozeParameters,
unlockedScreenOffAnimationController, /* animateYPos= */ false);
mUserDetailAdapter = new KeyguardUserDetailAdapter(context, userDetailViewAdapterProvider);
+ mFeatureFlags = featureFlags;
+ mUserSwitchDialogController = userSwitchDialogController;
}
@Override
@@ -162,7 +170,11 @@ public class KeyguardQsUserSwitchController extends ViewController
}
// Tapping anywhere in the view will open QS user panel
- openQsUserPanel();
+ if (mFeatureFlags.useNewUserSwitcher()) {
+ mUserSwitchDialogController.showDialog(mView);
+ } else {
+ openQsUserPanel();
+ }
});
mUserAvatarView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserDialogTest.kt
new file mode 100644
index 0000000000000..d5fe588b21150
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserDialogTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.user
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class UserDialogTest : SysuiTestCase() {
+
+ private lateinit var dialog: UserDialog
+
+ @Before
+ fun setUp() {
+ dialog = UserDialog(mContext)
+ }
+
+ @After
+ fun tearDown() {
+ dialog.dismiss()
+ }
+
+ @Test
+ fun doneButtonExists() {
+ assertThat(dialog.doneButton).isInstanceOf(View::class.java)
+ }
+
+ @Test
+ fun settingsButtonExists() {
+ assertThat(dialog.settingsButton).isInstanceOf(View::class.java)
+ }
+
+ @Test
+ fun gridExistsAndIsViewGroup() {
+ assertThat(dialog.grid).isInstanceOf(ViewGroup::class.java)
+ }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000000000..a1760a79417f4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.user
+
+import android.content.Intent
+import android.provider.Settings
+import android.testing.AndroidTestingRunner
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.ActivityStarter
+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.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatcher
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+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)
+class UserSwitchDialogControllerTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var dialog: UserDialog
+ @Mock
+ private lateinit var falsingManager: FalsingManager
+ @Mock
+ private lateinit var settingsView: View
+ @Mock
+ private lateinit var doneView: View
+ @Mock
+ private lateinit var activityStarter: ActivityStarter
+ @Mock
+ private lateinit var userDetailViewAdapter: UserDetailView.Adapter
+ @Mock
+ private lateinit var launchView: View
+ @Mock
+ private lateinit var gridView: PseudoGridView
+ @Captor
+ private lateinit var clickCaptor: ArgumentCaptor
+
+ private lateinit var controller: UserSwitchDialogController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ `when`(dialog.settingsButton).thenReturn(settingsView)
+ `when`(dialog.doneButton).thenReturn(doneView)
+ `when`(dialog.grid).thenReturn(gridView)
+
+ `when`(launchView.context).thenReturn(mContext)
+
+ controller = UserSwitchDialogController(
+ { userDetailViewAdapter },
+ activityStarter,
+ falsingManager,
+ { dialog }
+ )
+ }
+
+ @Test
+ fun showDialog_callsDialogShow() {
+ controller.showDialog(launchView)
+ verify(dialog).show()
+ }
+
+ @Test
+ fun createCalledBeforeDoneButton() {
+ controller.showDialog(launchView)
+ val inOrder = inOrder(dialog)
+ inOrder.verify(dialog).create()
+ inOrder.verify(dialog).doneButton
+ }
+
+ @Test
+ fun createCalledBeforeSettingsButton() {
+ controller.showDialog(launchView)
+ val inOrder = inOrder(dialog)
+ inOrder.verify(dialog).create()
+ inOrder.verify(dialog).settingsButton
+ }
+
+ @Test
+ fun createCalledBeforeGrid() {
+ controller.showDialog(launchView)
+ val inOrder = inOrder(dialog)
+ inOrder.verify(dialog).create()
+ inOrder.verify(dialog).grid
+ }
+
+ @Test
+ fun dialog_showForAllUsers() {
+ controller.showDialog(launchView)
+ verify(dialog).setShowForAllUsers(true)
+ }
+
+ @Test
+ fun dialog_cancelOnTouchOutside() {
+ controller.showDialog(launchView)
+ verify(dialog).setCanceledOnTouchOutside(true)
+ }
+
+ @Test
+ fun adapterAndGridLinked() {
+ controller.showDialog(launchView)
+ verify(userDetailViewAdapter).linkToViewGroup(gridView)
+ }
+
+ @Test
+ fun clickDoneButton_dismiss() {
+ controller.showDialog(launchView)
+
+ verify(doneView).setOnClickListener(capture(clickCaptor))
+
+ clickCaptor.value.onClick(doneView)
+
+ verify(activityStarter, never()).postStartActivityDismissingKeyguard(any(), anyInt())
+ verify(dialog).dismiss()
+ }
+
+ @Test
+ fun clickSettingsButton_noFalsing_opensSettingsAndDismisses() {
+ `when`(falsingManager.isFalseTap(anyInt())).thenReturn(false)
+
+ controller.showDialog(launchView)
+
+ verify(settingsView).setOnClickListener(capture(clickCaptor))
+
+ clickCaptor.value.onClick(settingsView)
+
+ verify(activityStarter)
+ .postStartActivityDismissingKeyguard(
+ argThat(IntentMatcher(Settings.ACTION_USER_SETTINGS)),
+ eq(0)
+ )
+ verify(dialog).dismiss()
+ }
+
+ @Test
+ fun clickSettingsButton_Falsing_notOpensSettingsAndDismisses() {
+ `when`(falsingManager.isFalseTap(anyInt())).thenReturn(true)
+
+ controller.showDialog(launchView)
+
+ verify(settingsView).setOnClickListener(capture(clickCaptor))
+
+ clickCaptor.value.onClick(settingsView)
+
+ verify(activityStarter, never()).postStartActivityDismissingKeyguard(any(), anyInt())
+ verify(dialog).dismiss()
+ }
+
+ @Test
+ fun callbackFromDetailView_dismissesDialog() {
+ val captor = argumentCaptor>()
+
+ controller.showDialog(launchView)
+ verify(userDetailViewAdapter).injectCallback(capture(captor))
+
+ captor.value.accept(mock(UserSwitcherController.UserRecord::class.java))
+
+ verify(dialog).dismiss()
+ }
+
+ private class IntentMatcher(private val action: String) : ArgumentMatcher {
+ override fun matches(argument: Intent?): Boolean {
+ return argument?.action == action
+ }
+ }
+}
\ No newline at end of file