Merge "Make use of config for auto-created guest users" into sc-dev am: f11fdb3e50

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Settings/+/14988090

Change-Id: Ic7fe5344e6a75d60eab7767b2f89cadc7ec1d2b2
diff --git a/src/com/android/settings/users/UserDetailsSettings.java b/src/com/android/settings/users/UserDetailsSettings.java
index ac6a8ea..ce186f2 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.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 @@
     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 @@
         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 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 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 @@
                                 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 @@
             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 9aab5b0..6915e95 100644
--- a/src/com/android/settings/users/UserDialogs.java
+++ b/src/com/android/settings/users/UserDialogs.java
@@ -154,4 +154,24 @@
                         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 e95fed7..ee23fc3 100644
--- a/src/com/android/settings/users/UserSettings.java
+++ b/src/com/android/settings/users/UserSettings.java
@@ -87,6 +87,9 @@
 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 @@
     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 @@
     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 @@
     SparseArray<Bitmap> 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 @@
     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 @@
             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 @@
     @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 @@
         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 @@
             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 @@
         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 @@
                 }
                 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 @@
             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 @@
         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 @@
                 && 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 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);
             }