Improve multi user settings screen

- Added switch and user delete functionality to details screen.
- Added robo tests.

Screenshots: http://shortn/_S6fbIMhAYO
Bug: 142798722
Test: Run robo tests with this command:
  make -j64 RunSettingsRoboTests ROBOTEST_FILTER="com.android.settings.users.*SettingsTest"
Change-Id: Ied67290e8fed87feb0a60a3f2c40eb91cc57988e
This commit is contained in:
Andras Kloczl
2020-04-21 11:32:12 +01:00
parent 73d3367901
commit 4d7d4effa5
16 changed files with 1469 additions and 523 deletions

View File

@@ -0,0 +1,36 @@
<!--
~ Copyright (C) 2020 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="40dp"
android:height="40dp"
android:viewportWidth="20"
android:viewportHeight="20"
android:tint="?android:attr/colorControlNormal">
<group
android:scaleX="0.5"
android:scaleY="0.5"
android:pivotX="8"
android:pivotY="8">
<path
android:fillColor="@android:color/white"
android:pathData="M20,13h-7v7h-2v-7H4v-2h7V4h2v7h7V13z"/>
</group>
</vector>

29
res/drawable/ic_phone.xml Normal file
View File

@@ -0,0 +1,29 @@
<!--
~ Copyright (C) 2020 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.0"
android:viewportHeight="24.0"
android:tint="?android:attr/colorControlNormal"
android:autoMirrored="true">
<path
android:fillColor="#FF000000"
android:pathData="M6.62,10.79c1.44,2.83 3.76,5.14 6.59,6.59l2.2,-2.2c0.27,-0.27 0.67,-0.36
1.02,-0.24 1.12,0.37 2.33,0.57 3.57,0.57 0.55,0 1,0.45 1,1V20c0,0.55 -0.45,1 -1,1
-9.39,0 -17,-7.61 -17,-17 0,-0.55 0.45,-1 1,-1h3.5c0.55,0 1,0.45 1,1 0,1.25 0.2,2.45
0.57,3.57 0.11,0.35 0.03,0.74 -0.25,1.02l-2.2,2.2z"/>
</vector>

25
res/drawable/ic_swap.xml Normal file
View File

@@ -0,0 +1,25 @@
<!--
~ Copyright (C) 2020 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="24.0dp"
android:height="24.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
android:tint="?android:attr/colorControlNormal">
<path
android:pathData="M6.99,11L3,15l3.99,4v-3H14v-2H6.99v-3zM21,9l-3.99,-4v3H10v2h7.01v3L21,9z"
android:fillColor="#000000"/>
</vector>

View File

@@ -1,60 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2016 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.
-->
<!-- Used by UserPreference to show the trash icon -->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<include layout="@layout/restricted_icon" />
<LinearLayout android:id="@+id/user_delete_widget"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal">
<View
android:id="@+id/divider_manage"
android:layout_width="2dip"
android:layout_height="match_parent"
android:layout_marginTop="5dip"
android:layout_marginBottom="5dip"
android:background="@android:drawable/divider_horizontal_dark" />
<ImageView
android:id="@+id/manage_user"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:paddingStart="16dip"
android:paddingEnd="16dip"
android:src="@drawable/ic_settings_accent"
android:contentDescription="@string/settings_label"
android:layout_gravity="center"
android:background="?android:attr/selectableItemBackground" />
<View
android:id="@+id/divider_delete"
android:layout_width="2dip"
android:layout_height="match_parent"
android:layout_marginTop="5dip"
android:layout_marginBottom="5dip"
android:background="@android:drawable/divider_horizontal_dark" />
<ImageView
android:id="@+id/trash_user"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:paddingStart="16dip"
android:paddingEnd="16dip"
android:src="@drawable/ic_delete"
android:contentDescription="@string/user_delete_user_description"
android:layout_gravity="center"
android:background="?android:attr/selectableItemBackground" />
</LinearLayout>
</merge>

View File

@@ -15,6 +15,7 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
@@ -45,8 +46,7 @@
android:layout_gravity="center"
android:minWidth="48dp"
android:scaleType="centerInside"
android:layout_marginEnd="@*android:dimen/preference_item_padding_inner"
/>
android:layout_marginEnd="@*android:dimen/preference_item_padding_inner"/>
</LinearLayout>
<RelativeLayout
android:layout_width="wrap_content"
@@ -60,6 +60,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
tools:text="Richard"
android:textAppearance="?android:attr/textAppearanceMedium"
android:ellipsize="marquee"
android:fadingEdge="horizontal"/>
@@ -88,6 +89,40 @@
android:layout_gravity="center"
android:background="?android:attr/selectableItemBackground" />
</LinearLayout>
<LinearLayout
android:id="@+id/switch_pref"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:visibility="gone"
tools:visibility="visible"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:background="?android:attr/selectableItemBackground" >
<ImageView
android:id="@+id/switchIcon"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:src="@drawable/ic_swap"
android:minWidth="48dp"
android:scaleType="centerInside"
android:layout_marginEnd="@*android:dimen/preference_item_padding_inner"/>
<TextView
android:id="@+id/switchTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
tools:text="Switch to Richard"
android:textAppearance="?android:attr/textAppearanceMedium"
android:ellipsize="marquee"
android:fadingEdge="horizontal"/>
</LinearLayout>
<View android:layout_width="match_parent"
android:layout_height="2dp"
android:background="@color/divider_color" />

View File

@@ -17,12 +17,17 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto">
<com.android.settingslib.RestrictedPreference
android:key="switch_user"
android:icon="@drawable/ic_swap" />
<SwitchPreference
android:key="enable_calling"
android:icon="@drawable/ic_phone"
android:title="@string/user_enable_calling_sms" />
<com.android.settingslib.RestrictedPreference
android:key="remove_user"
android:title="@string/user_remove_user"
android:icon="@drawable/ic_delete"
settings:userRestriction="no_remove_user"
settings:useAdminDisabledSummary="true" />

View File

@@ -27,10 +27,15 @@
settings:searchable="false">
</PreferenceCategory>
<com.android.settingslib.RestrictedPreference
android:key="guest_add"
android:title="@string/guest_new_guest"
android:order="15"/>
<com.android.settingslib.RestrictedPreference
android:key="user_add"
android:title="@string/user_add_user_or_profile_menu"
android:icon="@drawable/ic_add_24dp"
android:icon="@drawable/ic_add_40dp"
android:order="20"/>
<com.android.settingslib.RestrictedSwitchPreference

View File

@@ -97,8 +97,15 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen
/** Key for extra passed in from calling fragment to indicate if this is a newly created user */
public static final String EXTRA_NEW_USER = "new_user";
/**
* Key for extra passed in from calling fragment to indicate if
* switch to user should be shown
*/
public static final String EXTRA_SHOW_SWITCH_USER = "enable_switch";
private boolean mFirstTime = true;
private boolean mNewUser;
protected boolean mShowSwitchUser;
private boolean mAppListChanged;
protected boolean mRestrictedProfile;
@@ -219,6 +226,7 @@ public class AppRestrictionsFragment extends SettingsPreferenceFragment implemen
mUser = new UserHandle(args.getInt(EXTRA_USER_ID));
}
mNewUser = args.getBoolean(EXTRA_NEW_USER, false);
mShowSwitchUser = args.getBoolean(EXTRA_SHOW_SWITCH_USER, false);
}
}

View File

