1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; 4 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; 5 import static android.os.Build.VERSION_CODES.LOLLIPOP; 6 import static android.os.Build.VERSION_CODES.M; 7 import static android.os.Build.VERSION_CODES.N; 8 import static android.os.Build.VERSION_CODES.N_MR1; 9 import static org.robolectric.shadow.api.Shadow.directlyOn; 10 11 import android.Manifest.permission; 12 import android.content.Context; 13 import android.content.pm.PackageManager; 14 import android.content.pm.UserInfo; 15 import android.os.Bundle; 16 import android.os.IUserManager; 17 import android.os.Process; 18 import android.os.UserHandle; 19 import android.os.UserManager; 20 import com.google.common.collect.BiMap; 21 import com.google.common.collect.HashBiMap; 22 import com.google.common.collect.ImmutableList; 23 import java.util.ArrayList; 24 import java.util.HashMap; 25 import java.util.List; 26 import java.util.Map; 27 import org.robolectric.annotation.Implementation; 28 import org.robolectric.annotation.Implements; 29 import org.robolectric.annotation.RealObject; 30 import org.robolectric.annotation.Resetter; 31 32 /** 33 * Robolectric implementation of {@link android.os.UserManager}. 34 */ 35 @Implements(value = UserManager.class, minSdk = JELLY_BEAN_MR1) 36 public class ShadowUserManager { 37 38 /** 39 * The default user ID user for secondary user testing, when the ID is not otherwise specified. 40 */ 41 public static final int DEFAULT_SECONDARY_USER_ID = 10; 42 43 public static final int FLAG_PRIMARY = UserInfo.FLAG_PRIMARY; 44 public static final int FLAG_ADMIN = UserInfo.FLAG_ADMIN; 45 public static final int FLAG_GUEST = UserInfo.FLAG_GUEST; 46 public static final int FLAG_RESTRICTED = UserInfo.FLAG_RESTRICTED; 47 48 private static Map<Integer, Integer> userPidMap = new HashMap<>(); 49 50 @RealObject private UserManager realObject; 51 52 private boolean userUnlocked = true; 53 private boolean managedProfile = false; 54 private boolean isSystemUser = true; 55 private Map<Integer, Bundle> userRestrictions = new HashMap<>(); 56 private BiMap<UserHandle, Long> userProfiles = HashBiMap.create(); 57 private Map<String, Bundle> applicationRestrictions = new HashMap<>(); 58 private long nextUserSerial = 0; 59 private Map<Integer, UserState> userState = new HashMap<>(); 60 private Map<Integer, UserInfo> userInfoMap = new HashMap<>(); 61 62 private Context context; 63 private boolean enforcePermissions; 64 private boolean canSwitchUser = false; 65 66 @Implementation __constructor__(Context context, IUserManager service)67 protected void __constructor__(Context context, IUserManager service) { 68 this.context = context; 69 addUser(UserHandle.USER_SYSTEM, "system_user", UserInfo.FLAG_PRIMARY | UserInfo.FLAG_ADMIN); 70 } 71 72 /** 73 * Compared to real Android, there is no check that the package name matches the application 74 * package name and the method returns instantly. 75 * 76 * @see #setApplicationRestrictions(String, Bundle) 77 */ 78 @Implementation(minSdk = JELLY_BEAN_MR2) getApplicationRestrictions(String packageName)79 protected Bundle getApplicationRestrictions(String packageName) { 80 Bundle bundle = applicationRestrictions.get(packageName); 81 return bundle != null ? bundle : new Bundle(); 82 } 83 84 /** 85 * Sets the value returned by {@link UserManager#getApplicationRestrictions(String)}. 86 */ setApplicationRestrictions(String packageName, Bundle restrictions)87 public void setApplicationRestrictions(String packageName, Bundle restrictions) { 88 applicationRestrictions.put(packageName, restrictions); 89 } 90 91 /** 92 * Adds a profile associated for the user that the calling process is running on. 93 * 94 * The user is assigned an arbitrary unique serial number. 95 * 96 * @return the user's serial number 97 */ addUserProfile(UserHandle userHandle)98 public long addUserProfile(UserHandle userHandle) { 99 long serialNumber = nextUserSerial++; 100 userProfiles.put(userHandle, serialNumber); 101 return serialNumber; 102 } 103 104 @Implementation(minSdk = LOLLIPOP) getUserProfiles()105 protected List<UserHandle> getUserProfiles() { 106 return ImmutableList.copyOf(userProfiles.keySet()); 107 } 108 109 @Implementation(minSdk = N) isUserUnlocked()110 protected boolean isUserUnlocked() { 111 return userUnlocked; 112 } 113 114 /** 115 * Setter for {@link UserManager#isUserUnlocked()} 116 */ setUserUnlocked(boolean userUnlocked)117 public void setUserUnlocked(boolean userUnlocked) { 118 this.userUnlocked = userUnlocked; 119 } 120 121 /** 122 * If permissions are enforced (see {@link #enforcePermissionChecks(boolean)}) and the application 123 * doesn't have the {@link android.Manifest.permission#MANAGE_USERS} permission, throws a 124 * {@link SecurityManager} exception. 125 * 126 * @return `false` by default, or the value specified via {@link #setManagedProfile(boolean)} 127 * @see #enforcePermissionChecks(boolean) 128 * @see #setManagedProfile(boolean) 129 */ 130 @Implementation(minSdk = LOLLIPOP) isManagedProfile()131 protected boolean isManagedProfile() { 132 if (enforcePermissions && !hasManageUsersPermission()) { 133 throw new SecurityException( 134 "You need MANAGE_USERS permission to: check if specified user a " + 135 "managed profile outside your profile group"); 136 } 137 return managedProfile; 138 } 139 enforcePermissionChecks(boolean enforcePermissions)140 public void enforcePermissionChecks(boolean enforcePermissions) { 141 this.enforcePermissions = enforcePermissions; 142 } 143 144 /** 145 * Setter for {@link UserManager#isManagedProfile()}. 146 */ setManagedProfile(boolean managedProfile)147 public void setManagedProfile(boolean managedProfile) { 148 this.managedProfile = managedProfile; 149 } 150 151 @Implementation(minSdk = LOLLIPOP) hasUserRestriction(String restrictionKey, UserHandle userHandle)152 protected boolean hasUserRestriction(String restrictionKey, UserHandle userHandle) { 153 Bundle bundle = userRestrictions.get(userHandle.getIdentifier()); 154 return bundle != null && bundle.getBoolean(restrictionKey); 155 } 156 setUserRestriction(UserHandle userHandle, String restrictionKey, boolean value)157 public void setUserRestriction(UserHandle userHandle, String restrictionKey, boolean value) { 158 Bundle bundle = getUserRestrictionsForUser(userHandle); 159 bundle.putBoolean(restrictionKey, value); 160 } 161 162 /** 163 * Removes all user restrictions set of a user identified by {@code userHandle}. 164 */ clearUserRestrictions(UserHandle userHandle)165 public void clearUserRestrictions(UserHandle userHandle) { 166 userRestrictions.remove(userHandle.getIdentifier()); 167 } 168 169 @Implementation(minSdk = JELLY_BEAN_MR2) getUserRestrictions(UserHandle userHandle)170 protected Bundle getUserRestrictions(UserHandle userHandle) { 171 return new Bundle(getUserRestrictionsForUser(userHandle)); 172 } 173 getUserRestrictionsForUser(UserHandle userHandle)174 private Bundle getUserRestrictionsForUser(UserHandle userHandle) { 175 Bundle bundle = userRestrictions.get(userHandle.getIdentifier()); 176 if (bundle == null) { 177 bundle = new Bundle(); 178 userRestrictions.put(userHandle.getIdentifier(), bundle); 179 } 180 return bundle; 181 } 182 183 /** 184 * @see #addUserProfile(UserHandle) 185 */ 186 @Implementation getSerialNumberForUser(UserHandle userHandle)187 protected long getSerialNumberForUser(UserHandle userHandle) { 188 Long result = userProfiles.get(userHandle); 189 return result == null ? -1L : result; 190 } 191 192 /** 193 * @deprecated prefer {@link #addUserProfile(UserHandle)} to ensure consistency of profiles known 194 * to the {@link UserManager}. Furthermore, calling this method for the current user, i.e: {@link 195 * Process#myUserHandle()} is no longer necessary as this user is always known to UserManager and 196 * has a preassigned serial number. 197 */ 198 @Deprecated setSerialNumberForUser(UserHandle userHandle, long serialNumber)199 public void setSerialNumberForUser(UserHandle userHandle, long serialNumber) { 200 userProfiles.put(userHandle, serialNumber); 201 } 202 203 /** 204 * @see #addUserProfile(UserHandle) 205 */ 206 @Implementation getUserForSerialNumber(long serialNumber)207 protected UserHandle getUserForSerialNumber(long serialNumber) { 208 return userProfiles.inverse().get(serialNumber); 209 } 210 hasManageUsersPermission()211 private boolean hasManageUsersPermission() { 212 return context.getPackageManager().checkPermission(permission.MANAGE_USERS, context.getPackageName()) == PackageManager.PERMISSION_GRANTED; 213 } 214 checkPermissions()215 private void checkPermissions() { 216 // TODO Ensure permisions 217 // throw new SecurityException("You need INTERACT_ACROSS_USERS or MANAGE_USERS 218 // permission " 219 // + "to: check " + name);throw new SecurityException(); 220 } 221 222 /** 223 * @return `false` by default, or the value specified via {@link #setIsDemoUser(boolean)} 224 */ 225 @Implementation(minSdk = N_MR1) isDemoUser()226 protected boolean isDemoUser() { 227 return getUserInfo(UserHandle.myUserId()).isDemo(); 228 } 229 230 /** 231 * Sets that the current user is a demo user; controls the return value of {@link 232 * UserManager#isDemoUser()}. 233 * 234 * @deprecated Use {@link ShadowUserManager#addUser(int, String, int)} to create a demo user 235 * instead of changing default user flags. 236 */ 237 @Deprecated setIsDemoUser(boolean isDemoUser)238 public void setIsDemoUser(boolean isDemoUser) { 239 UserInfo userInfo = getUserInfo(UserHandle.myUserId()); 240 if (isDemoUser) { 241 userInfo.flags |= UserInfo.FLAG_DEMO; 242 } else { 243 userInfo.flags &= ~UserInfo.FLAG_DEMO; 244 } 245 } 246 247 /** 248 * @return {@code false} by default, or the value specified via {@link #setIsAdminUser(boolean)} 249 */ 250 @Implementation(minSdk = N_MR1) isAdminUser()251 public boolean isAdminUser() { 252 return getUserInfo(UserHandle.myUserId()).isAdmin(); 253 } 254 255 /** 256 * Sets that the current user is an admin user; controls the return value of 257 * {@link UserManager#isAdminUser}. 258 */ setIsAdminUser(boolean isAdminUser)259 public void setIsAdminUser(boolean isAdminUser) { 260 UserInfo userInfo = getUserInfo(UserHandle.myUserId()); 261 if (isAdminUser) { 262 userInfo.flags |= UserInfo.FLAG_ADMIN; 263 } else { 264 userInfo.flags &= ~UserInfo.FLAG_ADMIN; 265 } 266 } 267 268 /** 269 * @return 'true' by default, or the value specified via {@link #setIsSystemUser(boolean)} 270 */ 271 @Implementation(minSdk = M) isSystemUser()272 protected boolean isSystemUser() { 273 if (isSystemUser == false) { 274 return false; 275 } else { 276 return directlyOn(realObject, UserManager.class, "isSystemUser"); 277 } 278 } 279 280 /** 281 * Sets that the current user is the system user; controls the return value of {@link 282 * UserManager#isSystemUser()}. 283 * 284 * @deprecated Use {@link ShadowUserManager#addUser(int, String, int)} to create a system user 285 * instead of changing default user flags. 286 */ 287 @Deprecated setIsSystemUser(boolean isSystemUser)288 public void setIsSystemUser(boolean isSystemUser) { 289 this.isSystemUser = isSystemUser; 290 } 291 292 /** 293 * Sets that the current user is the primary user; controls the return value of {@link 294 * UserManager#isPrimaryUser()}. 295 * 296 * @deprecated Use {@link ShadowUserManager#addUser(int, String, int)} to create a primary user 297 * instead of changing default user flags. 298 */ 299 @Deprecated setIsPrimaryUser(boolean isPrimaryUser)300 public void setIsPrimaryUser(boolean isPrimaryUser) { 301 UserInfo userInfo = getUserInfo(UserHandle.myUserId()); 302 if (isPrimaryUser) { 303 userInfo.flags |= UserInfo.FLAG_PRIMARY; 304 } else { 305 userInfo.flags &= ~UserInfo.FLAG_PRIMARY; 306 } 307 } 308 309 /** 310 * @return 'false' by default, or the value specified via {@link #setIsLinkedUser(boolean)} 311 */ 312 @Implementation(minSdk = JELLY_BEAN_MR2) isLinkedUser()313 protected boolean isLinkedUser() { 314 return getUserInfo(UserHandle.myUserId()).isRestricted(); 315 } 316 317 /** 318 * Sets that the current user is the linked user; controls the return value of {@link 319 * UserManager#isLinkedUser()}. 320 * 321 * @deprecated Use {@link ShadowUserManager#addUser(int, String, int)} to create a linked user 322 * instead of changing default user flags. 323 */ 324 @Deprecated setIsLinkedUser(boolean isLinkedUser)325 public void setIsLinkedUser(boolean isLinkedUser) { 326 UserInfo userInfo = getUserInfo(UserHandle.myUserId()); 327 if (isLinkedUser) { 328 userInfo.flags |= UserInfo.FLAG_RESTRICTED; 329 } else { 330 userInfo.flags &= ~UserInfo.FLAG_RESTRICTED; 331 } 332 } 333 334 /** 335 * Sets that the current user is the guest user; controls the return value of {@link 336 * UserManager#isGuestUser()}. 337 * 338 * @deprecated Use {@link ShadowUserManager#addUser(int, String, int)} to create a guest user 339 * instead of changing default user flags. 340 */ 341 @Deprecated setIsGuestUser(boolean isGuestUser)342 public void setIsGuestUser(boolean isGuestUser) { 343 UserInfo userInfo = getUserInfo(UserHandle.myUserId()); 344 if (isGuestUser) { 345 userInfo.flags |= UserInfo.FLAG_GUEST; 346 } else { 347 userInfo.flags &= ~UserInfo.FLAG_GUEST; 348 } 349 } 350 351 /** 352 * @see #setUserState(UserHandle, UserState) 353 */ 354 @Implementation isUserRunning(UserHandle handle)355 protected boolean isUserRunning(UserHandle handle) { 356 checkPermissions(); 357 UserState state = userState.get(handle.getIdentifier()); 358 359 if (state == UserState.STATE_RUNNING_LOCKED 360 || state == UserState.STATE_RUNNING_UNLOCKED 361 || state == UserState.STATE_RUNNING_UNLOCKING) { 362 return true; 363 } else { 364 return false; 365 } 366 } 367 368 /** 369 * @see #setUserState(UserHandle, UserState) 370 */ 371 @Implementation isUserRunningOrStopping(UserHandle handle)372 protected boolean isUserRunningOrStopping(UserHandle handle) { 373 checkPermissions(); 374 UserState state = userState.get(handle.getIdentifier()); 375 376 if (state == UserState.STATE_RUNNING_LOCKED 377 || state == UserState.STATE_RUNNING_UNLOCKED 378 || state == UserState.STATE_RUNNING_UNLOCKING 379 || state == UserState.STATE_STOPPING) { 380 return true; 381 } else { 382 return false; 383 } 384 } 385 386 /** 387 * Describes the current state of the user. State can be set using 388 * {@link #setUserState(UserHandle, UserState)}. 389 */ 390 public enum UserState { 391 // User is first coming up. 392 STATE_BOOTING, 393 // User is in the locked state. 394 STATE_RUNNING_LOCKED, 395 // User is in the unlocking state. 396 STATE_RUNNING_UNLOCKING, 397 // User is in the running state. 398 STATE_RUNNING_UNLOCKED, 399 // User is in the initial process of being stopped. 400 STATE_STOPPING, 401 // User is in the final phase of stopping, sending Intent.ACTION_SHUTDOWN. 402 STATE_SHUTDOWN 403 } 404 405 /** 406 * Sets the current state for a given user, see {@link UserManager#isUserRunning(UserHandle)} 407 * and {@link UserManager#isUserRunningOrStopping(UserHandle)} 408 */ setUserState(UserHandle handle, UserState state)409 public void setUserState(UserHandle handle, UserState state) { 410 userState.put(handle.getIdentifier(), state); 411 } 412 413 @Implementation getUsers()414 protected List<UserInfo> getUsers() { 415 return new ArrayList<UserInfo>(userInfoMap.values()); 416 } 417 418 @Implementation getUserInfo(int userHandle)419 protected UserInfo getUserInfo(int userHandle) { 420 return userInfoMap.get(userHandle); 421 } 422 423 /** 424 * Returns {@code true} by default, or the value specified via {@link #setCanSwitchUser(boolean)}. 425 */ 426 @Implementation(minSdk = N) canSwitchUsers()427 protected boolean canSwitchUsers() { 428 return canSwitchUser; 429 } 430 431 /** 432 * Sets whether switching users is allowed or not; controls the return value of {@link 433 * UserManager#canSwitchUser()} 434 */ setCanSwitchUser(boolean canSwitchUser)435 public void setCanSwitchUser(boolean canSwitchUser) { 436 this.canSwitchUser = canSwitchUser; 437 } 438 439 @Implementation(minSdk = JELLY_BEAN_MR1) removeUser(int userHandle)440 protected boolean removeUser(int userHandle) { 441 userInfoMap.remove(userHandle); 442 return true; 443 } 444 445 /** 446 * Switches the current user to {@code userHandle}. 447 * 448 * @param userId the integer handle of the user, where 0 is the primary user. 449 */ switchUser(int userId)450 public void switchUser(int userId) { 451 if (!userInfoMap.containsKey(userId)) { 452 throw new UnsupportedOperationException("Must add user before switching to it"); 453 } 454 455 ShadowProcess.setUid(userPidMap.get(userId)); 456 } 457 458 /** 459 * Creates a user with the specified name, userId and flags. 460 * 461 * @param id the unique id of user 462 * @param name name of the user 463 * @param flags 16 bits for user type. See {@link UserInfo#flags} 464 */ addUser(int id, String name, int flags)465 public void addUser(int id, String name, int flags) { 466 UserHandle userHandle = 467 id == UserHandle.USER_SYSTEM ? Process.myUserHandle() : new UserHandle(id); 468 addUserProfile(userHandle); 469 setSerialNumberForUser(userHandle, (long) id); 470 userInfoMap.put(id, new UserInfo(id, name, flags)); 471 userPidMap.put( 472 id, 473 id == UserHandle.USER_SYSTEM 474 ? Process.myUid() 475 : id * UserHandle.PER_USER_RANGE + ShadowProcess.getRandomApplicationUid()); 476 } 477 478 @Resetter reset()479 public static void reset() { 480 if (userPidMap != null && userPidMap.isEmpty() == false) { 481 ShadowProcess.setUid(userPidMap.get(UserHandle.USER_SYSTEM)); 482 483 userPidMap.clear(); 484 userPidMap.put(UserHandle.USER_SYSTEM, Process.myUid()); 485 } 486 } 487 } 488