diff options
author | Andras Kloczl <andraskloczl@google.com> | 2020-04-21 11:32:12 +0100 |
---|---|---|
committer | Andras Kloczl <andraskloczl@google.com> | 2020-05-07 22:01:33 +0100 |
commit | 4d7d4effa5d98a0a84bf0c7539cd60bd05571c7b (patch) | |
tree | 6713d6bbba271db5e9db886399a7633857edb93d /src/com/android/settings/users | |
parent | 73d336790195315895554147f2cdb2efce7e6d46 (diff) | |
download | packages_apps_Settings-4d7d4effa5d98a0a84bf0c7539cd60bd05571c7b.tar.gz packages_apps_Settings-4d7d4effa5d98a0a84bf0c7539cd60bd05571c7b.tar.bz2 packages_apps_Settings-4d7d4effa5d98a0a84bf0c7539cd60bd05571c7b.zip |
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
Diffstat (limited to 'src/com/android/settings/users')
5 files changed, 320 insertions, 308 deletions
diff --git a/src/com/android/settings/users/AppRestrictionsFragment.java b/src/com/android/settings/users/AppRestrictionsFragment.java index 7b15e8a090..1caf49c449 100644 --- a/src/com/android/settings/users/AppRestrictionsFragment.java +++ b/src/com/android/settings/users/AppRestrictionsFragment.java @@ -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); } } diff --git a/src/com/android/settings/users/RestrictedProfileSettings.java b/src/com/android/settings/users/RestrictedProfileSettings.java index 57c0d02886..44657cf959 100644 --- a/src/com/android/settings/users/RestrictedProfileSettings.java +++ b/src/com/android/settings/users/RestrictedProfileSettings.java @@ -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); diff --git a/src/com/android/settings/users/UserDetailsSettings.java b/src/com/android/settings/users/UserDetailsSettings.java index 371c152e15..2696ddc131 100644 --- a/src/com/android/settings/users/UserDetailsSettings.java +++ b/src/com/android/settings/users/UserDetailsSettings.java @@ -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,9 +124,135 @@ public class UserDetailsSettings extends SettingsPreferenceFragment return true; } - void enableCallsAndSms(boolean enabled) { + @Override + public int getDialogMetricsCategory(int dialogId) { + switch (dialogId) { + case DIALOG_CONFIRM_REMOVE: + return SettingsEnums.DIALOG_USER_REMOVE; + case DIALOG_CONFIRM_ENABLE_CALLING: + return SettingsEnums.DIALOG_USER_ENABLE_CALLING; + case DIALOG_CONFIRM_ENABLE_CALLING_AND_SMS: + return SettingsEnums.DIALOG_USER_ENABLE_CALLING_AND_SMS; + default: + return 0; + } + } + + @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 (mGuestUser) { + if (mUserInfo.isGuest()) { mDefaultGuestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, !enabled); // SMS is always disabled for guest mDefaultGuestRestrictions.putBoolean(UserManager.DISALLOW_SMS, true); @@ -146,7 +262,7 @@ public class UserDetailsSettings extends SettingsPreferenceFragment // 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) { + for (UserInfo user : users) { if (user.isGuest()) { UserHandle userHandle = UserHandle.of(user.id); for (String key : mDefaultGuestRestrictions.keySet()) { @@ -163,51 +279,7 @@ public class UserDetailsSettings extends SettingsPreferenceFragment } } - @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) { - case DIALOG_CONFIRM_REMOVE: - return SettingsEnums.DIALOG_USER_REMOVE; - case DIALOG_CONFIRM_ENABLE_CALLING: - return SettingsEnums.DIALOG_USER_ENABLE_CALLING; - case DIALOG_CONFIRM_ENABLE_CALLING_AND_SMS: - return SettingsEnums.DIALOG_USER_ENABLE_CALLING_AND_SMS; - default: - return 0; - } - } - - void removeUser() { + private void removeUser() { mUserManager.removeUser(mUserInfo.id); finishFragment(); } diff --git a/src/com/android/settings/users/UserPreference.java b/src/com/android/settings/users/UserPreference.java index 3603d44ea0..0b78d78760 100644 --- a/src/com/android/settings/users/UserPreference.java +++ b/src/com/android/settings/users/UserPreference.java @@ -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() { diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java index 38ef199c5c..7d4ab5d531 100644 --- a/src/com/android/settings/users/UserSettings.java +++ b/src/com/android/settings/users/UserSettings.java @@ -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,40 +1161,20 @@ 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) { mRemovingUserId = -1; |