Merge "New user switcher dialog" into sc-v2-dev

This commit is contained in:
TreeHugger Robot
2021-10-08 10:05:37 +00:00
committed by Android (Google) Code Review
19 changed files with 765 additions and 13 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -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)
}
}

View File

@@ -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()
}
}
}

View File

@@ -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

View File

@@ -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() {

View File

@@ -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)
}
}

View File

@@ -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
}
}
}