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_ADD_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.car.Car;
29 import android.car.user.CarUserManager;
30 import android.content.Context;
31 import android.os.UserManager;
32 import android.widget.Toast;
33 
34 import androidx.annotation.VisibleForTesting;
35 import androidx.preference.Preference;
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.common.PreferenceController;
42 import com.android.car.settings.enterprise.EnterpriseUtils;
43 
44 /**
45  * Consolidates adding profile logic into one handler so we can have consistent logic across various
46  * parts of the Settings app.
47  */
48 public class AddProfileHandler implements AddNewProfileTask.AddNewProfileListener {
49 
50     @VisibleForTesting
51     static final String CONFIRM_CREATE_NEW_PROFILE_DIALOG_TAG =
52             "com.android.car.settings.profiles.ConfirmCreateNewProfileDialog";
53     @VisibleForTesting
54     static final String MAX_PROFILES_LIMIT_REACHED_DIALOG_TAG =
55             "com.android.car.settings.profiles.MaxProfilesLimitReachedDialog";
56 
57     @VisibleForTesting
58     AddNewProfileTask mAddNewProfileTask;
59     /** Indicates that a task is running. */
60     private boolean mIsBusy;
61 
62     private final Context mContext;
63     private final FragmentController mFragmentController;
64     private final PreferenceController mPreferenceController;
65     private final Car mCar;
66     private CarUserManager mCarUserManager;
67 
68     @VisibleForTesting
69     ConfirmationDialogFragment.ConfirmListener mConfirmCreateNewProfileListener;
70 
AddProfileHandler(Context context, FragmentController fragmentController, PreferenceController preferenceController)71     public AddProfileHandler(Context context, FragmentController fragmentController,
72             PreferenceController preferenceController) {
73         mContext = context;
74         mFragmentController = fragmentController;
75         mPreferenceController = preferenceController;
76         mCar = Car.createCar(context);
77         mCarUserManager = (CarUserManager) mCar.getCarManager(Car.CAR_USER_SERVICE);
78 
79         mConfirmCreateNewProfileListener = arguments -> {
80             mAddNewProfileTask = new AddNewProfileTask(mContext,
81                     mCarUserManager, /* addNewProfileListener= */ this);
82             mAddNewProfileTask.execute(mContext.getString(R.string.user_new_user_name));
83 
84             mIsBusy = true;
85             mPreferenceController.refreshUi();
86         };
87     }
88 
89     /**
90      * Handles operations that should happen in host's onCreateInternal().
91      * Resets listeners as they can get unregistered with certain configuration changes.
92      */
onCreateInternal()93     public void onCreateInternal() {
94         ConfirmationDialogFragment.resetListeners(
95                 (ConfirmationDialogFragment) mFragmentController.findDialogByTag(
96                         CONFIRM_CREATE_NEW_PROFILE_DIALOG_TAG),
97                 mConfirmCreateNewProfileListener,
98                 /* rejectListener= */ null,
99                 /* neutralListener= */ null);
100     }
101 
102     /**
103      * Handles operations that should happen in host's onStopInternal().
104      */
onStopInternal()105     public void onStopInternal() {
106         mFragmentController.showProgressBar(false);
107     }
108 
109     /**
110      * Handles events that should happen in host's onDestroyInternal().
111      */
onDestroyInternal()112     public void onDestroyInternal() {
113         if (mAddNewProfileTask != null) {
114             mAddNewProfileTask.cancel(/* mayInterruptIfRunning= */ false);
115         }
116         if (mCar != null) {
117             mCar.disconnect();
118         }
119     }
120 
121     /**
122      * Handles events that should happen in host's updateState() when there is task running.
123      */
updateState(Preference preference)124     public void updateState(Preference preference) {
125         preference.setEnabled(!mIsBusy);
126         mFragmentController.showProgressBar(mIsBusy);
127     }
128 
129     @Override
onProfileAddedSuccess()130     public void onProfileAddedSuccess() {
131         mIsBusy = false;
132         mPreferenceController.refreshUi();
133     }
134 
135     @Override
onProfileAddedFailure()136     public void onProfileAddedFailure() {
137         mIsBusy = false;
138         mPreferenceController.refreshUi();
139         // Display failure dialog.
140         mFragmentController.showDialog(
141                 ErrorDialog.newInstance(R.string.add_user_error_title), /* tag= */ null);
142     }
143 
144     /**
145      *  Determines whether the user manager instance has the permission to add profiles
146      *
147      * @param userManager UserManager instance to evaluate
148      * @return whether the user has permissions to add profiles
149      */
canAddProfiles(UserManager userManager)150     public static boolean canAddProfiles(UserManager userManager) {
151         return !userManager.hasUserRestriction(DISALLOW_ADD_USER);
152     }
153 
154     /**
155      * Returns {@code PreferenceController.AVAILABLE} when preference should be available,
156      * {@code PreferenceController.DISABLED_FOR_PROFILE} when preference should be unavailable,
157      * {@code PreferenceController.AVAILABLE_FOR_VIEWING} when preference is visible but
158      * disabled.
159      */
getAddProfilePreferenceAvailabilityStatus(Context context)160     public static int getAddProfilePreferenceAvailabilityStatus(Context context) {
161         UserManager um = getUserManager(context);
162         if (um.isDemoUser() || canAddProfiles(um)) {
163             return AVAILABLE;
164         }
165         if (hasUserRestrictionByUm(context, DISALLOW_ADD_USER)) {
166             return DISABLED_FOR_PROFILE;
167         }
168         return AVAILABLE_FOR_VIEWING;
169     }
170 
171     /**
172      * Display dialog to add a profile
173      */
showAddProfileDialog()174     public void showAddProfileDialog() {
175         ConfirmationDialogFragment dialogFragment =
176                 ProfilesDialogProvider.getConfirmCreateNewProfileDialogFragment(
177                         mContext, mConfirmCreateNewProfileListener, null);
178 
179         mFragmentController.showDialog(dialogFragment, CONFIRM_CREATE_NEW_PROFILE_DIALOG_TAG);
180     }
181 
182     /**
183      * Shows a dialog or toast when the Preference is disabled while still visible.
184      */
runClickableWhileDisabled()185     public void runClickableWhileDisabled() {
186         if (hasUserRestrictionByDpm(mContext, DISALLOW_ADD_USER)) {
187             // Shows a dialog if this PreferenceController is disabled because there is
188             // restriction set from DevicePolicyManager
189             showActionDisabledByAdminDialog();
190         } else if (!getUserManager(mContext).canAddMoreUsers()) {
191             // Shows a dialog if no more profiles can be added because the maximum allowed number
192             // is reached
193             ConfirmationDialogFragment dialogFragment =
194                     ProfilesDialogProvider.getMaxProfilesLimitReachedDialogFragment(mContext,
195                             ProfileHelper.getInstance(mContext).getMaxSupportedRealProfiles());
196             mFragmentController.showDialog(dialogFragment, MAX_PROFILES_LIMIT_REACHED_DIALOG_TAG);
197         } else {
198             Toast.makeText(mContext, mContext.getString(R.string.action_unavailable),
199                     Toast.LENGTH_LONG).show();
200         }
201     }
202 
showActionDisabledByAdminDialog()203     private void showActionDisabledByAdminDialog() {
204         mFragmentController.showDialog(
205                 EnterpriseUtils.getActionDisabledByAdminDialog(mContext, DISALLOW_ADD_USER),
206                 DISABLED_BY_ADMIN_CONFIRM_DIALOG_TAG);
207     }
208 
getUserManager(Context context)209     private static UserManager getUserManager(Context context) {
210         return context.getSystemService(UserManager.class);
211     }
212 
213     @VisibleForTesting
setCarUserManager(CarUserManager carUserManager)214     void setCarUserManager(CarUserManager carUserManager) {
215         mCarUserManager = carUserManager;
216     }
217 }
218