• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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 
17 package com.android.settings.users;
18 
19 import static android.os.UserHandle.USER_NULL;
20 
21 import android.app.ActivityManager;
22 import android.app.Dialog;
23 import android.app.settings.SettingsEnums;
24 import android.content.Context;
25 import android.content.pm.UserInfo;
26 import android.os.Bundle;
27 import android.os.RemoteException;
28 import android.os.UserHandle;
29 import android.os.UserManager;
30 import android.util.Log;
31 
32 import androidx.annotation.VisibleForTesting;
33 import androidx.preference.Preference;
34 import androidx.preference.SwitchPreference;
35 
36 import com.android.settings.R;
37 import com.android.settings.SettingsPreferenceFragment;
38 import com.android.settings.Utils;
39 import com.android.settings.core.SubSettingLauncher;
40 import com.android.settingslib.RestrictedLockUtils;
41 import com.android.settingslib.RestrictedLockUtilsInternal;
42 import com.android.settingslib.RestrictedPreference;
43 
44 import java.util.List;
45 import java.util.concurrent.ExecutorService;
46 import java.util.concurrent.Executors;
47 import java.util.concurrent.atomic.AtomicBoolean;
48 
49 /**
50  * Settings screen for configuring, deleting or switching to a specific user.
51  * It is shown when you tap on a user in the user management (UserSettings) screen.
52  *
53  * Arguments to this fragment must include the userId of the user (in EXTRA_USER_ID) for whom
54  * to display controls.
55  */
56 public class UserDetailsSettings extends SettingsPreferenceFragment
57         implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener {
58 
59     private static final String TAG = UserDetailsSettings.class.getSimpleName();
60 
61     private static final String KEY_SWITCH_USER = "switch_user";
62     private static final String KEY_ENABLE_TELEPHONY = "enable_calling";
63     private static final String KEY_REMOVE_USER = "remove_user";
64     private static final String KEY_APP_AND_CONTENT_ACCESS = "app_and_content_access";
65 
66     /** Integer extra containing the userId to manage */
67     static final String EXTRA_USER_ID = "user_id";
68 
69     private static final int DIALOG_CONFIRM_REMOVE = 1;
70     private static final int DIALOG_CONFIRM_ENABLE_CALLING = 2;
71     private static final int DIALOG_CONFIRM_ENABLE_CALLING_AND_SMS = 3;
72     private static final int DIALOG_SETUP_USER = 4;
73     private static final int DIALOG_CONFIRM_RESET_GUEST = 5;
74 
75     private UserManager mUserManager;
76     private UserCapabilities mUserCaps;
77     private boolean mGuestUserAutoCreated;
78     private final AtomicBoolean mGuestCreationScheduled = new AtomicBoolean();
79     private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
80 
81     @VisibleForTesting
82     RestrictedPreference mSwitchUserPref;
83     private SwitchPreference mPhonePref;
84     @VisibleForTesting
85     Preference mAppAndContentAccessPref;
86     @VisibleForTesting
87     Preference mRemoveUserPref;
88 
89     @VisibleForTesting
90     UserInfo mUserInfo;
91     private Bundle mDefaultGuestRestrictions;
92 
93     @Override
getMetricsCategory()94     public int getMetricsCategory() {
95         return SettingsEnums.USER_DETAILS;
96     }
97 
98     @Override
onCreate(Bundle icicle)99     public void onCreate(Bundle icicle) {
100         super.onCreate(icicle);
101 
102         final Context context = getActivity();
103         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
104         mUserCaps = UserCapabilities.create(context);
105         addPreferencesFromResource(R.xml.user_details_settings);
106 
107         mGuestUserAutoCreated = getPrefContext().getResources().getBoolean(
108                 com.android.internal.R.bool.config_guestUserAutoCreated);
109 
110         initialize(context, getArguments());
111     }
112 
113     @Override
onResume()114     public void onResume() {
115         super.onResume();
116         mSwitchUserPref.setEnabled(canSwitchUserNow());
117         if (mGuestUserAutoCreated) {
118             mRemoveUserPref.setEnabled((mUserInfo.flags & UserInfo.FLAG_INITIALIZED) != 0);
119         }
120     }
121 
122     @Override
onPreferenceClick(Preference preference)123     public boolean onPreferenceClick(Preference preference) {
124         if (preference == mRemoveUserPref) {
125             if (canDeleteUser()) {
126                 if (mUserInfo.isGuest()) {
127                     showDialog(DIALOG_CONFIRM_RESET_GUEST);
128                 } else {
129                     showDialog(DIALOG_CONFIRM_REMOVE);
130                 }
131                 return true;
132             }
133         } else if (preference == mSwitchUserPref) {
134             if (canSwitchUserNow()) {
135                 if (shouldShowSetupPromptDialog()) {
136                     showDialog(DIALOG_SETUP_USER);
137                 } else {
138                     switchUser();
139                 }
140                 return true;
141             }
142         } else if (preference == mAppAndContentAccessPref) {
143             openAppAndContentAccessScreen(false);
144             return true;
145         }
146         return false;
147     }
148 
149     @Override
onPreferenceChange(Preference preference, Object newValue)150     public boolean onPreferenceChange(Preference preference, Object newValue) {
151         if (Boolean.TRUE.equals(newValue)) {
152             showDialog(mUserInfo.isGuest() ? DIALOG_CONFIRM_ENABLE_CALLING
153                     : DIALOG_CONFIRM_ENABLE_CALLING_AND_SMS);
154             return false;
155         }
156         enableCallsAndSms(false);
157         return true;
158     }
159 
160     @Override
getDialogMetricsCategory(int dialogId)161     public int getDialogMetricsCategory(int dialogId) {
162         switch (dialogId) {
163             case DIALOG_CONFIRM_REMOVE:
164             case DIALOG_CONFIRM_RESET_GUEST:
165                 return SettingsEnums.DIALOG_USER_REMOVE;
166             case DIALOG_CONFIRM_ENABLE_CALLING:
167                 return SettingsEnums.DIALOG_USER_ENABLE_CALLING;
168             case DIALOG_CONFIRM_ENABLE_CALLING_AND_SMS:
169                 return SettingsEnums.DIALOG_USER_ENABLE_CALLING_AND_SMS;
170             case DIALOG_SETUP_USER:
171                 return SettingsEnums.DIALOG_USER_SETUP;
172             default:
173                 return 0;
174         }
175     }
176 
177     @Override
onCreateDialog(int dialogId)178     public Dialog onCreateDialog(int dialogId) {
179         Context context = getActivity();
180         if (context == null) {
181             return null;
182         }
183         switch (dialogId) {
184             case DIALOG_CONFIRM_REMOVE:
185                 return UserDialogs.createRemoveDialog(getActivity(), mUserInfo.id,
186                         (dialog, which) -> removeUser());
187             case DIALOG_CONFIRM_ENABLE_CALLING:
188                 return UserDialogs.createEnablePhoneCallsDialog(getActivity(),
189                         (dialog, which) -> enableCallsAndSms(true));
190             case DIALOG_CONFIRM_ENABLE_CALLING_AND_SMS:
191                 return UserDialogs.createEnablePhoneCallsAndSmsDialog(getActivity(),
192                         (dialog, which) -> enableCallsAndSms(true));
193             case DIALOG_SETUP_USER:
194                 return UserDialogs.createSetupUserDialog(getActivity(),
195                         (dialog, which) -> {
196                             if (canSwitchUserNow()) {
197                                 switchUser();
198                             }
199                         });
200             case DIALOG_CONFIRM_RESET_GUEST:
201                 return UserDialogs.createResetGuestDialog(getActivity(),
202                         (dialog, which) -> resetGuest());
203         }
204         throw new IllegalArgumentException("Unsupported dialogId " + dialogId);
205     }
206 
207     /**
208      * Erase the current guest user and create a new one in the background. UserSettings will
209      * handle guest creation after receiving the {@link UserSettings.RESULT_GUEST_REMOVED} result.
210      */
211     private void resetGuest() {
212         // Just to be safe, check that the selected user is a guest
213         if (!mUserInfo.isGuest()) {
214             return;
215         }
216         mMetricsFeatureProvider.action(getActivity(),
217                 SettingsEnums.ACTION_USER_GUEST_EXIT_CONFIRMED);
218 
219         mUserManager.removeUser(mUserInfo.id);
220         setResult(UserSettings.RESULT_GUEST_REMOVED);
221         finishFragment();
222     }
223 
224     @VisibleForTesting
225     @Override
226     protected void showDialog(int dialogId) {
227         super.showDialog(dialogId);
228     }
229 
230     @VisibleForTesting
231     void initialize(Context context, Bundle arguments) {
232         int userId = arguments != null ? arguments.getInt(EXTRA_USER_ID, USER_NULL) : USER_NULL;
233         if (userId == USER_NULL) {
234             throw new IllegalStateException("Arguments to this fragment must contain the user id");
235         }
236         boolean isNewUser =
237                 arguments.getBoolean(AppRestrictionsFragment.EXTRA_NEW_USER, false);
238         mUserInfo = mUserManager.getUserInfo(userId);
239 
240         mSwitchUserPref = findPreference(KEY_SWITCH_USER);
241         mPhonePref = findPreference(KEY_ENABLE_TELEPHONY);
242         mRemoveUserPref = findPreference(KEY_REMOVE_USER);
243         mAppAndContentAccessPref = findPreference(KEY_APP_AND_CONTENT_ACCESS);
244 
245         mSwitchUserPref.setTitle(
246                 context.getString(com.android.settingslib.R.string.user_switch_to_user,
247                         UserSettings.getUserName(context, mUserInfo)));
248 
249         if (mUserCaps.mDisallowSwitchUser) {
250             mSwitchUserPref.setDisabledByAdmin(RestrictedLockUtilsInternal.getDeviceOwner(context));
251         } else {
252             mSwitchUserPref.setDisabledByAdmin(null);
253             mSwitchUserPref.setSelectable(true);
254             mSwitchUserPref.setOnPreferenceClickListener(this);
255         }
256 
257         if (!mUserManager.isAdminUser()) { // non admin users can't remove users and allow calls
258             removePreference(KEY_ENABLE_TELEPHONY);
259             removePreference(KEY_REMOVE_USER);
260             removePreference(KEY_APP_AND_CONTENT_ACCESS);
261         } else {
262             if (!Utils.isVoiceCapable(context)) { // no telephony
263                 removePreference(KEY_ENABLE_TELEPHONY);
264             }
265 
266             if (mUserInfo.isRestricted()) {
267                 removePreference(KEY_ENABLE_TELEPHONY);
268                 if (isNewUser) {
269                     // for newly created restricted users we should open the apps and content access
270                     // screen to initialize the default restrictions
271                     openAppAndContentAccessScreen(true);
272                 }
273             } else {
274                 removePreference(KEY_APP_AND_CONTENT_ACCESS);
275             }
276 
277             if (mUserInfo.isGuest()) {
278                 // These are not for an existing user, just general Guest settings.
279                 // Default title is for calling and SMS. Change to calling-only here
280                 // TODO(b/191483069): These settings can't be changed unless guest user exists
281                 mPhonePref.setTitle(R.string.user_enable_calling);
282                 mDefaultGuestRestrictions = mUserManager.getDefaultGuestRestrictions();
283                 mPhonePref.setChecked(
284                         !mDefaultGuestRestrictions.getBoolean(UserManager.DISALLOW_OUTGOING_CALLS));
285                 mRemoveUserPref.setTitle(mGuestUserAutoCreated
286                         ? com.android.settingslib.R.string.guest_reset_guest
287                         : R.string.user_exit_guest_title);
288                 if (mGuestUserAutoCreated) {
289                     mRemoveUserPref.setEnabled((mUserInfo.flags & UserInfo.FLAG_INITIALIZED) != 0);
290                 }
291             } else {
292                 mPhonePref.setChecked(!mUserManager.hasUserRestriction(
293                         UserManager.DISALLOW_OUTGOING_CALLS, new UserHandle(userId)));
294                 mRemoveUserPref.setTitle(R.string.user_remove_user);
295             }
296             if (RestrictedLockUtilsInternal.hasBaseUserRestriction(context,
297                     UserManager.DISALLOW_REMOVE_USER, UserHandle.myUserId())) {
298                 removePreference(KEY_REMOVE_USER);
299             }
300 
301             mRemoveUserPref.setOnPreferenceClickListener(this);
302             mPhonePref.setOnPreferenceChangeListener(this);
303             mAppAndContentAccessPref.setOnPreferenceClickListener(this);
304         }
305     }
306 
307     @VisibleForTesting
308     boolean canDeleteUser() {
309         if (!mUserManager.isAdminUser()) {
310             return false;
311         }
312 
313         Context context = getActivity();
314         if (context == null) {
315             return false;
316         }
317 
318         final RestrictedLockUtils.EnforcedAdmin removeDisallowedAdmin =
319                 RestrictedLockUtilsInternal.checkIfRestrictionEnforced(context,
320                         UserManager.DISALLOW_REMOVE_USER, UserHandle.myUserId());
321         if (removeDisallowedAdmin != null) {
322             RestrictedLockUtils.sendShowAdminSupportDetailsIntent(context,
323                     removeDisallowedAdmin);
324             return false;
325         }
326         return true;
327     }
328 
329     @VisibleForTesting
330     boolean canSwitchUserNow() {
331         return mUserManager.getUserSwitchability() == UserManager.SWITCHABILITY_STATUS_OK;
332     }
333 
334     @VisibleForTesting
335     void switchUser() {
336         try {
337             if (mUserInfo.isGuest()) {
338                 mMetricsFeatureProvider.action(getActivity(), SettingsEnums.ACTION_SWITCH_TO_GUEST);
339             }
340             ActivityManager.getService().switchUser(mUserInfo.id);
341         } catch (RemoteException re) {
342             Log.e(TAG, "Error while switching to other user.");
343         } finally {
344             finishFragment();
345         }
346     }
347 
348     private void enableCallsAndSms(boolean enabled) {
349         mPhonePref.setChecked(enabled);
350         if (mUserInfo.isGuest()) {
351             mDefaultGuestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, !enabled);
352             // SMS is always disabled for guest
353             mDefaultGuestRestrictions.putBoolean(UserManager.DISALLOW_SMS, true);
354             mUserManager.setDefaultGuestRestrictions(mDefaultGuestRestrictions);
355 
356             // Update the guest's restrictions, if there is a guest
357             // TODO: Maybe setDefaultGuestRestrictions() can internally just set the restrictions
358             // on any existing guest rather than do it here with multiple Binder calls.
359             List<UserInfo> users = mUserManager.getAliveUsers();
360             for (UserInfo user : users) {
361                 if (user.isGuest()) {
362                     UserHandle userHandle = UserHandle.of(user.id);
363                     for (String key : mDefaultGuestRestrictions.keySet()) {
364                         mUserManager.setUserRestriction(
365                                 key, mDefaultGuestRestrictions.getBoolean(key), userHandle);
366                     }
367                 }
368             }
369         } else {
370             UserHandle userHandle = UserHandle.of(mUserInfo.id);
371             mUserManager.setUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, !enabled,
372                     userHandle);
373             mUserManager.setUserRestriction(UserManager.DISALLOW_SMS, !enabled, userHandle);
374         }
375     }
376 
377     private void removeUser() {
378         mUserManager.removeUser(mUserInfo.id);
379         finishFragment();
380     }
381 
382     /**
383      * @param isNewUser indicates if a user was created recently, for new users
384      *                  AppRestrictionsFragment should set the default restrictions
385      */
386     private void openAppAndContentAccessScreen(boolean isNewUser) {
387         Bundle extras = new Bundle();
388         extras.putInt(AppRestrictionsFragment.EXTRA_USER_ID, mUserInfo.id);
389         extras.putBoolean(AppRestrictionsFragment.EXTRA_NEW_USER, isNewUser);
390         new SubSettingLauncher(getContext())
391                 .setDestination(AppRestrictionsFragment.class.getName())
392                 .setArguments(extras)
393                 .setTitleRes(R.string.user_restrictions_title)
394                 .setSourceMetricsCategory(getMetricsCategory())
395                 .launch();
396     }
397 
398     private boolean isSecondaryUser(UserInfo user) {
399         return UserManager.USER_TYPE_FULL_SECONDARY.equals(user.userType);
400     }
401 
402     private boolean shouldShowSetupPromptDialog() {
403         // TODO: FLAG_INITIALIZED is set when a user is switched to for the first time,
404         //  but what we would really need here is a flag that shows if the setup process was
405         //  completed. After the user cancels the setup process, mUserInfo.isInitialized() will
406         //  return true so there will be no setup prompt dialog shown to the user anymore.
407         return isSecondaryUser(mUserInfo) && !mUserInfo.isInitialized();
408     }
409 }
410