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
This commit is contained in:
Tetiana Meronyk
2022-11-24 16:04:16 +00:00
parent ecbb3a2933
commit e5acef9034
7 changed files with 170 additions and 11 deletions

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"
android:viewportHeight="24"
android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M17,17Q17.625,17 18.062,16.562Q18.5,16.125 18.5,15.5Q18.5,14.875 18.062,14.438Q17.625,14 17,14Q16.375,14 15.938,14.438Q15.5,14.875 15.5,15.5Q15.5,16.125 15.938,16.562Q16.375,17 17,17ZM17,20Q17.775,20 18.425,19.637Q19.075,19.275 19.475,18.675Q18.925,18.35 18.3,18.175Q17.675,18 17,18Q16.325,18 15.7,18.175Q15.075,18.35 14.525,18.675Q14.925,19.275 15.575,19.637Q16.225,20 17,20ZM12,22Q8.525,21.125 6.263,18.012Q4,14.9 4,11.1V5L12,2L20,5V10.675Q19.525,10.475 19.025,10.312Q18.525,10.15 18,10.075V6.4L12,4.15L6,6.4V11.1Q6,12.275 6.312,13.45Q6.625,14.625 7.188,15.688Q7.75,16.75 8.55,17.65Q9.35,18.55 10.325,19.15Q10.6,19.95 11.05,20.675Q11.5,21.4 12.075,21.975Q12.05,21.975 12.038,21.988Q12.025,22 12,22ZM17,22Q14.925,22 13.463,20.538Q12,19.075 12,17Q12,14.925 13.463,13.462Q14.925,12 17,12Q19.075,12 20.538,13.462Q22,14.925 22,17Q22,19.075 20.538,20.538Q19.075,22 17,22ZM12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Q12,11.65 12,11.65Z"/>
</vector>

View File

@@ -401,6 +401,8 @@
<string name="share">Share</string>
<!-- Button label for generic add action [CHAR LIMIT=20] -->
<string name="add">Add</string>
<!-- Button label for remove action [CHAR LIMIT=20] -->
<string name="remove">Remove</string>
<!-- Title of the Settings activity shown within the application itself. -->
<string name="settings_label">Settings</string>
@@ -6020,12 +6022,18 @@
<!-- Title of preference to enable calling and SMS [CHAR LIMIT=45] -->
<string name="user_enable_calling_sms">Turn on phone calls &amp; SMS</string>
<!-- Title of preference to grant user admin privileges [CHAR LIMIT=45] -->
<string name="user_grant_admin">Give this user admin privileges</string>
<!-- Title of preference to remove the user [CHAR LIMIT=35] -->
<string name="user_remove_user">Delete user</string>
<!-- Title for confirmation of turning on calls and SMS [CHAR LIMIT=45] -->
<string name="user_enable_calling_and_sms_confirm_title">Turn on phone calls &amp; SMS?</string>
<!-- Message for confirmation of turning on calls and SMS [CHAR LIMIT=none] -->
<string name="user_enable_calling_and_sms_confirm_message">Call and SMS history will be shared with this user.</string>
<!-- Title for confirmation of revoking admin privileges [CHAR LIMIT=45] -->
<string name="user_revoke_admin_confirm_title">Remove admin privileges?</string>
<!-- Message for confirmation of revoking admin privileges [CHAR LIMIT=none] -->
<string name="user_revoke_admin_confirm_message">Are you sure you want to remove this user\'s admin privileges?</string>
<!-- Title for the emergency info preference [CHAR LIMIT=40] -->
<string name="emergency_info_title">Emergency information</string>
<!-- Summary for the emergency info preference [CHAR LIMIT=40] -->

View File

@@ -21,6 +21,10 @@
<com.android.settingslib.RestrictedPreference
android:key="switch_user"
android:icon="@drawable/ic_swap" />
<SwitchPreference
android:key="user_grant_admin"
android:icon="@drawable/ic_admin_panel_settings"
android:title="@string/user_grant_admin" />
<SwitchPreference
android:key="enable_calling"
android:icon="@drawable/ic_phone"

View File

@@ -61,6 +61,7 @@ public class UserDetailsSettings extends SettingsPreferenceFragment
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 String KEY_GRANT_ADMIN = "user_grant_admin";
private static final String KEY_APP_AND_CONTENT_ACCESS = "app_and_content_access";
private static final String KEY_APP_COPYING = "app_copying";
@@ -72,6 +73,7 @@ public class UserDetailsSettings extends SettingsPreferenceFragment
private static final int DIALOG_SETUP_USER = 3;
private static final int DIALOG_CONFIRM_RESET_GUEST = 4;
private static final int DIALOG_CONFIRM_RESET_GUEST_AND_SWITCH_USER = 5;
private static final int DIALOG_CONFIRM_REVOKE_ADMIN = 6;
/** Whether to enable the app_copying fragment. */
private static final boolean SHOW_APP_COPYING_PREF = false;
@@ -91,6 +93,8 @@ public class UserDetailsSettings extends SettingsPreferenceFragment
Preference mAppCopyingPref;
@VisibleForTesting
Preference mRemoveUserPref;
@VisibleForTesting
SwitchPreference mGrantAdminPref;
@VisibleForTesting
/** The user being studied (not the user doing the studying). */
@@ -179,6 +183,12 @@ public class UserDetailsSettings extends SettingsPreferenceFragment
mMetricsFeatureProvider.action(getActivity(),
SettingsEnums.ACTION_DISABLE_USER_CALL);
enableCallsAndSms(false);
} else if (preference == mGrantAdminPref) {
if (Boolean.FALSE.equals(newValue)) {
showDialog(DIALOG_CONFIRM_REVOKE_ADMIN);
return false;
}
updateUserAdminStatus(true);
}
return true;
}
@@ -192,6 +202,8 @@ public class UserDetailsSettings extends SettingsPreferenceFragment
return SettingsEnums.DIALOG_USER_REMOVE;
case DIALOG_CONFIRM_ENABLE_CALLING_AND_SMS:
return SettingsEnums.DIALOG_USER_ENABLE_CALLING_AND_SMS;
case DIALOG_CONFIRM_REVOKE_ADMIN:
return SettingsEnums.DIALOG_REVOKE_USER_ADMIN;
case DIALOG_SETUP_USER:
return SettingsEnums.DIALOG_USER_SETUP;
default:
@@ -235,6 +247,9 @@ public class UserDetailsSettings extends SettingsPreferenceFragment
return UserDialogs.createRemoveGuestDialog(getActivity(),
(dialog, which) -> 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();

View File

@@ -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();
}
}

View File

@@ -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<Bitmap> 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) {
@@ -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<HashMap<String, String>> data = new ArrayList<HashMap<String, String>>();
HashMap<String, String> addUserItem = new HashMap<String, String>();
@@ -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);
}

View File

@@ -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,