summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Matt Pietal <mpietal@google.com> 2022-02-07 11:03:06 -0500
committer Matt Pietal <mpietal@google.com> 2022-02-08 15:14:07 -0500
commitd04be1df3ce49734e44c0a2886173548c2b347a3 (patch)
tree054b4163062840b4e4423f9f4585589c6f06d12c
parent5c5cdad706ac04c5aab2dcdb533f4696c43d09f4 (diff)
Fullscreen user switcher
Supports fullscreen user switcher launched from QS, and enabled by config_enableFullscreenUserSwitcher. Allows adding guests, with support for adding new users coming shortly. First round of work on this feature. Bug: 217365397 Test: atest UserSwitcherControllerTest Change-Id: Ib81f44c9b950830a7d89b95cbaef53b7228ed5a4
-rw-r--r--packages/SystemUI/AndroidManifest.xml12
-rw-r--r--packages/SystemUI/docs/user-switching.md45
-rw-r--r--packages/SystemUI/res-keyguard/values/dimens.xml10
-rw-r--r--packages/SystemUI/res/drawable/user_switcher_icon_large.xml46
-rw-r--r--packages/SystemUI/res/layout/user_switcher_fullscreen.xml70
-rw-r--r--packages/SystemUI/res/layout/user_switcher_fullscreen_item.xml33
-rw-r--r--packages/SystemUI/res/layout/user_switcher_fullscreen_popup_item.xml47
-rw-r--r--packages/SystemUI/res/values/colors.xml1
-rw-r--r--packages/SystemUI/res/values/strings.xml3
-rw-r--r--packages/SystemUI/res/values/styles.xml8
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/UserModule.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt330
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt108
17 files changed, 770 insertions, 26 deletions
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index c9bd3710ca79..6b6aa71d74c0 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -845,6 +845,18 @@
android:visibleToInstantApps="true">
</activity>
+ <activity android:name=".user.UserSwitcherActivity"
+ android:label="@string/accessibility_multi_user_switch_switcher"
+ android:theme="@style/Theme.UserSwitcherActivity"
+ android:excludeFromRecents="true"
+ android:showWhenLocked="true"
+ android:showForAllUsers="true"
+ android:finishOnTaskLaunch="true"
+ android:launchMode="singleInstance"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden"
+ android:visibleToInstantApps="true">
+ </activity>
+
<receiver android:name=".controls.management.ControlsRequestReceiver"
android:exported="true">
<intent-filter>
diff --git a/packages/SystemUI/docs/user-switching.md b/packages/SystemUI/docs/user-switching.md
new file mode 100644
index 000000000000..dcf66b943f1d
--- /dev/null
+++ b/packages/SystemUI/docs/user-switching.md
@@ -0,0 +1,45 @@
+# User Switching
+
+Multiple users and the ability to switch between them is controlled by Settings -> System -> Multiple Users.
+
+## Entry Points
+
+### Quick Settings
+
+In the QS footer, an icon becomes available for users to tap on. The view and its onClick actions are handled by [MultiUserSwitchController][2]. Multiple visual implementations are currently in use; one for phones/foldables ([UserSwitchDialogController][6]) and one for tablets ([UserSwitcherActivity][5]).
+
+### Bouncer
+
+May allow changing or adding new users directly from they bouncer. See [KeyguardBouncer][1]
+
+### Keyguard affordance
+
+[KeyguardQsUserSwitchController][4]
+
+## Components
+
+All visual implementations should derive their logic and use the adapter specified in:
+
+### [UserSwitcherController][3]
+
+* Contains the current list of all system users
+* Listens for relevant events and broadcasts to make sure this list stays up to date
+* Manages user switching and dialogs for exiting from guest users
+* Is settings aware regarding adding users from the lockscreen
+
+## Visual Components
+
+### [UserSwitcherActivity][5]
+
+A fullscreen user switching activity, supporting add guest/user actions if configured.
+
+### [UserSwitchDialogController][6]
+
+Renders user switching as a dialog over the current surface, and supports add guest user/actions if configured.
+
+[1]: /frameworks/base/packages/SystemUI/docs/keyguard/bouncer.md
+[2]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserController.java
+[3]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+[4]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+[5]: /frameworks/base/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+[6]: /frameworks/base/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index cbf4f83daeb5..dad4c19799af 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -113,9 +113,17 @@
<dimen name="bouncer_user_switcher_item_icon_size">28dp</dimen>
<dimen name="bouncer_user_switcher_item_icon_padding">12dp</dimen>
<dimen name="bouncer_user_switcher_width">248dp</dimen>
- <dimen name="bouncer_user_switcher_icon_size">190dp</dimen>
<dimen name="bouncer_user_switcher_popup_header_height">12dp</dimen>
<dimen name="bouncer_user_switcher_popup_divider_height">4dp</dimen>
<dimen name="bouncer_user_switcher_item_padding_vertical">10dp</dimen>
<dimen name="bouncer_user_switcher_item_padding_horizontal">12dp</dimen>
+
+ <!-- 2 * the margin + size should equal the plus_margin -->
+ <dimen name="user_switcher_icon_large_margin">16dp</dimen>
+ <dimen name="bouncer_user_switcher_icon_size">190dp</dimen>
+ <dimen name="bouncer_user_switcher_icon_size_plus_margin">222dp</dimen>
+
+ <dimen name="user_switcher_icon_selected_width">8dp</dimen>
+ <dimen name="user_switcher_fullscreen_button_text_size">14sp</dimen>
+ <dimen name="user_switcher_fullscreen_button_padding">12dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/drawable/user_switcher_icon_large.xml b/packages/SystemUI/res/drawable/user_switcher_icon_large.xml
new file mode 100644
index 000000000000..b78b2216c9f9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/user_switcher_icon_large.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright 2022, 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.
+*/
+-->
+<layer-list
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="@dimen/bouncer_user_switcher_icon_size_plus_margin"
+ android:height="@dimen/bouncer_user_switcher_icon_size_plus_margin">
+ <!-- The final layer is inset, so it needs this background -->
+ <item>
+ <shape android:shape="oval">
+ <solid android:color="@color/user_switcher_fullscreen_bg" />
+ </shape>
+ </item>
+ <!-- When an item is selected, this layer will show a ring around the icon -->
+ <item>
+ <shape android:shape="oval">
+ <stroke
+ android:width="@dimen/user_switcher_icon_selected_width"
+ android:color="@android:color/transparent" />
+ </shape>
+ </item>
+ <!-- Where the user drawable/bitmap will be placed -->
+ <item
+ android:drawable="@drawable/kg_bg_avatar"
+ android:width="@dimen/bouncer_user_switcher_icon_size"
+ android:height="@dimen/bouncer_user_switcher_icon_size"
+ android:top="@dimen/user_switcher_icon_large_margin"
+ android:left="@dimen/user_switcher_icon_large_margin"
+ android:right="@dimen/user_switcher_icon_large_margin"
+ android:bottom="@dimen/user_switcher_icon_large_margin" />
+</layer-list>
diff --git a/packages/SystemUI/res/layout/user_switcher_fullscreen.xml b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
new file mode 100644
index 000000000000..7b95cf3cfa34
--- /dev/null
+++ b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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:app="http://schemas.android.com/apk/res-auto"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:id="@+id/user_switcher_root"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="60dp"
+ android:layout_marginStart="60dp">
+
+ <androidx.constraintlayout.helper.widget.Flow
+ android:id="@+id/flow"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:flow_horizontalBias="0.5"
+ app:flow_verticalAlign="center"
+ app:flow_wrapMode="chain"
+ app:flow_horizontalGap="64dp"
+ app:flow_verticalGap="44dp"
+ app:flow_horizontalStyle="packed"/>
+
+ <TextView
+ android:id="@+id/cancel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:gravity="center"
+ app:layout_constraintHeight_min="48dp"
+ app:layout_constraintEnd_toStartOf="@+id/add"
+ app:layout_constraintBottom_toBottomOf="parent"
+ android:paddingHorizontal="@dimen/user_switcher_fullscreen_button_padding"
+ android:textSize="@dimen/user_switcher_fullscreen_button_text_size"
+ android:textColor="?androidprv:attr/colorAccentPrimary"
+ android:text="@string/cancel" />
+
+ <TextView
+ android:id="@+id/add"
+ android:visibility="gone"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:gravity="center"
+ app:layout_constraintHeight_min="48dp"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ android:paddingHorizontal="@dimen/user_switcher_fullscreen_button_padding"
+ android:textSize="@dimen/user_switcher_fullscreen_button_text_size"
+ android:textColor="?androidprv:attr/colorAccentPrimary"
+ android:text="@string/add" />
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/user_switcher_fullscreen_item.xml b/packages/SystemUI/res/layout/user_switcher_fullscreen_item.xml
new file mode 100644
index 000000000000..3319442a1a68
--- /dev/null
+++ b/packages/SystemUI/res/layout/user_switcher_fullscreen_item.xml
@@ -0,0 +1,33 @@
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <ImageView
+ android:id="@+id/user_switcher_icon"
+ android:layout_gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <TextView
+ style="@style/Bouncer.UserSwitcher.Spinner.Item"
+ android:id="@+id/user_switcher_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="@*android:color/text_color_primary_device_default_dark"
+ android:layout_gravity="center" />
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/user_switcher_fullscreen_popup_item.xml b/packages/SystemUI/res/layout/user_switcher_fullscreen_popup_item.xml
new file mode 100644
index 000000000000..8d02429150f0
--- /dev/null
+++ b/packages/SystemUI/res/layout/user_switcher_fullscreen_popup_item.xml
@@ -0,0 +1,47 @@
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingVertical="18dp"
+ android:paddingStart="18dp"
+ android:paddingEnd="65dp">
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="start">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_gravity="center"
+ android:layout_width="20dp"
+ android:layout_height="20dp"
+ android:contentDescription="@null"
+ android:layout_marginEnd="10dp" />
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="@*android:color/text_color_primary_device_default_dark"
+ android:textSize="14sp"
+ android:layout_gravity="start" />
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 3ab569a19c0c..15147786e557 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -87,6 +87,7 @@
<color name="notification_section_clear_all_btn_color">@color/GM2_grey_700</color>
<color name="keyguard_user_switcher_background_gradient_color">#77000000</color>
+ <color name="user_switcher_fullscreen_bg">@android:color/system_neutral1_900</color>
<!-- The color of the navigation bar icons. Need to be in sync with ic_sysbar_* -->
<color name="navigation_bar_icon_color">#E5FFFFFF</color>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 793647d2909d..53aebda8a4b4 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2396,4 +2396,7 @@
<string name="clipboard_edit_image_description">Edit copied image</string>
<!-- Label for button to send copied content to a nearby device [CHAR LIMIT=NONE] -->
<string name="clipboard_send_nearby_description">Send to nearby device</string>
+
+ <!-- Generic "add" string [CHAR LIMIT=NONE] -->
+ <string name="add">Add</string>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 590cc9b4eb0a..9448d3f342b9 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -771,6 +771,14 @@
<item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item>
</style>
+ <style name="Theme.UserSwitcherActivity" parent="@android:style/Theme.DeviceDefault.NoActionBar">
+ <item name="android:statusBarColor">@color/user_switcher_fullscreen_bg</item>
+ <item name="android:windowBackground">@color/user_switcher_fullscreen_bg</item>
+ <item name="android:navigationBarColor">@color/user_switcher_fullscreen_bg</item>
+ <!-- Setting a placeholder will avoid using the SystemUI icon on the splash screen -->
+ <item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_blank</item>
+ </style>
+
<style name="Theme.CreateUser" parent="@style/Theme.SystemUI">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">#33000000</item>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index c387260005b4..7bc343e91f8c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -856,10 +856,9 @@ public class KeyguardSecurityContainer extends FrameLayout {
}
private void setupUserSwitcher() {
- String currentUserName = mUserSwitcherController.getCurrentUserName();
- mUserSwitcher.setText(currentUserName);
+ final UserRecord currentUser = mUserSwitcherController.getCurrentUserRecord();
+ mUserSwitcher.setText(mUserSwitcherController.getCurrentUserName());
- final UserRecord currentUser = getCurrentUser();
ViewGroup anchor = mView.findViewById(R.id.user_switcher_anchor);
BaseUserAdapter adapter = new BaseUserAdapter(mUserSwitcherController) {
@Override
@@ -961,16 +960,6 @@ public class KeyguardSecurityContainer extends FrameLayout {
});
}
- private UserRecord getCurrentUser() {
- for (int i = 0; i < mUserSwitcherController.getUsers().size(); ++i) {
- UserRecord userRecord = mUserSwitcherController.getUsers().get(i);
- if (userRecord.isCurrent) {
- return userRecord;
- }
- }
- return null;
- }
-
/**
* Each view will get half the width. Yes, it would be easier to use something other than
* FrameLayout but it was too disruptive to downstream projects to change.
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 1af9e413b226..552f18894408 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -110,6 +110,8 @@ public class Flags {
public static final BooleanFlag NEW_FOOTER = new BooleanFlag(504, false);
public static final BooleanFlag NEW_HEADER = new BooleanFlag(505, false);
+ public static final ResourceBooleanFlag FULL_SCREEN_USER_SWITCHER =
+ new ResourceBooleanFlag(506, R.bool.config_enableFullscreenUserSwitcher);
/***************************************/
// 600- status bar
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 eb5db299ec69..357a12b09b0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
@@ -18,13 +18,16 @@ package com.android.systemui.statusbar.phone;
import static com.android.systemui.DejankUtils.whitelistIpcs;
+import android.content.Intent;
import android.os.UserManager;
import android.view.View;
import android.view.ViewGroup;
import com.android.systemui.R;
+import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.qs.FooterActionsView;
@@ -32,6 +35,7 @@ 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.user.UserSwitcherActivity;
import com.android.systemui.util.ViewController;
import javax.inject.Inject;
@@ -43,6 +47,7 @@ public class MultiUserSwitchController extends ViewController<MultiUserSwitch> {
private final QSDetailDisplayer mQsDetailDisplayer;
private final FalsingManager mFalsingManager;
private final UserSwitchDialogController mUserSwitchDialogController;
+ private final ActivityStarter mActivityStarter;
private final FeatureFlags mFeatureFlags;
private UserSwitcherController.BaseUserAdapter mUserListener;
@@ -54,7 +59,14 @@ public class MultiUserSwitchController extends ViewController<MultiUserSwitch> {
return;
}
- if (mFeatureFlags.isEnabled(Flags.NEW_USER_SWITCHER)) {
+ if (mFeatureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
+ Intent intent = new Intent(v.getContext(), UserSwitcherActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ mActivityStarter.startActivity(intent, true /* dismissShade */,
+ ActivityLaunchAnimator.Controller.fromView(v, null),
+ true /* showOverlockscreenwhenlocked */);
+ } else if (mFeatureFlags.isEnabled(Flags.NEW_USER_SWITCHER)) {
mUserSwitchDialogController.showDialog(v);
} else {
View center = mView.getChildCount() > 0 ? mView.getChildAt(0) : mView;
@@ -76,31 +88,35 @@ public class MultiUserSwitchController extends ViewController<MultiUserSwitch> {
private final QSDetailDisplayer mQsDetailDisplayer;
private final FalsingManager mFalsingManager;
private final UserSwitchDialogController mUserSwitchDialogController;
+ private final ActivityStarter mActivityStarter;
private final FeatureFlags mFeatureFlags;
@Inject
public Factory(UserManager userManager, UserSwitcherController userSwitcherController,
QSDetailDisplayer qsDetailDisplayer, FalsingManager falsingManager,
- UserSwitchDialogController userSwitchDialogController, FeatureFlags featureFlags) {
+ UserSwitchDialogController userSwitchDialogController, FeatureFlags featureFlags,
+ ActivityStarter activityStarter) {
mUserManager = userManager;
mUserSwitcherController = userSwitcherController;
mQsDetailDisplayer = qsDetailDisplayer;
mFalsingManager = falsingManager;
mUserSwitchDialogController = userSwitchDialogController;
+ mActivityStarter = activityStarter;
mFeatureFlags = featureFlags;
}
public MultiUserSwitchController create(FooterActionsView view) {
return new MultiUserSwitchController(view.findViewById(R.id.multi_user_switch),
mUserManager, mUserSwitcherController, mQsDetailDisplayer,
- mFalsingManager, mUserSwitchDialogController, mFeatureFlags);
+ mFalsingManager, mUserSwitchDialogController, mFeatureFlags,
+ mActivityStarter);
}
}
private MultiUserSwitchController(MultiUserSwitch view, UserManager userManager,
UserSwitcherController userSwitcherController, QSDetailDisplayer qsDetailDisplayer,
FalsingManager falsingManager, UserSwitchDialogController userSwitchDialogController,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags, ActivityStarter activityStarter) {
super(view);
mUserManager = userManager;
mUserSwitcherController = userSwitcherController;
@@ -108,6 +124,7 @@ public class MultiUserSwitchController extends ViewController<MultiUserSwitch> {
mFalsingManager = falsingManager;
mUserSwitchDialogController = userSwitchDialogController;
mFeatureFlags = featureFlags;
+ mActivityStarter = activityStarter;
}
@Override
@@ -166,5 +183,4 @@ public class MultiUserSwitchController extends ViewController<MultiUserSwitch> {
return whitelistIpcs(() -> mUserManager.isUserSwitcherEnabled(
getResources().getBoolean(R.bool.qs_show_user_switcher_for_single_user)));
}
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 1b73595beb7c..3ece240bc576 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -81,7 +81,6 @@ import com.android.systemui.qs.QSUserSwitcherEvent;
import com.android.systemui.qs.tiles.UserDetailView;
import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.user.CreateUserActivity;
@@ -159,7 +158,7 @@ public class UserSwitcherController implements Dumpable {
private final AtomicBoolean mGuestIsResetting;
private final AtomicBoolean mGuestCreationScheduled;
private FalsingManager mFalsingManager;
- private NotificationShadeWindowView mRootView;
+ private View mView;
@Inject
public UserSwitcherController(Context context,
@@ -458,6 +457,19 @@ public class UserSwitcherController implements Dumpable {
}
}
+ /**
+ * @return UserRecord for the current user
+ */
+ public @Nullable UserRecord getCurrentUserRecord() {
+ for (int i = 0; i < mUsers.size(); ++i) {
+ UserRecord userRecord = mUsers.get(i);
+ if (userRecord.isCurrent) {
+ return userRecord;
+ }
+ }
+ return null;
+ }
+
@VisibleForTesting
void onUserListItemClicked(UserRecord record, DialogShower dialogShower) {
int id;
@@ -504,7 +516,7 @@ public class UserSwitcherController implements Dumpable {
protected void switchToUserId(int id) {
try {
mInteractionJankMonitor.begin(InteractionJankMonitor.Configuration.Builder
- .withView(InteractionJankMonitor.CUJ_USER_SWITCH, mRootView)
+ .withView(InteractionJankMonitor.CUJ_USER_SWITCH, mView)
.setTimeout(MULTI_USER_JOURNEY_TIMEOUT));
mLatencyTracker.onActionStart(LatencyTracker.ACTION_USER_SWITCH);
pauseRefreshUsers();
@@ -823,8 +835,11 @@ public class UserSwitcherController implements Dumpable {
return guest.id;
}
- public void init(NotificationShadeWindowView notificationShadeWindowView) {
- mRootView = notificationShadeWindowView;
+ /**
+ * Require a view for jank detection
+ */
+ public void init(View view) {
+ mView = view;
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserModule.java b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
index 0ad0984e8231..469d54ff8ffa 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserModule.java
+++ b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
@@ -16,21 +16,32 @@
package com.android.systemui.user;
+import android.app.Activity;
+
import com.android.settingslib.users.EditUserInfoController;
+import dagger.Binds;
import dagger.Module;
import dagger.Provides;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
/**
* Dagger module for User related classes.
*/
@Module
-public class UserModule {
+public abstract class UserModule {
private static final String FILE_PROVIDER_AUTHORITY = "com.android.systemui.fileprovider";
@Provides
- EditUserInfoController provideEditUserInfoController() {
+ public static EditUserInfoController provideEditUserInfoController() {
return new EditUserInfoController(FILE_PROVIDER_AUTHORITY);
}
+
+ /** Provides UserSwitcherActivity */
+ @Binds
+ @IntoMap
+ @ClassKey(UserSwitcherActivity.class)
+ public abstract Activity provideUserSwitcherActivity(UserSwitcherActivity activity);
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
new file mode 100644
index 000000000000..d6a8ab270b84
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2022 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.user
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.GradientDrawable
+import android.graphics.drawable.InsetDrawable
+import android.graphics.drawable.LayerDrawable
+import android.os.Bundle
+import android.os.UserManager
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowInsets
+import android.view.WindowInsets.Type
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
+import android.widget.ImageView
+import android.widget.TextView
+
+import androidx.constraintlayout.helper.widget.Flow
+
+import com.android.internal.util.UserIcons
+import com.android.settingslib.Utils
+import com.android.systemui.R
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.FalsingManager.LOW_PENALTY
+import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.statusbar.policy.UserSwitcherController.BaseUserAdapter
+import com.android.systemui.statusbar.policy.UserSwitcherController.UserRecord
+import com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_DISABLED_ALPHA
+import com.android.systemui.statusbar.policy.UserSwitcherController.USER_SWITCH_ENABLED_ALPHA
+import com.android.systemui.util.LifecycleActivity
+
+import javax.inject.Inject
+
+private const val USER_VIEW = "user_view"
+
+/**
+ * Support a fullscreen user switcher
+ */
+class UserSwitcherActivity @Inject constructor(
+ private val userSwitcherController: UserSwitcherController,
+ private val broadcastDispatcher: BroadcastDispatcher,
+ private val layoutInflater: LayoutInflater,
+ private val falsingManager: FalsingManager,
+ private val userManager: UserManager
+) : LifecycleActivity() {
+
+ private lateinit var parent: ViewGroup
+ private lateinit var broadcastReceiver: BroadcastReceiver
+ private var popupMenu: UserSwitcherPopupMenu? = null
+ private lateinit var addButton: View
+ private var addUserItem: UserRecord? = null
+ private var addGuestItem: UserRecord? = null
+
+ private val adapter = object : BaseUserAdapter(userSwitcherController) {
+ override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
+ val item = getItem(position)
+ var view = convertView as ViewGroup?
+ if (view == null) {
+ view = layoutInflater.inflate(
+ R.layout.user_switcher_fullscreen_item,
+ parent,
+ false
+ ) as ViewGroup
+ }
+ (view.getChildAt(0) as ImageView).apply {
+ setImageDrawable(getDrawable(item))
+ }
+ (view.getChildAt(1) as TextView).apply {
+ setText(getName(getContext(), item))
+ }
+
+ view.setEnabled(item.isSwitchToEnabled)
+ view.setAlpha(
+ if (view.isEnabled()) {
+ USER_SWITCH_ENABLED_ALPHA
+ } else {
+ USER_SWITCH_DISABLED_ALPHA
+ }
+ )
+ view.setTag(USER_VIEW)
+ return view
+ }
+
+ fun findUserIcon(item: UserRecord): Drawable {
+ if (item.info == null) {
+ return getIconDrawable(this@UserSwitcherActivity, item)
+ }
+ val userIcon = userManager.getUserIcon(item.info.id)
+ if (userIcon != null) {
+ return BitmapDrawable(userIcon)
+ }
+ return UserIcons.getDefaultUserIcon(resources, item.info.id, false)
+ }
+
+ private fun getDrawable(item: UserRecord): Drawable {
+ var drawable = if (item.isCurrent && item.isGuest) {
+ getDrawable(R.drawable.ic_avatar_guest_user)
+ } else {
+ findUserIcon(item)
+ }
+ drawable.mutate()
+
+ if (!item.isCurrent && !item.isSwitchToEnabled) {
+ drawable.setTint(
+ resources.getColor(
+ R.color.kg_user_switcher_restricted_avatar_icon_color,
+ getTheme()
+ )
+ )
+ }
+
+ val ld = getDrawable(R.drawable.user_switcher_icon_large).mutate()
+ as LayerDrawable
+ if (item == userSwitcherController.getCurrentUserRecord()) {
+ (ld.getDrawable(1) as GradientDrawable).apply {
+ val stroke = resources
+ .getDimensionPixelSize(R.dimen.user_switcher_icon_selected_width)
+ val color = Utils.getColorAttrDefaultColor(
+ this@UserSwitcherActivity,
+ com.android.internal.R.attr.colorAccentPrimary
+ )
+
+ setStroke(stroke, color)
+ }
+ }
+
+ ld.addLayer(
+ InsetDrawable(
+ drawable,
+ resources.getDimensionPixelSize(
+ R.dimen.user_switcher_icon_large_margin
+ )
+ )
+ )
+
+ return ld
+ }
+
+ override fun notifyDataSetChanged() {
+ super.notifyDataSetChanged()
+ buildUserViews()
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContentView(R.layout.user_switcher_fullscreen)
+
+ parent = requireViewById<ViewGroup>(R.id.user_switcher_root).apply {
+ setOnApplyWindowInsetsListener {
+ v: View, insets: WindowInsets ->
+ v.apply {
+ val l = getPaddingLeft()
+ val t = getPaddingTop()
+ val r = getPaddingRight()
+ setPadding(l, t, r, insets.getInsets(Type.systemBars()).bottom)
+ }
+
+ WindowInsets.CONSUMED
+ }
+ }
+
+ requireViewById<View>(R.id.cancel).apply {
+ setOnClickListener {
+ _ -> finish()
+ }
+ }
+
+ addButton = requireViewById<View>(R.id.add).apply {
+ setOnClickListener {
+ _ -> showPopupMenu()
+ }
+ }
+
+ userSwitcherController.init(parent)
+ initBroadcastReceiver()
+ buildUserViews()
+ }
+
+ private fun showPopupMenu() {
+ val items = mutableListOf<UserRecord>()
+ addUserItem?.let { items.add(it) }
+ addGuestItem?.let { items.add(it) }
+
+ var popupMenuAdapter = ItemAdapter(
+ this,
+ R.layout.user_switcher_fullscreen_popup_item,
+ layoutInflater,
+ { item: UserRecord -> adapter.getName(this@UserSwitcherActivity, item) },
+ { item: UserRecord -> adapter.findUserIcon(item) }
+ )
+ popupMenuAdapter.addAll(items)
+
+ popupMenu = UserSwitcherPopupMenu(this, falsingManager).apply {
+ setAnchorView(addButton)
+ setAdapter(popupMenuAdapter)
+ setOnItemClickListener {
+ parent: AdapterView<*>, view: View, pos: Int, id: Long ->
+ if (falsingManager.isFalseTap(LOW_PENALTY) || !view.isEnabled()) {
+ return@setOnItemClickListener
+ }
+ // -1 for the header
+ val item = popupMenuAdapter.getItem(pos - 1)
+ adapter.onUserListItemClicked(item)
+
+ dismiss()
+ popupMenu = null
+ }
+
+ show()
+ }
+ }
+
+ private fun buildUserViews() {
+ var count = 0
+ var start = 0
+ for (i in 0 until parent.getChildCount()) {
+ if (parent.getChildAt(i).getTag() == USER_VIEW) {
+ if (count == 0) start = i
+ count++
+ }
+ }
+ parent.removeViews(start, count)
+
+ val flow = requireViewById<Flow>(R.id.flow)
+ for (i in 0 until adapter.getCount()) {
+ val item = adapter.getItem(i)
+ if (item.isAddUser) {
+ addUserItem = item
+ } else if (item.isGuest && item.info == null) {
+ addGuestItem = item
+ } else {
+ val userView = adapter.getView(i, null, parent)
+ userView.setId(View.generateViewId())
+ parent.addView(userView)
+
+ // Views must have an id and a parent in order for Flow to lay them out
+ flow.addView(userView)
+
+ userView.setOnClickListener { v ->
+ if (falsingManager.isFalseTap(LOW_PENALTY) || !v.isEnabled()) {
+ return@setOnClickListener
+ }
+
+ if (!item.isCurrent || item.isGuest) {
+ adapter.onUserListItemClicked(item)
+ }
+ }
+ }
+ }
+
+ if (addUserItem != null || addGuestItem != null) {
+ addButton.visibility = View.VISIBLE
+ } else {
+ addButton.visibility = View.GONE
+ }
+ }
+
+ override fun onBackPressed() {
+ finish()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+
+ broadcastDispatcher.unregisterReceiver(broadcastReceiver)
+ }
+
+ private fun initBroadcastReceiver() {
+ broadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ val action = intent.getAction()
+ if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+ finish()
+ }
+ }
+ }
+
+ val filter = IntentFilter()
+ filter.addAction(Intent.ACTION_SCREEN_OFF)
+ broadcastDispatcher.registerReceiver(broadcastReceiver, filter)
+ }
+
+ private class ItemAdapter(
+ val parentContext: Context,
+ val resource: Int,
+ val layoutInflater: LayoutInflater,
+ val textGetter: (UserRecord) -> String,
+ val iconGetter: (UserRecord) -> Drawable
+ ) : ArrayAdapter<UserRecord>(parentContext, resource) {
+
+ override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
+ val item = getItem(position)
+ val view = convertView ?: layoutInflater.inflate(resource, parent, false)
+
+ view.requireViewById<ImageView>(R.id.icon).apply {
+ setImageDrawable(iconGetter(item))
+ }
+ view.requireViewById<TextView>(R.id.text).apply {
+ setText(textGetter(item))
+ }
+
+ return view
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
new file mode 100644
index 000000000000..896354737e46
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2022 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.user
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.drawable.ShapeDrawable
+import android.view.View
+import android.view.View.MeasureSpec
+import android.widget.ListAdapter
+import android.widget.ListPopupWindow
+import android.widget.ListView
+
+import com.android.systemui.R
+import com.android.systemui.plugins.FalsingManager
+
+/**
+ * Popup menu for displaying items on the fullscreen user switcher.
+ */
+class UserSwitcherPopupMenu(
+ private val context: Context,
+ private val falsingManager: FalsingManager
+) : ListPopupWindow(context) {
+
+ private val res = context.resources
+ private var adapter: ListAdapter? = null
+
+ init {
+ setBackgroundDrawable(
+ res.getDrawable(R.drawable.bouncer_user_switcher_popup_bg, context.getTheme())
+ )
+ setModal(true)
+ setOverlapAnchor(true)
+ }
+
+ override fun setAdapter(adapter: ListAdapter?) {
+ super.setAdapter(adapter)
+ this.adapter = adapter
+ }
+
+ /**
+ * Show the dialog.
+ */
+ override fun show() {
+ // need to call show() first in order to construct the listView
+ super.show()
+ val listView = getListView()
+
+ listView.setVerticalScrollBarEnabled(false)
+ listView.setHorizontalScrollBarEnabled(false)
+
+ // Creates a transparent spacer between items
+ val shape = ShapeDrawable()
+ shape.setAlpha(0)
+ listView.setDivider(shape)
+ listView.setDividerHeight(res.getDimensionPixelSize(
+ R.dimen.bouncer_user_switcher_popup_divider_height))
+
+ val height = res.getDimensionPixelSize(R.dimen.bouncer_user_switcher_popup_header_height)
+ listView.addHeaderView(createSpacer(height), null, false)
+ listView.addFooterView(createSpacer(height), null, false)
+ setWidth(findMaxWidth(listView))
+
+ super.show()
+ }
+
+ private fun findMaxWidth(listView: ListView): Int {
+ var maxWidth = 0
+ adapter?.let {
+ val parentWidth = res.getDisplayMetrics().widthPixels
+ val spec = MeasureSpec.makeMeasureSpec(
+ (parentWidth * 0.25).toInt(),
+ MeasureSpec.AT_MOST
+ )
+
+ for (i in 0 until it.getCount()) {
+ val child = it.getView(i, null, listView)
+ child.measure(spec, MeasureSpec.UNSPECIFIED)
+ maxWidth = Math.max(child.getMeasuredWidth(), maxWidth)
+ }
+ }
+ return maxWidth
+ }
+
+ private fun createSpacer(height: Int): View {
+ return object : View(context) {
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ setMeasuredDimension(1, height)
+ }
+
+ override fun draw(canvas: Canvas) {
+ }
+ }
+ }
+}