Merge changes from topic "mpietal_supervised_user"

* changes:
  User switcher - Manage users
  Add supervised user support
This commit is contained in:
Matt Pietal
2022-02-10 15:23:10 +00:00
committed by Android (Google) Code Review
15 changed files with 320 additions and 81 deletions

View File

@@ -0,0 +1,24 @@
<!--
~ 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M5.85,17.1Q7.125,16.125 8.7,15.562Q10.275,15 12,15Q13.725,15 15.3,15.562Q16.875,16.125 18.15,17.1Q19.025,16.075 19.513,14.775Q20,13.475 20,12Q20,8.675 17.663,6.337Q15.325,4 12,4Q8.675,4 6.338,6.337Q4,8.675 4,12Q4,13.475 4.488,14.775Q4.975,16.075 5.85,17.1ZM12,13Q10.525,13 9.512,11.988Q8.5,10.975 8.5,9.5Q8.5,8.025 9.512,7.012Q10.525,6 12,6Q13.475,6 14.488,7.012Q15.5,8.025 15.5,9.5Q15.5,10.975 14.488,11.988Q13.475,13 12,13ZM12,22Q9.925,22 8.1,21.212Q6.275,20.425 4.925,19.075Q3.575,17.725 2.788,15.9Q2,14.075 2,12Q2,9.925 2.788,8.1Q3.575,6.275 4.925,4.925Q6.275,3.575 8.1,2.787Q9.925,2 12,2Q14.075,2 15.9,2.787Q17.725,3.575 19.075,4.925Q20.425,6.275 21.212,8.1Q22,9.925 22,12Q22,14.075 21.212,15.9Q20.425,17.725 19.075,19.075Q17.725,20.425 15.9,21.212Q14.075,22 12,22ZM12,20Q13.325,20 14.5,19.613Q15.675,19.225 16.65,18.5Q15.675,17.775 14.5,17.387Q13.325,17 12,17Q10.675,17 9.5,17.387Q8.325,17.775 7.35,18.5Q8.325,19.225 9.5,19.613Q10.675,20 12,20ZM12,11Q12.65,11 13.075,10.575Q13.5,10.15 13.5,9.5Q13.5,8.85 13.075,8.425Q12.65,8 12,8Q11.35,8 10.925,8.425Q10.5,8.85 10.5,9.5Q10.5,10.15 10.925,10.575Q11.35,11 12,11ZM12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5Q12,9.5 12,9.5ZM12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Q12,18.5 12,18.5Z"/>
</vector>

View File

@@ -0,0 +1,27 @@
<!--
~ 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM18.36,16.83c-1.43,-1.74 -4.9,-2.33 -6.36,-2.33s-4.93,0.59 -6.36,2.33A7.95,7.95 0,0 1,4 12c0,-4.41 3.59,-8 8,-8s8,3.59 8,8c0,1.82 -0.62,3.49 -1.64,4.83z"/>
<path
android:fillColor="#FFFFFF"
android:pathData="M12,6c-1.94,0 -3.5,1.56 -3.5,3.5S10.06,13 12,13s3.5,-1.56 3.5,-3.5S13.94,6 12,6z"/>
</vector>

View File

@@ -0,0 +1,19 @@
<!--
~ 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.
-->
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:drawable="@*android:drawable/ic_add_supervised_user" />
</layer-list>

View File

