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