1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; 4 import static android.os.Build.VERSION_CODES.LOLLIPOP; 5 import static android.os.Build.VERSION_CODES.N; 6 import static org.robolectric.Shadows.shadowOf; 7 8 import android.annotation.Nullable; 9 import android.app.admin.DevicePolicyManager; 10 import android.content.ComponentName; 11 import android.content.Context; 12 import android.os.Build; 13 import android.os.Bundle; 14 import android.os.Process; 15 import android.os.UserManager; 16 import android.text.TextUtils; 17 import java.util.ArrayList; 18 import java.util.HashMap; 19 import java.util.HashSet; 20 import java.util.List; 21 import java.util.Map; 22 import java.util.Set; 23 import org.robolectric.RuntimeEnvironment; 24 import org.robolectric.annotation.Implementation; 25 import org.robolectric.annotation.Implements; 26 27 /** Shadow for {@link DevicePolicyManager} */ 28 @Implements(DevicePolicyManager.class) 29 public class ShadowDevicePolicyManager { 30 /** 31 * @see 32 * https://developer.android.com/reference/android/app/admin/DevicePolicyManager.html#setOrganizationColor(android.content.ComponentName, 33 * int) 34 */ 35 private static final int DEFAULT_ORGANIZATION_COLOR = 0xFF008080; // teal 36 37 private ComponentName deviceOwner; 38 private ComponentName profileOwner; 39 private List<ComponentName> deviceAdmins = new ArrayList<>(); 40 private List<String> permittedAccessibilityServices = new ArrayList<>(); 41 private List<String> permittedInputMethods = new ArrayList<>(); 42 private Map<String, Bundle> applicationRestrictionsMap = new HashMap<>(); 43 private CharSequence organizationName; 44 private int organizationColor; 45 private boolean isAutoTimeRequired; 46 47 private final Set<String> hiddenPackages = new HashSet<>(); 48 private final Set<String> wasHiddenPackages = new HashSet<>(); 49 private final Set<String> accountTypesWithManagementDisabled = new HashSet<>(); 50 private final Set<String> systemAppsEnabled = new HashSet<>(); 51 private final Set<String> uninstallBlockedPackages = new HashSet<>(); 52 53 public ShadowDevicePolicyManager() { 54 organizationColor = DEFAULT_ORGANIZATION_COLOR; 55 } 56 57 @Implementation 58 public boolean isDeviceOwnerApp(String packageName) { 59 return deviceOwner != null && deviceOwner.getPackageName().equals(packageName); 60 } 61 62 @Implementation 63 public boolean isProfileOwnerApp(String packageName) { 64 return profileOwner != null && profileOwner.getPackageName().equals(packageName); 65 } 66 67 @Implementation 68 public boolean isAdminActive(ComponentName who) { 69 return who != null && deviceAdmins.contains(who); 70 } 71 72 @Implementation 73 public List<ComponentName> getActiveAdmins() { 74 return deviceAdmins; 75 } 76 77 @Implementation 78 public void addUserRestriction(ComponentName admin, String key) { 79 enforceActiveAdmin(admin); 80 getShadowUserManager().setUserRestriction(Process.myUserHandle(), key, true); 81 } 82 83 @Implementation 84 public void clearUserRestriction(ComponentName admin, String key) { 85 enforceActiveAdmin(admin); 86 getShadowUserManager().setUserRestriction(Process.myUserHandle(), key, false); 87 } 88 89 @Implementation 90 public void setApplicationHidden(ComponentName admin, String packageName, boolean hidden) { 91 enforceActiveAdmin(admin); 92 if (hidden) { 93 hiddenPackages.add(packageName); 94 wasHiddenPackages.add(packageName); 95 } else { 96 hiddenPackages.remove(packageName); 97 } 98 } 99 100 @Implementation 101 public boolean isApplicationHidden(ComponentName admin, String packageName) { 102 enforceActiveAdmin(admin); 103 return hiddenPackages.contains(packageName); 104 } 105 106 /** Returns {@code true} if the given {@code packageName} was ever hidden. */ 107 public boolean wasPackageEverHidden(String packageName) { 108 return wasHiddenPackages.contains(packageName); 109 } 110 111 @Implementation 112 public int enableSystemApp(ComponentName admin, String packageName) { 113 enforceActiveAdmin(admin); 114 systemAppsEnabled.add(packageName); 115 return 1; 116 } 117 118 /** Returns {@code true} if the given {@code packageName} was a system app and was enabled. */ 119 public boolean wasSystemAppEnabled(String packageName) { 120 return systemAppsEnabled.contains(packageName); 121 } 122 123 @Implementation 124 public void setUninstallBlocked( 125 ComponentName admin, String packageName, boolean uninstallBlocked) { 126 enforceActiveAdmin(admin); 127 if (uninstallBlocked) { 128 uninstallBlockedPackages.add(packageName); 129 } else { 130 uninstallBlockedPackages.remove(packageName); 131 } 132 } 133 134 @Implementation 135 public boolean isUninstallBlocked(ComponentName admin, String packageName) { 136 enforceActiveAdmin(admin); 137 return uninstallBlockedPackages.contains(packageName); 138 } 139 140 @Implementation(minSdk = JELLY_BEAN_MR2) 141 public String getDeviceOwner() { 142 return deviceOwner != null ? deviceOwner.getPackageName() : null; 143 } 144 145 @Implementation(minSdk = LOLLIPOP) 146 public ComponentName getProfileOwner() { 147 return profileOwner; 148 } 149 150 private ShadowUserManager getShadowUserManager() { 151 return shadowOf( 152 (UserManager) RuntimeEnvironment.application.getSystemService(Context.USER_SERVICE)); 153 } 154 155 /** Sets the admin as active admin and device owner. */ 156 public void setDeviceOwner(ComponentName admin) { 157 setActiveAdmin(admin); 158 deviceOwner = admin; 159 } 160 161 /** Sets the admin as active admin and profile owner. */ 162 public void setProfileOwner(ComponentName admin) { 163 setActiveAdmin(admin); 164 profileOwner = admin; 165 } 166 167 /** Sets the given {@code componentName} as one of the active admins. */ 168 public void setActiveAdmin(ComponentName componentName) { 169 deviceAdmins.add(componentName); 170 } 171 172 @Implementation 173 public Bundle getApplicationRestrictions(ComponentName admin, String packageName) { 174 enforceDeviceOwnerOrProfileOwner(admin); 175 return getApplicationRestrictions(packageName); 176 } 177 178 /** Returns all application restrictions of the {@code packageName} in a {@link Bundle}. */ 179 public Bundle getApplicationRestrictions(String packageName) { 180 Bundle bundle = applicationRestrictionsMap.get(packageName); 181 // If no restrictions were saved, DPM method should return an empty Bundle as per JavaDoc. 182 return bundle != null ? bundle : Bundle.EMPTY; 183 } 184 185 @Implementation 186 public void setApplicationRestrictions( 187 ComponentName admin, String packageName, Bundle applicationRestrictions) { 188 enforceDeviceOwnerOrProfileOwner(admin); 189 setApplicationRestrictions(packageName, applicationRestrictions); 190 } 191 192 /** 193 * Sets the application restrictions of the {@code packageName}. 194 * 195 * The new {@code applicationRestrictions} always completely overwrites any existing ones. 196 */ 197 public void setApplicationRestrictions(String packageName, Bundle applicationRestrictions) { 198 applicationRestrictionsMap.put(packageName, applicationRestrictions); 199 } 200 201 private void enforceProfileOwner(ComponentName admin) { 202 if (!admin.equals(profileOwner)) { 203 throw new SecurityException("[" + admin + "] is not a profile owner"); 204 } 205 } 206 207 private void enforceDeviceOwner(ComponentName admin) { 208 if (!admin.equals(deviceOwner)) { 209 throw new SecurityException("[" + admin + "] is not a device owner"); 210 } 211 } 212 213 private void enforceDeviceOwnerOrProfileOwner(ComponentName admin) { 214 if (!admin.equals(deviceOwner) && !admin.equals(profileOwner)) { 215 throw new SecurityException("[" + admin + "] is neither a device owner nor a profile owner."); 216 } 217 } 218 219 private void enforceActiveAdmin(ComponentName admin) { 220 if (!deviceAdmins.contains(admin)) { 221 throw new SecurityException("[" + admin + "] is not an active device admin"); 222 } 223 } 224 225 @Implementation 226 public void setAccountManagementDisabled( 227 ComponentName admin, String accountType, boolean disabled) { 228 enforceDeviceOwnerOrProfileOwner(admin); 229 if (disabled) { 230 accountTypesWithManagementDisabled.add(accountType); 231 } else { 232 accountTypesWithManagementDisabled.remove(accountType); 233 } 234 } 235 236 @Implementation 237 public String[] getAccountTypesWithManagementDisabled() { 238 return accountTypesWithManagementDisabled.toArray(new String[0]); 239 } 240 241 /** 242 * Sets organization name. 243 * 244 * The API can only be called by profile owner since Android N and can be called by both of 245 * profile owner and device owner since Android O. 246 */ 247 @Implementation(minSdk = N) 248 public void setOrganizationName(ComponentName admin, @Nullable CharSequence name) { 249 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 250 enforceDeviceOwnerOrProfileOwner(admin); 251 } else { 252 enforceProfileOwner(admin); 253 } 254 255 if (TextUtils.isEmpty(name)) { 256 organizationName = null; 257 } else { 258 organizationName = name; 259 } 260 } 261 262 @Implementation(minSdk = N) 263 public void setOrganizationColor(ComponentName admin, int color) { 264 enforceProfileOwner(admin); 265 organizationColor = color; 266 } 267 268 /** 269 * Returns organization name. 270 * 271 * The API can only be called by profile owner since Android N. 272 * 273 * Android framework has a hidden API for getting the organization name for device owner since 274 * Android O. This method, however, is extended to return the organization name for device owners 275 * too to make testing of {@link #setOrganizationName(ComponentName, CharSequence)} easier for 276 * device owner cases. 277 */ 278 @Implementation(minSdk = N) 279 @Nullable 280 public CharSequence getOrganizationName(ComponentName admin) { 281 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 282 enforceDeviceOwnerOrProfileOwner(admin); 283 } else { 284 enforceProfileOwner(admin); 285 } 286 287 return organizationName; 288 } 289 290 @Implementation(minSdk = N) 291 public int getOrganizationColor(ComponentName admin) { 292 enforceProfileOwner(admin); 293 return organizationColor; 294 } 295 296 @Implementation 297 public void setAutoTimeRequired(ComponentName admin, boolean required) { 298 enforceDeviceOwnerOrProfileOwner(admin); 299 isAutoTimeRequired = required; 300 } 301 302 @Implementation 303 public boolean getAutoTimeRequired() { 304 return isAutoTimeRequired; 305 } 306 307 /** 308 * Sets permitted accessibility services. 309 * 310 * The API can be called by either a profile or device owner. 311 * 312 * This method does not check already enabled non-system accessibility services, so will always 313 * set the restriction and return true. 314 */ 315 @Implementation 316 public boolean setPermittedAccessibilityServices(ComponentName admin, List<String> packageNames) { 317 enforceDeviceOwnerOrProfileOwner(admin); 318 permittedAccessibilityServices = packageNames; 319 return true; 320 } 321 322 @Implementation 323 @Nullable 324 public List<String> getPermittedAccessibilityServices(ComponentName admin) { 325 enforceDeviceOwnerOrProfileOwner(admin); 326 return permittedAccessibilityServices; 327 } 328 329 /** 330 * Sets permitted input methods. 331 * 332 * The API can be called by either a profile or device owner. 333 * 334 * This method does not check already enabled non-system input methods, so will always set the 335 * restriction and return true. 336 */ 337 @Implementation 338 public boolean setPermittedInputMethods(ComponentName admin, List<String> packageNames) { 339 enforceDeviceOwnerOrProfileOwner(admin); 340 permittedInputMethods = packageNames; 341 return true; 342 } 343 344 @Implementation 345 @Nullable 346 public List<String> getPermittedInputMethods(ComponentName admin) { 347 enforceDeviceOwnerOrProfileOwner(admin); 348 return permittedInputMethods; 349 } 350 } 351