1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.settings.users; 17 18 import android.app.Dialog; 19 import android.app.settings.SettingsEnums; 20 import android.content.Context; 21 import android.content.DialogInterface; 22 import android.content.pm.UserInfo; 23 import android.os.Bundle; 24 import android.os.Handler; 25 import android.os.UserHandle; 26 import android.os.UserManager; 27 import android.provider.Settings; 28 import android.util.Log; 29 30 import androidx.appcompat.app.AlertDialog; 31 import androidx.fragment.app.Fragment; 32 import androidx.preference.Preference; 33 34 import com.android.settings.R; 35 import com.android.settings.core.BasePreferenceController; 36 import com.android.settings.core.instrumentation.InstrumentedDialogFragment; 37 import com.android.settingslib.RestrictedLockUtils; 38 import com.android.settingslib.RestrictedLockUtilsInternal; 39 import com.android.settingslib.RestrictedSwitchPreference; 40 41 /** 42 * Controller to control the preference toggle for "remove guest on exit" 43 * 44 * Note, class is not 'final' since we need to mock it for unit tests 45 */ 46 public class RemoveGuestOnExitPreferenceController extends BasePreferenceController 47 implements Preference.OnPreferenceChangeListener { 48 49 private static final String TAG = RemoveGuestOnExitPreferenceController.class.getSimpleName(); 50 private static final String TAG_CONFIRM_GUEST_REMOVE = "confirmGuestRemove"; 51 private static final int REMOVE_GUEST_ON_EXIT_DEFAULT = 1; 52 53 private final UserCapabilities mUserCaps; 54 private final UserManager mUserManager; 55 private final Fragment mParentFragment; 56 private final Handler mHandler; 57 RemoveGuestOnExitPreferenceController(Context context, String key, Fragment parent, Handler handler)58 public RemoveGuestOnExitPreferenceController(Context context, String key, 59 Fragment parent, Handler handler) { 60 super(context, key); 61 mUserCaps = UserCapabilities.create(context); 62 mUserManager = context.getSystemService(UserManager.class); 63 mParentFragment = parent; 64 mHandler = handler; 65 } 66 67 @Override updateState(Preference preference)68 public void updateState(Preference preference) { 69 mUserCaps.updateAddUserCapabilities(mContext); 70 final RestrictedSwitchPreference restrictedSwitchPreference = 71 (RestrictedSwitchPreference) preference; 72 restrictedSwitchPreference.setChecked(isChecked()); 73 if (!isAvailable()) { 74 restrictedSwitchPreference.setVisible(false); 75 } else { 76 if (android.multiuser.Flags.newMultiuserSettingsUx()) { 77 restrictedSwitchPreference.setVisible(true); 78 final RestrictedLockUtils.EnforcedAdmin disallowRemoveUserAdmin = 79 RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext, 80 UserManager.DISALLOW_REMOVE_USER, UserHandle.myUserId()); 81 if (disallowRemoveUserAdmin != null) { 82 restrictedSwitchPreference.setDisabledByAdmin(disallowRemoveUserAdmin); 83 } else if (mUserCaps.mDisallowAddUserSetByAdmin) { 84 restrictedSwitchPreference.setDisabledByAdmin(mUserCaps.mEnforcedAdmin); 85 } else if (mUserCaps.mDisallowAddUser) { 86 // Adding user is restricted by system 87 restrictedSwitchPreference.setVisible(false); 88 } 89 } else { 90 restrictedSwitchPreference.setDisabledByAdmin( 91 mUserCaps.disallowAddUser() ? mUserCaps.getEnforcedAdmin() : null); 92 restrictedSwitchPreference.setVisible(mUserCaps.mUserSwitcherEnabled); 93 } 94 } 95 } 96 97 @Override getAvailabilityStatus()98 public int getAvailabilityStatus() { 99 // if guest is forced to be ephemeral via config_guestUserEphemeral 100 // then disable this controller 101 // also disable this controller for non-admin users 102 // also disable when config_guestUserAllowEphemeralStateChange is false 103 if (android.multiuser.Flags.newMultiuserSettingsUx()) { 104 if (mUserManager.isGuestUserAlwaysEphemeral() 105 || !UserManager.isGuestUserAllowEphemeralStateChange() 106 || !mUserCaps.isAdmin()) { 107 return DISABLED_FOR_USER; 108 } else { 109 return AVAILABLE; 110 } 111 } else { 112 if (mUserManager.isGuestUserAlwaysEphemeral() 113 || !UserManager.isGuestUserAllowEphemeralStateChange() 114 || !mUserCaps.isAdmin() 115 || mUserCaps.disallowAddUser() 116 || mUserCaps.disallowAddUserSetByAdmin()) { 117 return DISABLED_FOR_USER; 118 } else { 119 return mUserCaps.mUserSwitcherEnabled ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; 120 } 121 } 122 } 123 isChecked()124 private boolean isChecked() { 125 return Settings.Global.getInt(mContext.getContentResolver(), 126 Settings.Global.REMOVE_GUEST_ON_EXIT, REMOVE_GUEST_ON_EXIT_DEFAULT) != 0; 127 } 128 setChecked(Context context, boolean isChecked)129 private static boolean setChecked(Context context, boolean isChecked) { 130 Settings.Global.putInt(context.getContentResolver(), 131 Settings.Global.REMOVE_GUEST_ON_EXIT, isChecked ? 1 : 0); 132 return true; 133 } 134 135 @Override onPreferenceChange(Preference preference, Object newValue)136 public boolean onPreferenceChange(Preference preference, Object newValue) { 137 boolean enable = (boolean) newValue; 138 UserInfo guestInfo = mUserManager.findCurrentGuestUser(); 139 140 // no guest do the setting and return 141 // guest ephemeral state will take effect on guest create 142 if (guestInfo == null) { 143 return setChecked(mContext, enable); 144 } 145 // if guest has never been initialized or started 146 // we can change guest ephemeral state 147 if (!guestInfo.isInitialized()) { 148 boolean isSuccess = mUserManager.setUserEphemeral(guestInfo.id, enable); 149 if (isSuccess) { 150 return setChecked(mContext, enable); 151 } else { 152 Log.w(TAG, "Unused guest, id=" + guestInfo.id 153 + ". Mark ephemeral as " + enable + " failed !!!"); 154 return false; 155 } 156 } 157 // if guest has been used before and is not ephemeral 158 // but now we are making reset guest on exit preference as enabled 159 // then show confirmation dialog box and remove this guest if confirmed by user 160 if (guestInfo.isInitialized() && !guestInfo.isEphemeral() && enable) { 161 ConfirmGuestRemoveFragment.show(mParentFragment, 162 mHandler, 163 enable, 164 guestInfo.id, 165 (RestrictedSwitchPreference) preference); 166 return false; 167 } 168 // all other cases, there should be none, don't change state 169 return false; 170 } 171 172 173 /** 174 * Dialog to confirm guest removal on toggle clicked set to true 175 * 176 * Fragment must be a public static class to be properly recreated from instance state 177 * else we will get "AndroidRuntime: java.lang.IllegalStateException" 178 */ 179 public static final class ConfirmGuestRemoveFragment extends InstrumentedDialogFragment 180 implements DialogInterface.OnClickListener { 181 182 private static final String TAG = ConfirmGuestRemoveFragment.class.getSimpleName(); 183 private static final String SAVE_ENABLING = "enabling"; 184 private static final String SAVE_GUEST_USER_ID = "guestUserId"; 185 186 private boolean mEnabling; 187 private int mGuestUserId; 188 private RestrictedSwitchPreference mPreference; 189 private Handler mHandler; 190 show(Fragment parent, Handler handler, boolean enabling, int guestUserId, RestrictedSwitchPreference preference)191 private static void show(Fragment parent, 192 Handler handler, 193 boolean enabling, int guestUserId, 194 RestrictedSwitchPreference preference) { 195 if (!parent.isAdded()) return; 196 197 final ConfirmGuestRemoveFragment dialog = new ConfirmGuestRemoveFragment(); 198 dialog.mHandler = handler; 199 dialog.mEnabling = enabling; 200 dialog.mGuestUserId = guestUserId; 201 dialog.setTargetFragment(parent, 0); 202 dialog.mPreference = preference; 203 dialog.show(parent.getFragmentManager(), TAG_CONFIRM_GUEST_REMOVE); 204 } 205 206 @Override onCreateDialog(Bundle savedInstanceState)207 public Dialog onCreateDialog(Bundle savedInstanceState) { 208 final Context context = getActivity(); 209 if (savedInstanceState != null) { 210 mEnabling = savedInstanceState.getBoolean(SAVE_ENABLING); 211 mGuestUserId = savedInstanceState.getInt(SAVE_GUEST_USER_ID); 212 } 213 214 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 215 builder.setTitle(R.string.remove_guest_on_exit_dialog_title); 216 builder.setMessage(R.string.remove_guest_on_exit_dialog_message); 217 builder.setPositiveButton( 218 com.android.settingslib.R.string.guest_exit_clear_data_button, this); 219 builder.setNegativeButton(android.R.string.cancel, null); 220 221 return builder.create(); 222 } 223 224 @Override onSaveInstanceState(Bundle outState)225 public void onSaveInstanceState(Bundle outState) { 226 super.onSaveInstanceState(outState); 227 outState.putBoolean(SAVE_ENABLING, mEnabling); 228 outState.putInt(SAVE_GUEST_USER_ID, mGuestUserId); 229 } 230 231 @Override getMetricsCategory()232 public int getMetricsCategory() { 233 return SettingsEnums.DIALOG_USER_REMOVE; 234 } 235 236 @Override onClick(DialogInterface dialog, int which)237 public void onClick(DialogInterface dialog, int which) { 238 if (which != DialogInterface.BUTTON_POSITIVE) { 239 return; 240 } 241 UserManager userManager = getContext().getSystemService(UserManager.class); 242 if (userManager == null) { 243 Log.e(TAG, "Unable to get user manager service"); 244 return; 245 } 246 UserInfo guestUserInfo = userManager.getUserInfo(mGuestUserId); 247 // only do action for guests and when enabling the preference 248 if (guestUserInfo == null || !guestUserInfo.isGuest() || !mEnabling) { 249 Log.w(TAG, "Removing guest user ... failed, id=" + mGuestUserId); 250 return; 251 } 252 if (mPreference != null) { 253 // Using markGuestForDeletion allows us to create a new guest before this one is 254 // fully removed. 255 boolean isSuccess = userManager.markGuestForDeletion(guestUserInfo.id); 256 if (!isSuccess) { 257 Log.w(TAG, "Couldn't mark the guest for deletion for user " 258 + guestUserInfo.id); 259 return; 260 } 261 userManager.removeUser(guestUserInfo.id); 262 if (setChecked(getContext(), mEnabling)) { 263 mPreference.setChecked(mEnabling); 264 mHandler.sendEmptyMessage( 265 UserSettings 266 .MESSAGE_REMOVE_GUEST_ON_EXIT_CONTROLLER_GUEST_REMOVED); 267 } 268 } 269 } 270 } 271 } 272