Merge "Add UI for multiple admins on Headless"
This commit is contained in:
committed by
Android (Google) Code Review
commit
0c1fb1fb2a
@@ -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);
|
||||
|
||||
@@ -4102,6 +4102,26 @@ public class UserManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Revokes admin privileges from the user, if such a user exists.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="@dimen/grant_admin_dialog_padding">
|
||||
<RadioGroup
|
||||
android:id="@+id/choose_admin"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
<RadioButton
|
||||
android:id="@+id/grant_admin_yes"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/grant_admin"/>
|
||||
<RadioButton
|
||||
android:id="@+id/grant_admin_no"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/not_grant_admin"/>
|
||||
</RadioGroup>
|
||||
</LinearLayout>
|
||||
@@ -109,4 +109,7 @@
|
||||
<dimen name="broadcast_dialog_btn_text_size">16sp</dimen>
|
||||
<dimen name="broadcast_dialog_btn_minHeight">44dp</dimen>
|
||||
<dimen name="broadcast_dialog_margin">16dp</dimen>
|
||||
|
||||
<!-- Size of grant admin privileges dialog padding -->
|
||||
<dimen name="grant_admin_dialog_padding">16dp</dimen>
|
||||
</resources>
|
||||
|
||||
@@ -1368,6 +1368,10 @@
|
||||
<string name="user_add_user_message_long">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.</string>
|
||||
<!-- Message for add user confirmation dialog - short version. [CHAR LIMIT=none] -->
|
||||
<string name="user_add_user_message_short">When you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users. </string>
|
||||
<!-- Title for grant user admin privileges dialog [CHAR LIMIT=30] -->
|
||||
<string name="user_grant_admin_title">Give this user admin privileges?</string>
|
||||
<!-- Message for grant admin privileges dialog. [CHAR LIMIT=none] -->
|
||||
<string name="user_grant_admin_message">As an admin, they will be able to manage other users, modify device settings and factory reset the device.</string>
|
||||
<!-- Title of dialog to setup a new user [CHAR LIMIT=30] -->
|
||||
<string name="user_setup_dialog_title">Set up user now?</string>
|
||||
<!-- Message in dialog to setup a new user after creation [CHAR LIMIT=none] -->
|
||||
@@ -1434,6 +1438,10 @@
|
||||
<!-- Dialog message on action exit guest (ephemeral guest) [CHAR LIMIT=80] -->
|
||||
<string name="guest_exit_dialog_message">This will delete
|
||||
apps and data from the current guest session</string>
|
||||
<!-- Dialog message on action grant admin privileges [CHAR LIMIT=60] -->
|
||||
<string name="grant_admin">Give this user admin privileges</string>
|
||||
<!-- Dialog message on action not grant admin privileges [CHAR LIMIT=60] -->
|
||||
<string name="not_grant_admin">Do not give user admin privileges</string>
|
||||
<!-- Dialog button on action exit guest (ephemeral guest) [CHAR LIMIT=80] -->
|
||||
<string name="guest_exit_dialog_button">Exit</string>
|
||||
<!-- Dialog title on action exit guest (non-ephemeral guest) [CHAR LIMIT=32] -->
|
||||
|
||||
@@ -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<Boolean> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}, () -> {
|
||||
|
||||
@@ -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<UserInfo?>,
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user