From 8d37a2bb85a5e9273cb3068956f4a033a9c0dc16 Mon Sep 17 00:00:00 2001 From: Peter Kalauskas Date: Tue, 15 Jun 2021 17:11:24 -0700 Subject: [PATCH] Make use of config for auto-created guest users If frameworks config config_guestUserAutoCreated=true, then Settings will: - Create a new guest user any time the current guest user is removed - Show "Guest" instead of "Add guest" - Show "Reset guest" instead of "Remove guest" Bug: 188542158 Test: With config_guestUserAutoCreated=true, delete current guest user using adb (`adb shell cmd user list -v --all` to find the user ids, then remove each guest user with `adb shell pm remove-user `), then as owner, open multi-users settings page. Check that there is a item for "Guest" instead of "Add guest". Select "Guest", then confirm there is an item for "Reset guest" (it should be disabled). Select "Switch to guest", then switch back to owner. Open the multi-users settings page again. Check that "Reset guest" is now enabled. Tap "Reset guest", and press confirmation button, named "Reset". You should be take back to the main multi-user settings page and still see "Guest". Wait a few seconds, then use adb to confirm there is a guest user present. Test: With config_guestUserAutoCreated=true, switch to guest user, open multi-users settings page, check that there is an option to "Reset guest". Select "Reset guest", and press confirmation button, named "Reset". Phone should switch back to last active user, and QS tile should now show "Guest" instead of "Add guest". Run `adb shell cmd user list -v --all` to confirm guest has a new user id. Test: With config_guestUserAutoCreated=false, confirm that "Add guest" and "Remove guest" features remain unchanged Change-Id: I7d5b81bd2e5c6b999ae18cd6b1280ae0496db94b --- .../settings/users/UserDetailsSettings.java | 48 +++++++- .../android/settings/users/UserDialogs.java | 20 ++++ .../android/settings/users/UserSettings.java | 103 ++++++++++++++++-- 3 files changed, 162 insertions(+), 9 deletions(-) diff --git a/src/com/android/settings/users/UserDetailsSettings.java b/src/com/android/settings/users/UserDetailsSettings.java index ac6a8ea6f04..ce186f2134a 100644 --- a/src/com/android/settings/users/UserDetailsSettings.java +++ b/src/com/android/settings/users/UserDetailsSettings.java @@ -42,6 +42,9 @@ import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.RestrictedPreference; import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; /** * Settings screen for configuring, deleting or switching to a specific user. @@ -67,9 +70,13 @@ public class UserDetailsSettings extends SettingsPreferenceFragment private static final int DIALOG_CONFIRM_ENABLE_CALLING = 2; private static final int DIALOG_CONFIRM_ENABLE_CALLING_AND_SMS = 3; private static final int DIALOG_SETUP_USER = 4; + private static final int DIALOG_CONFIRM_RESET_GUEST = 5; private UserManager mUserManager; private UserCapabilities mUserCaps; + private boolean mGuestUserAutoCreated; + private final AtomicBoolean mGuestCreationScheduled = new AtomicBoolean(); + private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); @VisibleForTesting RestrictedPreference mSwitchUserPref; @@ -97,6 +104,9 @@ public class UserDetailsSettings extends SettingsPreferenceFragment mUserCaps = UserCapabilities.create(context); addPreferencesFromResource(R.xml.user_details_settings); + mGuestUserAutoCreated = getPrefContext().getResources().getBoolean( + com.android.internal.R.bool.config_guestUserAutoCreated); + initialize(context, getArguments()); } @@ -104,13 +114,20 @@ public class UserDetailsSettings extends SettingsPreferenceFragment public void onResume() { super.onResume(); mSwitchUserPref.setEnabled(canSwitchUserNow()); + if (mGuestUserAutoCreated) { + mRemoveUserPref.setEnabled((mUserInfo.flags & UserInfo.FLAG_INITIALIZED) != 0); + } } @Override public boolean onPreferenceClick(Preference preference) { if (preference == mRemoveUserPref) { if (canDeleteUser()) { - showDialog(DIALOG_CONFIRM_REMOVE); + if (mUserInfo.isGuest()) { + showDialog(DIALOG_CONFIRM_RESET_GUEST); + } else { + showDialog(DIALOG_CONFIRM_REMOVE); + } return true; } } else if (preference == mSwitchUserPref) { @@ -144,6 +161,7 @@ public class UserDetailsSettings extends SettingsPreferenceFragment public int getDialogMetricsCategory(int dialogId) { switch (dialogId) { case DIALOG_CONFIRM_REMOVE: + case DIALOG_CONFIRM_RESET_GUEST: return SettingsEnums.DIALOG_USER_REMOVE; case DIALOG_CONFIRM_ENABLE_CALLING: return SettingsEnums.DIALOG_USER_ENABLE_CALLING; @@ -179,10 +197,30 @@ public class UserDetailsSettings extends SettingsPreferenceFragment switchUser(); } }); + case DIALOG_CONFIRM_RESET_GUEST: + return UserDialogs.createResetGuestDialog(getActivity(), + (dialog, which) -> resetGuest()); } throw new IllegalArgumentException("Unsupported dialogId " + dialogId); } + /** + * Erase the current guest user and create a new one in the background. UserSettings will + * handle guest creation after receiving the {@link UserSettings.RESULT_GUEST_REMOVED} result. + */ + private void resetGuest() { + // Just to be safe, check that the selected user is a guest + if (!mUserInfo.isGuest()) { + return; + } + mMetricsFeatureProvider.action(getActivity(), + SettingsEnums.ACTION_USER_GUEST_EXIT_CONFIRMED); + + mUserManager.removeUser(mUserInfo.id); + setResult(UserSettings.RESULT_GUEST_REMOVED); + finishFragment(); + } + @VisibleForTesting @Override protected void showDialog(int dialogId) { @@ -239,11 +277,17 @@ public class UserDetailsSettings extends SettingsPreferenceFragment if (mUserInfo.isGuest()) { // These are not for an existing user, just general Guest settings. // Default title is for calling and SMS. Change to calling-only here + // TODO(b/191483069): These settings can't be changed unless guest user exists mPhonePref.setTitle(R.string.user_enable_calling); mDefaultGuestRestrictions = mUserManager.getDefaultGuestRestrictions(); mPhonePref.setChecked( !mDefaultGuestRestrictions.getBoolean(UserManager.DISALLOW_OUTGOING_CALLS)); - mRemoveUserPref.setTitle(R.string.user_exit_guest_title); + mRemoveUserPref.setTitle(mGuestUserAutoCreated + ? com.android.settingslib.R.string.guest_reset_guest + : R.string.user_exit_guest_title); + if (mGuestUserAutoCreated) { + mRemoveUserPref.setEnabled((mUserInfo.flags & UserInfo.FLAG_INITIALIZED) != 0); + } } else { mPhonePref.setChecked(!mUserManager.hasUserRestriction( UserManager.DISALLOW_OUTGOING_CALLS, new UserHandle(userId))); diff --git a/src/com/android/settings/users/UserDialogs.java b/src/com/android/settings/users/UserDialogs.java index 9aab5b03da1..6915e95eeec 100644 --- a/src/com/android/settings/users/UserDialogs.java +++ b/src/com/android/settings/users/UserDialogs.java @@ -154,4 +154,24 @@ public final class UserDialogs { null) .create(); } + + /** + * Creates a dialog to confirm with the user if it's ok to reset the guest user, which will + * delete all the guest user's data. + * + * @param context a Context object + * @param onConfirmListener Callback object for positive action + * @return the created Dialog + */ + public static Dialog createResetGuestDialog(Context context, + DialogInterface.OnClickListener onConfirmListener) { + return new AlertDialog.Builder(context) + .setTitle(com.android.settingslib.R.string.guest_reset_guest_dialog_title) + .setMessage(R.string.user_exit_guest_confirm_message) + .setPositiveButton( + com.android.settingslib.R.string.guest_reset_guest_confirm_button, + 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 e95fed784ad..ee23fc379d8 100644 --- a/src/com/android/settings/users/UserSettings.java +++ b/src/com/android/settings/users/UserSettings.java @@ -87,6 +87,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; /** * Screen that manages the list of users on the device. @@ -128,6 +131,7 @@ public class UserSettings extends SettingsPreferenceFragment private static final int DIALOG_USER_PROFILE_EDITOR = 9; private static final int DIALOG_USER_PROFILE_EDITOR_ADD_USER = 10; private static final int DIALOG_USER_PROFILE_EDITOR_ADD_RESTRICTED_PROFILE = 11; + private static final int DIALOG_CONFIRM_RESET_GUEST = 12; private static final int MESSAGE_UPDATE_LIST = 1; private static final int MESSAGE_USER_CREATED = 2; @@ -136,6 +140,9 @@ public class UserSettings extends SettingsPreferenceFragment private static final int USER_TYPE_RESTRICTED_PROFILE = 2; private static final int REQUEST_CHOOSE_LOCK = 10; + private static final int REQUEST_EDIT_GUEST = 11; + + static final int RESULT_GUEST_REMOVED = 100; private static final String KEY_ADD_USER_LONG_MESSAGE_DISPLAYED = "key_add_user_long_message_displayed"; @@ -160,6 +167,7 @@ public class UserSettings extends SettingsPreferenceFragment SparseArray mUserIcons = new SparseArray<>(); private int mRemovingUserId = -1; private boolean mAddingUser; + private boolean mGuestUserAutoCreated; private String mAddingUserName; private UserCapabilities mUserCaps; private boolean mShouldUpdateUserList = true; @@ -173,6 +181,8 @@ public class UserSettings extends SettingsPreferenceFragment private AddUserWhenLockedPreferenceController mAddUserWhenLockedPreferenceController; private MultiUserTopIntroPreferenceController mMultiUserTopIntroPreferenceController; private UserCreatingDialog mUserCreatingDialog; + private final AtomicBoolean mGuestCreationScheduled = new AtomicBoolean(); + private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); private CharSequence mPendingUserName; private Drawable mPendingUserIcon; @@ -240,6 +250,9 @@ public class UserSettings extends SettingsPreferenceFragment return; } + mGuestUserAutoCreated = getPrefContext().getResources().getBoolean( + com.android.internal.R.bool.config_guestUserAutoCreated); + mAddUserWhenLockedPreferenceController = new AddUserWhenLockedPreferenceController( activity, KEY_ADD_USER_WHEN_LOCKED); @@ -343,7 +356,10 @@ public class UserSettings extends SettingsPreferenceFragment @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { int pos = 0; - if (!mUserCaps.mIsAdmin && canSwitchUserNow()) { + // TODO(b/191509236): The menu item does not need to be accessible for guest users, + // regardless of mGuestUserAutoCreated + if (!mUserCaps.mIsAdmin && canSwitchUserNow() && !(isCurrentUserGuest() + && mGuestUserAutoCreated)) { String nickname = mUserManager.getUserName(); MenuItem removeThisUser = menu.add(0, MENU_REMOVE_USER, pos++, getResources().getString(R.string.user_remove_user_menu, nickname)); @@ -387,7 +403,9 @@ public class UserSettings extends SettingsPreferenceFragment if (isCurrentUserGuest()) { // No need to load profile information mMePreference.setIcon(getEncircledDefaultIcon()); - mMePreference.setTitle(R.string.user_exit_guest_title); + mMePreference.setTitle( + mGuestUserAutoCreated ? com.android.settingslib.R.string.guest_reset_guest + : R.string.user_exit_guest_title); mMePreference.setSelectable(true); // removing a guest will result in switching back to the admin user mMePreference.setEnabled(canSwitchUserNow()); @@ -445,6 +463,9 @@ public class UserSettings extends SettingsPreferenceFragment if (resultCode != Activity.RESULT_CANCELED && hasLockscreenSecurity()) { addUserNow(USER_TYPE_RESTRICTED_PROFILE); } + } else if (mGuestUserAutoCreated && requestCode == REQUEST_EDIT_GUEST + && resultCode == RESULT_GUEST_REMOVED) { + scheduleGuestCreation(); } else { mEditUserInfoController.onActivityResult(requestCode, resultCode, data); } @@ -508,12 +529,15 @@ public class UserSettings extends SettingsPreferenceFragment extras.putBoolean(AppRestrictionsFragment.EXTRA_NEW_USER, newUser); final Context context = getContext(); - new SubSettingLauncher(context) + SubSettingLauncher launcher = new SubSettingLauncher(context) .setDestination(UserDetailsSettings.class.getName()) .setArguments(extras) .setTitleText(getUserName(context, userInfo)) - .setSourceMetricsCategory(getMetricsCategory()) - .launch(); + .setSourceMetricsCategory(getMetricsCategory()); + if (mGuestUserAutoCreated && userInfo.isGuest()) { + launcher.setResultListener(this, REQUEST_EDIT_GUEST); + } + launcher.launch(); } @Override @@ -651,6 +675,10 @@ public class UserSettings extends SettingsPreferenceFragment } return buildAddUserDialog(USER_TYPE_RESTRICTED_PROFILE); } + case DIALOG_CONFIRM_RESET_GUEST: { + return UserDialogs.createResetGuestDialog(getActivity(), + (dialog, which) -> resetGuest()); + } default: return null; } @@ -727,6 +755,7 @@ public class UserSettings extends SettingsPreferenceFragment case DIALOG_NEED_LOCKSCREEN: return SettingsEnums.DIALOG_USER_NEED_LOCKSCREEN; case DIALOG_CONFIRM_EXIT_GUEST: + case DIALOG_CONFIRM_RESET_GUEST: return SettingsEnums.DIALOG_USER_CONFIRM_EXIT_GUEST; case DIALOG_USER_PROFILE_EDITOR: case DIALOG_USER_PROFILE_EDITOR_ADD_USER: @@ -840,6 +869,55 @@ public class UserSettings extends SettingsPreferenceFragment removeThisUser(); } + /** + * Erase the current user (assuming it is a guest user), and create a new one in the background + */ + @VisibleForTesting + void resetGuest() { + // Just to be safe + if (!isCurrentUserGuest()) { + return; + } + int guestUserId = UserHandle.myUserId(); + // Using markGuestForDeletion allows us to create a new guest before this one is + // fully removed. This could happen if someone calls scheduleGuestCreation() + // immediately after calling this method. + boolean marked = mUserManager.markGuestForDeletion(guestUserId); + if (!marked) { + Log.w(TAG, "Couldn't mark the guest for deletion for user " + guestUserId); + return; + } + exitGuest(); + scheduleGuestCreation(); + } + + /** + * Create a guest user in the background + */ + @VisibleForTesting + void scheduleGuestCreation() { + // TODO(b/191067027): Move guest recreation to system_server + if (mGuestCreationScheduled.compareAndSet(/* expect= */ false, /* update= */ true)) { + // Once mGuestCreationScheduled=true, mAddGuest needs to be updated so that it shows + // "Resetting guest..." + mHandler.sendEmptyMessage(MESSAGE_UPDATE_LIST); + mExecutor.execute(() -> { + UserInfo guest = mUserManager.createGuest( + getContext(), getString(com.android.settingslib.R.string.user_guest)); + mGuestCreationScheduled.set(false); + if (guest == null) { + Log.e(TAG, "Unable to automatically recreate guest user"); + } + // The list needs to be updated whether or not guest creation worked. If guest + // creation failed, the list needs to update so that "Add guest" is displayed. + // Otherwise, the UX could be stuck in a state where there is no way to switch to + // the guest user (e.g. Guest would not be selectable, and it would be stuck + // saying "Resetting guest...") + mHandler.sendEmptyMessage(MESSAGE_UPDATE_LIST); + }); + } + } + @VisibleForTesting void updateUserList() { final Context context = getActivity(); @@ -988,8 +1066,15 @@ public class UserSettings extends SettingsPreferenceFragment && mUserCaps.mUserSwitcherEnabled) { mAddGuest.setVisible(true); mAddGuest.setIcon(getEncircledDefaultIcon()); - mAddGuest.setEnabled(canSwitchUserNow()); mAddGuest.setSelectable(true); + if (mGuestUserAutoCreated && mGuestCreationScheduled.get()) { + mAddGuest.setTitle(com.android.settingslib.R.string.user_guest); + mAddGuest.setSummary(R.string.guest_resetting); + mAddGuest.setEnabled(false); + } else { + mAddGuest.setTitle(com.android.settingslib.R.string.guest_new_guest); + mAddGuest.setEnabled(canSwitchUserNow()); + } } else { mAddGuest.setVisible(false); } @@ -1077,7 +1162,11 @@ public class UserSettings extends SettingsPreferenceFragment public boolean onPreferenceClick(Preference pref) { if (pref == mMePreference) { if (isCurrentUserGuest()) { - showDialog(DIALOG_CONFIRM_EXIT_GUEST); + if (mGuestUserAutoCreated) { + showDialog(DIALOG_CONFIRM_RESET_GUEST); + } else { + showDialog(DIALOG_CONFIRM_EXIT_GUEST); + } } else { showDialog(DIALOG_USER_PROFILE_EDITOR); }