@@ -16,6 +16,7 @@
package com.android.settings.users;
import android.app.ActivityManager;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.DialogInterface;
@@ -23,7 +24,10 @@ import android.content.Intent;
import android.content.pm.UserInfo;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
@@ -36,6 +40,7 @@ import com.android.settingslib.utils.ThreadUtils;
public class RestrictedProfileSettings extends AppRestrictionsFragment
implements EditUserInfoController.OnContentChangedCallback {
private static final String TAG = RestrictedProfileSettings.class.getSimpleName();
public static final String FILE_PROVIDER_AUTHORITY = "com.android.settings.files";
static final int DIALOG_ID_EDIT_USER_INFO = 1;
private static final int DIALOG_CONFIRM_REMOVE = 2;
@@ -44,6 +49,8 @@ public class RestrictedProfileSettings extends AppRestrictionsFragment
private ImageView mUserIconView;
private TextView mUserNameView;
private ImageView mDeleteButton;
private View mSwitchUserView;
private TextView mSwitchTitle;
private EditUserInfoController mEditUserInfoController =
new EditUserInfoController();
@@ -67,6 +74,11 @@ public class RestrictedProfileSettings extends AppRestrictionsFragment
mUserNameView = (TextView) mHeaderView.findViewById(android.R.id.title);
mDeleteButton = (ImageView) mHeaderView.findViewById(R.id.delete);
mDeleteButton.setOnClickListener(this);
mSwitchTitle = mHeaderView.findViewById(R.id.switchTitle);
mSwitchUserView = mHeaderView.findViewById(R.id.switch_pref);
mSwitchUserView.setOnClickListener(v -> switchUser());
// This is going to bind the preferences.
super.onActivityCreated(savedInstanceState);
}
@@ -80,7 +92,6 @@ public class RestrictedProfileSettings extends AppRestrictionsFragment
@Override
public void onResume() {
super.onResume();
// Check if user still exists
UserInfo info = Utils.getExistingUser(mUserManager, mUser);
if (info == null) {
@@ -89,6 +100,16 @@ public class RestrictedProfileSettings extends AppRestrictionsFragment
((TextView) mHeaderView.findViewById(android.R.id.title)).setText(info.name);
((ImageView) mHeaderView.findViewById(android.R.id.icon)).setImageDrawable(
com.android.settingslib.Utils.getUserIcon(getActivity(), mUserManager, info));
boolean canSwitchUser =
mUserManager.getUserSwitchability() == UserManager.SWITCHABILITY_STATUS_OK;
if (mShowSwitchUser && canSwitchUser) {
mSwitchUserView.setVisibility(View.VISIBLE);
mSwitchTitle.setText(getString(com.android.settingslib.R.string.user_switch_to_user,
info.name));
} else {
mSwitchUserView.setVisibility(View.GONE);
}
}
}
@@ -158,6 +179,16 @@ public class RestrictedProfileSettings extends AppRestrictionsFragment
});
}
private void switchUser() {
try {
ActivityManager.getService().switchUser(mUser.getIdentifier());
} catch (RemoteException re) {
Log.e(TAG, "Error while switching to other user.");
} finally {
finishFragment();
}
}
@Override
public void onPhotoChanged(UserHandle user, Drawable photo) {
mUserIconView.setImageDrawable(photo);

View File

@@ -16,55 +16,63 @@
package com.android.settings.users;
import static android.os.UserHandle.USER_NULL;
import android.app.ActivityManager;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.Utils;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtilsInternal;
import java.util.List;
/**
* Settings screen for configuring a specific user. It can contain user restrictions
* and deletion controls. It is shown when you tap on the settings icon in the
* user management (UserSettings) screen.
* Settings screen for configuring, deleting or switching to a specific user.
* It is shown when you tap on a user in the user management (UserSettings) screen.
*
* Arguments to this fragment must include the userId of the user (in EXTRA_USER_ID) for whom
* to display controls, or should contain the EXTRA_USER_GUEST = true.
* to display controls.
*/
public class UserDetailsSettings extends SettingsPreferenceFragment
implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener {
private static final String TAG = UserDetailsSettings.class.getSimpleName();
private static final String KEY_SWITCH_USER = "switch_user";
private static final String KEY_ENABLE_TELEPHONY = "enable_calling";
private static final String KEY_REMOVE_USER = "remove_user";
/** Integer extra containing the userId to manage */
static final String EXTRA_USER_ID = "user_id";
/** Boolean extra to indicate guest preferences */
static final String EXTRA_USER_GUEST = "guest_user";
private static final int DIALOG_CONFIRM_REMOVE = 1;
private static final int DIALOG_CONFIRM_ENABLE_CALLING = 2;
private static final int DIALOG_CONFIRM_ENABLE_CALLING_AND_SMS = 3;
private UserManager mUserManager;
@VisibleForTesting
Preference mSwitchUserPref;
private SwitchPreference mPhonePref;
private Preference mRemoveUserPref;
@VisibleForTesting
Preference mRemoveUserPref;
private UserInfo mUserInfo;
private boolean mGuestUser;
@VisibleForTesting
UserInfo mUserInfo;
private Bundle mDefaultGuestRestrictions;
@Override
@@ -78,46 +86,28 @@ public class UserDetailsSettings extends SettingsPreferenceFragment
final Context context = getActivity();
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
addPreferencesFromResource(R.xml.user_details_settings);
mPhonePref = (SwitchPreference) findPreference(KEY_ENABLE_TELEPHONY);
mRemoveUserPref = findPreference(KEY_REMOVE_USER);
mGuestUser = getArguments().getBoolean(EXTRA_USER_GUEST, false);
initialize(context, getArguments());
}
if (!mGuestUser) {
// Regular user. Get the user id from the caller.
final int userId = getArguments().getInt(EXTRA_USER_ID, -1);
if (userId == -1) {
throw new RuntimeException("Arguments to this fragment must contain the user id");
}
mUserInfo = mUserManager.getUserInfo(userId);
mPhonePref.setChecked(!mUserManager.hasUserRestriction(
UserManager.DISALLOW_OUTGOING_CALLS, new UserHandle(userId)));
mRemoveUserPref.setOnPreferenceClickListener(this);
} else {
// These are not for an existing user, just general Guest settings.
removePreference(KEY_REMOVE_USER);
// Default title is for calling and SMS. Change to calling-only here
mPhonePref.setTitle(R.string.user_enable_calling);
mDefaultGuestRestrictions = mUserManager.getDefaultGuestRestrictions();
mPhonePref.setChecked(
!mDefaultGuestRestrictions.getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
}
if (RestrictedLockUtilsInternal.hasBaseUserRestriction(context,
UserManager.DISALLOW_REMOVE_USER, UserHandle.myUserId())) {
removePreference(KEY_REMOVE_USER);
}
mPhonePref.setOnPreferenceChangeListener(this);
@Override
public void onResume() {
super.onResume();
mSwitchUserPref.setEnabled(canSwitchUserNow());
}
@Override
public boolean onPreferenceClick(Preference preference) {
if (preference == mRemoveUserPref) {
if (!mUserManager.isAdminUser()) {
throw new RuntimeException("Only admins can remove a user");
if (canDeleteUser()) {
showDialog(DIALOG_CONFIRM_REMOVE);
}
return true;
} else if (preference == mSwitchUserPref) {
if (canSwitchUserNow()) {
switchUser();
}
showDialog(DIALOG_CONFIRM_REMOVE);
return true;
}
return false;
@@ -126,7 +116,7 @@ public class UserDetailsSettings extends SettingsPreferenceFragment
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (Boolean.TRUE.equals(newValue)) {
showDialog(mGuestUser ? DIALOG_CONFIRM_ENABLE_CALLING
showDialog(mUserInfo.isGuest() ? DIALOG_CONFIRM_ENABLE_CALLING
: DIALOG_CONFIRM_ENABLE_CALLING_AND_SMS);
return false;
}
@@ -134,65 +124,6 @@ public class UserDetailsSettings extends SettingsPreferenceFragment
return true;
}
void enableCallsAndSms(boolean enabled) {
mPhonePref.setChecked(enabled);
if (mGuestUser) {
mDefaultGuestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, !enabled);
// SMS is always disabled for guest
mDefaultGuestRestrictions.putBoolean(UserManager.DISALLOW_SMS, true);
mUserManager.setDefaultGuestRestrictions(mDefaultGuestRestrictions);
// Update the guest's restrictions, if there is a guest
// TODO: Maybe setDefaultGuestRestrictions() can internally just set the restrictions
// on any existing guest rather than do it here with multiple Binder calls.
List<UserInfo> users = mUserManager.getUsers(true);
for (UserInfo user: users) {
if (user.isGuest()) {
UserHandle userHandle = UserHandle.of(user.id);
for (String key : mDefaultGuestRestrictions.keySet()) {
mUserManager.setUserRestriction(
key, mDefaultGuestRestrictions.getBoolean(key), userHandle);
}
}
}
} else {
UserHandle userHandle = UserHandle.of(mUserInfo.id);
mUserManager.setUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, !enabled,
userHandle);
mUserManager.setUserRestriction(UserManager.DISALLOW_SMS, !enabled, userHandle);
}
}
@Override
public Dialog onCreateDialog(int dialogId) {
Context context = getActivity();
if (context == null) return null;
switch (dialogId) {
case DIALOG_CONFIRM_REMOVE:
return UserDialogs.createRemoveDialog(getActivity(), mUserInfo.id,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
removeUser();
}
});
case DIALOG_CONFIRM_ENABLE_CALLING:
return UserDialogs.createEnablePhoneCallsDialog(getActivity(),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
enableCallsAndSms(true);
}
});
case DIALOG_CONFIRM_ENABLE_CALLING_AND_SMS:
return UserDialogs.createEnablePhoneCallsAndSmsDialog(getActivity(),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
enableCallsAndSms(true);
}
});
}
throw new IllegalArgumentException("Unsupported dialogId " + dialogId);
}
@Override
public int getDialogMetricsCategory(int dialogId) {
switch (dialogId) {
@@ -207,7 +138,148 @@ public class UserDetailsSettings extends SettingsPreferenceFragment
}
}
void removeUser() {
@Override
public Dialog onCreateDialog(int dialogId) {
Context context = getActivity();
if (context == null) {
return null;
}
switch (dialogId) {
case DIALOG_CONFIRM_REMOVE:
return UserDialogs.createRemoveDialog(getActivity(), mUserInfo.id,
(dialog, which) -> removeUser());
case DIALOG_CONFIRM_ENABLE_CALLING:
return UserDialogs.createEnablePhoneCallsDialog(getActivity(),
(dialog, which) -> enableCallsAndSms(true));
case DIALOG_CONFIRM_ENABLE_CALLING_AND_SMS:
return UserDialogs.createEnablePhoneCallsAndSmsDialog(getActivity(),
(dialog, which) -> enableCallsAndSms(true));
}
throw new IllegalArgumentException("Unsupported dialogId " + dialogId);
}
@VisibleForTesting
@Override
protected void showDialog(int dialogId) {
super.showDialog(dialogId);
}
@VisibleForTesting
void initialize(Context context, Bundle arguments) {
int userId = arguments != null ? arguments.getInt(EXTRA_USER_ID, USER_NULL) : USER_NULL;
if (userId == USER_NULL) {
throw new IllegalStateException("Arguments to this fragment must contain the user id");
}
mUserInfo = mUserManager.getUserInfo(userId);
mSwitchUserPref = findPreference(KEY_SWITCH_USER);
mPhonePref = findPreference(KEY_ENABLE_TELEPHONY);
mRemoveUserPref = findPreference(KEY_REMOVE_USER);
mSwitchUserPref.setTitle(
context.getString(com.android.settingslib.R.string.user_switch_to_user,
mUserInfo.name));
mSwitchUserPref.setOnPreferenceClickListener(this);
if (!mUserManager.isAdminUser()) { // non admin users can't remove users and allow calls
removePreference(KEY_ENABLE_TELEPHONY);
removePreference(KEY_REMOVE_USER);
} else {
if (!Utils.isVoiceCapable(context)) { // no telephony
removePreference(KEY_ENABLE_TELEPHONY);
}
if (!mUserInfo.isGuest()) {
mPhonePref.setChecked(!mUserManager.hasUserRestriction(
UserManager.DISALLOW_OUTGOING_CALLS, new UserHandle(userId)));
mRemoveUserPref.setTitle(R.string.user_remove_user);
} else {
// These are not for an existing user, just general Guest settings.
// Default title is for calling and SMS. Change to calling-only here
mPhonePref.setTitle(R.string.user_enable_calling);
mDefaultGuestRestrictions = mUserManager.getDefaultGuestRestrictions();
mPhonePref.setChecked(
!mDefaultGuestRestrictions.getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
mRemoveUserPref.setTitle(R.string.user_exit_guest_title);
}
if (RestrictedLockUtilsInternal.hasBaseUserRestriction(context,
UserManager.DISALLOW_REMOVE_USER, UserHandle.myUserId())) {
removePreference(KEY_REMOVE_USER);
}
mRemoveUserPref.setOnPreferenceClickListener(this);
mPhonePref.setOnPreferenceChangeListener(this);
}
}
@VisibleForTesting
boolean canDeleteUser() {
if (!mUserManager.isAdminUser()) {
return false;
}
Context context = getActivity();
if (context == null) {
return false;
}
final RestrictedLockUtils.EnforcedAdmin removeDisallowedAdmin =
RestrictedLockUtilsInternal.checkIfRestrictionEnforced(context,
UserManager.DISALLOW_REMOVE_USER, UserHandle.myUserId());
if (removeDisallowedAdmin != null) {
RestrictedLockUtils.sendShowAdminSupportDetailsIntent(context,
removeDisallowedAdmin);
return false;
}
return true;
}
@VisibleForTesting
boolean canSwitchUserNow() {
return mUserManager.getUserSwitchability() == UserManager.SWITCHABILITY_STATUS_OK;
}
@VisibleForTesting
void switchUser() {
try {
ActivityManager.getService().switchUser(mUserInfo.id);
} catch (RemoteException re) {
Log.e(TAG, "Error while switching to other user.");
} finally {
finishFragment();
}
}
private void enableCallsAndSms(boolean enabled) {
mPhonePref.setChecked(enabled);
if (mUserInfo.isGuest()) {
mDefaultGuestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, !enabled);
// SMS is always disabled for guest
mDefaultGuestRestrictions.putBoolean(UserManager.DISALLOW_SMS, true);
mUserManager.setDefaultGuestRestrictions(mDefaultGuestRestrictions);
// Update the guest's restrictions, if there is a guest
// TODO: Maybe setDefaultGuestRestrictions() can internally just set the restrictions
// on any existing guest rather than do it here with multiple Binder calls.
List<UserInfo> users = mUserManager.getUsers(true);
for (UserInfo user : users) {
if (user.isGuest()) {
UserHandle userHandle = UserHandle.of(user.id);
for (String key : mDefaultGuestRestrictions.keySet()) {
mUserManager.setUserRestriction(
key, mDefaultGuestRestrictions.getBoolean(key), userHandle);
}
}
}
} else {
UserHandle userHandle = UserHandle.of(mUserInfo.id);
mUserManager.setUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, !enabled,
userHandle);
mUserManager.setUserRestriction(UserManager.DISALLOW_SMS, !enabled, userHandle);
}
}
private void removeUser() {
mUserManager.removeUser(mUserInfo.id);
finishFragment();
}

View File

