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