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:
25
res/drawable/ic_admin_panel_settings.xml
Normal file
25
res/drawable/ic_admin_panel_settings.xml
Normal 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>
|
@@ -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 & 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 & 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] -->
|
||||
|
@@ -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"
|
||||
|
@@ -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();
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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,
|
||||
|
Reference in New Issue
Block a user