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