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