@@ -21,18 +21,16 @@ import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.AttributeSet;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.RestrictedPreference;
import java.util.Comparator;
/**
* Preference for a user that appear on {@link UserSettings} screen.
*/
public class UserPreference extends RestrictedPreference {
private static final int ALPHA_ENABLED = 255;
private static final int ALPHA_DISABLED = 102;
@@ -44,8 +42,7 @@ public class UserPreference extends RestrictedPreference {
if (p1 == null) {
return -1;
}
else if (p2 == null) {
} else if (p2 == null) {
return 1;
}
int sn1 = p1.getSerialNumber();
@@ -58,26 +55,15 @@ public class UserPreference extends RestrictedPreference {
return 0;
};
private OnClickListener mDeleteClickListener;
private OnClickListener mSettingsClickListener;
private int mSerialNumber = -1;
private int mUserId = USERID_UNKNOWN;
static final int SETTINGS_ID = R.id.manage_user;
static final int DELETE_ID = R.id.trash_user;
public UserPreference(Context context, AttributeSet attrs) {
this(context, attrs, USERID_UNKNOWN, null, null);
this(context, attrs, USERID_UNKNOWN);
}
UserPreference(Context context, AttributeSet attrs, int userId,
OnClickListener settingsListener,
OnClickListener deleteListener) {
UserPreference(Context context, AttributeSet attrs, int userId) {
super(context, attrs);
if (deleteListener != null || settingsListener != null) {
setWidgetLayoutResource(R.layout.restricted_preference_user_delete_widget);
}
mDeleteClickListener = deleteListener;
mSettingsClickListener = settingsListener;
mUserId = userId;
useAdminDisabledSummary(true);
}
@@ -92,62 +78,13 @@ public class UserPreference extends RestrictedPreference {
@Override
protected boolean shouldHideSecondTarget() {
if (isDisabledByAdmin()) {
// Disabled by admin, show no secondary target.
return true;
}
if (canDeleteUser()) {
// Need to show delete user target so don't hide.
return false;
}
// Hide if don't have advanced setting listener.
return mSettingsClickListener == null;
return true;
}
@Override
public void onBindViewHolder(PreferenceViewHolder view) {
super.onBindViewHolder(view);
final boolean disabledByAdmin = isDisabledByAdmin();
dimIcon(disabledByAdmin);
View userDeleteWidget = view.findViewById(R.id.user_delete_widget);
if (userDeleteWidget != null) {
userDeleteWidget.setVisibility(disabledByAdmin ? View.GONE : View.VISIBLE);
}
if (!disabledByAdmin) {
View deleteDividerView = view.findViewById(R.id.divider_delete);
View manageDividerView = view.findViewById(R.id.divider_manage);
View deleteView = view.findViewById(R.id.trash_user);
if (deleteView != null) {
if (canDeleteUser()) {
deleteView.setVisibility(View.VISIBLE);
deleteDividerView.setVisibility(View.VISIBLE);
deleteView.setOnClickListener(mDeleteClickListener);
deleteView.setTag(this);
} else {
deleteView.setVisibility(View.GONE);
deleteDividerView.setVisibility(View.GONE);
}
}
ImageView manageView = (ImageView) view.findViewById(R.id.manage_user);
if (manageView != null) {
if (mSettingsClickListener != null) {
manageView.setVisibility(View.VISIBLE);
manageDividerView.setVisibility(mDeleteClickListener == null
? View.VISIBLE : View.GONE);
manageView.setOnClickListener(mSettingsClickListener);
manageView.setTag(this);
} else {
manageView.setVisibility(View.GONE);
manageDividerView.setVisibility(View.GONE);
}
}
}
}
private boolean canDeleteUser() {
return mDeleteClickListener != null
&& !RestrictedLockUtilsInternal.hasBaseUserRestriction(getContext(),
UserManager.DISALLOW_REMOVE_USER, UserHandle.myUserId());
dimIcon(isDisabledByAdmin());
}
private int getSerialNumber() {

View File

@@ -48,7 +48,6 @@ import android.util.SparseArray;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.SimpleAdapter;
import androidx.annotation.VisibleForTesting;
@@ -69,7 +68,6 @@ import com.android.settings.password.ChooseLockGeneric;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.widget.SwitchBar;
import com.android.settings.widget.SwitchBarController;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.RestrictedPreference;
@@ -89,15 +87,14 @@ import java.util.Random;
/**
* Screen that manages the list of users on the device.
* Guest user is an always visible entry, even if the guest is not currently
* active/created. It is meant for controlling properties of a guest user.
* Secondary users and a guest user can be created if there is no restriction.
*
* The first one is always the current user.
* The first user in the list is always the current user.
* Owner is the primary user.
*/
@SearchIndexable
public class UserSettings extends SettingsPreferenceFragment
implements Preference.OnPreferenceClickListener, View.OnClickListener,
implements Preference.OnPreferenceClickListener,
MultiUserSwitchBarController.OnMultiUserSwitchChangedListener,
DialogInterface.OnDismissListener {
@@ -111,6 +108,7 @@ public class UserSettings extends SettingsPreferenceFragment
private static final String KEY_USER_LIST = "user_list";
private static final String KEY_USER_ME = "user_me";
private static final String KEY_USER_GUEST = "user_guest";
private static final String KEY_ADD_GUEST = "guest_add";
private static final String KEY_ADD_USER = "user_add";
private static final String KEY_ADD_USER_WHEN_LOCKED = "user_settings_add_users_when_locked";
private static final String KEY_MULTIUSER_FOOTER = "multiuser_footer";
@@ -156,7 +154,11 @@ public class UserSettings extends SettingsPreferenceFragment
@VisibleForTesting
UserPreference mMePreference;
@VisibleForTesting
RestrictedPreference mAddGuest;
@VisibleForTesting
RestrictedPreference mAddUser;
@VisibleForTesting
SparseArray<Bitmap> mUserIcons = new SparseArray<>();
private int mRemovingUserId = -1;
private int mAddedUserId = 0;
private boolean mAddingUser;
@@ -165,7 +167,6 @@ public class UserSettings extends SettingsPreferenceFragment
private boolean mShouldUpdateUserList = true;
private final Object mUserLock = new Object();
private UserManager mUserManager;
private SparseArray<Bitmap> mUserIcons = new SparseArray<>();
private static SparseArray<Bitmap> sDarkDefaultUserBitmapCache = new SparseArray<>();
private MultiUserSwitchBarController mSwitchBarController;
@@ -271,15 +272,17 @@ public class UserSettings extends SettingsPreferenceFragment
final int myUserId = UserHandle.myUserId();
mUserListCategory = (PreferenceGroup) findPreference(KEY_USER_LIST);
mMePreference = new UserPreference(getPrefContext(), null /* attrs */, myUserId,
null /* settings icon handler */,
null /* delete icon handler */);
mMePreference = new UserPreference(getPrefContext(), null /* attrs */, myUserId);
mMePreference.setKey(KEY_USER_ME);
mMePreference.setOnPreferenceClickListener(this);
if (mUserCaps.mIsAdmin) {
mMePreference.setSummary(R.string.user_admin);
}
mAddUser = (RestrictedPreference) findPreference(KEY_ADD_USER);
mAddGuest = findPreference(KEY_ADD_GUEST);
mAddGuest.setOnPreferenceClickListener(this);
mAddUser = findPreference(KEY_ADD_USER);
if (!mUserCaps.mCanAddRestrictedProfile) {
// Label should only mention adding a "user", not a "profile"
mAddUser.setTitle(R.string.user_add_user_menu);
@@ -344,8 +347,7 @@ public class UserSettings extends SettingsPreferenceFragment
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
int pos = 0;
final boolean canSwitchUsers = mUserManager.canSwitchUsers();
if (!mUserCaps.mIsAdmin && canSwitchUsers) {
if (!mUserCaps.mIsAdmin && canSwitchUserNow()) {
String nickname = mUserManager.getUserName();
MenuItem removeThisUser = menu.add(0, MENU_REMOVE_USER, pos++,
getResources().getString(R.string.user_remove_user_menu, nickname));
@@ -386,10 +388,13 @@ public class UserSettings extends SettingsPreferenceFragment
* Loads profile information for the current user.
*/
private void loadProfile() {
if (mUserCaps.mIsGuest) {
if (isCurrentUserGuest()) {
// No need to load profile information
mMePreference.setIcon(getEncircledDefaultIcon());
mMePreference.setTitle(R.string.user_exit_guest_title);
mMePreference.setSelectable(true);
// removing a guest will result in switching back to the admin user
mMePreference.setEnabled(canSwitchUserNow());
return;
}
@@ -412,7 +417,9 @@ public class UserSettings extends SettingsPreferenceFragment
}
private void finishLoadProfile(String profileName) {
if (getActivity() == null) return;
if (getActivity() == null) {
return;
}
mMePreference.setTitle(getString(R.string.user_you, profileName));
int myUserId = UserHandle.myUserId();
Bitmap b = mUserManager.getUserIcon(myUserId);
@@ -477,38 +484,28 @@ public class UserSettings extends SettingsPreferenceFragment
private void onManageUserClicked(int userId, boolean newUser) {
mAddingUser = false;
if (userId == UserPreference.USERID_GUEST_DEFAULTS) {
Bundle extras = new Bundle();
extras.putBoolean(UserDetailsSettings.EXTRA_USER_GUEST, true);
new SubSettingLauncher(getContext())
.setDestination(UserDetailsSettings.class.getName())
.setArguments(extras)
.setTitleRes(R.string.user_guest)
.setSourceMetricsCategory(getMetricsCategory())
.launch();
return;
}
UserInfo info = mUserManager.getUserInfo(userId);
if (info.isRestricted() && mUserCaps.mIsAdmin) {
UserInfo userInfo = mUserManager.getUserInfo(userId);
if (userInfo.isRestricted() && mUserCaps.mIsAdmin) {
Bundle extras = new Bundle();
extras.putInt(RestrictedProfileSettings.EXTRA_USER_ID, userId);
extras.putBoolean(RestrictedProfileSettings.EXTRA_NEW_USER, newUser);
extras.putBoolean(RestrictedProfileSettings.EXTRA_SHOW_SWITCH_USER, canSwitchUserNow());
new SubSettingLauncher(getContext())
.setDestination(RestrictedProfileSettings.class.getName())
.setArguments(extras)
.setTitleRes(R.string.user_restrictions_title)
.setSourceMetricsCategory(getMetricsCategory())
.launch();
} else if (info.id == UserHandle.myUserId()) {
} else if (userId == UserHandle.myUserId()) {
// Jump to owner info panel
OwnerInfoSettings.show(this);
} else if (mUserCaps.mIsAdmin) {
final Bundle extras = new Bundle();
} else {
Bundle extras = new Bundle();
extras.putInt(UserDetailsSettings.EXTRA_USER_ID, userId);
new SubSettingLauncher(getContext())
.setDestination(UserDetailsSettings.class.getName())
.setArguments(extras)
.setTitleText(info.name)
.setTitleText(userInfo.name)
.setSourceMetricsCategory(getMetricsCategory())
.launch();
}
@@ -538,7 +535,9 @@ public class UserSettings extends SettingsPreferenceFragment
@Override
public Dialog onCreateDialog(int dialogId) {
Context context = getActivity();
if (context == null) return null;
if (context == null) {
return null;
}
switch (dialogId) {
case DIALOG_CONFIRM_REMOVE: {
Dialog dlg =
@@ -811,7 +810,7 @@ public class UserSettings extends SettingsPreferenceFragment
}
private void removeThisUser() {
if (!mUserManager.canSwitchUsers()) {
if (!canSwitchUserNow()) {
Log.w(TAG, "Cannot remove current user when switching is disabled");
return;
}
@@ -882,10 +881,14 @@ public class UserSettings extends SettingsPreferenceFragment
}
private void switchUserNow(int userId) {
if (!canSwitchUserNow()) {
return;
}
try {
ActivityManager.getService().switchUser(userId);
} catch (RemoteException re) {
// Nothing to do
Log.e(TAG, "Error while switching to other user.");
}
}
@@ -894,7 +897,7 @@ public class UserSettings extends SettingsPreferenceFragment
*/
private void exitGuest() {
// Just to be safe
if (!mUserCaps.mIsGuest) {
if (!isCurrentUserGuest()) {
return;
}
removeThisUser();
@@ -908,12 +911,12 @@ public class UserSettings extends SettingsPreferenceFragment
}
final List<UserInfo> users = mUserManager.getUsers(true);
final boolean voiceCapable = Utils.isVoiceCapable(context);
final ArrayList<Integer> missingIcons = new ArrayList<>();
final ArrayList<UserPreference> userPreferences = new ArrayList<>();
int guestId = UserPreference.USERID_GUEST_DEFAULTS;
userPreferences.add(mMePreference);
boolean canOpenUserDetails =
mUserCaps.mIsAdmin || (canSwitchUserNow() && !mUserCaps.mDisallowSwitchUser);
for (UserInfo user : users) {
if (!user.supportsSwitchToByUser()) {
// Only users that can be switched to should show up here.
@@ -924,37 +927,38 @@ public class UserSettings extends SettingsPreferenceFragment
if (user.id == UserHandle.myUserId()) {
pref = mMePreference;
} else if (user.isGuest()) {
// Skip over Guest. We add generic Guest settings after this loop
guestId = user.id;
continue;
pref = new UserPreference(getPrefContext(), null, user.id);
pref.setTitle(R.string.user_guest);
pref.setIcon(getEncircledDefaultIcon());
pref.setKey(KEY_USER_GUEST);
userPreferences.add(pref);
pref.setEnabled(canOpenUserDetails);
pref.setSelectable(true);
if (mUserCaps.mDisallowSwitchUser) {
pref.setDisabledByAdmin(RestrictedLockUtilsInternal.getDeviceOwner(context));
} else {
pref.setDisabledByAdmin(null);
}
pref.setOnPreferenceClickListener(this);
} else {
// With Telephony:
// Secondary user: Settings
// Guest: Settings
// Restricted Profile: There is no Restricted Profile
// Without Telephony:
// Secondary user: Delete
// Guest: Nothing
// Restricted Profile: Settings
final boolean showSettings = mUserCaps.mIsAdmin
&& (voiceCapable || user.isRestricted());
final boolean showDelete = mUserCaps.mIsAdmin
&& (!voiceCapable && !user.isRestricted() && !user.isGuest());
pref = new UserPreference(getPrefContext(), null, user.id,
showSettings ? this : null,
showDelete ? this : null);
pref = new UserPreference(getPrefContext(), null, user.id);
pref.setKey("id=" + user.id);
userPreferences.add(pref);
if (user.isAdmin()) {
pref.setSummary(R.string.user_admin);
}
pref.setTitle(user.name);
pref.setSelectable(false);
pref.setOnPreferenceClickListener(this);
pref.setEnabled(canOpenUserDetails);
pref.setSelectable(true);
}
if (pref == null) {
continue;
}
if (!isInitialized(user)) {
if (user.id != UserHandle.myUserId() && !user.isGuest() && !user.isInitialized()) {
// sometimes after creating a guest the initialized flag isn't immediately set
// and we don't want to show "Not set up" summary for them
if (user.isRestricted()) {
pref.setSummary(R.string.user_summary_restricted_not_set_up);
} else {
@@ -962,10 +966,7 @@ public class UserSettings extends SettingsPreferenceFragment
}
// Disallow setting up user which results in user switching when the restriction is
// set.
if (!mUserCaps.mDisallowSwitchUser) {
pref.setOnPreferenceClickListener(this);
pref.setSelectable(mUserManager.canSwitchUsers());
}
pref.setEnabled(!mUserCaps.mDisallowSwitchUser && canSwitchUserNow());
} else if (user.isRestricted()) {
pref.setSummary(R.string.user_summary_restricted_profile);
}
@@ -986,53 +987,13 @@ public class UserSettings extends SettingsPreferenceFragment
// Add a temporary entry for the user being created
if (mAddingUser) {
UserPreference pref = new UserPreference(getPrefContext(), null,
UserPreference.USERID_UNKNOWN, null, null);
UserPreference.USERID_UNKNOWN);
pref.setEnabled(false);
pref.setTitle(mAddingUserName);
pref.setIcon(getEncircledDefaultIcon());
userPreferences.add(pref);
}
// Check if Guest tile should be added.
if (!mUserCaps.mIsGuest && (mUserCaps.mCanAddGuest ||
mUserCaps.mDisallowAddUserSetByAdmin)) {
// Add a virtual Guest user for guest defaults
UserPreference pref = new UserPreference(getPrefContext(), null,
UserPreference.USERID_GUEST_DEFAULTS,
mUserCaps.mIsAdmin && voiceCapable ? this : null /* settings icon handler */,
null /* delete icon handler */);
pref.setTitle(R.string.user_guest);
pref.setIcon(getEncircledDefaultIcon());
pref.setKey(KEY_USER_GUEST);
userPreferences.add(pref);
if (mUserCaps.mDisallowAddUser) {
pref.setDisabledByAdmin(mUserCaps.mEnforcedAdmin);
} else if (mUserCaps.mDisallowSwitchUser) {
pref.setDisabledByAdmin(RestrictedLockUtilsInternal.getDeviceOwner(context));
} else {
pref.setDisabledByAdmin(null);
}
if (!mUserManager.canSwitchUsers()) {
pref.setSelectable(false);
}
int finalGuestId = guestId;
pref.setOnPreferenceClickListener(preference -> {
int id = finalGuestId;
if (id == UserPreference.USERID_GUEST_DEFAULTS) {
UserInfo guest = mUserManager.createGuest(
getContext(), preference.getTitle().toString());
if (guest != null) {
id = guest.id;
}
}
try {
ActivityManager.getService().switchUser(id);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
return true;
});
}
// Sort list of users by serialNum
Collections.sort(userPreferences, UserPreference.SERIAL_NUMBER_COMPARATOR);
@@ -1064,6 +1025,7 @@ public class UserSettings extends SettingsPreferenceFragment
mMultiUserFooterPreferenceController.updateState(multiUserFooterPrefence);
mUserListCategory.setVisible(mUserCaps.mUserSwitcherEnabled);
updateAddGuest(context, users.stream().anyMatch(UserInfo::isGuest));
updateAddUser(context);
if (!mUserCaps.mUserSwitcherEnabled) {
@@ -1077,15 +1039,38 @@ public class UserSettings extends SettingsPreferenceFragment
}
private boolean isCurrentUserGuest() {
return mUserCaps.mIsGuest;
}
private boolean canSwitchUserNow() {
return mUserManager.getUserSwitchability() == UserManager.SWITCHABILITY_STATUS_OK;
}
private void updateAddGuest(Context context, boolean isGuestAlreadyCreated) {
if (!isGuestAlreadyCreated && mUserCaps.mCanAddGuest
&& WizardManagerHelper.isDeviceProvisioned(context)
&& mUserCaps.mUserSwitcherEnabled) {
mAddGuest.setVisible(true);
mAddGuest.setIcon(getEncircledDefaultIcon());
mAddGuest.setEnabled(canSwitchUserNow());
mAddGuest.setSelectable(true);
} else {
mAddGuest.setVisible(false);
}
}
private void updateAddUser(Context context) {
if ((mUserCaps.mCanAddUser || mUserCaps.mDisallowAddUserSetByAdmin)
&& WizardManagerHelper.isDeviceProvisioned(context)
&& mUserCaps.mUserSwitcherEnabled) {
mAddUser.setVisible(true);
final boolean moreUsers = mUserManager.canAddMoreUsers();
mAddUser.setEnabled(moreUsers && !mAddingUser && mUserManager.canSwitchUsers());
if (!moreUsers) {
mAddUser.setSummary(getString(R.string.user_add_max_count, getMaxRealUsers()));
mAddUser.setSelectable(true);
final boolean canAddMoreUsers = mUserManager.canAddMoreUsers();
mAddUser.setEnabled(canAddMoreUsers && !mAddingUser && canSwitchUserNow());
if (!canAddMoreUsers) {
mAddUser.setSummary(
getString(R.string.user_add_max_count, getRealUsersCount()));
} else {
mAddUser.setSummary(null);
}
@@ -1098,18 +1083,15 @@ public class UserSettings extends SettingsPreferenceFragment
}
}
private int getMaxRealUsers() {
// guest is not counted against getMaxSupportedUsers() number
final int maxUsersAndGuest = UserManager.getMaxSupportedUsers() + 1;
final List<UserInfo> users = mUserManager.getUsers();
// managed profiles are counted against getMaxSupportedUsers()
int managedProfiles = 0;
for (UserInfo user : users) {
if (user.isManagedProfile()) {
managedProfiles++;
}
}
return maxUsersAndGuest - managedProfiles;
/**
* @return number of non-guest non-managed users
*/
@VisibleForTesting
int getRealUsersCount() {
return (int) mUserManager.getUsers()
.stream()
.filter(user -> !user.isGuest() && !user.isProfile())
.count();
}
private void loadIconsAsync(List<Integer> missingIcons) {
@@ -1151,12 +1133,12 @@ public class UserSettings extends SettingsPreferenceFragment
@Override
public boolean onPreferenceClick(Preference pref) {
if (pref == mMePreference) {
if (mUserCaps.mIsGuest) {
if (isCurrentUserGuest()) {
showDialog(DIALOG_CONFIRM_EXIT_GUEST);
return true;
}
// If this is a limited user, launch the user info settings instead of profile editor
if (mUserManager.isLinkedUser()) {
if (mUserManager.isRestrictedProfile()) {
onManageUserClicked(UserHandle.myUserId(), false);
} else {
showDialog(DIALOG_USER_PROFILE_EDITOR);
@@ -1165,9 +1147,11 @@ public class UserSettings extends SettingsPreferenceFragment
int userId = ((UserPreference) pref).getUserId();
// Get the latest status of the user
UserInfo user = mUserManager.getUserInfo(userId);
if (!isInitialized(user)) {
if (!user.isInitialized()) {
mHandler.sendMessage(mHandler.obtainMessage(
MESSAGE_SETUP_USER, user.id, user.serialNumber));
} else {
onManageUserClicked(userId, false);
}
} else if (pref == mAddUser) {
// If we allow both types, show a picker, otherwise directly go to
@@ -1177,39 +1161,19 @@ public class UserSettings extends SettingsPreferenceFragment
} else {
onAddUserClicked(USER_TYPE_USER);
}
} else if (pref == mAddGuest) {
UserInfo guest = mUserManager.createGuest(
getContext(), getString(com.android.settingslib.R.string.user_guest));
switchUserNow(guest.id);
}
return false;
}
private boolean isInitialized(UserInfo user) {
return (user.flags & UserInfo.FLAG_INITIALIZED) != 0;
}
private Drawable encircle(Bitmap icon) {
Drawable circled = CircleFramedDrawable.getInstance(getActivity(), icon);
return circled;
}
@Override
public void onClick(View v) {
if (v.getTag() instanceof UserPreference) {
int userId = ((UserPreference) v.getTag()).getUserId();
if (v.getId() == UserPreference.DELETE_ID) {
final EnforcedAdmin removeDisallowedAdmin =
RestrictedLockUtilsInternal.checkIfRestrictionEnforced(getContext(),
UserManager.DISALLOW_REMOVE_USER, UserHandle.myUserId());
if (removeDisallowedAdmin != null) {
RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(),
removeDisallowedAdmin);
} else {
onRemoveUserClicked(userId);
}
} else if (v.getId() == UserPreference.SETTINGS_ID) {
onManageUserClicked(userId, false);
}
}
}
@Override
public void onDismiss(DialogInterface dialog) {
synchronized (mUserLock) {

View File

@@ -18,6 +18,7 @@ package com.android.settings.testutils.shadow;
import android.annotation.UserIdInt;
import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManager.EnforcingUser;
@@ -43,13 +44,17 @@ public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager
private static boolean sIsSupportsMultipleUsers;
private final List<String> mRestrictions = new ArrayList<>();
private final List<String> mBaseRestrictions = new ArrayList<>();
private final List<String> mGuestRestrictions = new ArrayList<>();
private final Map<String, List<EnforcingUser>> mRestrictionSources = new HashMap<>();
private final List<UserInfo> mUserProfileInfos = new ArrayList<>();
private final Set<Integer> mManagedProfiles = new HashSet<>();
private boolean mIsQuietModeEnabled = false;
private int[] profileIdsForUser = new int[0];
private boolean mUserSwitchEnabled;
private @UserManager.UserSwitchabilityResult int mSwitchabilityStatus =
UserManager.SWITCHABILITY_STATUS_OK;
private final Map<Integer, Integer> mSameProfileGroupIds = Maps.newHashMap();
public void addProfile(UserInfo userInfo) {
@@ -82,11 +87,22 @@ public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager
@Implementation
protected boolean hasBaseUserRestriction(String restrictionKey, UserHandle userHandle) {
return mRestrictions.contains(restrictionKey);
return mBaseRestrictions.contains(restrictionKey);
}
public void addBaseUserRestriction(String restriction) {
mRestrictions.add(restriction);
mBaseRestrictions.add(restriction);
}
@Implementation
protected Bundle getDefaultGuestRestrictions() {
Bundle bundle = new Bundle();
mGuestRestrictions.forEach(restriction -> bundle.putBoolean(restriction, true));
return bundle;
}
public void addGuestUserRestriction(String restriction) {
mGuestRestrictions.add(restriction);
}
public static ShadowUserManager getShadow() {
@@ -166,4 +182,21 @@ public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager
public void setSupportsMultipleUsers(boolean supports) {
sIsSupportsMultipleUsers = supports;
}
@Implementation
protected UserInfo getUserInfo(@UserIdInt int userId) {
return mUserProfileInfos.stream()
.filter(userInfo -> userInfo.id == userId)
.findFirst()
.orElse(super.getUserInfo(userId));
}
@Implementation
protected @UserManager.UserSwitchabilityResult int getUserSwitchability() {
return mSwitchabilityStatus;
}
public void setSwitchabilityStatus(@UserManager.UserSwitchabilityResult int newStatus) {
mSwitchabilityStatus = newStatus;
}
}

View File

@@ -0,0 +1,467 @@
/*
* Copyright (C) 2020 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.settings.users;
import static android.os.UserManager.SWITCHABILITY_STATUS_OK;
import static android.os.UserManager.SWITCHABILITY_STATUS_USER_IN_CALL;
import static android.os.UserManager.SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.telephony.TelephonyManager;
import androidx.fragment.app.FragmentActivity;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.testutils.shadow.ShadowDevicePolicyManager;
import com.android.settings.testutils.shadow.ShadowUserManager;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.android.controller.ActivityController;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {
ShadowUserManager.class,
ShadowDevicePolicyManager.class
})
public class UserDetailsSettingsTest {
private static final String KEY_SWITCH_USER = "switch_user";
private static final String KEY_ENABLE_TELEPHONY = "enable_calling";
private static final String KEY_REMOVE_USER = "remove_user";
private static final int DIALOG_CONFIRM_REMOVE = 1;
@Mock
private TelephonyManager mTelephonyManager;
private ShadowUserManager mUserManager;
@Mock
private Preference mSwitchUserPref;
@Mock
private SwitchPreference mPhonePref;
@Mock
private Preference mRemoveUserPref;
private FragmentActivity mActivity;
private Context mContext;
private UserDetailsSettings mFragment;
private Bundle mArguments;
private UserInfo mUserInfo;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mActivity = spy(ActivityController.of(new FragmentActivity()).get());
mContext = spy(RuntimeEnvironment.application);
mFragment = spy(new UserDetailsSettings());
mArguments = new Bundle();
UserManager userManager = (UserManager) mContext.getSystemService(
Context.USER_SERVICE);
mUserManager = Shadow.extract(userManager);
doReturn(mTelephonyManager).when(mActivity).getSystemService(Context.TELEPHONY_SERVICE);
ReflectionHelpers.setField(mFragment, "mUserManager", userManager);
doReturn(mActivity).when(mFragment).getActivity();
doReturn(mContext).when(mFragment).getContext();
doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen();
doReturn("").when(mActivity).getString(anyInt(), anyString());
doReturn(mSwitchUserPref).when(mFragment).findPreference(KEY_SWITCH_USER);
doReturn(mPhonePref).when(mFragment).findPreference(KEY_ENABLE_TELEPHONY);
doReturn(mRemoveUserPref).when(mFragment).findPreference(KEY_REMOVE_USER);
}
@After
public void tearDown() {
ShadowUserManager.reset();
}
@Test(expected = IllegalStateException.class)
public void initialize_nullArguments_shouldThrowException() {
mFragment.initialize(mActivity, null);
}
@Test(expected = IllegalStateException.class)
public void initialize_emptyArguments_shouldThrowException() {
mFragment.initialize(mActivity, new Bundle());
}
@Test
public void initialize_userSelected_shouldSetupSwitchPref() {
setupSelectedUser();
doReturn("Switch to " + mUserInfo.name)
.when(mActivity).getString(anyInt(), anyString());
mFragment.initialize(mActivity, mArguments);
verify(mActivity).getString(com.android.settingslib.R.string.user_switch_to_user,
mUserInfo.name);
verify(mSwitchUserPref).setTitle("Switch to " + mUserInfo.name);
verify(mSwitchUserPref).setOnPreferenceClickListener(mFragment);
verify(mFragment, never()).removePreference(KEY_SWITCH_USER);
}
@Test
public void initialize_guestSelected_shouldSetupSwitchPref() {
setupSelectedGuest();
doReturn("Switch to " + mUserInfo.name)
.when(mActivity).getString(anyInt(), anyString());
mFragment.initialize(mActivity, mArguments);
verify(mActivity).getString(com.android.settingslib.R.string.user_switch_to_user,
mUserInfo.name);
verify(mSwitchUserPref).setTitle("Switch to " + mUserInfo.name);
verify(mSwitchUserPref).setOnPreferenceClickListener(mFragment);
verify(mFragment, never()).removePreference(KEY_SWITCH_USER);
}
@Test
public void onResume_canSwitch_shouldEnableSwitchPref() {
mUserManager.setSwitchabilityStatus(SWITCHABILITY_STATUS_OK);
mFragment.mSwitchUserPref = mSwitchUserPref;
mFragment.onAttach(mContext);
mFragment.onResume();
verify(mSwitchUserPref).setEnabled(true);
}
@Test
public void onResume_userInCall_shouldDisableSwitchPref() {
mUserManager.setSwitchabilityStatus(SWITCHABILITY_STATUS_USER_IN_CALL);
mFragment.mSwitchUserPref = mSwitchUserPref;
mFragment.onAttach(mContext);
mFragment.onResume();
verify(mSwitchUserPref).setEnabled(false);
}
@Test
public void onResume_switchDisallowed_shouldDisableSwitchPref() {
mUserManager.setSwitchabilityStatus(SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED);
mFragment.mSwitchUserPref = mSwitchUserPref;
mFragment.onAttach(mContext);
mFragment.onResume();
verify(mSwitchUserPref).setEnabled(false);
}
@Test
public void onResume_systemUserLocked_shouldDisableSwitchPref() {
mUserManager.setSwitchabilityStatus(UserManager.SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED);
mFragment.mSwitchUserPref = mSwitchUserPref;
mFragment.onAttach(mContext);
mFragment.onResume();
verify(mSwitchUserPref).setEnabled(false);
}
@Test
public void initialize_adminWithTelephony_shouldShowPhonePreference() {
setupSelectedUser();
doReturn(true).when(mTelephonyManager).isVoiceCapable();
mUserManager.setIsAdminUser(true);
mFragment.initialize(mActivity, mArguments);
verify(mFragment, never()).removePreference(KEY_ENABLE_TELEPHONY);
verify(mPhonePref).setOnPreferenceChangeListener(mFragment);
}
@Test
public void initialize_adminNoTelephony_shouldNotShowPhonePreference() {
setupSelectedUser();
doReturn(false).when(mTelephonyManager).isVoiceCapable();
mUserManager.setIsAdminUser(true);
doReturn(null).when(mActivity).getSystemService(Context.TELEPHONY_SERVICE);
mFragment.initialize(mActivity, mArguments);
verify(mFragment).removePreference(KEY_ENABLE_TELEPHONY);
}
@Test
public void initialize_nonAdminWithTelephony_shouldNotShowPhonePreference() {
setupSelectedUser();
doReturn(true).when(mTelephonyManager).isVoiceCapable();
mUserManager.setIsAdminUser(false);
mFragment.initialize(mActivity, mArguments);
verify(mFragment).removePreference(KEY_ENABLE_TELEPHONY);
}
@Test
public void initialize_adminSelectsSecondaryUser_shouldShowRemovePreference() {
setupSelectedUser();
mUserManager.setIsAdminUser(true);
mFragment.initialize(mActivity, mArguments);
verify(mRemoveUserPref).setOnPreferenceClickListener(mFragment);
verify(mRemoveUserPref).setTitle(R.string.user_remove_user);
verify(mFragment, never()).removePreference(KEY_REMOVE_USER);
}
@Test
public void initialize_adminSelectsGuest_shouldShowRemovePreference() {
setupSelectedGuest();
mUserManager.setIsAdminUser(true);
mFragment.initialize(mActivity, mArguments);
verify(mRemoveUserPref).setOnPreferenceClickListener(mFragment);
verify(mRemoveUserPref).setTitle(R.string.user_exit_guest_title);
verify(mFragment, never()).removePreference(KEY_REMOVE_USER);
}
@Test
public void initialize_nonAdmin_shouldNotShowRemovePreference() {
setupSelectedUser();
mUserManager.setIsAdminUser(false);
mFragment.initialize(mActivity, mArguments);
verify(mFragment).removePreference(KEY_REMOVE_USER);
}
@Test
public void initialize_disallowRemoveUserRestriction_shouldNotShowRemovePreference() {
setupSelectedUser();
mUserManager.setIsAdminUser(true);
mUserManager.addBaseUserRestriction(UserManager.DISALLOW_REMOVE_USER);
mFragment.initialize(mActivity, mArguments);
verify(mFragment).removePreference(KEY_REMOVE_USER);
}
@Test
public void initialize_userHasCallRestriction_shouldSetPhoneSwitchUnChecked() {
setupSelectedUser();
mUserManager.setIsAdminUser(true);
mUserManager.setUserRestriction(mUserInfo.getUserHandle(),
UserManager.DISALLOW_OUTGOING_CALLS, true);
mFragment.initialize(mActivity, mArguments);
verify(mPhonePref).setChecked(false);
}
@Test
public void initialize_noCallRestriction_shouldSetPhoneSwitchChecked() {
setupSelectedUser();
mUserManager.setIsAdminUser(true);
mFragment.initialize(mActivity, mArguments);
verify(mPhonePref).setChecked(true);
}
@Test
public void initialize_guestSelected_noCallRestriction_shouldSetPhonePreference() {
setupSelectedGuest();
mUserManager.setIsAdminUser(true);
mFragment.initialize(mActivity, mArguments);
verify(mPhonePref).setTitle(R.string.user_enable_calling);
verify(mPhonePref).setChecked(true);
}
@Test
public void initialize_guestSelected_callRestriction_shouldSetPhonePreference() {
setupSelectedGuest();
mUserManager.setIsAdminUser(true);
mUserManager.addGuestUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS);
mFragment.initialize(mActivity, mArguments);
verify(mPhonePref).setTitle(R.string.user_enable_calling);
verify(mPhonePref).setChecked(false);
}
@Test
public void onPreferenceClick_switchClicked_canSwitch_shouldSwitch() {
setupSelectedUser();
mUserManager.setSwitchabilityStatus(SWITCHABILITY_STATUS_OK);
mFragment.mSwitchUserPref = mSwitchUserPref;
mFragment.mRemoveUserPref = mRemoveUserPref;
mFragment.mUserInfo = mUserInfo;
mFragment.onPreferenceClick(mSwitchUserPref);
verify(mFragment).switchUser();
}
@Test
public void onPreferenceClick_switchClicked_canNotSwitch_doNothing() {
setupSelectedUser();
mUserManager.setSwitchabilityStatus(SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED);
mFragment.mSwitchUserPref = mSwitchUserPref;
mFragment.mRemoveUserPref = mRemoveUserPref;
mFragment.mUserInfo = mUserInfo;
mFragment.onPreferenceClick(mSwitchUserPref);
verify(mFragment, never()).switchUser();
}
@Test
public void onPreferenceClick_removeClicked_canDelete_shouldShowDialog() {
setupSelectedUser();
mFragment.mUserInfo = mUserInfo;
mUserManager.setIsAdminUser(true);
mFragment.mSwitchUserPref = mSwitchUserPref;
mFragment.mRemoveUserPref = mRemoveUserPref;
doNothing().when(mFragment).showDialog(anyInt());
mFragment.onPreferenceClick(mRemoveUserPref);
verify(mFragment).canDeleteUser();
verify(mFragment).showDialog(DIALOG_CONFIRM_REMOVE);
}
@Test
public void onPreferenceClick_removeClicked_canNotDelete_doNothing() {
setupSelectedUser();
mFragment.mUserInfo = mUserInfo;
mUserManager.setIsAdminUser(false);
mFragment.mSwitchUserPref = mSwitchUserPref;
mFragment.mRemoveUserPref = mRemoveUserPref;
doNothing().when(mFragment).showDialog(anyInt());
mFragment.onPreferenceClick(mRemoveUserPref);
verify(mFragment).canDeleteUser();
verify(mFragment, never()).showDialog(DIALOG_CONFIRM_REMOVE);
}
@Test
public void onPreferenceClick_unknownPreferenceClicked_doNothing() {
setupSelectedUser();
mFragment.mUserInfo = mUserInfo;
mFragment.mSwitchUserPref = mSwitchUserPref;
mFragment.mRemoveUserPref = mRemoveUserPref;
mFragment.onPreferenceClick(mock(UserPreference.class));
verify(mFragment).onPreferenceClick(any());
verifyNoMoreInteractions(mFragment);
}
@Test
public void canDeleteUser_nonAdminUser_shouldReturnFalse() {
mUserManager.setIsAdminUser(false);
boolean result = mFragment.canDeleteUser();
assertThat(result).isFalse();
}
@Test
public void canDeleteUser_adminSelectsUser_noRestrictions_shouldReturnTrue() {
setupSelectedUser();
mUserManager.setIsAdminUser(true);
boolean result = mFragment.canDeleteUser();
assertThat(result).isTrue();
}
@Test
public void canDeleteUser_adminSelectsUser_hasRemoveRestriction_shouldReturnFalse() {
setupSelectedUser();
mUserManager.setIsAdminUser(true);
ComponentName componentName = new ComponentName("test", "test");
ShadowDevicePolicyManager.getShadow().setDeviceOwnerComponentOnAnyUser(componentName);
ShadowDevicePolicyManager.getShadow().setDeviceOwnerUserId(UserHandle.myUserId());
List<UserManager.EnforcingUser> enforcingUsers = new ArrayList<>();
enforcingUsers.add(new UserManager.EnforcingUser(UserHandle.myUserId(),
UserManager.RESTRICTION_SOURCE_DEVICE_OWNER));
mUserManager.setUserRestrictionSources(
UserManager.DISALLOW_REMOVE_USER,
UserHandle.of(UserHandle.myUserId()),
enforcingUsers
);
boolean result = mFragment.canDeleteUser();
assertThat(result).isFalse();
}
private void setupSelectedUser() {
mArguments.putInt("user_id", 1);
mUserInfo = new UserInfo(1, "Tom", null,
UserInfo.FLAG_FULL | UserInfo.FLAG_INITIALIZED,
UserManager.USER_TYPE_FULL_SECONDARY);
mUserManager.addProfile(mUserInfo);
}
private void setupSelectedGuest() {
mArguments.putInt("user_id", 23);
mUserInfo = new UserInfo(23, "Guest", null,
UserInfo.FLAG_FULL | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_GUEST,
UserManager.USER_TYPE_FULL_GUEST);
mUserManager.addProfile(mUserInfo);
}
}

View File

@@ -18,12 +18,8 @@ package com.android.settings.users;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.os.UserHandle;
import android.view.View;
import com.android.settingslib.RestrictedPreferenceHelper;
@@ -48,28 +44,12 @@ public class UserPreferenceTest {
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mUserPreference = new UserPreference(mContext, null /* attrs */, UserHandle.USER_CURRENT,
null /* settingsListener */, null /* deleteListener */);
mUserPreference = new UserPreference(mContext, null /* attrs */, UserHandle.USER_CURRENT);
ReflectionHelpers.setField(mUserPreference, "mHelper", mRestrictedPreferenceHelper);
}
@Test
public void testShouldHideSecondTarget_noListener_shouldHide() {
public void testShouldHideSecondTarget_shouldHide() {
assertThat(mUserPreference.shouldHideSecondTarget()).isTrue();
}
@Test
public void testShouldHideSecondTarget_disabledByAdmin_shouldHide() {
when(mRestrictedPreferenceHelper.isDisabledByAdmin()).thenReturn(true);
assertThat(mUserPreference.shouldHideSecondTarget()).isTrue();
}
@Test
public void testShouldHideSecondTarget_hasSettingListener_shouldNotHide() {
ReflectionHelpers.setField(mUserPreference, "mSettingsClickListener",
mock(View.OnClickListener.class));
assertThat(mUserPreference.shouldHideSecondTarget()).isFalse();
}
}

View File

@@ -16,9 +16,14 @@
package com.android.settings.users;
import static android.os.UserManager.SWITCHABILITY_STATUS_OK;
import static android.os.UserManager.SWITCHABILITY_STATUS_USER_IN_CALL;
import static android.os.UserManager.SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.notNull;
@@ -26,12 +31,14 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ComponentName;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.UserInfo;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.os.UserManager;
@@ -42,14 +49,13 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import androidx.fragment.app.FragmentActivity;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import com.android.settings.testutils.shadow.ShadowDevicePolicyManager;
import com.android.settings.testutils.shadow.ShadowUserManager;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedPreference;
import org.junit.After;
@@ -57,6 +63,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.AdditionalMatchers;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
@@ -65,6 +72,7 @@ import org.robolectric.android.controller.ActivityController;
import org.robolectric.annotation.Config;
import org.robolectric.util.ReflectionHelpers;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -73,7 +81,18 @@ import java.util.List;
public class UserSettingsTest {
private static final String KEY_USER_GUEST = "user_guest";
private int mProvisioned;
private static final int ACTIVE_USER_ID = 0;
private static final int INACTIVE_ADMIN_USER_ID = 1;
private static final int INACTIVE_SECONDARY_USER_ID = 14;
private static final int INACTIVE_RESTRICTED_USER_ID = 21;
private static final int INACTIVE_GUEST_USER_ID = 23;
private static final int MANAGED_USER_ID = 11;
private static final String ADMIN_USER_NAME = "Owner";
private static final String SECONDARY_USER_NAME = "Tom";
private static final String RESTRICTED_USER_NAME = "Bob";
private static final String GUEST_USER_NAME = "Guest";
private static final String MANAGED_USER_NAME = "Work profile";
private int mProvisionedBackupValue;
@Mock
private Drawable mDefaultIconDrawable;
@@ -82,6 +101,10 @@ public class UserSettingsTest {
@Mock
private UserPreference mMePreference;
@Mock
private RestrictedPreference mAddUserPreference;
@Mock
private RestrictedPreference mAddGuestPreference;
@Mock
private UserManager mUserManager;
private FragmentActivity mActivity;
@@ -95,6 +118,7 @@ public class UserSettingsTest {
mActivity = spy(ActivityController.of(new FragmentActivity()).get());
mContext = spy(RuntimeEnvironment.application);
mUserCapabilities = UserCapabilities.create(mContext);
mUserCapabilities.mUserSwitcherEnabled = true;
mFragment = spy(new UserSettings());
ReflectionHelpers.setField(mFragment, "mAddUserWhenLockedPreferenceController",
@@ -105,100 +129,41 @@ public class UserSettingsTest {
ReflectionHelpers.setField(mFragment, "mUserCaps", mUserCapabilities);
ReflectionHelpers.setField(mFragment, "mDefaultIconDrawable", mDefaultIconDrawable);
ReflectionHelpers.setField(mFragment, "mAddingUser", false);
mFragment.mMePreference = mMePreference;
when((Object) mActivity.getSystemService(UserManager.class)).thenReturn(mUserManager);
doReturn(mUserManager).when(mActivity).getSystemService(UserManager.class);
doReturn(mActivity).when(mFragment).getActivity();
doReturn(mContext).when(mFragment).getContext();
doReturn(mMockPreferenceManager).when(mFragment).getPreferenceManager();
doReturn(mUserManager).when(mContext).getSystemService(UserManager.class);
mProvisioned = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.DEVICE_PROVISIONED, 0);
final SharedPreferences prefs = mock(SharedPreferences .class);
when(mMockPreferenceManager.getSharedPreferences()).thenReturn(prefs);
when(mMockPreferenceManager.getContext()).thenReturn(mContext);
mProvisionedBackupValue = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.DEVICE_PROVISIONED, 0);
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.DEVICE_PROVISIONED, 1); //default state
final SharedPreferences prefs = mock(SharedPreferences.class);
doReturn(prefs).when(mMockPreferenceManager).getSharedPreferences();
doReturn(mContext).when(mMockPreferenceManager).getContext();
doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen();
mFragment.mMePreference = mMePreference;
mFragment.mAddUser = mAddUserPreference;
mFragment.mAddGuest = mAddGuestPreference;
mFragment.mUserListCategory = mock(PreferenceCategory.class);
}
@After
public void tearDown() {
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.DEVICE_PROVISIONED, mProvisioned);
Settings.Global.DEVICE_PROVISIONED, mProvisionedBackupValue);
}
@Test
public void testAssignDefaultPhoto_ContextNull_ReturnFalseAndNotCrash() {
// Should not crash here
assertThat(UserSettings.assignDefaultPhoto(null, 0)).isFalse();
}
@Test
public void updateUserList_cannotSwitchUser_shouldNotBeSelectableForGuest() {
final RestrictedPreference addUser = spy(new RestrictedPreference(mContext));
final PreferenceGroup userListCategory = spy(new PreferenceCategory(mContext));
mUserCapabilities.mIsGuest = false;
mUserCapabilities.mCanAddGuest = true;
mUserCapabilities.mDisallowAddUser = false;
mUserCapabilities.mDisallowSwitchUser = false;
mUserCapabilities.mUserSwitcherEnabled = true;
mFragment.mUserListCategory = userListCategory;
mFragment.mAddUser = addUser;
when(mUserManager.canSwitchUsers()).thenReturn(false);
doReturn(mMockPreferenceManager).when(mFragment).getPreferenceManager();
doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen();
doReturn(mMockPreferenceManager).when(userListCategory).getPreferenceManager();
mFragment.updateUserList();
final Preference guest = userListCategory.findPreference(KEY_USER_GUEST);
assertThat(guest.isSelectable()).isFalse();
}
@Test
public void updateUserList_cannotSwitchUser_shouldDisableAddUser() {
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.DEVICE_PROVISIONED, 1);
final RestrictedPreference addUser = spy(new RestrictedPreference(mContext));
final PreferenceGroup userListCategory = spy(new PreferenceCategory(mContext));
mUserCapabilities.mCanAddUser = true;
mUserCapabilities.mDisallowAddUser = false;
mUserCapabilities.mUserSwitcherEnabled = true;
mFragment.mUserListCategory = userListCategory;
mFragment.mAddUser = addUser;
when(mUserManager.canSwitchUsers()).thenReturn(false);
when(mUserManager.canAddMoreUsers()).thenReturn(true);
doReturn(mMockPreferenceManager).when(mFragment).getPreferenceManager();
doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen();
doReturn(mMockPreferenceManager).when(userListCategory).getPreferenceManager();
mFragment.updateUserList();
assertThat(addUser.isEnabled()).isFalse();
}
@Test
public void updateUserList_cannotAddUserButCanSwitchUser_shouldNotShowAddUser() {
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.DEVICE_PROVISIONED, 1);
final RestrictedPreference addUser = mock(RestrictedPreference.class);
mUserCapabilities.mCanAddUser = false;
mUserCapabilities.mDisallowAddUser = true;
mUserCapabilities.mUserSwitcherEnabled = true;
mFragment.mUserListCategory = mock(PreferenceCategory.class);
mFragment.mAddUser = addUser;
doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen();
mFragment.updateUserList();
verify(addUser, never()).setVisible(true);
assertThat(UserSettings.assignDefaultPhoto(null, ACTIVE_USER_ID)).isFalse();
}
@Test
@@ -218,7 +183,7 @@ public class UserSettingsTest {
ShadowDevicePolicyManager.getShadow().setDeviceOwnerComponentOnAnyUser(
new ComponentName("test", "test"));
doReturn(true).when(mUserManager).canSwitchUsers();
doReturn(SWITCHABILITY_STATUS_OK).when(mUserManager).getUserSwitchability();
mUserCapabilities.mIsAdmin = false;
Menu menu = mock(Menu.class);
@@ -243,7 +208,7 @@ public class UserSettingsTest {
@Test
public void withoutDisallowRemoveUser_ShouldNotDisableRemoveUser() {
// Arrange
doReturn(true).when(mUserManager).canSwitchUsers();
doReturn(SWITCHABILITY_STATUS_OK).when(mUserManager).getUserSwitchability();
mUserCapabilities.mIsAdmin = false;
Menu menu = mock(Menu.class);
@@ -266,44 +231,458 @@ public class UserSettingsTest {
@Test
public void updateUserList_canAddUserAndSwitchUser_shouldShowAddUser() {
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.DEVICE_PROVISIONED, 1);
final RestrictedPreference addUser = mock(RestrictedPreference.class);
mUserCapabilities.mCanAddUser = true;
mUserCapabilities.mDisallowAddUser = false;
mUserCapabilities.mUserSwitcherEnabled = true;
mFragment.mAddUser = addUser;
mFragment.mUserListCategory = mock(PreferenceCategory.class);
doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen();
doReturn("Test summary").when(mFragment).getString(anyInt(), anyInt());
doReturn(true).when(mUserManager).canAddMoreUsers();
doReturn(true).when(mAddUserPreference).isEnabled();
doReturn(SWITCHABILITY_STATUS_OK).when(mUserManager).getUserSwitchability();
mFragment.updateUserList();
verify(addUser).setVisible(true);
verify(mAddUserPreference).setVisible(true);
verify(mAddUserPreference).setSummary(null);
verify(mAddUserPreference).setEnabled(true);
verify(mAddUserPreference).setDisabledByAdmin(null);
verify(mAddUserPreference).setSelectable(true);
}
@Test
public void updateUserList_addUserDisallowedByAdmin_shouldShowAddUserDisabled() {
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.DEVICE_PROVISIONED, 1);
final RestrictedPreference addUser = mock(RestrictedPreference.class);
mUserCapabilities.mCanAddUser = false;
mUserCapabilities.mDisallowAddUser = true;
mUserCapabilities.mDisallowAddUserSetByAdmin = true;
mUserCapabilities.mUserSwitcherEnabled = true;
mFragment.mUserListCategory = mock(PreferenceCategory.class);
mFragment.mAddUser = addUser;
doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen();
public void updateUserList_canAddGuestAndSwitchUser_shouldShowAddGuest() {
mUserCapabilities.mCanAddGuest = true;
doReturn(true).when(mUserManager).canAddMoreUsers();
doReturn(SWITCHABILITY_STATUS_OK).when(mUserManager).getUserSwitchability();
mFragment.updateUserList();
verify(addUser).setVisible(true);
assertThat(addUser.isEnabled()).isFalse();
verify(mAddGuestPreference).setVisible(true);
verify(mAddGuestPreference).setEnabled(true);
verify(mAddGuestPreference).setIcon(any(Drawable.class));
verify(mAddGuestPreference).setSelectable(true);
}
@Test
public void updateUserList_cannotSwitchUser_shouldDisableAddUser() {
mUserCapabilities.mCanAddUser = true;
doReturn(true).when(mUserManager).canAddMoreUsers();
doReturn(true).when(mAddUserPreference).isEnabled();
doReturn(SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED)
.when(mUserManager).getUserSwitchability();
mFragment.updateUserList();
verify(mAddUserPreference).setVisible(true);
verify(mAddUserPreference).setSummary(null);
verify(mAddUserPreference).setEnabled(false);
verify(mAddUserPreference).setSelectable(true);
}
@Test
public void updateUserList_canNotAddMoreUsers_shouldDisableAddUserWithSummary() {
mUserCapabilities.mCanAddUser = true;
doReturn(false).when(mUserManager).canAddMoreUsers();
doReturn(false).when(mAddUserPreference).isEnabled();
doReturn(SWITCHABILITY_STATUS_OK).when(mUserManager).getUserSwitchability();
doReturn(4).when(mFragment).getRealUsersCount();
mFragment.updateUserList();
verify(mAddUserPreference).setVisible(true);
verify(mAddUserPreference).setSummary("You can add up to 4 users");
verify(mAddUserPreference).setEnabled(false);
verify(mAddUserPreference).setSelectable(true);
}
@Test
public void updateUserList_cannotSwitchUser_shouldDisableAddGuest() {
mUserCapabilities.mCanAddGuest = true;
doReturn(true).when(mUserManager).canAddMoreUsers();
doReturn(SWITCHABILITY_STATUS_USER_IN_CALL).when(mUserManager).getUserSwitchability();
mFragment.updateUserList();
verify(mAddGuestPreference).setVisible(true);
verify(mAddGuestPreference).setEnabled(false);
verify(mAddGuestPreference).setIcon(any(Drawable.class));
verify(mAddGuestPreference).setSelectable(true);
}
@Test
public void updateUserList_addUserDisallowedByAdmin_shouldShowDisabledAddUser() {
RestrictedLockUtils.EnforcedAdmin enforcedAdmin = mock(
RestrictedLockUtils.EnforcedAdmin.class);
mUserCapabilities.mEnforcedAdmin = enforcedAdmin;
mUserCapabilities.mCanAddUser = false;
mUserCapabilities.mDisallowAddUser = true;
mUserCapabilities.mDisallowAddUserSetByAdmin = true;
doReturn(true).when(mAddUserPreference).isEnabled();
mFragment.updateUserList();
verify(mAddUserPreference).setVisible(true);
ArgumentCaptor<RestrictedLockUtils.EnforcedAdmin> captor = ArgumentCaptor.forClass(
RestrictedLockUtils.EnforcedAdmin.class);
verify(mAddUserPreference).setDisabledByAdmin(captor.capture());
assertThat(captor.getValue()).isEqualTo(enforcedAdmin);
}
@Test
public void updateUserList_cannotAddUserButCanSwitchUser_shouldNotShowAddUser() {
mUserCapabilities.mCanAddUser = false;
mFragment.updateUserList();
verify(mAddUserPreference).setVisible(false);
}
@Test
public void updateUserList_canNotAddGuest_shouldNotShowAddGuest() {
mUserCapabilities.mCanAddGuest = false;
mFragment.updateUserList();
verify(mAddGuestPreference).setVisible(false);
}
@Test
public void updateUserList_notProvisionedDevice_shouldNotShowAddUser() {
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.DEVICE_PROVISIONED, 0);
mUserCapabilities.mCanAddUser = true;
mFragment.updateUserList();
verify(mAddUserPreference).setVisible(false);
}
@Test
public void updateUserList_notProvisionedDevice_shouldNotShowAddGuest() {
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.DEVICE_PROVISIONED, 0);
mUserCapabilities.mCanAddGuest = true;
mFragment.updateUserList();
verify(mAddGuestPreference).setVisible(false);
}
@Test
public void updateUserList_userSwitcherDisabled_shouldNotShowAddUser() {
mUserCapabilities.mCanAddUser = true;
mUserCapabilities.mUserSwitcherEnabled = false;
mFragment.updateUserList();
verify(mAddUserPreference).setVisible(false);
}
@Test
public void updateUserList_userSwitcherDisabled_shouldNotShowAddGuest() {
mUserCapabilities.mCanAddGuest = true;
mUserCapabilities.mUserSwitcherEnabled = false;
mFragment.updateUserList();
verify(mAddGuestPreference).setVisible(false);
}
@Test
public void updateUserList_shouldAddAdminUserPreference() {
givenUsers(getAdminUser(true));
mFragment.updateUserList();
ArgumentCaptor<UserPreference> captor = ArgumentCaptor.forClass(UserPreference.class);
verify(mFragment.mUserListCategory).addPreference(captor.capture());
UserPreference adminPref = captor.getValue();
assertThat(adminPref).isSameAs(mMePreference);
}
@Test
public void updateUserList_existingGuest_shouldAddGuestUserPreference() {
givenUsers(getAdminUser(true), getGuest(false));
mFragment.updateUserList();
ArgumentCaptor<UserPreference> captor = ArgumentCaptor.forClass(UserPreference.class);
verify(mFragment.mUserListCategory, times(2))
.addPreference(captor.capture());
UserPreference guestPref = captor.getAllValues().get(1);
assertThat(guestPref.getUserId()).isEqualTo(INACTIVE_GUEST_USER_ID);
assertThat(guestPref.getTitle()).isEqualTo("Guest");
assertThat(guestPref.getIcon()).isNotNull();
assertThat(guestPref.getKey()).isEqualTo(KEY_USER_GUEST);
assertThat(guestPref.isEnabled()).isEqualTo(true);
assertThat(guestPref.isSelectable()).isEqualTo(true);
assertThat(guestPref.getOnPreferenceClickListener()).isSameAs(mFragment);
}
@Test
public void updateUserList_existingSecondaryUser_shouldAddSecondaryUserPreference() {
givenUsers(getAdminUser(true), getSecondaryUser(false));
mFragment.updateUserList();
ArgumentCaptor<UserPreference> captor = ArgumentCaptor.forClass(UserPreference.class);
verify(mFragment.mUserListCategory, times(2))
.addPreference(captor.capture());
UserPreference userPref = captor.getAllValues().get(1);
assertThat(userPref.getUserId()).isEqualTo(INACTIVE_SECONDARY_USER_ID);
assertThat(userPref.getTitle()).isEqualTo(SECONDARY_USER_NAME);
assertThat(userPref.getIcon()).isNotNull();
assertThat(userPref.getKey()).isEqualTo("id=" + INACTIVE_SECONDARY_USER_ID);
assertThat(userPref.isEnabled()).isEqualTo(true);
assertThat(userPref.isSelectable()).isEqualTo(true);
assertThat(userPref.getOnPreferenceClickListener()).isSameAs(mFragment);
}
@Test
public void updateUserList_existingRestrictedUser_shouldAddRestrictedUserPreference() {
givenUsers(getAdminUser(true), getRestrictedUser(false));
mFragment.updateUserList();
ArgumentCaptor<UserPreference> captor = ArgumentCaptor.forClass(UserPreference.class);
verify(mFragment.mUserListCategory, times(2))
.addPreference(captor.capture());
UserPreference userPref = captor.getAllValues().get(1);
assertThat(userPref.getUserId()).isEqualTo(INACTIVE_RESTRICTED_USER_ID);
assertThat(userPref.getTitle()).isEqualTo(RESTRICTED_USER_NAME);
assertThat(userPref.getIcon()).isNotNull();
assertThat(userPref.getKey()).isEqualTo("id=" + INACTIVE_RESTRICTED_USER_ID);
assertThat(userPref.getSummary()).isEqualTo("Restricted profile");
assertThat(userPref.isEnabled()).isEqualTo(true);
assertThat(userPref.isSelectable()).isEqualTo(true);
assertThat(userPref.getOnPreferenceClickListener()).isSameAs(mFragment);
}
@Test
public void updateUserList_existingManagedUser_shouldNotAddUserPreference() {
givenUsers(getAdminUser(true), getManagedUser());
mFragment.updateUserList();
ArgumentCaptor<UserPreference> captor = ArgumentCaptor.forClass(UserPreference.class);
verify(mFragment.mUserListCategory).addPreference(captor.capture());
List<UserPreference> userPreferences = captor.getAllValues();
assertThat(userPreferences.size()).isEqualTo(1);
assertThat(userPreferences.get(0).getUserId()).isEqualTo(ACTIVE_USER_ID);
}
@Test
public void updateUserList_uninitializedRestrictedUser_shouldAddUserPreference() {
UserInfo restrictedUser = getRestrictedUser(false);
removeFlag(restrictedUser, UserInfo.FLAG_INITIALIZED);
givenUsers(getAdminUser(true), restrictedUser);
doReturn(SWITCHABILITY_STATUS_OK).when(mUserManager).getUserSwitchability();
mUserCapabilities.mDisallowSwitchUser = false;
mFragment.updateUserList();
ArgumentCaptor<UserPreference> captor = ArgumentCaptor.forClass(UserPreference.class);
verify(mFragment.mUserListCategory, times(2))
.addPreference(captor.capture());
UserPreference userPref = captor.getAllValues().get(1);
assertThat(userPref.getUserId()).isEqualTo(INACTIVE_RESTRICTED_USER_ID);
assertThat(userPref.getTitle()).isEqualTo(RESTRICTED_USER_NAME);
assertThat(userPref.getIcon()).isNotNull();
assertThat(userPref.getKey()).isEqualTo("id=" + INACTIVE_RESTRICTED_USER_ID);
assertThat(userPref.getSummary()).isEqualTo("Not set up - Restricted profile");
assertThat(userPref.isEnabled()).isEqualTo(true);
assertThat(userPref.isSelectable()).isEqualTo(true);
assertThat(userPref.getOnPreferenceClickListener()).isSameAs(mFragment);
}
@Test
public void updateUserList_uninitializedUserAndCanNotSwitchUser_shouldDisablePref() {
UserInfo uninitializedUser = getSecondaryUser(false);
removeFlag(uninitializedUser, UserInfo.FLAG_INITIALIZED);
givenUsers(getAdminUser(true), uninitializedUser);
doReturn(SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED)
.when(mUserManager).getUserSwitchability();
mUserCapabilities.mDisallowSwitchUser = false;
mFragment.updateUserList();
ArgumentCaptor<UserPreference> captor = ArgumentCaptor.forClass(UserPreference.class);
verify(mFragment.mUserListCategory, times(2))
.addPreference(captor.capture());
UserPreference userPref = captor.getAllValues().get(1);
assertThat(userPref.getUserId()).isEqualTo(INACTIVE_SECONDARY_USER_ID);
assertThat(userPref.getTitle()).isEqualTo(SECONDARY_USER_NAME);
assertThat(userPref.getIcon()).isNotNull();
assertThat(userPref.getKey()).isEqualTo("id=" + INACTIVE_SECONDARY_USER_ID);
assertThat(userPref.getSummary()).isEqualTo("Not set up");
assertThat(userPref.isEnabled()).isEqualTo(false);
assertThat(userPref.isSelectable()).isEqualTo(true);
assertThat(userPref.getOnPreferenceClickListener()).isSameAs(mFragment);
}
@Test
public void updateUserList_guestWithoutInitializedFlag_shouldNotSetSummary() {
UserInfo guest = getGuest(false);
removeFlag(guest, UserInfo.FLAG_INITIALIZED);
givenUsers(getAdminUser(true), guest);
mFragment.updateUserList();
ArgumentCaptor<UserPreference> captor = ArgumentCaptor.forClass(UserPreference.class);
verify(mFragment.mUserListCategory, times(2))
.addPreference(captor.capture());
UserPreference userPref = captor.getAllValues().get(1);
assertThat(userPref.getUserId()).isEqualTo(INACTIVE_GUEST_USER_ID);
assertThat(userPref.getSummary()).isNull();
}
@Test
public void updateUserList_activeUserWithoutInitializedFlag_shouldNotSetSummary() {
UserInfo activeUser = getSecondaryUser(true);
removeFlag(activeUser, UserInfo.FLAG_INITIALIZED);
givenUsers(activeUser);
mFragment.updateUserList();
ArgumentCaptor<UserPreference> captor = ArgumentCaptor.forClass(UserPreference.class);
verify(mFragment.mUserListCategory).addPreference(captor.capture());
UserPreference userPref = captor.getValue();
assertThat(userPref.getUserId()).isEqualTo(ACTIVE_USER_ID);
assertThat(userPref.getSummary()).isNull();
}
@Test
public void updateUserList_guestIsAlreadyCreated_shouldNotShowAddGuest() {
givenUsers(getAdminUser(true), getGuest(true));
mUserCapabilities.mCanAddGuest = true;
mFragment.updateUserList();
verify(mAddGuestPreference).setVisible(false);
}
@Test
public void updateUserList_userIconLoaded_shouldNotLoadIcon() {
UserInfo currentUser = getAdminUser(true);
currentUser.iconPath = "/data/system/users/0/photo.png";
givenUsers(currentUser);
mFragment.mUserIcons.put(ACTIVE_USER_ID,
Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888));
mFragment.updateUserList();
verify(mUserManager, never()).getUserIcon(anyInt());
// updateUserList should be called only once
verify(mUserManager).getUsers(true);
}
@Test
public void updateUserList_userIconMissing_shouldLoadIcon() {
UserInfo currentUser = getAdminUser(true);
currentUser.iconPath = "/data/system/users/0/photo.png";
givenUsers(currentUser);
// create a non-empty sparsearray
mFragment.mUserIcons.put(5, Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888));
Bitmap userIcon = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
doReturn(userIcon).when(mUserManager).getUserIcon(ACTIVE_USER_ID);
mFragment.updateUserList();
verify(mUserManager).getUserIcon(ACTIVE_USER_ID);
// updateUserList should be called another time after loading the icons
verify(mUserManager, times(2)).getUsers(true);
}
@Test
public void getRealUsersCount_onlyAdmin_shouldCount() {
givenUsers(getAdminUser(true));
int result = mFragment.getRealUsersCount();
assertThat(result).isEqualTo(1);
verify(mUserManager).getUsers();
}
@Test
public void getRealUsersCount_secondaryUser_shouldCount() {
givenUsers(getAdminUser(true), getSecondaryUser(false));
int result = mFragment.getRealUsersCount();
assertThat(result).isEqualTo(2);
verify(mUserManager).getUsers();
}
@Test
public void getRealUsersCount_restrictedUser_shouldCount() {
givenUsers(getAdminUser(true), getSecondaryUser(false));
int result = mFragment.getRealUsersCount();
assertThat(result).isEqualTo(2);
verify(mUserManager).getUsers();
}
@Test
public void getRealUsersCount_guest_shouldNotCount() {
givenUsers(getAdminUser(true), getGuest(false));
int result = mFragment.getRealUsersCount();
assertThat(result).isEqualTo(1);
verify(mUserManager).getUsers();
}
@Test
public void getRealUsersCount_managedUser_shouldNotCount() {
givenUsers(getAdminUser(true), getManagedUser());
int result = mFragment.getRealUsersCount();
assertThat(result).isEqualTo(1);
verify(mUserManager).getUsers();
}
private void givenUsers(UserInfo... userInfo) {
List<UserInfo> users = Arrays.asList(userInfo);
doReturn(users).when(mUserManager).getUsers();
doReturn(users).when(mUserManager).getUsers(anyBoolean());
}
private static void removeFlag(UserInfo userInfo, int flag) {
userInfo.flags &= ~flag;
}
private static UserInfo getAdminUser(boolean active) {
return new UserInfo(active ? ACTIVE_USER_ID : INACTIVE_ADMIN_USER_ID, ADMIN_USER_NAME,
null,
UserInfo.FLAG_FULL | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN,
UserManager.USER_TYPE_FULL_SYSTEM);
}
private static UserInfo getSecondaryUser(boolean active) {
return new UserInfo(active ? ACTIVE_USER_ID : INACTIVE_SECONDARY_USER_ID,
SECONDARY_USER_NAME, null,
UserInfo.FLAG_FULL | UserInfo.FLAG_INITIALIZED,
UserManager.USER_TYPE_FULL_SECONDARY);
}
private static UserInfo getRestrictedUser(boolean active) {
return new UserInfo(active ? ACTIVE_USER_ID : INACTIVE_RESTRICTED_USER_ID,
RESTRICTED_USER_NAME, null,
UserInfo.FLAG_FULL | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_RESTRICTED,
UserManager.USER_TYPE_FULL_RESTRICTED);
}
private static UserInfo getManagedUser() {
return new UserInfo(MANAGED_USER_ID,
MANAGED_USER_NAME, null,
UserInfo.FLAG_PROFILE | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_MANAGED_PROFILE,
UserManager.USER_TYPE_PROFILE_MANAGED);
}
private static UserInfo getGuest(boolean active) {
return new UserInfo(active ? ACTIVE_USER_ID : INACTIVE_GUEST_USER_ID, GUEST_USER_NAME,
null,
UserInfo.FLAG_FULL | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_GUEST,
UserManager.USER_TYPE_FULL_GUEST);
}
}