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.Trace;
29 import android.os.UserHandle;
30 import android.os.UserManager;
31 import android.util.Log;
32 
33 import androidx.annotation.VisibleForTesting;
34 import androidx.preference.Preference;
35 import androidx.preference.TwoStatePreference;
36 
37 import com.android.settings.R;
38 import com.android.settings.SettingsPreferenceFragment;
39 import com.android.settings.Utils;
40 import com.android.settings.core.SubSettingLauncher;
41 import com.android.settingslib.RestrictedLockUtils;
42 import com.android.settingslib.RestrictedLockUtilsInternal;
43 import com.android.settingslib.RestrictedPreference;
44 import com.android.settingslib.utils.CustomDialogHelper;
45 
46 import java.util.concurrent.ExecutorService;
47 import java.util.concurrent.Executors;
48 import java.util.concurrent.atomic.AtomicBoolean;
49 
50 /**
51  * Settings screen for configuring, deleting or switching to a specific user.
52  * It is shown when you tap on a user in the user management (UserSettings) screen.
53  *
54  * Arguments to this fragment must include the userId of the user (in EXTRA_USER_ID) for whom
55  * to display controls.
56  */
57 public class UserDetailsSettings extends SettingsPreferenceFragment
58         implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener {
59 
60     private static final String TAG = UserDetailsSettings.class.getSimpleName();
61 
62     private static final String KEY_SWITCH_USER = "switch_user";
63     private static final String KEY_ENABLE_TELEPHONY = "enable_calling";
64     private static final String KEY_REMOVE_USER = "remove_user";
65     private static final String KEY_GRANT_ADMIN = "user_grant_admin";
66     private static final String KEY_APP_AND_CONTENT_ACCESS = "app_and_content_access";
67     private static final String KEY_APP_COPYING = "app_copying";
68 
69     /** Integer extra containing the userId to manage */
70     static final String EXTRA_USER_ID = "user_id";
71 
72     private static final int DIALOG_CONFIRM_REMOVE = 1;
73     private static final int DIALOG_CONFIRM_ENABLE_CALLING_AND_SMS = 2;
74     private static final int DIALOG_SETUP_USER = 3;
75     private static final int DIALOG_CONFIRM_RESET_GUEST = 4;
76     private static final int DIALOG_CONFIRM_RESET_GUEST_AND_SWITCH_USER = 5;
77     private static final int DIALOG_CONFIRM_REVOKE_ADMIN = 6;
78     private static final int DIALOG_CONFIRM_GRANT_ADMIN = 7;
79 
80     /** Whether to enable the app_copying fragment. */
81     private static final boolean SHOW_APP_COPYING_PREF = false;
82     private static final int MESSAGE_PADDING = 20;
83 
84     private UserManager mUserManager;
85     private UserCapabilities mUserCaps;
86     private boolean mGuestUserAutoCreated;
87     private final AtomicBoolean mGuestCreationScheduled = new AtomicBoolean();
88     private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
89 
90     @VisibleForTesting
91     RestrictedPreference mSwitchUserPref;
92     private TwoStatePreference mPhonePref;
93     @VisibleForTesting
94     Preference mAppAndContentAccessPref;
95     @VisibleForTesting
96     Preference mAppCopyingPref;
97     @VisibleForTesting
98     Preference mRemoveUserPref;
99     @VisibleForTesting
100     TwoStatePreference mGrantAdminPref;
101 
102     @VisibleForTesting
103     /** The user being studied (not the user doing the studying). */
104     UserInfo mUserInfo;
105 
106     @Override
getMetricsCategory()107     public int getMetricsCategory() {
108         return SettingsEnums.USER_DETAILS;
109     }
110 
111     @Override
onCreate(Bundle icicle)112     public void onCreate(Bundle icicle) {
113         super.onCreate(icicle);
114 
115         final Context context = getActivity();
116         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
117         mUserCaps = UserCapabilities.create(context);
118         addPreferencesFromResource(R.xml.user_details_settings);
119 
120         mGuestUserAutoCreated = getPrefContext().getResources().getBoolean(
121                 com.android.internal.R.bool.config_guestUserAutoCreated);
122 
123         initialize(context, getArguments());
124     }
125 
126     @Override
onResume()127     public void onResume() {
128         super.onResume();
129         mSwitchUserPref.setEnabled(canSwitchUserNow());
130         if (mUserInfo.isGuest() && mGuestUserAutoCreated) {
131             mRemoveUserPref.setEnabled((mUserInfo.flags & UserInfo.FLAG_INITIALIZED) != 0);
132         }
133     }
134 
135     @Override
onPreferenceClick(Preference preference)136     public boolean onPreferenceClick(Preference preference) {
137         if (preference != null && preference.getKey() != null) {
138             mMetricsFeatureProvider.logSettingsTileClick(preference.getKey(), getMetricsCategory());
139         }
140         if (preference == mRemoveUserPref) {
141             mMetricsFeatureProvider.action(getActivity(),
142                     UserMetricsUtils.getRemoveUserMetricCategory(mUserInfo));
143             if (canDeleteUser()) {
144                 if (mUserInfo.isGuest()) {
145                     showDialog(DIALOG_CONFIRM_RESET_GUEST);
146                 } else {
147                     showDialog(DIALOG_CONFIRM_REMOVE);
148                 }
149                 return true;
150             }
151         } else if (preference == mSwitchUserPref) {
152             mMetricsFeatureProvider.action(getActivity(),
153                     UserMetricsUtils.getSwitchUserMetricCategory(mUserInfo));
154             if (canSwitchUserNow()) {
155                 if (shouldShowSetupPromptDialog()) {
156                     showDialog(DIALOG_SETUP_USER);
157                 } else if (mUserCaps.mIsGuest && mUserCaps.mIsEphemeral) {
158                     // if we are switching away from a ephemeral guest then,
159                     // show a dialog that guest user will be reset and then switch
160                     // the user
161                     showDialog(DIALOG_CONFIRM_RESET_GUEST_AND_SWITCH_USER);
162                 } else {
163                     switchUser();
164                 }
165                 return true;
166             }
167         } else if (preference == mAppAndContentAccessPref) {
168             openAppAndContentAccessScreen(false);
169             return true;
170         } else if (preference == mAppCopyingPref) {
171             openAppCopyingScreen();
172             return true;
173         }
174         return false;
175     }
176 
177     @Override
onPreferenceChange(Preference preference, Object newValue)178     public boolean onPreferenceChange(Preference preference, Object newValue) {
179         if (preference == mPhonePref) {
180             if (Boolean.TRUE.equals(newValue)) {
181                 mMetricsFeatureProvider.action(getActivity(),
182                         SettingsEnums.ACTION_ENABLE_USER_CALL);
183                 showDialog(DIALOG_CONFIRM_ENABLE_CALLING_AND_SMS);
184                 return false;
185             }
186             mMetricsFeatureProvider.action(getActivity(),
187                     SettingsEnums.ACTION_DISABLE_USER_CALL);
188             enableCallsAndSms(false);
189         } else if (preference == mGrantAdminPref) {
190             if (Boolean.FALSE.equals(newValue)) {
191                 mMetricsFeatureProvider.action(getActivity(),
192                         SettingsEnums.ACTION_REVOKE_ADMIN_FROM_SETTINGS);
193                 showDialog(DIALOG_CONFIRM_REVOKE_ADMIN);
194             } else {
195                 mMetricsFeatureProvider.action(getActivity(),
196                         SettingsEnums.ACTION_GRANT_ADMIN_FROM_SETTINGS);
197                 showDialog(DIALOG_CONFIRM_GRANT_ADMIN);
198             }
199             return false;
200         }
201         return true;
202     }
203 
204     @Override
getDialogMetricsCategory(int dialogId)205     public int getDialogMetricsCategory(int dialogId) {
206         switch (dialogId) {
207             case DIALOG_CONFIRM_REMOVE:
208             case DIALOG_CONFIRM_RESET_GUEST:
209             case DIALOG_CONFIRM_RESET_GUEST_AND_SWITCH_USER:
210                 return SettingsEnums.DIALOG_USER_REMOVE;
211             case DIALOG_CONFIRM_ENABLE_CALLING_AND_SMS:
212                 return SettingsEnums.DIALOG_USER_ENABLE_CALLING_AND_SMS;
213             case DIALOG_CONFIRM_REVOKE_ADMIN:
214                 return SettingsEnums.DIALOG_REVOKE_USER_ADMIN;
215             case DIALOG_CONFIRM_GRANT_ADMIN:
216                 return SettingsEnums.DIALOG_GRANT_USER_ADMIN;
217             case DIALOG_SETUP_USER:
218                 return SettingsEnums.DIALOG_USER_SETUP;
219             default:
220                 return 0;
221         }
222     }
223 
224     @Override
onCreateDialog(int dialogId)225     public Dialog onCreateDialog(int dialogId) {
226         Context context = getActivity();
227         if (context == null) {
228             return null;
229         }
230         switch (dialogId) {
231             case DIALOG_CONFIRM_REMOVE:
232                 return UserDialogs.createRemoveDialog(getActivity(), mUserInfo.id,
233                         (dialog, which) -> removeUser());
234             case DIALOG_CONFIRM_ENABLE_CALLING_AND_SMS:
235                 return UserDialogs.createEnablePhoneCallsAndSmsDialog(getActivity(),
236                         (dialog, which) -> enableCallsAndSms(true));
237             case DIALOG_SETUP_USER:
238                 return UserDialogs.createSetupUserDialog(getActivity(),
239                         (dialog, which) -> {
240                             if (canSwitchUserNow()) {
241                                 switchUser();
242                             }
243                         });
244             case DIALOG_CONFIRM_RESET_GUEST:
245                 if (mGuestUserAutoCreated) {
246                     return UserDialogs.createResetGuestDialog(getActivity(),
247                         (dialog, which) -> resetGuest());
248                 } else {
249                     return UserDialogs.createRemoveGuestDialog(getActivity(),
250                         (dialog, which) -> resetGuest());
251                 }
252             case DIALOG_CONFIRM_RESET_GUEST_AND_SWITCH_USER:
253                 if (mGuestUserAutoCreated) {
254                     return UserDialogs.createResetGuestDialog(getActivity(),
255                         (dialog, which) -> switchUser());
256                 } else {
257                     return UserDialogs.createRemoveGuestDialog(getActivity(),
258                         (dialog, which) -> switchUser());
259                 }
260             case DIALOG_CONFIRM_REVOKE_ADMIN:
261                 return createRevokeAdminDialog(getContext());
262             case DIALOG_CONFIRM_GRANT_ADMIN:
263                 return createGrantAdminDialog(getContext());
264         }
265         throw new IllegalArgumentException("Unsupported dialogId " + dialogId);
266     }
267 
268     /**
269      * Creates dialog to confirm revoking admin rights.
270      * @return created confirmation dialog
271      */
272     private Dialog createRevokeAdminDialog(Context context) {
273         CustomDialogHelper dialogHelper = new CustomDialogHelper(context);
274         dialogHelper.setIcon(
275                 context.getDrawable(com.android.settingslib.R.drawable.ic_admin_panel_settings));
276         dialogHelper.setTitle(R.string.user_revoke_admin_confirm_title);
277         dialogHelper.setMessage(R.string.user_revoke_admin_confirm_message);
278         dialogHelper.setMessagePadding(MESSAGE_PADDING);
279         dialogHelper.setPositiveButton(R.string.remove, view -> {
280             updateUserAdminStatus(false);
281             dialogHelper.getDialog().dismiss();
282         });
283         dialogHelper.setBackButton(R.string.cancel, view -> {
284             dialogHelper.getDialog().dismiss();
285         });
286         return dialogHelper.getDialog();
287     }
288 
289     /**
290      * Creates dialog to confirm granting admin rights.
291      * @return created confirmation dialog
292      */
293     private Dialog createGrantAdminDialog(Context context) {
294         CustomDialogHelper dialogHelper = new CustomDialogHelper(context);
295         dialogHelper.setIcon(
296                 context.getDrawable(com.android.settingslib.R.drawable.ic_admin_panel_settings));
297         dialogHelper.setTitle(com.android.settingslib.R.string.user_grant_admin_title);
298         dialogHelper.setMessage(com.android.settingslib.R.string.user_grant_admin_message);
299         dialogHelper.setMessagePadding(MESSAGE_PADDING);
300         dialogHelper.setPositiveButton(com.android.settingslib.R.string.user_grant_admin_button,
301                 view -> {
302                     updateUserAdminStatus(true);
303                     dialogHelper.getDialog().dismiss();
304                 });
305         dialogHelper.setBackButton(R.string.cancel, view -> {
306             dialogHelper.getDialog().dismiss();
307         });
308         return dialogHelper.getDialog();
309     }
310 
311     /**
312      * Erase the current guest user and create a new one in the background. UserSettings will
313      * handle guest creation after receiving the {@link UserSettings.RESULT_GUEST_REMOVED} result.
314      */
315     private void resetGuest() {
316         // Just to be safe, check that the selected user is a guest
317         if (!mUserInfo.isGuest()) {
318             return;
319         }
320         mMetricsFeatureProvider.action(getActivity(),
321                 SettingsEnums.ACTION_USER_GUEST_EXIT_CONFIRMED);
322 
323         mUserManager.removeUser(mUserInfo.id);
324         setResult(UserSettings.RESULT_GUEST_REMOVED);
325         finishFragment();
326     }
327 
328     @VisibleForTesting
329     @Override
330     protected void showDialog(int dialogId) {
331         super.showDialog(dialogId);
332     }
333 
334     @VisibleForTesting
335     void initialize(Context context, Bundle arguments) {
336         int userId = arguments != null ? arguments.getInt(EXTRA_USER_ID, USER_NULL) : USER_NULL;
337         if (userId == USER_NULL) {
338             throw new IllegalStateException("Arguments to this fragment must contain the user id");
339         }
340         boolean isNewUser =
341                 arguments.getBoolean(AppRestrictionsFragment.EXTRA_NEW_USER, false);
342         mUserInfo = mUserManager.getUserInfo(userId);
343 
344         mSwitchUserPref = findPreference(KEY_SWITCH_USER);
345         mPhonePref = findPreference(KEY_ENABLE_TELEPHONY);
346         mRemoveUserPref = findPreference(KEY_REMOVE_USER);
347         mAppAndContentAccessPref = findPreference(KEY_APP_AND_CONTENT_ACCESS);
348         mAppCopyingPref = findPreference(KEY_APP_COPYING);
349         mGrantAdminPref = findPreference(KEY_GRANT_ADMIN);
350 
351         mGrantAdminPref.setChecked(mUserInfo.isAdmin());
352 
353         mSwitchUserPref.setTitle(
354                 context.getString(com.android.settingslib.R.string.user_switch_to_user,
355                         mUserInfo.name));
356 
357         if (mUserCaps.mDisallowSwitchUser) {
358             mSwitchUserPref.setDisabledByAdmin(RestrictedLockUtilsInternal.getDeviceOwner(context));
359         } else {
360             mSwitchUserPref.setDisabledByAdmin(null);
361             mSwitchUserPref.setSelectable(true);
362             mSwitchUserPref.setOnPreferenceClickListener(this);
363         }
364         if (mUserInfo.isMain() || mUserInfo.isGuest() || !UserManager.isMultipleAdminEnabled()
365                 || mUserManager.hasUserRestrictionForUser(UserManager.DISALLOW_GRANT_ADMIN,
366                 mUserInfo.getUserHandle())) {
367             removePreference(KEY_GRANT_ADMIN);
368         }
369         if (!mUserManager.isAdminUser()) { // non admin users can't remove users and allow calls
370             removePreference(KEY_ENABLE_TELEPHONY);
371             removePreference(KEY_REMOVE_USER);
372             removePreference(KEY_GRANT_ADMIN);
373             removePreference(KEY_APP_AND_CONTENT_ACCESS);
374             removePreference(KEY_APP_COPYING);
375         } else {
376             if (!Utils.isVoiceCapable(context)) { // no telephony
377                 removePreference(KEY_ENABLE_TELEPHONY);
378             }
379             if (mUserInfo.isMain() || UserManager.isHeadlessSystemUserMode()) {
380                 removePreference(KEY_ENABLE_TELEPHONY);
381             }
382             if (mUserInfo.isRestricted()) {
383                 removePreference(KEY_ENABLE_TELEPHONY);
384                 if (isNewUser) {
385                     // for newly created restricted users we should open the apps and content access
386                     // screen to initialize the default restrictions
387                     openAppAndContentAccessScreen(true);
388                 }
389             } else {
390                 removePreference(KEY_APP_AND_CONTENT_ACCESS);
391             }
392 
393             if (mUserInfo.isGuest()) {
394                 removePreference(KEY_ENABLE_TELEPHONY);
395                 mRemoveUserPref.setTitle(mGuestUserAutoCreated
396                         ? com.android.settingslib.R.string.guest_reset_guest
397                         : com.android.settingslib.R.string.guest_exit_guest);
398                 if (mGuestUserAutoCreated) {
399                     mRemoveUserPref.setEnabled((mUserInfo.flags & UserInfo.FLAG_INITIALIZED) != 0);
400                 }
401                 if (!SHOW_APP_COPYING_PREF) {
402                     removePreference(KEY_APP_COPYING);
403                 }
404             } else {
405                 mPhonePref.setChecked(!mUserManager.hasUserRestriction(
406                         UserManager.DISALLOW_OUTGOING_CALLS, new UserHandle(userId)));
407                 mRemoveUserPref.setTitle(R.string.user_remove_user);
408                 removePreference(KEY_APP_COPYING);
409             }
410 
411             // Remove preference KEY_REMOVE_USER if DISALLOW_REMOVE_USER restriction is set
412             // on the current user or the user selected in user details settings is a main user.
413             if (RestrictedLockUtilsInternal.hasBaseUserRestriction(context,
414                     UserManager.DISALLOW_REMOVE_USER, UserHandle.myUserId())
415                     || mUserInfo.isMain()) {
416                 removePreference(KEY_REMOVE_USER);
417             }
418 
419             mRemoveUserPref.setOnPreferenceClickListener(this);
420             mPhonePref.setOnPreferenceChangeListener(this);
421             mGrantAdminPref.setOnPreferenceChangeListener(this);
422             mAppAndContentAccessPref.setOnPreferenceClickListener(this);
423             mAppCopyingPref.setOnPreferenceClickListener(this);
424         }
425     }
426 
427     @VisibleForTesting
428     boolean canDeleteUser() {
429         if (!mUserManager.isAdminUser() || mUserInfo.isMain()) {
430             return false;
431         }
432 
433         Context context = getActivity();
434         if (context == null) {
435             return false;
436         }
437 
438         final RestrictedLockUtils.EnforcedAdmin removeDisallowedAdmin =
439                 RestrictedLockUtilsInternal.checkIfRestrictionEnforced(context,
440                         UserManager.DISALLOW_REMOVE_USER, UserHandle.myUserId());
441         if (removeDisallowedAdmin != null) {
442             RestrictedLockUtils.sendShowAdminSupportDetailsIntent(context,
443                     removeDisallowedAdmin);
444             return false;
445         }
446         return true;
447     }
448 
449     @VisibleForTesting
450     boolean canSwitchUserNow() {
451         return mUserManager.getUserSwitchability() == UserManager.SWITCHABILITY_STATUS_OK;
452     }
453 
454     @VisibleForTesting
455     void switchUser() {
456         Trace.beginSection("UserDetailSettings.switchUser");
457         try {
458             if (mUserCaps.mIsGuest && mUserCaps.mIsEphemeral) {
459                 int guestUserId = UserHandle.myUserId();
460                 // Using markGuestForDeletion allows us to create a new guest before this one is
461                 // fully removed.
462                 boolean marked = mUserManager.markGuestForDeletion(guestUserId);
463                 if (!marked) {
464                     Log.w(TAG, "Couldn't mark the guest for deletion for user " + guestUserId);
465                     return;
466                 }
467             }
468             ActivityManager.getService().switchUser(mUserInfo.id);
469         } catch (RemoteException re) {
470             Log.e(TAG, "Error while switching to other user.");
471         } finally {
472             Trace.endSection();
473             finishFragment();
474         }
475     }
476 
477     private void enableCallsAndSms(boolean enabled) {
478         mPhonePref.setChecked(enabled);
479         int[] userProfiles = mUserManager.getProfileIdsWithDisabled(mUserInfo.id);
480         for (int userId : userProfiles) {
481             UserHandle user = UserHandle.of(userId);
482             mUserManager.setUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, !enabled, user);
483             mUserManager.setUserRestriction(UserManager.DISALLOW_SMS, !enabled, user);
484         }
485     }
486 
487     /**
488      * Sets admin status of selected user. Method is called when toggle in
489      * user details settings is switched.
490      * @param isSetAdmin indicates if user admin status needs to be set to true.
491      */
492     private void updateUserAdminStatus(boolean isSetAdmin) {
493         mGrantAdminPref.setChecked(isSetAdmin);
494         if (!isSetAdmin) {
495             mUserManager.revokeUserAdmin(mUserInfo.id);
496         } else if ((mUserInfo.flags & UserInfo.FLAG_ADMIN) == 0) {
497             mUserManager.setUserAdmin(mUserInfo.id);
498         }
499     }
500 
501     private void removeUser() {
502         mUserManager.removeUser(mUserInfo.id);
503         finishFragment();
504     }
505 
506     /**
507      * @param isNewUser indicates if a user was created recently, for new users
508      *                  AppRestrictionsFragment should set the default restrictions
509      */
510     private void openAppAndContentAccessScreen(boolean isNewUser) {
511         Bundle extras = new Bundle();
512         extras.putInt(AppRestrictionsFragment.EXTRA_USER_ID, mUserInfo.id);
513         extras.putBoolean(AppRestrictionsFragment.EXTRA_NEW_USER, isNewUser);
514         new SubSettingLauncher(getContext())
515                 .setDestination(AppRestrictionsFragment.class.getName())
516                 .setArguments(extras)
517                 .setTitleRes(R.string.user_restrictions_title)
518                 .setSourceMetricsCategory(getMetricsCategory())
519                 .launch();
520     }
521 
522     private void openAppCopyingScreen() {
523         if (!SHOW_APP_COPYING_PREF) {
524             return;
525         }
526         final Bundle extras = new Bundle();
527         extras.putInt(AppRestrictionsFragment.EXTRA_USER_ID, mUserInfo.id);
528         new SubSettingLauncher(getContext())
529                 .setDestination(AppCopyFragment.class.getName())
530                 .setArguments(extras)
531                 .setTitleRes(R.string.user_copy_apps_menu_title)
532                 .setSourceMetricsCategory(getMetricsCategory())
533                 .launch();
534     }
535 
536     private boolean isSecondaryUser(UserInfo user) {
537         return UserManager.USER_TYPE_FULL_SECONDARY.equals(user.userType);
538     }
539 
540     private boolean shouldShowSetupPromptDialog() {
541         // TODO: FLAG_INITIALIZED is set when a user is switched to for the first time,
542         //  but what we would really need here is a flag that shows if the setup process was
543         //  completed. After the user cancels the setup process, mUserInfo.isInitialized() will
544         //  return true so there will be no setup prompt dialog shown to the user anymore.
545         return isSecondaryUser(mUserInfo) && !mUserInfo.isInitialized();
546     }
547 }
548