• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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