@@ -0,0 +1,23 @@
<!--
~ 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="@android:color/white"
android:pathData="M10,12Q8.35,12 7.175,10.825Q6,9.65 6,8Q6,6.35 7.175,5.175Q8.35,4 10,4Q11.65,4 12.825,5.175Q14,6.35 14,8Q14,9.65 12.825,10.825Q11.65,12 10,12ZM2,20V17.2Q2,16.375 2.425,15.65Q2.85,14.925 3.6,14.55Q4.875,13.9 6.475,13.45Q8.075,13 10,13Q10.2,13 10.35,13Q10.5,13 10.65,13.05Q10.45,13.5 10.312,13.988Q10.175,14.475 10.1,15H10Q8.225,15 6.812,15.45Q5.4,15.9 4.5,16.35Q4.275,16.475 4.138,16.7Q4,16.925 4,17.2V18H10.3Q10.45,18.525 10.7,19.038Q10.95,19.55 11.25,20ZM16,21L15.7,19.5Q15.4,19.375 15.137,19.238Q14.875,19.1 14.6,18.9L13.15,19.35L12.15,17.65L13.3,16.65Q13.25,16.3 13.25,16Q13.25,15.7 13.3,15.35L12.15,14.35L13.15,12.65L14.6,13.1Q14.875,12.9 15.137,12.762Q15.4,12.625 15.7,12.5L16,11H18L18.3,12.5Q18.6,12.625 18.863,12.775Q19.125,12.925 19.4,13.15L20.85,12.65L21.85,14.4L20.7,15.4Q20.75,15.7 20.75,16.025Q20.75,16.35 20.7,16.65L21.85,17.65L20.85,19.35L19.4,18.9Q19.125,19.1 18.863,19.238Q18.6,19.375 18.3,19.5L18,21ZM17,18Q17.825,18 18.413,17.413Q19,16.825 19,16Q19,15.175 18.413,14.587Q17.825,14 17,14Q16.175,14 15.588,14.587Q15,15.175 15,16Q15,16.825 15.588,17.413Q16.175,18 17,18ZM10,10Q10.825,10 11.413,9.412Q12,8.825 12,8Q12,7.175 11.413,6.588Q10.825,6 10,6Q9.175,6 8.588,6.588Q8,7.175 8,8Q8,8.825 8.588,9.412Q9.175,10 10,10ZM10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8Q10,8 10,8ZM10,15Q10,15 10,15Q10,15 10,15Q10,15 10,15Q10,15 10,15Q10,15 10,15Q10,15 10,15Z"/>
</vector>

View File

@@ -21,6 +21,7 @@
android:id="@+id/user_switcher_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="64dp"
android:layout_marginEnd="60dp"
android:layout_marginStart="60dp">

View File

@@ -29,17 +29,19 @@
<ImageView
android:id="@+id/icon"
android:scaleType="centerInside"
android:layout_gravity="center"
android:layout_width="20dp"
android:layout_height="20dp"
android:contentDescription="@null"
android:tint="@color/user_switcher_fullscreen_popup_item_tint"
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:textColor="@color/user_switcher_fullscreen_popup_item_tint"
android:textSize="14sp"
android:layout_gravity="start" />
</LinearLayout>

View File

@@ -88,6 +88,7 @@
<color name="keyguard_user_switcher_background_gradient_color">#77000000</color>
<color name="user_switcher_fullscreen_bg">@android:color/system_neutral1_900</color>
<color name="user_switcher_fullscreen_popup_item_tint">@*android:color/text_color_primary_device_default_dark</color>
<!-- The color of the navigation bar icons. Need to be in sync with ic_sysbar_* -->
<color name="navigation_bar_icon_color">#E5FFFFFF</color>

View File

@@ -2402,4 +2402,8 @@
<!-- Generic "add" string [CHAR LIMIT=NONE] -->
<string name="add">Add</string>
<!-- Add supervised user -->
<string name="add_user_supervised" translatable="false">@*android:string/supervised_user_creation_label</string>
<!-- Manage users - For system user management [CHAR LIMIT=40] -->
<string name="manage_users">Manage users</string>
</resources>

View File

