From d6738a478f76df9508bf6fdeef116c03d9c0c20b Mon Sep 17 00:00:00 2001 From: Tetiana Meronyk Date: Thu, 24 Nov 2022 16:04:49 +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: I34a6d223c009b7217e5e85077c886241ff06a2cb --- core/java/android/os/IUserManager.aidl | 1 + core/java/android/os/UserManager.java | 20 ++++++ .../res/layout/grant_admin_dialog_content.xml | 37 ++++++++++ packages/SettingsLib/res/values/dimens.xml | 3 + packages/SettingsLib/res/values/strings.xml | 8 +++ .../users/GrantAdminDialogController.java | 71 +++++++++++++++++++ .../systemui/user/CreateUserActivity.java | 38 ++++++++-- .../com/android/systemui/user/UserCreator.kt | 22 ++++-- .../android/server/pm/UserManagerService.java | 21 ++++++ .../android/server/pm/UserManagerTest.java | 24 +++++++ 10 files changed, 234 insertions(+), 11 deletions(-) create mode 100644 packages/SettingsLib/res/layout/grant_admin_dialog_content.xml create mode 100644 packages/SettingsLib/src/com/android/settingslib/users/GrantAdminDialogController.java diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index 1490c6a32fbd7..3b4e8cd39697c 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -51,6 +51,7 @@ interface IUserManager { String[] getPreInstallableSystemPackages(in String userType); void setUserEnabled(int userId); void setUserAdmin(int userId); + void revokeUserAdmin(int userId); void evictCredentialEncryptionKey(int userId); boolean removeUser(int userId); boolean removeUserEvenWhenDisallowed(int userId); diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 08d15c72c47fb..88fccf19804e1 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -4101,6 +4101,26 @@ public class UserManager { } } + /** + * Revokes admin privileges from the user, if such a user exists. + * + *

Note that this does not alter the user's pre-existing user restrictions. + * + * @param userId the id of the user to revoke admin rights from + * @hide + */ + @RequiresPermission(allOf = { + Manifest.permission.INTERACT_ACROSS_USERS_FULL, + Manifest.permission.MANAGE_USERS + }) + public void revokeUserAdmin(@UserIdInt int userId) { + try { + mService.revokeUserAdmin(userId); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + /** * Evicts the user's credential encryption key from memory by stopping and restarting the user. * diff --git a/packages/SettingsLib/res/layout/grant_admin_dialog_content.xml b/packages/SettingsLib/res/layout/grant_admin_dialog_content.xml new file mode 100644 index 0000000000000..d6acac2cf4955 --- /dev/null +++ b/packages/SettingsLib/res/layout/grant_admin_dialog_content.xml @@ -0,0 +1,37 @@ + + + + + + + diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml index 3ef3d36422627..dbfd1c27fce83 100644 --- a/packages/SettingsLib/res/values/dimens.xml +++ b/packages/SettingsLib/res/values/dimens.xml @@ -109,4 +109,7 @@ 16sp 44dp 16dp + + + 16dp diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index e675db42787e4..2845916fc2dd8 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1368,6 +1368,10 @@ You can share this device with other people by creating additional users. Each user has their own space, which they can customize with apps, wallpaper, and so on. Users can also adjust device settings like Wi\u2011Fi that affect everyone.\n\nWhen you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users. Accessibility settings and services may not transfer to the new user. When you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users. + + Give this user admin privileges? + + As an admin, they will be able to manage other users, modify device settings and factory reset the device. Set up user now? @@ -1434,6 +1438,10 @@ This will delete apps and data from the current guest session + + Give this user admin privileges + + Do not give user admin privileges Exit diff --git a/packages/SettingsLib/src/com/android/settingslib/users/GrantAdminDialogController.java b/packages/SettingsLib/src/com/android/settingslib/users/GrantAdminDialogController.java new file mode 100644 index 0000000000000..5cc8d5e602ecf --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/users/GrantAdminDialogController.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2022 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.settingslib.users; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.RadioButton; +import android.widget.RadioGroup; + +import com.android.settingslib.R; + +import java.util.function.Consumer; + +/** + * This class encapsulates a Dialog for choosing whether to grant admin privileges. + */ +public class GrantAdminDialogController { + + /** + * Creates a dialog with option to grant user admin privileges. + */ + public Dialog createDialog(Activity activity, + Consumer successCallback, Runnable cancelCallback) { + LayoutInflater inflater = LayoutInflater.from(activity); + View content = inflater.inflate(R.layout.grant_admin_dialog_content, null); + RadioGroup radioGroup = content.findViewById(R.id.choose_admin); + RadioButton radioButton = radioGroup.findViewById(R.id.grant_admin_yes); + radioButton.setChecked(true); + Dialog dlg = new AlertDialog.Builder(activity) + .setView(content) + .setTitle(R.string.user_grant_admin_title) + .setMessage(R.string.user_grant_admin_message) + .setPositiveButton(android.R.string.ok, + (dialog, which) -> { + if (successCallback != null) { + successCallback.accept(radioButton.isChecked()); + } + }) + .setNegativeButton(android.R.string.cancel, (dialog, which) -> { + if (cancelCallback != null) { + cancelCallback.run(); + } + }) + .setOnCancelListener(dialog -> { + if (cancelCallback != null) { + cancelCallback.run(); + } + }) + .create(); + + return dlg; + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java index b56c4034936ff..cd21a45be0ce1 100644 --- a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java +++ b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java @@ -32,6 +32,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.settingslib.users.EditUserInfoController; +import com.android.settingslib.users.GrantAdminDialogController; import com.android.systemui.R; import com.android.systemui.plugins.ActivityStarter; @@ -60,9 +61,10 @@ public class CreateUserActivity extends Activity { private final IActivityManager mActivityManager; private final ActivityStarter mActivityStarter; + private Dialog mGrantAdminDialog; private Dialog mSetupUserDialog; private final OnBackInvokedCallback mBackCallback = this::onBackInvoked; - + private Boolean mGrantAdminRights; @Inject public CreateUserActivity(UserCreator userCreator, EditUserInfoController editUserInfoController, IActivityManager activityManager, @@ -78,14 +80,17 @@ public class CreateUserActivity extends Activity { super.onCreate(savedInstanceState); setShowWhenLocked(true); setContentView(R.layout.activity_create_new_user); - if (savedInstanceState != null) { mEditUserInfoController.onRestoreInstanceState(savedInstanceState); } - mSetupUserDialog = createDialog(); - mSetupUserDialog.show(); - + if (mUserCreator.isHeadlessSystemUserMode()) { + mGrantAdminDialog = buildGrantAdminDialog(); + mGrantAdminDialog.show(); + } else { + mSetupUserDialog = createDialog(); + mSetupUserDialog.show(); + } getOnBackInvokedDispatcher().registerOnBackInvokedCallback( OnBackInvokedDispatcher.PRIORITY_DEFAULT, mBackCallback); @@ -124,6 +129,22 @@ public class CreateUserActivity extends Activity { ); } + private Dialog buildGrantAdminDialog() { + return new GrantAdminDialogController().createDialog( + this, + (grantAdminRights) -> { + mGrantAdminDialog.dismiss(); + mGrantAdminRights = grantAdminRights; + mSetupUserDialog = createDialog(); + mSetupUserDialog.show(); + }, + () -> { + mGrantAdminRights = false; + finish(); + } + ); + } + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); @@ -139,6 +160,9 @@ public class CreateUserActivity extends Activity { if (mSetupUserDialog != null) { mSetupUserDialog.dismiss(); } + if (mGrantAdminDialog != null) { + mGrantAdminDialog.dismiss(); + } finish(); } @@ -150,13 +174,15 @@ public class CreateUserActivity extends Activity { private void addUserNow(String userName, Drawable userIcon) { mSetupUserDialog.dismiss(); - userName = (userName == null || userName.trim().isEmpty()) ? getString(com.android.settingslib.R.string.user_new_user_name) : userName; mUserCreator.createUser(userName, userIcon, userInfo -> { + if (mGrantAdminRights) { + mUserCreator.setUserAdmin(userInfo.id); + } switchToUser(userInfo.id); finishIfNeeded(); }, () -> { diff --git a/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt b/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt index dcbbe74419223..277f670597d34 100644 --- a/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt +++ b/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt @@ -32,7 +32,9 @@ import javax.inject.Inject * A class to do the user creation process. It shows a progress dialog, and manages the user * creation */ -class UserCreator @Inject constructor( +class UserCreator +@Inject +constructor( private val context: Context, private val userManager: UserManager, @Main private val mainExecutor: Executor, @@ -42,14 +44,14 @@ class UserCreator @Inject constructor( * Shows a progress dialog then starts the user creation process on the main thread. * * @param successCallback is called when the user creation is successful. - * @param errorCallback is called when userManager.createUser returns null. - * (Exceptions are not handled by this class) + * @param errorCallback is called when userManager.createUser returns null. (Exceptions are not + * handled by this class) */ fun createUser( userName: String?, userIcon: Drawable?, successCallback: Consumer, - errorCallback: Runnable + errorCallback: Runnable ) { val userCreationProgressDialog: Dialog = UserCreatingDialog(context) userCreationProgressDialog.show() @@ -71,11 +73,21 @@ class UserCreator @Inject constructor( newUserIcon = UserIcons.getDefaultUserIcon(res, user.id, false) } userManager.setUserIcon( - user.id, UserIcons.convertToBitmapAtUserIconSize(res, newUserIcon)) + user.id, + UserIcons.convertToBitmapAtUserIconSize(res, newUserIcon) + ) } userCreationProgressDialog.dismiss() successCallback.accept(user) } } } + + fun setUserAdmin(userId: Int) { + userManager.setUserAdmin(userId) + } + + fun isHeadlessSystemUserMode(): Boolean { + return UserManager.isHeadlessSystemUserMode() + } } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index e6600469e998a..2a00eb02a9a8a 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1473,6 +1473,27 @@ public class UserManagerService extends IUserManager.Stub { } } + @Override + public void revokeUserAdmin(@UserIdInt int userId) { + checkManageUserAndAcrossUsersFullPermission("revoke admin privileges"); + + synchronized (mPackagesLock) { + UserInfo info; + synchronized (mUsersLock) { + info = getUserInfoLU(userId); + } + if (info == null || !info.isAdmin()) { + // Exit if no user found with that id, or the user is not an Admin. + return; + } + + info.flags ^= UserInfo.FLAG_ADMIN; + synchronized (mUsersLock) { + writeUserLP(getUserDataLU(info.id)); + } + } + } + /** * Evicts a user's CE key by stopping and restarting the user. * diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java index 810b294f27c3b..5059ef3d1e3af 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -617,6 +617,30 @@ public final class UserManagerTest { assertThat(userInfo.isAdmin()).isTrue(); } + @MediumTest + @Test + public void testRevokeUserAdmin() throws Exception { + UserInfo userInfo = createUser("Admin", /*flags=*/ UserInfo.FLAG_ADMIN); + assertThat(userInfo.isAdmin()).isTrue(); + + mUserManager.revokeUserAdmin(userInfo.id); + + userInfo = mUserManager.getUserInfo(userInfo.id); + assertThat(userInfo.isAdmin()).isFalse(); + } + + @MediumTest + @Test + public void testRevokeUserAdminFromNonAdmin() throws Exception { + UserInfo userInfo = createUser("NonAdmin", /*flags=*/ 0); + assertThat(userInfo.isAdmin()).isFalse(); + + mUserManager.revokeUserAdmin(userInfo.id); + + userInfo = mUserManager.getUserInfo(userInfo.id); + assertThat(userInfo.isAdmin()).isFalse(); + } + @MediumTest @Test public void testGetProfileParent() throws Exception {