diff options
19 files changed, 765 insertions, 13 deletions
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 000000000000..b9d016c46ba0 --- /dev/null +++ b/packages/SystemUI/res/color/prv_color_surface.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<selector xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="?androidprv:attr/colorSurface" /> +</selector>
\ 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 000000000000..9f44acadb420 --- /dev/null +++ b/packages/SystemUI/res/color/prv_text_color_on_accent.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<selector xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="?androidprv:attr/textColorOnAccent" /> +</selector>
\ 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 000000000000..1a128dfe8b10 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<inset xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:insetTop="@dimen/qs_dialog_button_vertical_inset" + android:insetBottom="@dimen/qs_dialog_button_vertical_inset"> + <ripple android:color="?android:attr/colorControlHighlight"> + <item android:id="@android:id/mask"> + <shape android:shape="rectangle"> + <solid android:color="@android:color/white"/> + <corners android:radius="?android:attr/buttonCornerRadius"/> + </shape> + </item> + <item> + <shape android:shape="rectangle"> + <corners android:radius="?android:attr/buttonCornerRadius"/> + <solid android:color="?androidprv:attr/colorAccentPrimary"/> + <padding android:left="@dimen/qs_dialog_button_horizontal_padding" + android:top="@dimen/qs_dialog_button_vertical_padding" + android:right="@dimen/qs_dialog_button_horizontal_padding" + android:bottom="@dimen/qs_dialog_button_vertical_padding"/> + </shape> + </item> + </ripple> +</inset> 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 000000000000..467c20f3ffcd --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<inset xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:insetTop="@dimen/qs_dialog_button_vertical_inset" + android:insetBottom="@dimen/qs_dialog_button_vertical_inset"> + <ripple android:color="?android:attr/colorControlHighlight"> + <item android:id="@android:id/mask"> + <shape android:shape="rectangle"> + <solid android:color="@android:color/white"/> + <corners android:radius="?android:attr/buttonCornerRadius"/> + </shape> + </item> + <item> + <shape android:shape="rectangle"> + <corners android:radius="?android:attr/buttonCornerRadius"/> + <solid android:color="@android:color/transparent"/> + <stroke android:color="?androidprv:attr/colorAccentPrimary" + android:width="1dp" + /> + <padding android:left="@dimen/qs_dialog_button_horizontal_padding" + android:top="@dimen/qs_dialog_button_vertical_padding" + android:right="@dimen/qs_dialog_button_horizontal_padding" + android:bottom="@dimen/qs_dialog_button_vertical_padding"/> + </shape> + </item> + </ripple> +</inset> 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 000000000000..321fe68e2f13 --- /dev/null +++ b/packages/SystemUI/res/layout/qs_user_dialog_content.xml @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ 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. + --> + +<androidx.constraintlayout.widget.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:sysui="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="24dp" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" + android:background="@drawable/qs_dialog_bg" +> + <TextView + android:id="@+id/title" + android:layout_height="wrap_content" + android:layout_width="0dp" + android:textAlignment="center" + android:text="@string/qs_user_switch_dialog_title" + android:textAppearance="@style/TextAppearance.QSDialog.Title" + android:layout_marginBottom="32dp" + sysui:layout_constraintTop_toTopOf="parent" + sysui:layout_constraintStart_toStartOf="parent" + sysui:layout_constraintEnd_toEndOf="parent" + sysui:layout_constraintBottom_toTopOf="@id/grid" + /> + + <com.android.systemui.qs.PseudoGridView + android:id="@+id/grid" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="28dp" + sysui:verticalSpacing="4dp" + sysui:horizontalSpacing="4dp" + sysui:fixedChildWidth="80dp" + sysui:layout_constraintTop_toBottomOf="@id/title" + sysui:layout_constraintStart_toStartOf="parent" + sysui:layout_constraintEnd_toEndOf="parent" + sysui:layout_constraintBottom_toTopOf="@id/barrier" + /> + + <androidx.constraintlayout.widget.Barrier + android:id="@+id/barrier" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + sysui:barrierDirection="top" + sysui:constraint_referenced_ids="settings,done" + /> + + <Button + android:id="@+id/settings" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:text="@string/quick_settings_more_user_settings" + sysui:layout_constraintTop_toBottomOf="@id/barrier" + sysui:layout_constraintBottom_toBottomOf="parent" + sysui:layout_constraintStart_toStartOf="parent" + sysui:layout_constraintEnd_toStartOf="@id/done" + sysui:layout_constraintHorizontal_chainStyle="spread_inside" + style="@style/Widget.QSDialog.Button.BorderButton" + /> + + <Button + android:id="@+id/done" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:text="@string/quick_settings_done" + sysui:layout_constraintTop_toBottomOf="@id/barrier" + sysui:layout_constraintBottom_toBottomOf="parent" + sysui:layout_constraintStart_toEndOf="@id/settings" + sysui:layout_constraintEnd_toEndOf="parent" + style="@style/Widget.QSDialog.Button" + /> +</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml index 3121ce37490a..db699242a061 100644 --- a/packages/SystemUI/res/values/attrs.xml +++ b/packages/SystemUI/res/values/attrs.xml @@ -77,6 +77,7 @@ <attr name="numColumns" format="integer" /> <attr name="verticalSpacing" format="dimension" /> <attr name="horizontalSpacing" format="dimension" /> + <attr name="fixedChildWidth" format="dimension" /> </declare-styleable> <!-- Theme for icons in the status/nav bar (light/dark). background/fillColor is used for dual diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 9f257466ff28..946bbc289a03 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1639,4 +1639,9 @@ <item name="communal_source_height_percentage" format="float" type="dimen">0.80</item> <dimen name="drag_and_drop_icon_size">70dp</dimen> + + <dimen name="qs_dialog_button_horizontal_padding">16dp</dimen> + <dimen name="qs_dialog_button_vertical_padding">8dp</dimen> + <!-- The button will be 48dp tall, but the background needs to be 36dp tall --> + <dimen name="qs_dialog_button_vertical_inset">6dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml index 9a438df5b7fc..d6d98b9397ac 100644 --- a/packages/SystemUI/res/values/flags.xml +++ b/packages/SystemUI/res/values/flags.xml @@ -53,4 +53,6 @@ <bool name="flag_smartspace_deduping">true</bool> <bool name="flag_combined_status_bar_signal_icons">false</bool> + + <bool name="flag_new_user_switcher">true</bool> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index c26de37517d4..ec63df5e0fe2 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -3026,4 +3026,7 @@ <string name="see_all_networks">See all</string> <!-- Summary for warning to disconnect ethernet first then switch to other networks. [CHAR LIMIT=60] --> <string name="to_switch_networks_disconnect_ethernet">To switch networks, disconnect ethernet</string> + + <!-- Title for User Switch dialog. [CHAR LIMIT=20] --> + <string name="qs_user_switch_dialog_title">Select user</string> </resources> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 6594216e4290..6e51ec715da5 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -929,6 +929,39 @@ <item name="actionDividerHeight">32dp</item> </style> + <style name="Theme.SystemUI.Dialog.QSDialog"> + <item name="android:windowIsTranslucent">true</item> + <item name="android:windowBackground">@android:color/transparent</item> + <item name="android:windowIsFloating">true</item> + <item name="android:backgroundDimEnabled">true</item> + <item name="android:windowCloseOnTouchOutside">true</item> + <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item> + <item name="android:dialogCornerRadius">28dp</item> + <item name="android:buttonCornerRadius">28dp</item> + <item name="android:colorBackground">@color/prv_color_surface</item> + </style> + + <style name="TextAppearance.QSDialog.Title" parent="Theme.SystemUI.Dialog.QSDialog"> + <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:textSize">24sp</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> + <item name="android:lineHeight">32sp</item> + </style> + + <style name="Widget.QSDialog.Button" parent = "Theme.SystemUI.Dialog.QSDialog"> + <item name="android:background">@drawable/qs_dialog_btn_filled</item> + <item name="android:textColor">@color/prv_text_color_on_accent</item> + <item name="android:textSize">14sp</item> + <item name="android:lineHeight">20sp</item> + <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> + <item name="android:stateListAnimator">@null</item> + </style> + + <style name="Widget.QSDialog.Button.BorderButton"> + <item name="android:background">@drawable/qs_dialog_btn_outline</item> + <item name="android:textColor">?android:attr/textColorPrimary</item> + </style> + <style name="Theme.SystemUI.Dialog.Internet"> <item name="android:windowBackground">@drawable/internet_dialog_background</item> </style> diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java index b17041bc83f2..f81811ac0ffb 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 87c64c78edc8..2f189beb7780 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 04437ea14bb3..821bd5117d18 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<UserSwitcherController.UserRecord> 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<UserSwitcherController.UserRecord> 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 000000000000..2ad06c1c172c --- /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 000000000000..a5e4ba1b5b4b --- /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<UserDetailView.Adapter>, + private val activityStarter: ActivityStarter, + private val falsingManager: FalsingManager, + private val dialogFactory: (Context) -> UserDialog +) { + + @Inject + constructor( + userDetailViewAdapterProvider: Provider<UserDetailView.Adapter>, + 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 3fdf1ceaa2d9..dd21c8af37e0 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<MultiUserSwitch> { 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<MultiUserSwitch> { 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<MultiUserSwitch> { 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 d838a05135e7..b4aba380d971 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<FrameLayout> 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<FrameLayout> SysuiStatusBarStateController statusBarStateController, DozeParameters dozeParameters, Provider<UserDetailView.Adapter> 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<FrameLayout> keyguardStateController, dozeParameters, unlockedScreenOffAnimationController, /* animateYPos= */ false); mUserDetailAdapter = new KeyguardUserDetailAdapter(context, userDetailViewAdapterProvider); + mFeatureFlags = featureFlags; + mUserSwitchDialogController = userSwitchDialogController; } @Override @@ -162,7 +170,11 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> } // 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 000000000000..d5fe588b2115 --- /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 000000000000..a1760a79417f --- /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<View.OnClickListener> + + 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<Consumer<UserSwitcherController.UserRecord>>() + + 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<Intent> { + override fun matches(argument: Intent?): Boolean { + return argument?.action == action + } + } +}
\ No newline at end of file |