New user switcher dialog
This dialog will eventually replace the UserDetailView. Right now, it uses the same adapter to display the content (which is not necessarily bad). Missing: * Red lines (including landscape size). * Bring user circles up to spec (once it's finalized). * Launch animations (to and from dialog). Test: manual Test: atest SystemUITests Bug: 185360864 Bug: 198796184 Change-Id: I903d111822d1958fb53c43932b2df1393c841c71 Merged-In: I903d111822d1958fb53c43932b2df1393c841c71 Merged-In: I624169c76e4c9efa30d922235616190666ae949e Merged-In: I2b4082dd65f06d2d08706ac3db017ac1495aefed
This commit is contained in:
20
packages/SystemUI/res/color/prv_color_surface.xml
Normal file
20
packages/SystemUI/res/color/prv_color_surface.xml
Normal 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>
|
||||
20
packages/SystemUI/res/color/prv_text_color_on_accent.xml
Normal file
20
packages/SystemUI/res/color/prv_text_color_on_accent.xml
Normal 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>
|
||||
39
packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml
Normal file
39
packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml
Normal 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>
|
||||
42
packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
Normal file
42
packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
Normal 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>
|
||||
89
packages/SystemUI/res/layout/qs_user_dialog_content.xml
Normal file
89
packages/SystemUI/res/layout/qs_user_dialog_content.xml
Normal 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>
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user