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.UserManager; 26 import android.provider.Settings; 27 import android.util.Log; 28 29 import androidx.appcompat.app.AlertDialog; 30 import androidx.fragment.app.Fragment; 31 import androidx.preference.Preference; 32 33 import com.android.settings.R; 34 import com.android.settings.core.BasePreferenceController; 35 import com.android.settings.core.instrumentation.InstrumentedDialogFragment; 36 import com.android.settingslib.RestrictedSwitchPreference; 37 38 /** 39 * Controller to control the preference toggle for "remove guest on exit" 40 * 41 * Note, class is not 'final' since we need to mock it for unit tests 42 */ 43 public class RemoveGuestOnExitPreferenceController extends BasePreferenceController 44 implements Preference.OnPreferenceChangeListener { 45 46 private static final String TAG = RemoveGuestOnExitPreferenceController.class.getSimpleName(); 47 private static final String TAG_CONFIRM_GUEST_REMOVE = "confirmGuestRemove"; 48 private static final int REMOVE_GUEST_ON_EXIT_DEFAULT = 1; 49 50 private final UserCapabilities mUserCaps; 51 private final UserManager mUserManager; 52 private final Fragment mParentFragment; 53 private final Handler mHandler; 54 RemoveGuestOnExitPreferenceController(Context context, String key, Fragment parent, Handler handler)55 public RemoveGuestOnExitPreferenceController(Context context, String key, 56 Fragment parent, Handler handler) { 57 super(context, key); 58 mUserCaps = UserCapabilities.create(context); 59 mUserManager = context.getSystemService(UserManager.class); 60 mParentFragment = parent; 61 mHandler = handler; 62 } 63 64 @Override updateState(Preference preference)65 public void updateState(Preference preference) { 66 mUserCaps.updateAddUserCapabilities(mContext); 67 final RestrictedSwitchPreference restrictedSwitchPreference = 68 (RestrictedSwitchPreference) preference; 69 restrictedSwitchPreference.setChecked(isChecked()); 70 if (!isAvailable()) { 71 restrictedSwitchPreference.setVisible(false); 72 } else { 73 restrictedSwitchPreference.setDisabledByAdmin( 74 mUserCaps.disallowAddUser() ? mUserCaps.getEnforcedAdmin() : null); 75 restrictedSwitchPreference.setVisible(mUserCaps.mUserSwitcherEnabled); 76 } 77 } 78 79 @Override getAvailabilityStatus()80 public int getAvailabilityStatus() { 81 // if guest is forced to be ephemeral via config_guestUserEphemeral 82 // then disable this controller 83 // also disable this controller for non-admin users 84 // also disable when config_guestUserAllowEphemeralStateChange is false 85 if (mUserManager.isGuestUserAlwaysEphemeral() 86 || !UserManager.isGuestUserAllowEphemeralStateChange() 87 || !mUserCaps.isAdmin() 88 || mUserCaps.disallowAddUser() 89 || mUserCaps.disallowAddUserSetByAdmin()) { 90 return DISABLED_FOR_USER; 91 } else { 92 return mUserCaps.mUserSwitcherEnabled ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; 93 } 94 } 95 isChecked()96 private boolean isChecked() { 97 return Settings.Global.getInt(mContext.getContentResolver(), 98 Settings.Global.REMOVE_GUEST_ON_EXIT, REMOVE_GUEST_ON_EXIT_DEFAULT) != 0; 99 } 100 setChecked(Context context, boolean isChecked)101 private static boolean setChecked(Context context, boolean isChecked) { 102 Settings.Global.putInt(context.getContentResolver(), 103 Settings.Global.REMOVE_GUEST_ON_EXIT, isChecked ? 1 : 0); 104 return true; 105 } 106 107 @Override onPreferenceChange(Preference preference, Object newValue)108 public boolean onPreferenceChange(Preference preference, Object newValue) { 109 boolean enable = (boolean) newValue; 110 UserInfo guestInfo = mUserManager.findCurrentGuestUser(); 111 112 // no guest do the setting and return 113 // guest ephemeral state will take effect on guest create 114 if (guestInfo == null) { 115 return setChecked(mContext, enable); 116 } 117 // if guest has never been initialized or started 118 // we can change guest ephemeral state 119 if (!guestInfo.isInitialized()) { 120 boolean isSuccess = mUserManager.setUserEphemeral(guestInfo.id, enable); 121 if (isSuccess) { 122 return setChecked(mContext, enable); 123 } else { 124 Log.w(TAG, "Unused guest, id=" + guestInfo.id 125 + ". Mark ephemeral as " + enable + " failed !!!"); 126 return false; 127 } 128 } 129 // if guest has been used before and is not ephemeral 130 // but now we are making reset guest on exit preference as enabled 131 // then show confirmation dialog box and remove this guest if confirmed by user 132 if (guestInfo.isInitialized() && !guestInfo.isEphemeral() && enable) { 133 ConfirmGuestRemoveFragment.show(mParentFragment, 134 mHandler, 135 enable, 136 guestInfo.id, 137 (RestrictedSwitchPreference) preference); 138 return false; 139 } 140 // all other cases, there should be none, don't change state 141 return false; 142 } 143 144 145 /** 146 * Dialog to confirm guest removal on toggle clicked set to true 147 * 148 * Fragment must be a public static class to be properly recreated from instance state 149 * else we will get "AndroidRuntime: java.lang.IllegalStateException" 150 */ 151 public static final class ConfirmGuestRemoveFragment extends InstrumentedDialogFragment 152 implements DialogInterface.OnClickListener { 153 154 private static final String TAG = ConfirmGuestRemoveFragment.class.getSimpleName(); 155 private static final String SAVE_ENABLING = "enabling"; 156 private static final String SAVE_GUEST_USER_ID = "guestUserId"; 157 158 private boolean mEnabling; 159 private int mGuestUserId; 160 private RestrictedSwitchPreference mPreference; 161 private Handler mHandler; 162 show(Fragment parent, Handler handler, boolean enabling, int guestUserId, RestrictedSwitchPreference preference)163 private static void show(Fragment parent, 164 Handler handler, 165 boolean enabling, int guestUserId, 166 RestrictedSwitchPreference preference) { 167 if (!parent.isAdded()) return; 168 169 final ConfirmGuestRemoveFragment dialog = new ConfirmGuestRemoveFragment(); 170 dialog.mHandler = handler; 171 dialog.mEnabling = enabling; 172 dialog.mGuestUserId = guestUserId; 173 dialog.setTargetFragment(parent, 0); 174 dialog.mPreference = preference; 175 dialog.show(parent.getFragmentManager(), TAG_CONFIRM_GUEST_REMOVE); 176 } 177 178 @Override onCreateDialog(Bundle savedInstanceState)179 public Dialog onCreateDialog(Bundle savedInstanceState) { 180 final Context context = getActivity(); 181 if (savedInstanceState != null) { 182 mEnabling = savedInstanceState.getBoolean(SAVE_ENABLING); 183 mGuestUserId = savedInstanceState.getInt(SAVE_GUEST_USER_ID); 184 } 185 186 final AlertDialog.Builder builder = new AlertDialog.Builder(context); 187 builder.setTitle(R.string.remove_guest_on_exit_dialog_title); 188 builder.setMessage(R.string.remove_guest_on_exit_dialog_message); 189 builder.setPositiveButton( 190 com.android.settingslib.R.string.guest_exit_clear_data_button, this); 191 builder.setNegativeButton(android.R.string.cancel, null); 192 193 return builder.create(); 194 } 195 196 @Override onSaveInstanceState(Bundle outState)197 public void onSaveInstanceState(Bundle outState) { 198 super.onSaveInstanceState(outState); 199 outState.putBoolean(SAVE_ENABLING, mEnabling); 200 outState.putInt(SAVE_GUEST_USER_ID, mGuestUserId); 201 } 202 203 @Override getMetricsCategory()204 public int getMetricsCategory() { 205 return SettingsEnums.DIALOG_USER_REMOVE; 206 } 207 208 @Override onClick(DialogInterface dialog, int which)209 public void onClick(DialogInterface dialog, int which) { 210 if (which != DialogInterface.BUTTON_POSITIVE) { 211 return; 212 } 213 UserManager userManager = getContext().getSystemService(UserManager.class); 214 if (userManager == null) { 215 Log.e(TAG, "Unable to get user manager service"); 216 return; 217 } 218 UserInfo guestUserInfo = userManager.getUserInfo(mGuestUserId); 219 // only do action for guests and when enabling the preference 220 if (guestUserInfo == null || !guestUserInfo.isGuest() || !mEnabling) { 221 Log.w(TAG, "Removing guest user ... failed, id=" + mGuestUserId); 222 return; 223 } 224 if (mPreference != null) { 225 // Using markGuestForDeletion allows us to create a new guest before this one is 226 // fully removed. 227 boolean isSuccess = userManager.markGuestForDeletion(guestUserInfo.id); 228 if (!isSuccess) { 229 Log.w(TAG, "Couldn't mark the guest for deletion for user " 230 + guestUserInfo.id); 231 return; 232 } 233 userManager.removeUser(guestUserInfo.id); 234 if (setChecked(getContext(), mEnabling)) { 235 mPreference.setChecked(mEnabling); 236 mHandler.sendEmptyMessage( 237 UserSettings 238 .MESSAGE_REMOVE_GUEST_ON_EXIT_CONTROLLER_GUEST_REMOVED); 239 } 240 } 241 } 242 } 243 } 244