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.enterprise;
18 
19 import android.annotation.Nullable;
20 import android.annotation.UserIdInt;
21 import android.app.Dialog;
22 import android.app.admin.DevicePolicyManager;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.pm.ApplicationInfo;
27 import android.content.pm.PackageManager;
28 import android.graphics.drawable.Drawable;
29 import android.os.Bundle;
30 import android.os.Process;
31 import android.os.UserHandle;
32 import android.util.IconDrawableFactory;
33 
34 import androidx.annotation.DrawableRes;
35 import androidx.annotation.VisibleForTesting;
36 
37 import com.android.car.settings.R;
38 import com.android.car.settings.common.Logger;
39 import com.android.car.ui.AlertDialogBuilder;
40 import com.android.car.ui.preference.CarUiDialogFragment;
41 import com.android.settingslib.RestrictedLockUtils;
42 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
43 import com.android.settingslib.RestrictedLockUtilsInternal;
44 import com.android.settingslib.enterprise.ActionDisabledByAdminController;
45 import com.android.settingslib.enterprise.ActionDisabledByAdminControllerFactory;
46 
47 /**
48  * Shows a dialog explaining that an action is not enabled due to restrictions imposed by an active
49  * device administrator.
50  */
51 // TODO(b/186905050): add unit tests
52 // TODO(b/188836559): move most of this class' logic to settingslib
53 public final class ActionDisabledByAdminDialogFragment extends CarUiDialogFragment {
54 
55     public static final String DISABLED_BY_ADMIN_CONFIRM_DIALOG_TAG =
56             "com.android.car.settings.applications.DisabledByAdminConfirmDialog";
57 
58     private static final String TAG = ActionDisabledByAdminDialogFragment.class.getSimpleName();
59     private static final Logger LOG = new Logger(TAG);
60 
61     private static final String EXTRA_RESTRICTION = TAG + "_restriction";
62     private static final String EXTRA_RESTRICTED_PKG = TAG + "_pkg";
63     private static final String EXTRA_ADMIN_USER_ID = TAG + "_userId";
64 
65     @VisibleForTesting
66     String mRestriction;
67     String mRestrictedPackage;
68 
69     @UserIdInt
70     private int mAdminUserId;
71 
72     private ActionDisabledByAdminController mActionDisabledByAdminController;
73     private DismissListener mDismissListener;
74 
75     /**
76      * Gets the dialog for the given user and restriction.
77      */
newInstance(String restriction, @UserIdInt int userId)78     public static ActionDisabledByAdminDialogFragment newInstance(String restriction,
79             @UserIdInt int userId) {
80         return newInstance(restriction, null, userId);
81     }
82 
83     /**
84      * Gets the dialog for the given user and restriction.
85      */
newInstance(String restriction, @Nullable String restrictedPackage, @UserIdInt int userId)86     public static ActionDisabledByAdminDialogFragment newInstance(String restriction,
87             @Nullable String restrictedPackage, @UserIdInt int userId) {
88         ActionDisabledByAdminDialogFragment instance = new ActionDisabledByAdminDialogFragment();
89         instance.mRestriction = restriction;
90         instance.mRestrictedPackage = restrictedPackage;
91         instance.mAdminUserId = userId;
92         return instance;
93     }
94 
95     @Override
onCreateDialog(Bundle savedInstanceState)96     public Dialog onCreateDialog(Bundle savedInstanceState) {
97         if (savedInstanceState != null) {
98             mRestriction = savedInstanceState.getString(EXTRA_RESTRICTION);
99             mRestrictedPackage = savedInstanceState.getString(EXTRA_RESTRICTED_PKG);
100             mAdminUserId = savedInstanceState.getInt(EXTRA_ADMIN_USER_ID);
101         }
102         return initialize(getContext()).create();
103     }
104 
105     @Override
onSaveInstanceState(Bundle outState)106     public void onSaveInstanceState(Bundle outState) {
107         super.onSaveInstanceState(outState);
108 
109         outState.putString(EXTRA_RESTRICTION, mRestriction);
110         outState.putString(EXTRA_RESTRICTED_PKG, mRestrictedPackage);
111         outState.putInt(EXTRA_ADMIN_USER_ID, mAdminUserId);
112     }
113 
114     @Override
onDialogClosed(boolean positiveResult)115     protected void onDialogClosed(boolean positiveResult) {
116         if (mDismissListener != null) {
117             mDismissListener.onDismiss(positiveResult);
118             mDismissListener = null;
119         }
120     }
121 
122     /** Sets the listeners which listens for the dialog dismissed */
setDismissListener(DismissListener dismissListener)123     public void setDismissListener(DismissListener dismissListener) {
124         mDismissListener = dismissListener;
125     }
126 
initialize(Context context)127     private AlertDialogBuilder initialize(Context context) {
128         Intent intent = getActivity().getIntent();
129         boolean hasValidIntent = intent != null
130                 && intent.getParcelableExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN) != null;
131         EnforcedAdmin enforcedAdmin = hasValidIntent
132                     ? EnterpriseUtils.getEnforcedAdminFromIntent(context, intent)
133                     : EnterpriseUtils.getEnforcedAdmin(context, mAdminUserId,
134                             mRestriction, mRestrictedPackage);
135         LOG.i("hasValidIntent: " + hasValidIntent + " enforcedAdmin: " + enforcedAdmin
136                 + " mAdminUserId: " + mAdminUserId);
137 
138         AlertDialogBuilder builder = new AlertDialogBuilder(context)
139                 .setPositiveButton(R.string.okay, /* listener= */ null);
140         mActionDisabledByAdminController = ActionDisabledByAdminControllerFactory
141                 .createInstance(context, mRestriction, new DeviceAdminStringProviderImpl(context),
142                         context.getUser());
143         // Learn more button should launch admin policy information on the current user
144         mActionDisabledByAdminController.initialize(
145                 new ActionDisabledLearnMoreButtonLauncherImpl(builder,
146                         /* preferredUser= */ context.getUser()));
147         if (enforcedAdmin != null) {
148             mActionDisabledByAdminController.updateEnforcedAdmin(enforcedAdmin, mAdminUserId);
149             mActionDisabledByAdminController.setupLearnMoreButton(context);
150         }
151         initializeDialogViews(context, builder, enforcedAdmin,
152                 getEnforcementAdminUserId(enforcedAdmin));
153         return builder;
154     }
155 
156     // NOTE: methods below were copied from phone Settings
157     // (com.android.settings.enterprise.ActionDisabledByAdminDialogHelper), but adjusted to
158     // use a AlertDialogBuilder directly, instead of an Activity hosting a dialog.
getEnforcementAdminUserId(@ullable EnforcedAdmin admin)159     private static @UserIdInt int getEnforcementAdminUserId(@Nullable EnforcedAdmin admin) {
160         return admin == null || admin.user == null ? UserHandle.USER_NULL
161                 : admin.user.getIdentifier();
162     }
163 
initializeDialogViews(Context context, AlertDialogBuilder builder, @Nullable EnforcedAdmin enforcedAdmin, @UserIdInt int userId)164     private void initializeDialogViews(Context context, AlertDialogBuilder builder,
165             @Nullable EnforcedAdmin enforcedAdmin, @UserIdInt int userId) {
166         ComponentName admin = null;
167 
168         if (enforcedAdmin != null) {
169             admin = enforcedAdmin.component;
170             if (admin == null) {
171                 return;
172             }
173 
174             mActionDisabledByAdminController.updateEnforcedAdmin(enforcedAdmin, userId);
175         }
176 
177         if (isNotValidEnforcedAdmin(context, enforcedAdmin)) {
178             admin = null;
179         }
180         setIcon(builder, R.drawable.ic_lock);
181         setAdminSupportTitle(context, builder, mRestriction);
182 
183         if (enforcedAdmin != null) {
184             setAdminSupportDetails(context, builder, enforcedAdmin);
185         }
186     }
187 
isNotValidEnforcedAdmin(Context context, EnforcedAdmin enforcedAdmin)188     private boolean isNotValidEnforcedAdmin(Context context, EnforcedAdmin enforcedAdmin) {
189         if (enforcedAdmin == null) {
190             LOG.w("isNotValidEnforcedAdmin(): enforcedAdmin is null");
191             return true;
192         }
193         ComponentName admin = enforcedAdmin.component;
194         int userId = getEnforcementAdminUserId(enforcedAdmin);
195         if (isNotCurrentUserOrProfile(context, admin, userId)
196                 && isNotDeviceOwner(context, admin, userId)) {
197             LOG.w("isNotValidEnforcedAdmin(): is not current user or profile/device owner");
198             return true;
199         }
200         return false;
201     }
202 
isNotCurrentUserOrProfile(Context context, ComponentName admin, @UserIdInt int userId)203     private boolean isNotCurrentUserOrProfile(Context context, ComponentName admin,
204             @UserIdInt int userId) {
205         return !RestrictedLockUtilsInternal.isAdminInCurrentUserOrProfile(context, admin)
206                 || !RestrictedLockUtils.isCurrentUserOrProfile(context, userId);
207     }
208 
isNotDeviceOwner(Context context, ComponentName admin, @UserIdInt int userId)209     private boolean isNotDeviceOwner(Context context, ComponentName admin,
210             @UserIdInt int userId) {
211         EnforcedAdmin deviceOwner = RestrictedLockUtilsInternal.getDeviceOwner(context);
212         return !((deviceOwner.component).equals(admin) && userId == UserHandle.USER_SYSTEM);
213     }
214 
setAdminSupportTitle(Context context, AlertDialogBuilder builder, String restriction)215     private void setAdminSupportTitle(Context context, AlertDialogBuilder builder,
216             String restriction) {
217         builder.setTitle(mActionDisabledByAdminController.getAdminSupportTitle(restriction));
218     }
219 
setIcon(AlertDialogBuilder builder, @DrawableRes int iconId)220     private void setIcon(AlertDialogBuilder builder, @DrawableRes int iconId) {
221         builder.setIcon(iconId);
222     }
223 
setAdminSupportDetails(Context context, AlertDialogBuilder builder, @Nullable EnforcedAdmin enforcedAdmin)224     private void setAdminSupportDetails(Context context, AlertDialogBuilder builder,
225             @Nullable EnforcedAdmin enforcedAdmin) {
226         if (enforcedAdmin == null || enforcedAdmin.component == null) {
227             LOG.i("setAdminSupportDetails(): no admin on " + enforcedAdmin);
228             return;
229         }
230         CharSequence supportMessage = null;
231         if (isNotValidEnforcedAdmin(context, enforcedAdmin)) {
232             enforcedAdmin.component = null;
233         } else {
234             if (enforcedAdmin.user == null) {
235                 enforcedAdmin.user = UserHandle.of(UserHandle.myUserId());
236             }
237             if (UserHandle.isSameApp(Process.myUid(), Process.SYSTEM_UID)) {
238                 supportMessage = context.getSystemService(DevicePolicyManager.class)
239                         .getShortSupportMessageForUser(enforcedAdmin.component,
240                                 getEnforcementAdminUserId(enforcedAdmin));
241             }
242         }
243         CharSequence supportContentString =
244                 mActionDisabledByAdminController.getAdminSupportContentString(
245                         context, supportMessage);
246         if (supportContentString != null) {
247             builder.setMessage(supportContentString);
248         }
249     }
250 
251     // Copied from com.android.settings.Utils
getBadgedIcon(IconDrawableFactory iconDrawableFactory, PackageManager packageManager, String packageName, int userId)252     private static Drawable getBadgedIcon(IconDrawableFactory iconDrawableFactory,
253             PackageManager packageManager, String packageName, int userId) {
254         try {
255             ApplicationInfo appInfo = packageManager.getApplicationInfoAsUser(
256                     packageName, PackageManager.GET_META_DATA, userId);
257             return iconDrawableFactory.getBadgedIcon(appInfo, userId);
258         } catch (PackageManager.NameNotFoundException e) {
259             return packageManager.getDefaultActivityIcon();
260         }
261     }
262 
263     /** Listens to the dismiss action. */
264     public interface DismissListener {
265         /**
266          * Defines the action to take when the dialog is closed.
267          *
268          * @param positiveResult - whether or not the dialog was dismissed because of a positive
269          * button press.
270          */
onDismiss(boolean positiveResult)271         void onDismiss(boolean positiveResult);
272     }
273 }
274