From e5acef90346474ca245ff348e05ab8eae4c9448b Mon Sep 17 00:00:00 2001 From: Tetiana Meronyk Date: Thu, 24 Nov 2022 16:04:16 +0000 Subject: [PATCH] Add UI for multiple admins on Headless In a series of CLs under topic add_ui_for_hsum_admins UI and functionality for allowing multiple admins on HSUM build is added. In User settings and User switcher when creating a new user there is a new dialog prompting to choose admin status of the user to be created. In User details view there is a toggle that is visible to admin users that allows to modify admin status of existing users. This toggle is only applicable to full users that are not supervised, guests or a main device user. Bug: 252790451 Test: croot && make RunSettingsRoboTests -j40 ROBOTEST_FILTER="com.android.settings.users.UserDetailsSettingsTest" Change-Id: I447dc168be30aa614aeb3f8b71ad14a7223fd7c1 --- res/drawable/ic_admin_panel_settings.xml | 25 +++++++++ res/values/strings.xml | 8 +++ res/xml/user_details_settings.xml | 4 ++ .../settings/users/UserDetailsSettings.java | 40 +++++++++++++- .../android/settings/users/UserDialogs.java | 15 ++++++ .../android/settings/users/UserSettings.java | 52 +++++++++++++++---- .../users/UserDetailsSettingsTest.java | 37 +++++++++++++ 7 files changed, 170 insertions(+), 11 deletions(-) create mode 100644 res/drawable/ic_admin_panel_settings.xml diff --git a/res/drawable/ic_admin_panel_settings.xml b/res/drawable/ic_admin_panel_settings.xml new file mode 100644 index 00000000000..2d3a7f1f43d --- /dev/null +++ b/res/drawable/ic_admin_panel_settings.xml @@ -0,0 +1,25 @@ + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 3d73571e060..41d465a8be8 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -401,6 +401,8 @@ Share Add + + Remove Settings @@ -6020,12 +6022,18 @@ Turn on phone calls & SMS + + Give this user admin privileges Delete user Turn on phone calls & SMS? Call and SMS history will be shared with this user. + + Remove admin privileges? + + Are you sure you want to remove this user\'s admin privileges? Emergency information diff --git a/res/xml/user_details_settings.xml b/res/xml/user_details_settings.xml index 2301bac1d42..068039cbd04 100644 --- a/res/xml/user_details_settings.xml +++ b/res/xml/user_details_settings.xml @@ -21,6 +21,10 @@ + switchUser()); } + case DIALOG_CONFIRM_REVOKE_ADMIN: + return UserDialogs.createConfirmRevokeAdmin(getActivity(), + (dialog, which) -> updateUserAdminStatus(false)); } throw new IllegalArgumentException("Unsupported dialogId " + dialogId); } @@ -277,6 +292,9 @@ public class UserDetailsSettings extends SettingsPreferenceFragment mRemoveUserPref = findPreference(KEY_REMOVE_USER); mAppAndContentAccessPref = findPreference(KEY_APP_AND_CONTENT_ACCESS); mAppCopyingPref = findPreference(KEY_APP_COPYING); + mGrantAdminPref = findPreference(KEY_GRANT_ADMIN); + + mGrantAdminPref.setChecked(mUserInfo.isAdmin()); mSwitchUserPref.setTitle( context.getString(com.android.settingslib.R.string.user_switch_to_user, @@ -289,10 +307,15 @@ public class UserDetailsSettings extends SettingsPreferenceFragment mSwitchUserPref.setSelectable(true); mSwitchUserPref.setOnPreferenceClickListener(this); } - + //TODO(b/261700461): remove preference for supervised user + //TODO(b/262371063): check whether multiple admins allowed, not for HSUM + if (mUserInfo.isMain() || mUserInfo.isGuest() || !UserManager.isHeadlessSystemUserMode()) { + removePreference(KEY_GRANT_ADMIN); + } if (!mUserManager.isAdminUser()) { // non admin users can't remove users and allow calls removePreference(KEY_ENABLE_TELEPHONY); removePreference(KEY_REMOVE_USER); + removePreference(KEY_GRANT_ADMIN); removePreference(KEY_APP_AND_CONTENT_ACCESS); removePreference(KEY_APP_COPYING); } else { @@ -339,6 +362,7 @@ public class UserDetailsSettings extends SettingsPreferenceFragment mRemoveUserPref.setOnPreferenceClickListener(this); mPhonePref.setOnPreferenceChangeListener(this); + mGrantAdminPref.setOnPreferenceChangeListener(this); mAppAndContentAccessPref.setOnPreferenceClickListener(this); mAppCopyingPref.setOnPreferenceClickListener(this); } @@ -401,6 +425,20 @@ public class UserDetailsSettings extends SettingsPreferenceFragment mUserManager.setUserRestriction(UserManager.DISALLOW_SMS, !enabled, userHandle); } + /** + * Sets admin status of selected user. Method is called when toggle in + * user details settings is switched. + * @param isSetAdmin indicates if user admin status needs to be set to true. + */ + private void updateUserAdminStatus(boolean isSetAdmin) { + mGrantAdminPref.setChecked(isSetAdmin); + if (!isSetAdmin) { + mUserManager.revokeUserAdmin(mUserInfo.id); + } else if ((mUserInfo.flags & UserInfo.FLAG_ADMIN) == 0) { + mUserManager.setUserAdmin(mUserInfo.id); + } + } + private void removeUser() { mUserManager.removeUser(mUserInfo.id); finishFragment(); diff --git a/src/com/android/settings/users/UserDialogs.java b/src/com/android/settings/users/UserDialogs.java index c2f2528530b..1e26effd5c3 100644 --- a/src/com/android/settings/users/UserDialogs.java +++ b/src/com/android/settings/users/UserDialogs.java @@ -202,4 +202,19 @@ public final class UserDialogs { .setNegativeButton(android.R.string.cancel, null) .create(); } + + /** + * Creates a dialog to confirm that the admin privileges of the user should be revoked. + * + * @param onConfirmListener Callback object for positive action + */ + public static Dialog createConfirmRevokeAdmin(Context context, + DialogInterface.OnClickListener onConfirmListener) { + return new AlertDialog.Builder(context) + .setTitle(R.string.user_revoke_admin_confirm_title) + .setMessage(R.string.user_revoke_admin_confirm_message) + .setPositiveButton(R.string.remove, onConfirmListener) + .setNegativeButton(android.R.string.cancel, null) + .create(); + } } diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java index da6339beb7e..54d0b452246 100644 --- a/src/com/android/settings/users/UserSettings.java +++ b/src/com/android/settings/users/UserSettings.java @@ -84,6 +84,7 @@ import com.android.settingslib.drawable.CircleFramedDrawable; import com.android.settingslib.search.SearchIndexable; import com.android.settingslib.search.SearchIndexableRaw; import com.android.settingslib.users.EditUserInfoController; +import com.android.settingslib.users.GrantAdminDialogController; import com.android.settingslib.users.UserCreatingDialog; import com.android.settingslib.utils.ThreadUtils; @@ -156,6 +157,7 @@ public class UserSettings extends SettingsPreferenceFragment private static final int DIALOG_CONFIRM_RESET_AND_RESTART_GUEST = 13; private static final int DIALOG_CONFIRM_EXIT_GUEST_EPHEMERAL = 14; private static final int DIALOG_CONFIRM_EXIT_GUEST_NON_EPHEMERAL = 15; + private static final int DIALOG_GRANT_ADMIN = 16; private static final int MESSAGE_UPDATE_LIST = 1; private static final int MESSAGE_USER_CREATED = 2; @@ -215,6 +217,9 @@ public class UserSettings extends SettingsPreferenceFragment private static SparseArray sDarkDefaultUserBitmapCache = new SparseArray<>(); private MultiUserSwitchBarController mSwitchBarController; + + private GrantAdminDialogController mGrantAdminDialogController = + new GrantAdminDialogController(); private EditUserInfoController mEditUserInfoController = new EditUserInfoController(Utils.FILE_PROVIDER_AUTHORITY); private AddUserWhenLockedPreferenceController mAddUserWhenLockedPreferenceController; @@ -228,6 +233,7 @@ public class UserSettings extends SettingsPreferenceFragment private CharSequence mPendingUserName; private Drawable mPendingUserIcon; + private boolean mGrantAdmin; // A place to cache the generated default avatar private Drawable mDefaultIconDrawable; @@ -287,7 +293,6 @@ public class UserSettings extends SettingsPreferenceFragment mSwitchBarController = new MultiUserSwitchBarController(activity, new MainSwitchBarController(switchBar), this /* listener */); getSettingsLifecycle().addObserver(mSwitchBarController); - boolean openUserEditDialog = getIntent().getBooleanExtra( EXTRA_OPEN_DIALOG_USER_PROFILE_EDITOR, false); if (switchBar.isChecked() && openUserEditDialog) { @@ -306,7 +311,7 @@ public class UserSettings extends SettingsPreferenceFragment } mGuestUserAutoCreated = getPrefContext().getResources().getBoolean( - com.android.internal.R.bool.config_guestUserAutoCreated); + com.android.internal.R.bool.config_guestUserAutoCreated); mAddUserWhenLockedPreferenceController = new AddUserWhenLockedPreferenceController( activity, KEY_ADD_USER_WHEN_LOCKED); @@ -712,18 +717,27 @@ public class UserSettings extends SettingsPreferenceFragment .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { - showDialog(DIALOG_USER_PROFILE_EDITOR_ADD_USER); if (!longMessageDisplayed) { preferences.edit().putBoolean( KEY_ADD_USER_LONG_MESSAGE_DISPLAYED, true).apply(); } + //TODO(b/262371063): check whether multiple admins allowed, + // not for HSUM + if (UserManager.isHeadlessSystemUserMode()) { + showDialog(DIALOG_GRANT_ADMIN); + } else { + showDialog(DIALOG_USER_PROFILE_EDITOR_ADD_USER); + } } }) .setNegativeButton(android.R.string.cancel, null) .create(); return dlg; } + case DIALOG_GRANT_ADMIN: { + return buildGrantAdminDialog(); + } case DIALOG_CHOOSE_USER_TYPE: { List> data = new ArrayList>(); HashMap addUserItem = new HashMap(); @@ -931,6 +945,19 @@ public class UserSettings extends SettingsPreferenceFragment return d; } + private Dialog buildGrantAdminDialog() { + return mGrantAdminDialogController.createDialog( + getActivity(), + (grantAdmin) -> { + mGrantAdmin = grantAdmin; + showDialog(DIALOG_USER_PROFILE_EDITOR_ADD_USER); + }, + () -> { + mGrantAdmin = false; + } + ); + } + @Override public int getDialogMetricsCategory(int dialogId) { switch (dialogId) { @@ -938,6 +965,8 @@ public class UserSettings extends SettingsPreferenceFragment return SettingsEnums.DIALOG_USER_REMOVE; case DIALOG_USER_CANNOT_MANAGE: return SettingsEnums.DIALOG_USER_CANNOT_MANAGE; + case DIALOG_GRANT_ADMIN: + return SettingsEnums.DIALOG_GRANT_USER_ADMIN; case DIALOG_ADD_USER: return SettingsEnums.DIALOG_USER_ADD; case DIALOG_CHOOSE_USER_TYPE: @@ -1031,6 +1060,9 @@ public class UserSettings extends SettingsPreferenceFragment userName, mUserManager.USER_TYPE_FULL_SECONDARY, 0); + if (mGrantAdmin) { + mUserManager.setUserAdmin(user.id); + } } else { user = mUserManager.createRestrictedProfile(userName); } @@ -1351,20 +1383,20 @@ public class UserSettings extends SettingsPreferenceFragment mGuestResetPreference.setVisible(true); boolean isGuestFirstLogin = Settings.Secure.getIntForUser( - getContext().getContentResolver(), - SETTING_GUEST_HAS_LOGGED_IN, - 0, - UserHandle.myUserId()) <= 1; + getContext().getContentResolver(), + SETTING_GUEST_HAS_LOGGED_IN, + 0, + UserHandle.myUserId()) <= 1; String guestExitSummary; if (mUserCaps.mIsEphemeral) { guestExitSummary = getContext().getString( - R.string.guest_notification_ephemeral); + R.string.guest_notification_ephemeral); } else if (isGuestFirstLogin) { guestExitSummary = getContext().getString( - R.string.guest_notification_non_ephemeral); + R.string.guest_notification_non_ephemeral); } else { guestExitSummary = getContext().getString( - R.string.guest_notification_non_ephemeral_non_first_login); + R.string.guest_notification_non_ephemeral_non_first_login); } mGuestExitPreference.setSummary(guestExitSummary); } diff --git a/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java b/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java index 14ca76fa9a7..5cd513ee147 100644 --- a/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java +++ b/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java @@ -22,6 +22,7 @@ import static android.os.UserManager.SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; @@ -84,6 +85,7 @@ import java.util.List; }) public class UserDetailsSettingsTest { + private static final String KEY_GRANT_ADMIN = "user_grant_admin"; 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"; @@ -103,6 +105,8 @@ public class UserDetailsSettingsTest { @Mock private SwitchPreference mPhonePref; @Mock + private SwitchPreference mGrantAdminPref; + @Mock private Preference mRemoveUserPref; @Mock private Preference mAppAndContentAccessPref; @@ -144,6 +148,7 @@ public class UserDetailsSettingsTest { doReturn(mock(PreferenceScreen.class)).when(mFragment).getPreferenceScreen(); doReturn(mSwitchUserPref).when(mFragment).findPreference(KEY_SWITCH_USER); + doReturn(mGrantAdminPref).when(mFragment).findPreference(KEY_GRANT_ADMIN); doReturn(mPhonePref).when(mFragment).findPreference(KEY_ENABLE_TELEPHONY); doReturn(mRemoveUserPref).when(mFragment).findPreference(KEY_REMOVE_USER); doReturn(mAppAndContentAccessPref) @@ -678,6 +683,38 @@ public class UserDetailsSettingsTest { assertThat(result).isFalse(); } + @Test + public void initialize_userSelected_shouldShowGrantAdminPref_HSUM() { + setupSelectedUser(); + ShadowUserManager.setIsHeadlessSystemUserMode(true); + mFragment.initialize(mActivity, mArguments); + assertTrue(UserManager.isHeadlessSystemUserMode()); + verify(mFragment, never()).removePreference(KEY_GRANT_ADMIN); + } + + @Test + public void initialize_userSelected_shouldNotShowGrantAdminPref() { + setupSelectedUser(); + mFragment.initialize(mActivity, mArguments); + verify(mFragment).removePreference(KEY_GRANT_ADMIN); + } + + @Test + public void initialize_mainUserSelected_shouldShowGrantAdminPref_HSUM() { + setupSelectedMainUser(); + ShadowUserManager.setIsHeadlessSystemUserMode(true); + mFragment.initialize(mActivity, mArguments); + verify(mFragment).removePreference(KEY_GRANT_ADMIN); + } + + @Test + public void initialize_guestSelected_shouldNotShowGrantAdminPref_HSUM() { + setupSelectedGuest(); + ShadowUserManager.setIsHeadlessSystemUserMode(true); + mFragment.initialize(mActivity, mArguments); + verify(mFragment).removePreference(KEY_GRANT_ADMIN); + } + private void setupSelectedUser() { mArguments.putInt("user_id", 1); mUserInfo = new UserInfo(1, "Tom", null,