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