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..377da45d0ffdf
--- /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 a9cc6ac47f69e..5ef91633cf7e3 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -56,4 +56,6 @@
true
false
+
+ false
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 7f4866f5a586b..dc17afb27f2dd 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -944,6 +944,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 1a6d6ae1c286d..3ff088358ab08 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
@@ -191,6 +191,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 ccc08e0517fb4..b718ebf035029 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -40,6 +40,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;
/**
@@ -76,6 +78,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,
@@ -93,6 +96,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(
@@ -170,6 +177,13 @@ public class UserDetailView extends PseudoGridView {
onUserListItemClicked(tag);
}
Trace.endSection();
+ 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 f27c7d28df447..2cacf92123d2b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
@@ -23,10 +23,12 @@ 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.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,26 +53,33 @@ 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]);
+ }
}
};
@Inject
public 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 a29b72c759dbb..b6c683f389e5c 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.settingslib.drawable.CircleFramedDrawable;
import com.android.systemui.R;
import com.android.systemui.communal.CommunalStateController;
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 userDetailViewAdapterProvider,
- UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
+ UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
+ FeatureFlags featureFlags,
+ UserSwitchDialogController userSwitchDialogController) {
super(view);
if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController");
mContext = context;
@@ -140,6 +146,8 @@ public class KeyguardQsUserSwitchController extends ViewController
+
+ 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