1 /* 2 * Copyright (C) 2021 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.car.settings.profiles; 18 19 import static android.os.UserManager.DISALLOW_REMOVE_USER; 20 21 import static com.android.car.settings.common.PreferenceController.AVAILABLE; 22 import static com.android.car.settings.common.PreferenceController.AVAILABLE_FOR_VIEWING; 23 import static com.android.car.settings.common.PreferenceController.DISABLED_FOR_PROFILE; 24 import static com.android.car.settings.enterprise.ActionDisabledByAdminDialogFragment.DISABLED_BY_ADMIN_CONFIRM_DIALOG_TAG; 25 import static com.android.car.settings.enterprise.EnterpriseUtils.hasUserRestrictionByDpm; 26 import static com.android.car.settings.enterprise.EnterpriseUtils.hasUserRestrictionByUm; 27 28 import android.app.ActivityManager; 29 import android.content.Context; 30 import android.content.pm.UserInfo; 31 import android.os.UserHandle; 32 import android.os.UserManager; 33 import android.widget.Toast; 34 35 import androidx.annotation.VisibleForTesting; 36 37 import com.android.car.settings.R; 38 import com.android.car.settings.common.ConfirmationDialogFragment; 39 import com.android.car.settings.common.ErrorDialog; 40 import com.android.car.settings.common.FragmentController; 41 import com.android.car.settings.enterprise.EnterpriseUtils; 42 43 /** 44 * Consolidates profile removal logic into one handler so we can have consistent logic across 45 * various parts of the Settings app. 46 */ 47 public class RemoveProfileHandler { 48 @VisibleForTesting 49 static final String REMOVE_PROFILE_DIALOG_TAG = "RemoveProfileDialogFragment"; 50 51 private final Context mContext; 52 private final ProfileHelper mProfileHelper; 53 private final UserManager mUserManager; 54 private final FragmentController mFragmentController; 55 56 private UserInfo mUserInfo; 57 58 @VisibleForTesting 59 ConfirmationDialogFragment.ConfirmListener mRemoveConfirmListener; 60 RemoveProfileHandler(Context context, ProfileHelper profileHelper, UserManager userManager, FragmentController fragmentController)61 public RemoveProfileHandler(Context context, ProfileHelper profileHelper, 62 UserManager userManager, FragmentController fragmentController) { 63 mContext = context; 64 mProfileHelper = profileHelper; 65 mUserManager = userManager; 66 mFragmentController = fragmentController; 67 } 68 69 /** 70 * Sets the profile info to be handled for removal 71 * @param userInfo UserInfo of the profile to remove 72 */ setUserInfo(UserInfo userInfo)73 public void setUserInfo(UserInfo userInfo) { 74 mUserInfo = userInfo; 75 76 mRemoveConfirmListener = arguments -> { 77 String profileType = arguments.getString(ProfilesDialogProvider.KEY_PROFILE_TYPE); 78 if (profileType.equals(ProfilesDialogProvider.LAST_ADMIN)) { 79 mFragmentController.launchFragment( 80 ChooseNewAdminFragment.newInstance(userInfo)); 81 } else { 82 int removeProfileResult = mProfileHelper.removeProfile(mContext, mUserInfo); 83 if (removeProfileResult == ProfileHelper.REMOVE_PROFILE_RESULT_SUCCESS) { 84 mFragmentController.goBack(); 85 } else { 86 // If failed, need to show error dialog for users. 87 mFragmentController.showDialog( 88 ErrorDialog.newInstance(mProfileHelper.getErrorMessageForProfileResult( 89 removeProfileResult)), null); 90 } 91 } 92 }; 93 } 94 95 /** 96 * Resets listeners as they can get unregistered with certain configuration changes. 97 */ resetListeners()98 public void resetListeners() { 99 ConfirmationDialogFragment removeProfileDialog = 100 (ConfirmationDialogFragment) mFragmentController.findDialogByTag( 101 REMOVE_PROFILE_DIALOG_TAG); 102 103 ConfirmationDialogFragment.resetListeners( 104 removeProfileDialog, 105 mRemoveConfirmListener, 106 /* rejectListener= */ null, 107 /* neutralListener= */ null); 108 } 109 110 /** 111 * Checks to see if the current active profile can delete the requested profile. 112 * @param userInfo UserInfo of the profile to delete 113 * @return True if the profile can be deleted by the current active profile. False otherwise. 114 */ canRemoveProfile(UserInfo userInfo)115 public boolean canRemoveProfile(UserInfo userInfo) { 116 return !mUserManager.hasUserRestriction(DISALLOW_REMOVE_USER) 117 && !isSystemOrDemoUser(userInfo) 118 && !isNonCurrentForegroundUser(userInfo); 119 } 120 121 /** 122 * Checks to see if the current active profile is {@code USER_SYSTEM} or has user type 123 * {@code USER_TYPE_FULL_DEMO} 124 */ isSystemOrDemoUser(UserInfo userInfo)125 public boolean isSystemOrDemoUser(UserInfo userInfo) { 126 return userInfo.id == UserHandle.USER_SYSTEM 127 || mUserManager.isDemoUser(); 128 } 129 130 /** 131 * Returns {@code PreferenceController.AVAILABLE} when preference should be available, 132 * {@code PreferenceController.DISABLED_FOR_PROFILE} when preference should be unavailable, 133 * {@code PreferenceController.AVAILABLE_FOR_VIEWING} when preference is visible but 134 * disabled. 135 * 136 * @param context to get user restriction 137 * @param userInfo target user to check 138 * @param availableForCurrentProcessUser when true, only available for current user process. 139 * When false, disabled for current user process. 140 */ getAvailabilityStatus(Context context, UserInfo userInfo, boolean availableForCurrentProcessUser)141 public int getAvailabilityStatus(Context context, UserInfo userInfo, 142 boolean availableForCurrentProcessUser) { 143 boolean allowCurrentProcess = 144 !(availableForCurrentProcessUser ^ mProfileHelper.isCurrentProcessUser(userInfo)); 145 if (canRemoveProfile(userInfo) && allowCurrentProcess) { 146 return AVAILABLE; 147 } 148 if (!allowCurrentProcess || isSystemOrDemoUser(userInfo) 149 || hasUserRestrictionByUm(context, DISALLOW_REMOVE_USER)) { 150 return DISABLED_FOR_PROFILE; 151 } 152 return AVAILABLE_FOR_VIEWING; 153 } 154 155 /** 156 * Show the remove profile confirmation dialog. This will handle edge cases such as removing 157 * the last profile or removing the last admin profile. 158 */ showConfirmRemoveProfileDialog()159 public void showConfirmRemoveProfileDialog() { 160 boolean isLastProfile = mProfileHelper.getAllPersistentProfiles().size() == 1; 161 boolean isLastAdmin = mUserInfo.isAdmin() 162 && mProfileHelper.getAllAdminProfiles().size() == 1; 163 164 ConfirmationDialogFragment dialogFragment; 165 166 if (isLastProfile) { 167 dialogFragment = ProfilesDialogProvider.getConfirmRemoveLastProfileDialogFragment( 168 mContext, mRemoveConfirmListener, /* rejectListener= */ null); 169 } else if (isLastAdmin) { 170 dialogFragment = ProfilesDialogProvider.getConfirmRemoveLastAdminDialogFragment( 171 mContext, mRemoveConfirmListener, /* rejectListener= */ null); 172 } else { 173 dialogFragment = ProfilesDialogProvider.getConfirmRemoveProfileDialogFragment(mContext, 174 mRemoveConfirmListener, /* rejectListener= */ null); 175 } 176 mFragmentController.showDialog(dialogFragment, REMOVE_PROFILE_DIALOG_TAG); 177 } 178 179 /** 180 * When the Preference is disabled while still visible, {@code ActionDisabledByAdminDialog} 181 * should be shown when the action is disallowed by a device owner or a profile owner. 182 * Otherwise, a {@code Toast} will be shown to inform the user that the action is disabled. 183 */ runClickableWhileDisabled()184 public void runClickableWhileDisabled() { 185 if (hasUserRestrictionByDpm(mContext, DISALLOW_REMOVE_USER)) { 186 showActionDisabledByAdminDialog(); 187 } else if (isNonCurrentForegroundUser(mUserInfo)) { 188 Toast.makeText(mContext, mContext.getString(R.string.cannot_remove_driver_profile), 189 Toast.LENGTH_LONG).show(); 190 } else { 191 Toast.makeText(mContext, mContext.getString(R.string.action_unavailable), 192 Toast.LENGTH_LONG).show(); 193 } 194 } 195 196 /** Returns whether the provided user is the foreground user and the current user is not. */ isNonCurrentForegroundUser(UserInfo userInfo)197 private boolean isNonCurrentForegroundUser(UserInfo userInfo) { 198 if (userInfo == null) { 199 return false; 200 } 201 int foregroundUserId = ActivityManager.getCurrentUser(); 202 return userInfo.id == foregroundUserId && UserHandle.myUserId() != foregroundUserId; 203 } 204 showActionDisabledByAdminDialog()205 private void showActionDisabledByAdminDialog() { 206 mFragmentController.showDialog( 207 EnterpriseUtils.getActionDisabledByAdminDialog(mContext, 208 DISALLOW_REMOVE_USER), 209 DISABLED_BY_ADMIN_CONFIRM_DIALOG_TAG); 210 } 211 } 212