@@ -46,6 +46,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.telephony.TelephonyCallback;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -159,6 +160,7 @@ public class UserSwitcherController implements Dumpable {
private final AtomicBoolean mGuestCreationScheduled;
private FalsingManager mFalsingManager;
private View mView;
private String mCreateSupervisedUserPackage;
@Inject
public UserSwitcherController(Context context,
@@ -255,6 +257,9 @@ public class UserSwitcherController implements Dumpable {
keyguardStateController.addCallback(mCallback);
listenForCallState();
mCreateSupervisedUserPackage = mContext.getString(
com.android.internal.R.string.config_supervisedUserCreationPackage);
dumpManager.registerDumpable(getClass().getSimpleName(), this);
refreshUsers(UserHandle.USER_NULL);
@@ -307,14 +312,10 @@ public class UserSwitcherController implements Dumpable {
// User 0
boolean canSwitchUsers = mUserManager.getUserSwitchability(
UserHandle.of(mUserTracker.getUserId())) == SWITCHABILITY_STATUS_OK;
UserInfo currentUserInfo = null;
UserRecord guestRecord = null;
for (UserInfo info : infos) {
boolean isCurrent = currentId == info.id;
if (isCurrent) {
currentUserInfo = info;
}
boolean switchToEnabled = canSwitchUsers || isCurrent;
if (info.isEnabled()) {
if (info.isGuest()) {
@@ -322,7 +323,8 @@ public class UserSwitcherController implements Dumpable {
// the icon shouldn't be enabled even if the user is current
guestRecord = new UserRecord(info, null /* picture */,
true /* isGuest */, isCurrent, false /* isAddUser */,
false /* isRestricted */, canSwitchUsers);
false /* isRestricted */, canSwitchUsers,
false /* isAddSupervisedUser */);
} else if (info.supportsSwitchToByUser()) {
Bitmap picture = bitmaps.get(info.id);
if (picture == null) {
@@ -337,7 +339,7 @@ public class UserSwitcherController implements Dumpable {
}
records.add(new UserRecord(info, picture, false /* isGuest */,
isCurrent, false /* isAddUser */, false /* isRestricted */,
switchToEnabled));
switchToEnabled, false /* isAddSupervisedUser */));
}
}
}
@@ -345,19 +347,6 @@ public class UserSwitcherController implements Dumpable {
Prefs.putBoolean(mContext, Key.SEEN_MULTI_USER, true);
}
boolean systemCanCreateUsers = !mUserManager.hasBaseUserRestriction(
UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM);
boolean currentUserCanCreateUsers = currentUserInfo != null
&& (currentUserInfo.isAdmin()
|| currentUserInfo.id == UserHandle.USER_SYSTEM)
&& systemCanCreateUsers;
boolean anyoneCanCreateUsers = systemCanCreateUsers && addUsersWhenLocked;
boolean canCreateGuest = (currentUserCanCreateUsers || anyoneCanCreateUsers)
&& guestRecord == null;
boolean canCreateUser = (currentUserCanCreateUsers || anyoneCanCreateUsers)
&& mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY);
boolean createIsRestricted = !addUsersWhenLocked;
if (guestRecord == null) {
if (mGuestUserAutoCreated) {
// If mGuestIsResetting=true, the switch should be disabled since
@@ -368,13 +357,14 @@ public class UserSwitcherController implements Dumpable {
guestRecord = new UserRecord(null /* info */, null /* picture */,
true /* isGuest */, false /* isCurrent */,
false /* isAddUser */, false /* isRestricted */,
isSwitchToGuestEnabled);
isSwitchToGuestEnabled, false /* isAddSupervisedUser */);
checkIfAddUserDisallowedByAdminOnly(guestRecord);
records.add(guestRecord);
} else if (canCreateGuest) {
} else if (canCreateGuest(guestRecord != null)) {
guestRecord = new UserRecord(null /* info */, null /* picture */,
true /* isGuest */, false /* isCurrent */,
false /* isAddUser */, createIsRestricted, canSwitchUsers);
false /* isAddUser */, createIsRestricted(), canSwitchUsers,
false /* isAddSupervisedUser */);
checkIfAddUserDisallowedByAdminOnly(guestRecord);
records.add(guestRecord);
}
@@ -382,10 +372,19 @@ public class UserSwitcherController implements Dumpable {
records.add(guestRecord);
}
if (canCreateUser) {
if (canCreateUser()) {
UserRecord addUserRecord = new UserRecord(null /* info */, null /* picture */,
false /* isGuest */, false /* isCurrent */, true /* isAddUser */,
createIsRestricted, canSwitchUsers);
createIsRestricted(), canSwitchUsers,
false /* isAddSupervisedUser */);
checkIfAddUserDisallowedByAdminOnly(addUserRecord);
records.add(addUserRecord);
}
if (canCreateSupervisedUser()) {
UserRecord addUserRecord = new UserRecord(null /* info */, null /* picture */,
false /* isGuest */, false /* isCurrent */, false /* isAddUser */,
createIsRestricted(), canSwitchUsers, true /* isAddSupervisedUser */);
checkIfAddUserDisallowedByAdminOnly(addUserRecord);
records.add(addUserRecord);
}
@@ -403,6 +402,40 @@ public class UserSwitcherController implements Dumpable {
}.execute((SparseArray) bitmaps);
}
boolean systemCanCreateUsers() {
return !mUserManager.hasBaseUserRestriction(
UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM);
}
boolean currentUserCanCreateUsers() {
UserInfo currentUser = mUserTracker.getUserInfo();
return currentUser != null
&& (currentUser.isAdmin() || mUserTracker.getUserId() == UserHandle.USER_SYSTEM)
&& systemCanCreateUsers();
}
boolean anyoneCanCreateUsers() {
return systemCanCreateUsers() && mAddUsersFromLockScreen;
}
boolean canCreateGuest(boolean hasExistingGuest) {
return (currentUserCanCreateUsers() || anyoneCanCreateUsers())
&& !hasExistingGuest;
}
boolean canCreateUser() {
return (currentUserCanCreateUsers() || anyoneCanCreateUsers())
&& mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY);
}
boolean createIsRestricted() {
return !mAddUsersFromLockScreen;
}
boolean canCreateSupervisedUser() {
return !TextUtils.isEmpty(mCreateSupervisedUserPackage) && canCreateUser();
}
private void pauseRefreshUsers() {
if (!mPauseRefreshUsers) {
mHandler.postDelayed(mUnpauseRefreshUsers, PAUSE_REFRESH_USERS_TIMEOUT_MS);
@@ -485,6 +518,9 @@ public class UserSwitcherController implements Dumpable {
} else if (record.isAddUser) {
showAddUserDialog(dialogShower);
return;
} else if (record.isAddSupervisedUser) {
startSupervisedUserActivity();
return;
} else {
id = record.info.id;
}
@@ -561,6 +597,22 @@ public class UserSwitcherController implements Dumpable {
}
}
private void startSupervisedUserActivity() {
final Intent intent = new Intent()
.setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
.setPackage(mCreateSupervisedUserPackage)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// TODO(b/209659998): [to-be-removed] fallback activity for supervised user creation.
if (mContext.getPackageManager().resolveActivity(intent, 0) == null) {
intent.setPackage(null)
.setClassName("com.android.settings",
"com.android.settings.users.AddSupervisedUserActivity");
}
mContext.startActivity(intent);
}
private void listenForCallState() {
mTelephonyListenerManager.addCallStateListener(mPhoneStateListener);
}
@@ -941,6 +993,8 @@ public class UserSwitcherController implements Dumpable {
}
} else if (item.isAddUser) {
return context.getString(R.string.user_add_user);
} else if (item.isAddSupervisedUser) {
return context.getString(R.string.add_user_supervised);
} else {
return item.info.name;
}
@@ -955,9 +1009,11 @@ public class UserSwitcherController implements Dumpable {
protected static Drawable getIconDrawable(Context context, UserRecord item) {
int iconRes;
if (item.isAddUser) {
iconRes = R.drawable.ic_add_circle;
iconRes = R.drawable.ic_account_circle;
} else if (item.isGuest) {
iconRes = R.drawable.ic_avatar_guest_user;
iconRes = R.drawable.ic_account_circle_filled;
} else if (item.isAddSupervisedUser) {
iconRes = R.drawable.ic_add_supervised_user;
} else {
iconRes = R.drawable.ic_avatar_user;
}
@@ -1000,6 +1056,7 @@ public class UserSwitcherController implements Dumpable {
public final boolean isGuest;
public final boolean isCurrent;
public final boolean isAddUser;
public final boolean isAddSupervisedUser;
/** If true, the record is only visible to the owner and only when unlocked. */
public final boolean isRestricted;
public boolean isDisabledByAdmin;
@@ -1007,7 +1064,8 @@ public class UserSwitcherController implements Dumpable {
public boolean isSwitchToEnabled;
public UserRecord(UserInfo info, Bitmap picture, boolean isGuest, boolean isCurrent,
boolean isAddUser, boolean isRestricted, boolean isSwitchToEnabled) {
boolean isAddUser, boolean isRestricted, boolean isSwitchToEnabled,
boolean isAddSupervisedUser) {
this.info = info;
this.picture = picture;
this.isGuest = isGuest;
@@ -1015,11 +1073,12 @@ public class UserSwitcherController implements Dumpable {
this.isAddUser = isAddUser;
this.isRestricted = isRestricted;
this.isSwitchToEnabled = isSwitchToEnabled;
this.isAddSupervisedUser = isAddSupervisedUser;
}
public UserRecord copyWithIsCurrent(boolean _isCurrent) {
return new UserRecord(info, picture, isGuest, _isCurrent, isAddUser, isRestricted,
isSwitchToEnabled);
isSwitchToEnabled, isAddSupervisedUser);
}
public int resolveId() {
@@ -1043,6 +1102,7 @@ public class UserSwitcherController implements Dumpable {
}
if (isGuest) sb.append(" <isGuest>");
if (isAddUser) sb.append(" <isAddUser>");
if (isAddSupervisedUser) sb.append(" <isAddSupervisedUser>");
if (isCurrent) sb.append(" <isCurrent>");
if (picture != null) sb.append(" <hasPicture>");
if (isRestricted) sb.append(" <isRestricted>");

View File

@@ -27,11 +27,10 @@ import android.graphics.drawable.InsetDrawable
import android.graphics.drawable.LayerDrawable
import android.os.Bundle
import android.os.UserManager
import android.provider.Settings
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
@@ -71,8 +70,18 @@ class UserSwitcherActivity @Inject constructor(
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 var addUserRecords = mutableListOf<UserRecord>()
// When the add users options become available, insert another option to manage users
private val manageUserRecord = UserRecord(
null /* info */,
null /* picture */,
false /* isGuest */,
false /* isCurrent */,
false /* isAddUser */,
false /* isRestricted */,
false /* isSwitchToEnabled */,
false /* isAddSupervisedUser */
)
private val adapter = object : BaseUserAdapter(userSwitcherController) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
@@ -104,7 +113,18 @@ class UserSwitcherActivity @Inject constructor(
return view
}
override fun getName(context: Context, item: UserRecord): String {
return if (item == manageUserRecord) {
getString(R.string.manage_users)
} else {
super.getName(context, item)
}
}
fun findUserIcon(item: UserRecord): Drawable {
if (item == manageUserRecord) {
return getDrawable(R.drawable.ic_manage_users)
}
if (item.info == null) {
return getIconDrawable(this@UserSwitcherActivity, item)
}
@@ -169,20 +189,11 @@ class UserSwitcherActivity @Inject constructor(
super.onCreate(savedInstanceState)
setContentView(R.layout.user_switcher_fullscreen)
window.decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
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
}
}
parent = requireViewById<ViewGroup>(R.id.user_switcher_root)
requireViewById<View>(R.id.cancel).apply {
setOnClickListener {
@@ -203,15 +214,19 @@ class UserSwitcherActivity @Inject constructor(
private fun showPopupMenu() {
val items = mutableListOf<UserRecord>()
addUserItem?.let { items.add(it) }
addGuestItem?.let { items.add(it) }
addUserRecords.forEach { 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) }
{ item: UserRecord -> adapter.findUserIcon(item).mutate().apply {
setTint(resources.getColor(
R.color.user_switcher_fullscreen_popup_item_tint,
getTheme()
))
} }
)
popupMenuAdapter.addAll(items)
@@ -225,10 +240,17 @@ class UserSwitcherActivity @Inject constructor(
}
// -1 for the header
val item = popupMenuAdapter.getItem(pos - 1)
adapter.onUserListItemClicked(item)
if (item == manageUserRecord) {
val i = Intent().setAction(Settings.ACTION_USER_SETTINGS)
this@UserSwitcherActivity.startActivity(i)
} else {
adapter.onUserListItemClicked(item)
}
dismiss()
popupMenu = null
this@UserSwitcherActivity.finish()
}
show()
@@ -245,14 +267,15 @@ class UserSwitcherActivity @Inject constructor(
}
}
parent.removeViews(start, count)
addUserRecords.clear()
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
if (item.isAddUser ||
item.isAddSupervisedUser ||
item.isGuest && item.info == null) {
addUserRecords.add(item)
} else {
val userView = adapter.getView(i, null, parent)
userView.setId(View.generateViewId())
@@ -273,7 +296,8 @@ class UserSwitcherActivity @Inject constructor(
}
}
if (addUserItem != null || addGuestItem != null) {
if (!addUserRecords.isEmpty()) {
addUserRecords.add(manageUserRecord)
addButton.visibility = View.VISIBLE
} else {
addButton.visibility = View.GONE

View File

@@ -42,7 +42,7 @@ class UserSwitcherPopupMenu(
setBackgroundDrawable(
res.getDrawable(R.drawable.bouncer_user_switcher_popup_bg, context.getTheme())
)
setModal(true)
setModal(false)
setOverlapAnchor(true)
}

View File

@@ -307,7 +307,8 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
UserInfo info = new UserInfo(i /* id */, "Name: " + i, null /* iconPath */,
0 /* flags */);
users.add(new UserRecord(info, null, false /* isGuest */, false /* isCurrent */,
false /* isAddUser */, false /* isRestricted */, true /* isSwitchToEnabled */));
false /* isAddUser */, false /* isRestricted */, true /* isSwitchToEnabled */,
false /* isAddSupervisedUser */));
}
return users;
}

View File

@@ -147,5 +147,6 @@ class UserDetailViewAdapterTest : SysuiTestCase() {
current,
false /* isAddUser */,
false /* isRestricted */,
true /* isSwitchToEnabled */)
true /* isSwitchToEnabled */,
false /* isAddSupervisedUser */)
}

View File

@@ -193,5 +193,6 @@ class KeyguardUserSwitcherAdapterTest : SysuiTestCase() {
isCurrentUser,
false /* isAddUser */,
false /* isRestricted */,
true /* isSwitchToEnabled */)
true /* isSwitchToEnabled */,
false /* isAddSupervisedUser */)
}

