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