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