View File

@@ -56,6 +56,7 @@ import com.android.systemui.util.time.FakeSystemClock
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -130,6 +131,21 @@ class UserSwitcherControllerTest : SysuiTestCase() {
.thenReturn(true)
`when`(notificationShadeWindowView.context).thenReturn(context)
// Since userSwitcherController involves InteractionJankMonitor.
// Let's fulfill the dependencies.
val mockedContext = mock(Context::class.java)
doReturn(mockedContext).`when`(notificationShadeWindowView).context
doReturn(true).`when`(notificationShadeWindowView).isAttachedToWindow
doNothing().`when`(threadedRenderer).addObserver(any())
doNothing().`when`(threadedRenderer).removeObserver(any())
doReturn(threadedRenderer).`when`(notificationShadeWindowView).threadedRenderer
picture = UserIcons.convertToBitmap(context.getDrawable(R.drawable.ic_avatar_user))
setupController()
}
private fun setupController() {
userSwitcherController = UserSwitcherController(
context,
activityManager,
@@ -153,18 +169,6 @@ class UserSwitcherControllerTest : SysuiTestCase() {
dumpManager,
dialogLaunchAnimator)
userSwitcherController.mPauseRefreshUsers = true
// Since userSwitcherController involves InteractionJankMonitor.
// Let's fulfill the dependencies.
val mockedContext = mock(Context::class.java)
doReturn(mockedContext).`when`(notificationShadeWindowView).context
doReturn(true).`when`(notificationShadeWindowView).isAttachedToWindow
doNothing().`when`(threadedRenderer).addObserver(any())
doNothing().`when`(threadedRenderer).removeObserver(any())
doReturn(threadedRenderer).`when`(notificationShadeWindowView).threadedRenderer
userSwitcherController.init(notificationShadeWindowView)
picture = UserIcons.convertToBitmap(context.getDrawable(R.drawable.ic_avatar_user))
userSwitcherController.init(notificationShadeWindowView)
}
@@ -177,7 +181,8 @@ class UserSwitcherControllerTest : SysuiTestCase() {
false /* current */,
false /* isAddUser */,
false /* isRestricted */,
true /* isSwitchToEnabled */)
true /* isSwitchToEnabled */,
false /* isAddSupervisedUser */)
`when`(userTracker.userId).thenReturn(ownerId)
`when`(userTracker.userInfo).thenReturn(ownerInfo)
@@ -196,7 +201,8 @@ class UserSwitcherControllerTest : SysuiTestCase() {
false /* current */,
false /* isAddUser */,
false /* isRestricted */,
true /* isSwitchToEnabled */)
true /* isSwitchToEnabled */,
false /* isAddSupervisedUser */)
`when`(userTracker.userId).thenReturn(ownerId)
`when`(userTracker.userInfo).thenReturn(ownerInfo)
@@ -220,7 +226,8 @@ class UserSwitcherControllerTest : SysuiTestCase() {
false /* current */,
false /* isAddUser */,
false /* isRestricted */,
true /* isSwitchToEnabled */)
true /* isSwitchToEnabled */,
false /* isAddSupervisedUser */)
`when`(userTracker.userId).thenReturn(ownerId)
`when`(userTracker.userInfo).thenReturn(ownerInfo)
@@ -240,7 +247,8 @@ class UserSwitcherControllerTest : SysuiTestCase() {
true /* current */,
false /* isAddUser */,
false /* isRestricted */,
true /* isSwitchToEnabled */)
true /* isSwitchToEnabled */,
false /* isAddSupervisedUser */)
`when`(userTracker.userId).thenReturn(guestInfo.id)
`when`(userTracker.userInfo).thenReturn(guestInfo)
@@ -262,7 +270,8 @@ class UserSwitcherControllerTest : SysuiTestCase() {
true /* current */,
false /* isAddUser */,
false /* isRestricted */,
true /* isSwitchToEnabled */)
true /* isSwitchToEnabled */,
false /* isAddSupervisedUser */)
`when`(userTracker.userId).thenReturn(guestInfo.id)
`when`(userTracker.userInfo).thenReturn(guestInfo)
@@ -283,7 +292,8 @@ class UserSwitcherControllerTest : SysuiTestCase() {
true /* current */,
false /* isAddUser */,
false /* isRestricted */,
true /* isSwitchToEnabled */)
true /* isSwitchToEnabled */,
false /* isAddSupervisedUser */)
`when`(userTracker.userId).thenReturn(guestInfo.id)
`when`(userTracker.userInfo).thenReturn(guestInfo)
@@ -302,7 +312,8 @@ class UserSwitcherControllerTest : SysuiTestCase() {
true /* current */,
false /* isAddUser */,
false /* isRestricted */,
true /* isSwitchToEnabled */)
true /* isSwitchToEnabled */,
false /* isAddSupervisedUser */)
`when`(userTracker.userId).thenReturn(guestId)
`when`(userTracker.userInfo).thenReturn(guestInfo)
@@ -323,7 +334,8 @@ class UserSwitcherControllerTest : SysuiTestCase() {
false /* current */,
false /* isAddUser */,
false /* isRestricted */,
true /* isSwitchToEnabled */)
true /* isSwitchToEnabled */,
false /* isAddSupervisedUser */)
`when`(userTracker.userId).thenReturn(guestId)
`when`(userTracker.userInfo).thenReturn(guestInfo)
@@ -357,7 +369,8 @@ class UserSwitcherControllerTest : SysuiTestCase() {
false /* current */,
false /* isAddUser */,
false /* isRestricted */,
true /* isSwitchToEnabled */)
true /* isSwitchToEnabled */,
false /* isAddSupervisedUser */)
`when`(userTracker.userId).thenReturn(guestId)
`when`(userTracker.userInfo).thenReturn(guestInfo)
@@ -389,7 +402,7 @@ class UserSwitcherControllerTest : SysuiTestCase() {
userSwitcherController.users.add(UserSwitcherController.UserRecord(
UserInfo(id, name, 0),
null, false, isCurrent, false,
false, false
false, false, false
))
}
val bgUserName = "background_user"
@@ -412,4 +425,42 @@ class UserSwitcherControllerTest : SysuiTestCase() {
`when`(userTracker.userId).thenReturn(1)
assertEquals(false, userSwitcherController.isSystemUser)
}
@Test
fun testCanCreateSupervisedUserWithConfiguredPackage() {
// GIVEN the supervised user creation package is configured
`when`(context.getString(
com.android.internal.R.string.config_supervisedUserCreationPackage))
.thenReturn("some_pkg")
// AND the current user is allowed to create new users
`when`(userTracker.userId).thenReturn(ownerId)
`when`(userTracker.userInfo).thenReturn(ownerInfo)
// WHEN the controller is started with the above config
setupController()
testableLooper.processAllMessages()
// THEN a supervised user can be constructed
assertTrue(userSwitcherController.canCreateSupervisedUser())
}
@Test
fun testCannotCreateSupervisedUserWithConfiguredPackage() {
// GIVEN the supervised user creation package is NOT configured
`when`(context.getString(
com.android.internal.R.string.config_supervisedUserCreationPackage))
.thenReturn(null)
// AND the current user is allowed to create new users
`when`(userTracker.userId).thenReturn(ownerId)
`when`(userTracker.userInfo).thenReturn(ownerInfo)
// WHEN the controller is started with the above config
setupController()
testableLooper.processAllMessages()
// THEN a supervised user can NOT be constructed
assertFalse(userSwitcherController.canCreateSupervisedUser())
}
}