1 /*
2  * Copyright (C) 2020 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_ADD_USER;
20 
21 import static com.android.car.settings.common.ActionButtonsPreference.ActionButtons;
22 import static com.android.car.settings.enterprise.EnterpriseUtils.hasUserRestrictionByDpm;
23 import static com.android.car.settings.enterprise.EnterpriseUtils.hasUserRestrictionByUm;
24 
25 import android.car.drivingstate.CarUxRestrictions;
26 import android.content.Context;
27 import android.content.pm.UserInfo;
28 import android.os.UserManager;
29 
30 import androidx.annotation.VisibleForTesting;
31 
32 import com.android.car.settings.R;
33 import com.android.car.settings.common.ActionButtonInfo;
34 import com.android.car.settings.common.ActionButtonsPreference;
35 import com.android.car.settings.common.ConfirmationDialogFragment;
36 import com.android.car.settings.common.FragmentController;
37 
38 /**
39  * Displays the action buttons for profile details.
40  *
41  * <p>The actions shown depends on the current and selected profile.
42  * <ol>
43  * <li>Rename: shown if selected profile is the current profile
44  * <li>Make admin: shown if current profile is an admin and the selected profile is not
45  * <li>Manage other profiles: shown if selected profile is the current profile and
46  * there are other profiles
47  * <li>Add a profile: shown if selected profile is the current profile and
48  * there are no other profiles
49  * <li> Exit demo: shown if selected profile is the current profile and is a demo
50  * <li>Delete: shown if the current profile is allowed to remove profiles, is not a demo
51  * profile, and selected profile is not the current profile
52  * </ol>
53  */
54 public final class ProfileDetailsActionButtonsPreferenceController
55         extends ProfileDetailsBasePreferenceController<ActionButtonsPreference> {
56 
57     @VisibleForTesting
58     static final String MAKE_ADMIN_DIALOG_TAG = "MakeAdminDialogFragment";
59 
60     private final ProfileHelper mProfileHelper;
61     private final UserManager mUserManager;
62     private DemoProfileDialogHandler mDemoProfileDialogHandler;
63     private AddProfileHandler mAddProfileHandler;
64 
65     @VisibleForTesting
66     final ConfirmationDialogFragment.ConfirmListener mMakeAdminConfirmListener =
67             arguments -> {
68                 UserInfo profileToMakeAdmin =
69                         (UserInfo) arguments.get(ProfilesDialogProvider.KEY_PROFILE_TO_MAKE_ADMIN);
70                 android.car.userlib.UserHelper.grantAdminPermissions(getContext(),
71                         profileToMakeAdmin);
72                 getFragmentController().goBack();
73             };
74 
75     private final RemoveProfileHandler mRemoveProfileHandler;
76 
ProfileDetailsActionButtonsPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)77     public ProfileDetailsActionButtonsPreferenceController(Context context,
78             String preferenceKey, FragmentController fragmentController,
79             CarUxRestrictions uxRestrictions) {
80         this(context, preferenceKey, fragmentController, uxRestrictions,
81                 ProfileHelper.getInstance(context), UserManager.get(context),
82                 new RemoveProfileHandler(context, ProfileHelper.getInstance(context),
83                         UserManager.get(context), fragmentController));
84     }
85 
86     @VisibleForTesting
ProfileDetailsActionButtonsPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions, ProfileHelper profileHelper, UserManager userManager, RemoveProfileHandler removeProfileHandler)87     ProfileDetailsActionButtonsPreferenceController(Context context,
88             String preferenceKey, FragmentController fragmentController,
89             CarUxRestrictions uxRestrictions, ProfileHelper profileHelper, UserManager userManager,
90             RemoveProfileHandler removeProfileHandler) {
91         super(context, preferenceKey, fragmentController, uxRestrictions);
92         mProfileHelper = profileHelper;
93         mUserManager = userManager;
94         mRemoveProfileHandler = removeProfileHandler;
95         mDemoProfileDialogHandler = new DemoProfileDialogHandler(context, fragmentController);
96         mAddProfileHandler = new AddProfileHandler(context, fragmentController, this);
97     }
98 
99     @Override
getPreferenceType()100     protected Class<ActionButtonsPreference> getPreferenceType() {
101         return ActionButtonsPreference.class;
102     }
103 
104     @Override
onCreateInternal()105     protected void onCreateInternal() {
106         super.onCreateInternal();
107 
108         mDemoProfileDialogHandler.onCreateInternal();
109         mAddProfileHandler.onCreateInternal();
110 
111         ConfirmationDialogFragment makeAdminDialog =
112                 (ConfirmationDialogFragment) getFragmentController().findDialogByTag(
113                         MAKE_ADMIN_DIALOG_TAG);
114         ConfirmationDialogFragment.resetListeners(
115                 makeAdminDialog,
116                 mMakeAdminConfirmListener,
117                 /* rejectListener= */ null,
118                 /* neutralListener= */ null);
119 
120         mRemoveProfileHandler.resetListeners();
121     }
122 
123     @Override
onStartInternal()124     protected void onStartInternal() {
125         super.onStartInternal();
126 
127         ActionButtonInfo renameButton = getPreference().getButton(ActionButtons.BUTTON1);
128         ActionButtonInfo makeAdminButton = getPreference().getButton(ActionButtons.BUTTON2);
129         ActionButtonInfo profilesButton = getPreference().getButton(ActionButtons.BUTTON3);
130         ActionButtonInfo deleteButton = getPreference().getButton(ActionButtons.BUTTON4);
131 
132         boolean isDemoProfile = mUserManager.isDemoUser();
133         // When DISALLOW_ADD_USER is set by device or profile owner, the button should still be
134         // visible but disabled
135         boolean shouldShowAddProfile = mUserManager.isAdminUser()
136                 && mProfileHelper.isCurrentProcessUser(getUserInfo())
137                 && !hasUserRestrictionByUm(getContext(), DISALLOW_ADD_USER)
138                 && !areThereOtherProfiles();
139         boolean shouldEnableAddProfile = shouldShowAddProfile
140                 && !hasUserRestrictionByDpm(getContext(), DISALLOW_ADD_USER);
141         boolean shouldShowProfilesButton = isDemoProfile || shouldShowAddProfile
142                 || mUserManager.isAdminUser() && mProfileHelper.isCurrentProcessUser(getUserInfo())
143                 && areThereOtherProfiles();
144 
145         int removeProfileAvailabilityStatus = mRemoveProfileHandler.getAvailabilityStatus(
146                 getContext(), getUserInfo(), /* availableForCurrentProcessUser= */ false);
147         boolean shouldShowDeleteProfile = removeProfileAvailabilityStatus != DISABLED_FOR_PROFILE;
148         boolean shouldEnableDeleteProfile = removeProfileAvailabilityStatus == AVAILABLE;
149 
150         int profileButtonText;
151         if (shouldShowAddProfile && isDemoProfile) {
152             profileButtonText = R.string.exit_retail_button_text;
153         } else if (shouldShowAddProfile) {
154             profileButtonText = R.string.add_a_profile_button_text;
155         } else {
156             profileButtonText = R.string.manage_other_profiles_button_text;
157         }
158 
159         renameButton
160                 .setText(R.string.bluetooth_rename_button)
161                 .setIcon(R.drawable.ic_edit)
162                 .setVisible(mProfileHelper.isCurrentProcessUser(getUserInfo()))
163                 .setOnClickListener(v -> getFragmentController().launchFragment(
164                         EditProfileNameFragment.newInstance(getUserInfo())));
165 
166         makeAdminButton
167                 .setText(R.string.grant_admin_permissions_button_text)
168                 .setIcon(R.drawable.ic_person)
169                 .setVisible(ProfileUtils.isAdminViewingNonAdmin(mUserManager, getUserInfo()))
170                 .setOnClickListener(v -> showConfirmMakeAdminDialog());
171 
172         profilesButton
173                 .setText(profileButtonText)
174                 .setVisible(shouldShowProfilesButton)
175                 .setOnClickListener(v -> {
176                     if (shouldShowAddProfile && isDemoProfile) {
177                         mDemoProfileDialogHandler.showExitRetailDialog();
178                     } else if (shouldShowAddProfile && !shouldEnableAddProfile) {
179                         mAddProfileHandler.runClickableWhileDisabled();
180                     } else if (shouldShowAddProfile) {
181                         mAddProfileHandler.showAddProfileDialog();
182                     } else {
183                         getFragmentController().launchFragment(
184                                 new ProfilesListFragment());
185                     }
186                 });
187 
188         if (!isDemoProfile && shouldShowAddProfile) {
189             profilesButton.setIcon(R.drawable.ic_add);
190         } else if (!isDemoProfile && shouldShowProfilesButton) {
191             profilesButton.setIcon(R.drawable.ic_people);
192         }
193 
194         // Do not show delete button if the current profile can't remove the selected profile
195         deleteButton
196                 .setText(R.string.delete_button)
197                 .setIcon(R.drawable.ic_delete)
198                 .setVisible(shouldShowDeleteProfile)
199                 .setOnClickListener(v -> {
200                     if (shouldEnableDeleteProfile) {
201                         mRemoveProfileHandler.showConfirmRemoveProfileDialog();
202                     } else {
203                         mRemoveProfileHandler.runClickableWhileDisabled();
204                     }
205                 });
206     }
207 
208     @Override
setUserInfo(UserInfo userInfo)209     public void setUserInfo(UserInfo userInfo) {
210         super.setUserInfo(userInfo);
211         mRemoveProfileHandler.setUserInfo(userInfo);
212     }
213 
214     @Override
onStopInternal()215     protected void onStopInternal() {
216         super.onStopInternal();
217         mAddProfileHandler.onStopInternal();
218     }
219 
220     @Override
onDestroyInternal()221     protected void onDestroyInternal() {
222         super.onDestroyInternal();
223         mAddProfileHandler.onDestroyInternal();
224     }
225 
226     @Override
updateState(ActionButtonsPreference preference)227     protected void updateState(ActionButtonsPreference preference) {
228         mAddProfileHandler.updateState(preference);
229     }
230 
showConfirmMakeAdminDialog()231     private void showConfirmMakeAdminDialog() {
232         ConfirmationDialogFragment dialogFragment =
233                 ProfilesDialogProvider.getConfirmGrantAdminDialogFragment(getContext(),
234                         mMakeAdminConfirmListener, /* rejectListener= */ null, getUserInfo());
235 
236         getFragmentController().showDialog(dialogFragment, MAKE_ADMIN_DIALOG_TAG);
237     }
238 
areThereOtherProfiles()239     private boolean areThereOtherProfiles() {
240         UserInfo currUserInfo = mProfileHelper.getCurrentProcessUserInfo();
241         return !mProfileHelper.getAllLivingProfiles(
242                 userInfo -> !userInfo.isGuest() && userInfo.id != currUserInfo.id).isEmpty();
243     }
244 }
245