All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robolectric.shadows.ShadowUserManager Maven / Gradle / Ivy

The newest version!
package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
import static android.os.Build.VERSION_CODES.N_MR1;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.R;
import static android.os.Build.VERSION_CODES.S;
import static android.os.Build.VERSION_CODES.TIRAMISU;
import static android.os.UserManager.RESTRICTION_SOURCE_SYSTEM;
import static android.os.UserManager.USER_TYPE_FULL_GUEST;
import static android.os.UserManager.USER_TYPE_FULL_RESTRICTED;
import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
import static org.robolectric.util.reflector.Reflector.reflector;

import android.Manifest.permission;
import android.accounts.Account;
import android.annotation.UserIdInt;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Bundle;
import android.os.IUserManager;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManager.EnforcingUser;
import com.google.common.base.Preconditions;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.HiddenApi;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers.ClassParameter;
import org.robolectric.util.reflector.Accessor;
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;

/** Robolectric implementation of {@link android.os.UserManager}. */
@Implements(value = UserManager.class)
public class ShadowUserManager {

  /**
   * The default user ID user for secondary user testing, when the ID is not otherwise specified.
   */
  public static final int DEFAULT_SECONDARY_USER_ID = 10;

  private static final int DEFAULT_MAX_SUPPORTED_USERS = 1;

  public static final int FLAG_PRIMARY = UserInfo.FLAG_PRIMARY;
  public static final int FLAG_ADMIN = UserInfo.FLAG_ADMIN;
  public static final int FLAG_GUEST = UserInfo.FLAG_GUEST;
  public static final int FLAG_RESTRICTED = UserInfo.FLAG_RESTRICTED;
  public static final int FLAG_DEMO = UserInfo.FLAG_DEMO;
  public static final int FLAG_MANAGED_PROFILE = UserInfo.FLAG_MANAGED_PROFILE;
  public static final int FLAG_PROFILE = UserInfo.FLAG_PROFILE;
  public static final int FLAG_FULL = UserInfo.FLAG_FULL;
  public static final int FLAG_SYSTEM = UserInfo.FLAG_SYSTEM;
  public static final int FLAG_MAIN = UserInfo.FLAG_MAIN;

  private final Object lock = new Object();

  @RealObject private UserManager realObject;
  private static UserManagerState userManagerState = new UserManagerState();

  private Context context;

  private Context getContext() {
    return context;
  }

  /**
   * Shadowing this is currently required because the real UserManager constructor stores the result
   * of context.getApplicationContext(), which may be null due to a separate Robolectric bug.
   */
  @Implementation
  protected void __constructor__(Context context, IUserManager service) {
    Shadow.invokeConstructor(
        UserManager.class,
        realObject,
        ClassParameter.from(Context.class, context),
        ClassParameter.from(IUserManager.class, service));
    this.context = context;
  }

  /**
   * Global UserManager state. Shared across {@link UserManager}s created in different {@link
   * Context}s.
   */
  static class UserManagerState {
    private int maxSupportedUsers = DEFAULT_MAX_SUPPORTED_USERS;
    private boolean isMultiUserSupported = false;
    private boolean isHeadlessSystemUserMode = false;
    private Boolean managedProfile;
    private Boolean cloneProfile;
    private boolean userUnlocked = true;
    private boolean isSystemUser = true;
    private volatile boolean isForegroundUser = true;

    private String seedAccountName;
    private String seedAccountType;
    private PersistableBundle seedAccountOptions;
    private boolean enforcePermissions;
    private int userSwitchability = UserManager.SWITCHABILITY_STATUS_OK;
    private final Set userAccounts = new HashSet<>();

    private final Map userPidMap = new HashMap<>();

    /** Holds the serial numbers for all users and profiles, indexed by UserHandle.id */
    private final BiMap userSerialNumbers = HashBiMap.create();

    /** Holds all UserStates, indexed by UserHandle.id */
    private final Map userState = new HashMap<>();

    /** Holds the UserInfo for all registered users and profiles, indexed by UserHandle.id */
    private final Map userInfoMap = new HashMap<>();

    /**
     * Each user holds a list of UserHandles of associated profiles and user itself. User is indexed
     * by UserHandle.id. See UserManager.getProfiles(userId).
     */
    private final Map> userProfilesListMap = new HashMap<>();

    private final Map userRestrictions = new HashMap<>();
    private final Map applicationRestrictions = new HashMap<>();

    /**
     * Holds whether or not a managed profile can be unlocked. If a profile is not in this map, it
     * is assume it can be unlocked.
     */
    private final Map profileIsLocked = new HashMap<>();

    private final Map userIcon = new HashMap<>();

    private int nextUserId = DEFAULT_SECONDARY_USER_ID;

    public UserManagerState() {
      int id = UserHandle.USER_SYSTEM;
      String name = "system_user";
      int flags = UserInfo.FLAG_PRIMARY | UserInfo.FLAG_ADMIN | UserInfo.FLAG_MAIN;

      userSerialNumbers.put(id, (long) id);
      // Start the user as shut down.
      userState.put(id, UserState.STATE_SHUTDOWN);

      // Update UserInfo regardless if was added or not
      userInfoMap.put(id, new UserInfo(id, name, flags));
      userProfilesListMap.put(id, new ArrayList<>());
      // getUserProfiles() includes user's handle
      userProfilesListMap.get(id).add(new UserHandle(id));
      userPidMap.put(id, Process.myUid());
    }
  }

  /**
   * Compared to real Android, there is no check that the package name matches the application
   * package name and the method returns instantly.
   *
   * @see #setApplicationRestrictions(String, Bundle)
   */
  @Implementation
  protected Bundle getApplicationRestrictions(String packageName) {
    Bundle bundle = userManagerState.applicationRestrictions.get(packageName);
    return bundle != null ? bundle : new Bundle();
  }

  /** Sets the value returned by {@link UserManager#getApplicationRestrictions(String)}. */
  public void setApplicationRestrictions(String packageName, Bundle restrictions) {
    userManagerState.applicationRestrictions.put(packageName, restrictions);
  }

  /**
   * Adds a profile associated for the user that the calling process is running on.
   *
   * 

The user is assigned an arbitrary unique serial number. * * @return the user's serial number * @deprecated use either addUser() or addProfile() */ @Deprecated public long addUserProfile(UserHandle userHandle) { addProfile(UserHandle.myUserId(), userHandle.getIdentifier(), "", 0); return userManagerState.userSerialNumbers.get(userHandle.getIdentifier()); } @Implementation protected List getUserProfiles() { ImmutableList.Builder builder = new ImmutableList.Builder<>(); List profiles = userManagerState.userProfilesListMap.get(UserHandle.myUserId()); if (profiles != null) { return builder.addAll(profiles).build(); } for (List profileList : userManagerState.userProfilesListMap.values()) { if (profileList.contains(Process.myUserHandle())) { return builder.addAll(profileList).build(); } } return ImmutableList.of(Process.myUserHandle()); } /** * If any profiles have been added using {@link #addProfile}, return those profiles. * *

Otherwise follow real android behaviour. */ @Implementation protected List getProfiles(int userHandle) { if (userManagerState.userProfilesListMap.containsKey(userHandle)) { ArrayList infos = new ArrayList<>(); for (UserHandle profileHandle : userManagerState.userProfilesListMap.get(userHandle)) { infos.add(userManagerState.userInfoMap.get(profileHandle.getIdentifier())); } return infos; } return reflector(UserManagerReflector.class, realObject).getProfiles(userHandle); } @Implementation(minSdk = R) protected List getEnabledProfiles() { ArrayList userHandles = new ArrayList<>(); for (UserHandle profileHandle : getAllProfiles()) { if (userManagerState.userInfoMap.get(profileHandle.getIdentifier()).isEnabled()) { userHandles.add(profileHandle); } } return userHandles; } @Implementation(minSdk = R) protected List getAllProfiles() { ArrayList userHandles = new ArrayList<>(); if (userManagerState.userProfilesListMap.containsKey(getContext().getUserId())) { userHandles.addAll(userManagerState.userProfilesListMap.get(getContext().getUserId())); return userHandles; } userHandles.add(UserHandle.of(getContext().getUserId())); return userHandles; } @Implementation protected UserInfo getProfileParent(int userId) { if (userManagerState.enforcePermissions && !hasManageUsersPermission()) { throw new SecurityException("Requires MANAGE_USERS permission"); } UserInfo profile = getUserInfo(userId); if (profile == null) { return null; } int parentUserId = profile.profileGroupId; if (parentUserId == userId || parentUserId == UserInfo.NO_PROFILE_GROUP_ID) { return null; } else { return getUserInfo(parentUserId); } } @Implementation(minSdk = R) protected UserHandle createProfile(String name, String userType, Set disallowedPackages) { int flags = getDefaultUserTypeFlags(userType); flags |= FLAG_PROFILE; // assume createProfile used with a profile userType if (userManagerState.enforcePermissions && !hasManageUsersPermission() && !hasCreateUsersPermission()) { throw new SecurityException( "You either need MANAGE_USERS or CREATE_USERS " + "permission to create a user with flags: " + flags); } if (userManagerState.userInfoMap.size() >= getMaxSupportedUsers()) { return null; } int profileId = userManagerState.nextUserId++; addProfile(getContext().getUserId(), profileId, name, flags); userManagerState.userInfoMap.get(profileId).userType = userType; return UserHandle.of(profileId); } private static int getDefaultUserTypeFlags(String userType) { switch (userType) { case UserManager.USER_TYPE_PROFILE_MANAGED: return FLAG_PROFILE | FLAG_MANAGED_PROFILE; case UserManager.USER_TYPE_FULL_SECONDARY: return FLAG_FULL; case UserManager.USER_TYPE_FULL_GUEST: return FLAG_FULL | FLAG_GUEST; case UserManager.USER_TYPE_FULL_DEMO: return FLAG_FULL | FLAG_DEMO; case UserManager.USER_TYPE_FULL_RESTRICTED: return FLAG_FULL | FLAG_RESTRICTED; case UserManager.USER_TYPE_FULL_SYSTEM: return FLAG_FULL | FLAG_SYSTEM; case UserManager.USER_TYPE_SYSTEM_HEADLESS: return FLAG_SYSTEM; default: return 0; } } /** Add a profile to be returned by {@link #getProfiles(int)}. */ public void addProfile( int userHandle, int profileUserHandle, String profileName, int profileFlags) { UserInfo profileUserInfo = new UserInfo(profileUserHandle, profileName, profileFlags); addProfile(userHandle, profileUserHandle, profileUserInfo); } /** Add a profile to be returned by {@link #getProfiles(int)}. */ public void addProfile(int userHandle, int profileUserHandle, UserInfo profileUserInfo) { // Don't override serial number set by setSerialNumberForUser() if (!userManagerState.userSerialNumbers.containsKey(profileUserHandle)) { // use UserHandle id as serial number unless setSerialNumberForUser() is used userManagerState.userSerialNumbers.put(profileUserHandle, (long) profileUserHandle); } profileUserInfo.profileGroupId = userHandle; UserInfo parentUserInfo = getUserInfo(userHandle); if (parentUserInfo != null) { parentUserInfo.profileGroupId = userHandle; } userManagerState.userInfoMap.put(profileUserHandle, profileUserInfo); // Insert profile to the belonging user's userProfilesList userManagerState.userProfilesListMap.putIfAbsent(userHandle, new ArrayList<>()); List list = userManagerState.userProfilesListMap.get(userHandle); UserHandle handle = new UserHandle(profileUserHandle); if (!list.contains(handle)) { list.add(handle); } } /** Setter for {@link UserManager#isUserUnlocked()} */ public void setUserUnlocked(boolean userUnlocked) { userManagerState.userUnlocked = userUnlocked; } @Implementation(minSdk = N) protected boolean isUserUnlocked() { return userManagerState.userUnlocked; } /** * @see #setUserState(UserHandle, UserState) */ @Implementation(minSdk = 24) protected boolean isUserUnlocked(UserHandle handle) { checkPermissions(); UserState state = userManagerState.userState.get(handle.getIdentifier()); return state == UserState.STATE_RUNNING_UNLOCKED; } /** * If permissions are enforced (see {@link #enforcePermissionChecks(boolean)}) and the application * doesn't have the {@link android.Manifest.permission#MANAGE_USERS} permission, throws a {@link * SecurityManager} exception. * * @return false by default, or the value specified via {@link #setManagedProfile(boolean)} * @see #enforcePermissionChecks(boolean) * @see #setManagedProfile(boolean) */ @Implementation protected boolean isManagedProfile() { if (userManagerState.enforcePermissions && !hasManageUsersPermission()) { throw new SecurityException( "You need MANAGE_USERS permission to: check if specified user a " + "managed profile outside your profile group"); } if (userManagerState.managedProfile != null) { return userManagerState.managedProfile; } if (RuntimeEnvironment.getApiLevel() >= R) { return isManagedProfile(getContext().getUserId()); } return false; } /** * If permissions are enforced (see {@link #enforcePermissionChecks(boolean)}) and the application * doesn't have the {@link android.Manifest.permission#MANAGE_USERS} permission, throws a {@link * SecurityManager} exception. * * @return true if the profile added has FLAG_MANAGED_PROFILE * @see #enforcePermissionChecks(boolean) * @see #addProfile(int, int, String, int) * @see #addUser(int, String, int) */ @Implementation(minSdk = N) protected boolean isManagedProfile(int userHandle) { if (userManagerState.enforcePermissions && !hasManageUsersPermission()) { throw new SecurityException( "You need MANAGE_USERS permission to: check if specified user a " + "managed profile outside your profile group"); } UserInfo info = getUserInfo(userHandle); return info != null && ((info.flags & FLAG_MANAGED_PROFILE) == FLAG_MANAGED_PROFILE); } public void enforcePermissionChecks(boolean enforcePermissions) { userManagerState.enforcePermissions = enforcePermissions; } /** Setter for {@link UserManager#isManagedProfile()}. */ public void setManagedProfile(boolean managedProfile) { userManagerState.managedProfile = managedProfile; } /** * If permissions are enforced (see {@link #enforcePermissionChecks(boolean)}) and the application * doesn't have the {@link android.Manifest.permission#MANAGE_USERS} permission, throws a {@link * SecurityManager} exception. * * @return true if the user is clone, or the value specified via {@link #setCloneProfile(boolean)} * @see #enforcePermissionChecks(boolean) * @see #setCloneProfile(boolean) */ @Implementation(minSdk = S) protected boolean isCloneProfile() { if (userManagerState.enforcePermissions && !hasManageUsersPermission()) { throw new SecurityException("You need MANAGE_USERS permission to: check isCloneProfile"); } if (userManagerState.cloneProfile != null) { return userManagerState.cloneProfile; } UserInfo info = getUserInfo(getContext().getUserId()); return info != null && info.isCloneProfile(); } /** Setter for {@link UserManager#isCloneProfile()}. */ public void setCloneProfile(boolean cloneProfile) { userManagerState.cloneProfile = cloneProfile; } @Implementation(minSdk = R) protected boolean isProfile() { if (userManagerState.enforcePermissions && !hasManageUsersPermission()) { throw new SecurityException( "You need INTERACT_ACROSS_USERS or MANAGE_USERS permission to: check isProfile"); } return getUserInfo(getContext().getUserId()).isProfile(); } @Implementation(minSdk = R) protected boolean isUserOfType(String userType) { if (userManagerState.enforcePermissions && !hasManageUsersPermission()) { throw new SecurityException("You need MANAGE_USERS permission to: check user type"); } UserInfo info = getUserInfo(getContext().getUserId()); return info != null && info.userType != null && info.userType.equals(userType); } @Implementation(minSdk = R) protected boolean isSameProfileGroup(UserHandle user, UserHandle otherUser) { if (userManagerState.enforcePermissions && !hasManageUsersPermission()) { throw new SecurityException( "You need MANAGE_USERS permission to: check if in the same profile group"); } UserInfo userInfo = userManagerState.userInfoMap.get(user.getIdentifier()); UserInfo otherUserInfo = userManagerState.userInfoMap.get(otherUser.getIdentifier()); if (userInfo == null || otherUserInfo == null || userInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID || otherUserInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) { return false; } return userInfo.profileGroupId == otherUserInfo.profileGroupId; } @Implementation protected boolean hasUserRestriction(String restrictionKey, UserHandle userHandle) { synchronized (lock) { Bundle bundle = userManagerState.userRestrictions.get(userHandle.getIdentifier()); return bundle != null && bundle.getBoolean(restrictionKey); } } /** * Shadows UserManager.setUserRestriction() API. This allows UserManager.hasUserRestriction() to * return meaningful results in test environment; thus, allowing test to verify the invoking of * UserManager.setUserRestriction(). */ @Implementation protected void setUserRestriction(String key, boolean value, UserHandle userHandle) { Bundle bundle = getUserRestrictionsForUser(userHandle); synchronized (lock) { bundle.putBoolean(key, value); } } @Implementation protected void setUserRestriction(String key, boolean value) { setUserRestriction(key, value, Process.myUserHandle()); } /** * @deprecated When possible, please use the real Android framework API {@link * UserManager#setUserRestriction()}. */ @Deprecated public void setUserRestriction(UserHandle userHandle, String restrictionKey, boolean value) { setUserRestriction(restrictionKey, value, userHandle); } /** Removes all user restrictions set of a user identified by {@code userHandle}. */ public void clearUserRestrictions(UserHandle userHandle) { userManagerState.userRestrictions.remove(userHandle.getIdentifier()); } @Implementation protected Bundle getUserRestrictions(UserHandle userHandle) { return new Bundle(getUserRestrictionsForUser(userHandle)); } private Bundle getUserRestrictionsForUser(UserHandle userHandle) { synchronized (lock) { Bundle bundle = userManagerState.userRestrictions.get(userHandle.getIdentifier()); if (bundle == null) { bundle = new Bundle(); userManagerState.userRestrictions.put(userHandle.getIdentifier(), bundle); } return bundle; } } /** * @see #addProfile(int, int, String, int) * @see #addUser(int, String, int) */ @Implementation protected long getSerialNumberForUser(UserHandle userHandle) { Long result = userManagerState.userSerialNumbers.get(userHandle.getIdentifier()); return result == null ? -1L : result; } /** * {@link #addUser} uses UserHandle for serialNumber. setSerialNumberForUser() allows assigning an * arbitrary serialNumber. Some test use serialNumber!=0 as secondary user check, so it's * necessary to "fake" the serialNumber to a non-zero value. */ public void setSerialNumberForUser(UserHandle userHandle, long serialNumber) { userManagerState.userSerialNumbers.put(userHandle.getIdentifier(), serialNumber); } /** * @see #addProfile(int, int, String, int) * @see #addUser(int, String, int) */ @Implementation protected UserHandle getUserForSerialNumber(long serialNumber) { Integer userHandle = userManagerState.userSerialNumbers.inverse().get(serialNumber); return userHandle == null ? null : new UserHandle(userHandle); } /** * @see #addProfile(int, int, String, int) * @see #addUser(int, String, int) */ @Implementation protected int getUserSerialNumber(@UserIdInt int userHandle) { Long result = userManagerState.userSerialNumbers.get(userHandle); return result != null ? result.intValue() : -1; } private String getUserName(@UserIdInt int userHandle) { UserInfo user = getUserInfo(userHandle); return user == null ? "" : user.name; } /** * Returns the name of the user. * *

On real Android, if a UserHandle.USER_SYSTEM user is found but does not have a name, it will * return a name like "Owner". In Robolectric, the USER_SYSTEM user always has a name. */ @Implementation(minSdk = Q) protected String getUserName() { if (RuntimeEnvironment.getApiLevel() >= R) { return getUserName(getContext().getUserId()); } return getUserName(UserHandle.myUserId()); } @Implementation(minSdk = R) protected void setUserName(String name) { if (userManagerState.enforcePermissions && !hasManageUsersPermission()) { throw new SecurityException("You need MANAGE_USERS permission to: rename users"); } UserInfo user = getUserInfo(getContext().getUserId()); user.name = name; } @Implementation(minSdk = Q) protected Bitmap getUserIcon() { if (userManagerState.enforcePermissions && !hasManageUsersPermission() && !hasGetAccountsPrivilegedPermission()) { throw new SecurityException( "You need MANAGE_USERS or GET_ACCOUNTS_PRIVILEGED permissions to: get user icon"); } int userId = UserHandle.myUserId(); if (RuntimeEnvironment.getApiLevel() >= R) { userId = getContext().getUserId(); } return userManagerState.userIcon.get(userId); } @Implementation(minSdk = Q) protected void setUserIcon(Bitmap icon) { if (userManagerState.enforcePermissions && !hasManageUsersPermission()) { throw new SecurityException("You need MANAGE_USERS permission to: update users"); } int userId = UserHandle.myUserId(); if (RuntimeEnvironment.getApiLevel() >= R) { userId = getContext().getUserId(); } userManagerState.userIcon.put(userId, icon); } /** * @return user id for given user serial number. */ @HiddenApi @Implementation @UserIdInt protected int getUserHandle(int serialNumber) { Integer userHandle = userManagerState.userSerialNumbers.inverse().get((long) serialNumber); return userHandle == null ? -1 : userHandle; } @HiddenApi @Implementation(minSdk = R) protected List getUserHandles(boolean excludeDying) { ArrayList userHandles = new ArrayList<>(); for (int id : userManagerState.userSerialNumbers.keySet()) { userHandles.addAll(userManagerState.userProfilesListMap.get(id)); } return userHandles; } @HiddenApi @Implementation protected static int getMaxSupportedUsers() { return userManagerState.maxSupportedUsers; } public void setMaxSupportedUsers(int maxSupportedUsers) { userManagerState.maxSupportedUsers = maxSupportedUsers; } private boolean hasManageUsersPermission() { return getContext() .getPackageManager() .checkPermission(permission.MANAGE_USERS, getContext().getPackageName()) == PackageManager.PERMISSION_GRANTED; } private boolean hasCreateUsersPermission() { return getContext() .getPackageManager() .checkPermission(permission.CREATE_USERS, getContext().getPackageName()) == PackageManager.PERMISSION_GRANTED; } private boolean hasModifyQuietModePermission() { return getContext() .getPackageManager() .checkPermission(permission.MODIFY_QUIET_MODE, getContext().getPackageName()) == PackageManager.PERMISSION_GRANTED; } private boolean hasGetAccountsPrivilegedPermission() { return getContext() .getPackageManager() .checkPermission(permission.GET_ACCOUNTS_PRIVILEGED, getContext().getPackageName()) == PackageManager.PERMISSION_GRANTED; } private void checkPermissions() { // TODO Ensure permissions // throw new SecurityException("You need INTERACT_ACROSS_USERS or MANAGE_USERS // permission " // + "to: check " + name);throw new SecurityException(); } /** * @return false by default, or the value specified via {@link #setIsDemoUser(boolean)} */ @Implementation(minSdk = N_MR1) protected boolean isDemoUser() { return getUserInfo(UserHandle.myUserId()).isDemo(); } /** * Sets that the current user is a demo user; controls the return value of {@link * UserManager#isDemoUser()}. * * @deprecated Use {@link ShadowUserManager#addUser(int, String, int)} to create a demo user * instead of changing default user flags. */ @Deprecated public void setIsDemoUser(boolean isDemoUser) { UserInfo userInfo = getUserInfo(UserHandle.myUserId()); if (isDemoUser) { userInfo.flags |= UserInfo.FLAG_DEMO; } else { userInfo.flags &= ~UserInfo.FLAG_DEMO; } } /** * @return 'true' by default, or the value specified via {@link #setIsSystemUser(boolean)} */ @Implementation(minSdk = M) protected boolean isSystemUser() { if (userManagerState.isSystemUser == false) { return false; } else { return reflector(UserManagerReflector.class, realObject).isSystemUser(); } } /** * Sets that the current user is the system user; controls the return value of {@link * UserManager#isSystemUser()}. * * @deprecated Use {@link ShadowUserManager#addUser(int, String, int)} to create a system user * instead of changing default user flags. */ @Deprecated public void setIsSystemUser(boolean isSystemUser) { userManagerState.isSystemUser = isSystemUser; } /** * Sets that the current user is the primary user; controls the return value of {@link * UserManager#isPrimaryUser()}. * * @deprecated Use {@link ShadowUserManager#addUser(int, String, int)} to create a primary user * instead of changing default user flags. */ @Deprecated public void setIsPrimaryUser(boolean isPrimaryUser) { UserInfo userInfo = getUserInfo(UserHandle.myUserId()); if (isPrimaryUser) { userInfo.flags |= UserInfo.FLAG_PRIMARY; } else { userInfo.flags &= ~UserInfo.FLAG_PRIMARY; } } /** * @return 'false' by default, or the value specified via {@link #setIsLinkedUser(boolean)} */ @Implementation protected boolean isLinkedUser() { return isRestrictedProfile(); } /** * Sets that the current user is the linked user; controls the return value of {@link * UserManager#isLinkedUser()}. * * @deprecated Use {@link ShadowUserManager#addUser(int, String, int)} to create a linked user * instead of changing default user flags. */ @Deprecated public void setIsLinkedUser(boolean isLinkedUser) { setIsRestrictedProfile(isLinkedUser); } /** * Returns 'false' by default, or the value specified via {@link * #setIsRestrictedProfile(boolean)}. */ @Implementation(minSdk = P) protected boolean isRestrictedProfile() { return getUserInfo(UserHandle.myUserId()).isRestricted(); } /** * Sets this process running under a restricted profile; controls the return value of {@link * UserManager#isRestrictedProfile()}. * * @deprecated use {@link ShadowUserManager#addUser()} instead */ @Deprecated public void setIsRestrictedProfile(boolean isRestrictedProfile) { if (RuntimeEnvironment.getApiLevel() >= R) { setUserType(isRestrictedProfile ? USER_TYPE_FULL_RESTRICTED : USER_TYPE_FULL_SECONDARY); return; } UserInfo userInfo = getUserInfo(UserHandle.myUserId()); if (isRestrictedProfile) { userInfo.flags |= UserInfo.FLAG_RESTRICTED; } else { userInfo.flags &= ~UserInfo.FLAG_RESTRICTED; } } /** * Sets that the current user is the guest user; controls the return value of {@link * UserManager#isGuestUser()}. * * @deprecated Use {@link ShadowUserManager#addUser(int, String, int)} to create a guest user * instead of changing default user flags. */ @Deprecated public void setIsGuestUser(boolean isGuestUser) { if (RuntimeEnvironment.getApiLevel() >= R) { setUserType(isGuestUser ? USER_TYPE_FULL_GUEST : USER_TYPE_FULL_SECONDARY); return; } UserInfo userInfo = getUserInfo(UserHandle.myUserId()); if (isGuestUser) { userInfo.flags |= UserInfo.FLAG_GUEST; } else { userInfo.flags &= ~UserInfo.FLAG_GUEST; } } public void setIsUserEnabled(int userId, boolean enabled) { UserInfo userInfo = getUserInfo(userId); if (enabled) { userInfo.flags &= ~UserInfo.FLAG_DISABLED; } else { userInfo.flags |= UserInfo.FLAG_DISABLED; } } /** * @see #setUserState(UserHandle, UserState) */ @Implementation protected boolean isUserRunning(UserHandle handle) { checkPermissions(); UserState state = userManagerState.userState.get(handle.getIdentifier()); if (state == UserState.STATE_RUNNING_LOCKED || state == UserState.STATE_RUNNING_UNLOCKED || state == UserState.STATE_RUNNING_UNLOCKING) { return true; } else { return false; } } /** * @see #setUserState(UserHandle, UserState) */ @Implementation protected boolean isUserRunningOrStopping(UserHandle handle) { checkPermissions(); UserState state = userManagerState.userState.get(handle.getIdentifier()); if (state == UserState.STATE_RUNNING_LOCKED || state == UserState.STATE_RUNNING_UNLOCKED || state == UserState.STATE_RUNNING_UNLOCKING || state == UserState.STATE_STOPPING) { return true; } else { return false; } } /** * @see #setUserState(UserHandle, UserState) */ @Implementation(minSdk = R) protected boolean isUserUnlockingOrUnlocked(UserHandle handle) { checkPermissions(); UserState state = userManagerState.userState.get(handle.getIdentifier()); return state == UserState.STATE_RUNNING_UNLOCKING || state == UserState.STATE_RUNNING_UNLOCKED; } /** * Describes the current state of the user. State can be set using {@link * #setUserState(UserHandle, UserState)}. */ public enum UserState { // User is first coming up. STATE_BOOTING, // User is in the locked state. STATE_RUNNING_LOCKED, // User is in the unlocking state. STATE_RUNNING_UNLOCKING, // User is in the running state. STATE_RUNNING_UNLOCKED, // User is in the initial process of being stopped. STATE_STOPPING, // User is in the final phase of stopping, sending Intent.ACTION_SHUTDOWN. STATE_SHUTDOWN } /** * Sets the current state for a given user, see {@link UserManager#isUserRunning(UserHandle)} and * {@link UserManager#isUserRunningOrStopping(UserHandle)} */ public void setUserState(UserHandle handle, UserState state) { userManagerState.userState.put(handle.getIdentifier(), state); } /** * Query whether the quiet mode is enabled for a managed profile. * *

This method checks whether the user handle corresponds to a managed profile, and then query * its state. When quiet, the user is not running. */ @Implementation(minSdk = N) protected boolean isQuietModeEnabled(UserHandle userHandle) { // Return false if this is not a managed profile (this is the OS's behavior). if (!isManagedProfileWithoutPermission(userHandle)) { return false; } UserInfo info = getUserInfo(userHandle.getIdentifier()); return (info.flags & UserInfo.FLAG_QUIET_MODE) == UserInfo.FLAG_QUIET_MODE; } /** * Request the quiet mode. * *

This will succeed unless {@link #setProfileIsLocked(UserHandle, boolean)} is called with * {@code true} for the managed profile, in which case it will always fail. */ @Implementation(minSdk = Q) protected boolean requestQuietModeEnabled(boolean enableQuietMode, UserHandle userHandle) { if (userManagerState.enforcePermissions && !hasManageUsersPermission() && !hasModifyQuietModePermission()) { throw new SecurityException("Requires MANAGE_USERS or MODIFY_QUIET_MODE permission"); } Preconditions.checkArgument(isManagedProfileWithoutPermission(userHandle)); int userProfileHandle = userHandle.getIdentifier(); UserInfo info = getUserInfo(userHandle.getIdentifier()); if (enableQuietMode) { userManagerState.userState.put(userProfileHandle, UserState.STATE_SHUTDOWN); info.flags |= UserInfo.FLAG_QUIET_MODE; } else { if (userManagerState.profileIsLocked.getOrDefault(userProfileHandle, false)) { return false; } userManagerState.userState.put(userProfileHandle, UserState.STATE_RUNNING_UNLOCKED); info.flags &= ~UserInfo.FLAG_QUIET_MODE; } if (enableQuietMode) { sendQuietModeBroadcast(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE, userHandle); } else { sendQuietModeBroadcast(Intent.ACTION_MANAGED_PROFILE_AVAILABLE, userHandle); sendQuietModeBroadcast(Intent.ACTION_MANAGED_PROFILE_UNLOCKED, userHandle); } return true; } /** * If the current application has the necessary rights, it will receive the background action too. */ protected void sendQuietModeBroadcast(String action, UserHandle profileHandle) { Intent intent = new Intent(action); intent.putExtra(Intent.EXTRA_USER, profileHandle); intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY); // Send the broadcast to the context-registered receivers. getContext().sendBroadcast(intent); } /** * Check if a profile is managed, not checking permissions. * *

This is useful to implement other methods. */ private boolean isManagedProfileWithoutPermission(UserHandle userHandle) { UserInfo info = getUserInfo(userHandle.getIdentifier()); return (info != null && ((info.flags & FLAG_MANAGED_PROFILE) == FLAG_MANAGED_PROFILE)); } public void setProfileIsLocked(UserHandle profileHandle, boolean isLocked) { userManagerState.profileIsLocked.put(profileHandle.getIdentifier(), isLocked); } @Implementation(minSdk = Build.VERSION_CODES.N) protected long[] getSerialNumbersOfUsers(boolean excludeDying) { return getUsers().stream() .map(userInfo -> getUserSerialNumber(userInfo.getUserHandle().getIdentifier())) .mapToLong(l -> l) .toArray(); } @Implementation protected List getUsers() { return new ArrayList<>(userManagerState.userInfoMap.values()); } @Implementation protected UserInfo getUserInfo(int userHandle) { return userManagerState.userInfoMap.get(userHandle); } /** * Sets whether switching users is allowed or not; controls the return value of {@link * UserManager#canSwitchUser()} * * @deprecated use {@link #setUserSwitchability} instead */ @Deprecated public void setCanSwitchUser(boolean canSwitchUser) { setUserSwitchability( canSwitchUser ? UserManager.SWITCHABILITY_STATUS_OK : UserManager.SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED); } @Implementation(minSdk = Build.VERSION_CODES.N) protected String getSeedAccountName() { return userManagerState.seedAccountName; } /** Setter for {@link UserManager#getSeedAccountName()} */ public void setSeedAccountName(String seedAccountName) { userManagerState.seedAccountName = seedAccountName; } @Implementation(minSdk = Build.VERSION_CODES.N) protected String getSeedAccountType() { return userManagerState.seedAccountType; } /** Setter for {@link UserManager#getSeedAccountType()} */ public void setSeedAccountType(String seedAccountType) { userManagerState.seedAccountType = seedAccountType; } @Implementation(minSdk = Build.VERSION_CODES.N) protected PersistableBundle getSeedAccountOptions() { return userManagerState.seedAccountOptions; } /** Setter for {@link UserManager#getSeedAccountOptions()} */ public void setSeedAccountOptions(PersistableBundle seedAccountOptions) { userManagerState.seedAccountOptions = seedAccountOptions; } @Implementation(minSdk = Build.VERSION_CODES.N) protected void clearSeedAccountData() { userManagerState.seedAccountName = null; userManagerState.seedAccountType = null; userManagerState.seedAccountOptions = null; } @Implementation protected boolean removeUser(int userHandle) { if (!userManagerState.userInfoMap.containsKey(userHandle)) { return false; } userManagerState.userInfoMap.remove(userHandle); userManagerState.userPidMap.remove(userHandle); userManagerState.userSerialNumbers.remove(userHandle); userManagerState.userState.remove(userHandle); userManagerState.userRestrictions.remove(userHandle); userManagerState.profileIsLocked.remove(userHandle); userManagerState.userIcon.remove(userHandle); userManagerState.userProfilesListMap.remove(userHandle); // if it's a profile, remove from the belong list in userManagerState.userProfilesListMap UserHandle profileHandle = new UserHandle(userHandle); for (List list : userManagerState.userProfilesListMap.values()) { if (list.remove(profileHandle)) { break; } } return true; } @Implementation(minSdk = Q) protected boolean removeUser(UserHandle user) { return removeUser(user.getIdentifier()); } @Implementation(minSdk = TIRAMISU) protected int removeUserWhenPossible(UserHandle user, boolean overrideDevicePolicy) { return removeUser(user.getIdentifier()) ? UserManager.REMOVE_RESULT_REMOVED : UserManager.REMOVE_RESULT_ERROR_UNKNOWN; } @Implementation(minSdk = N) protected static boolean supportsMultipleUsers() { return userManagerState.isMultiUserSupported; } /** * Sets whether multiple users are supported; controls the return value of {@link * UserManager#supportsMultipleUser}. */ public void setSupportsMultipleUsers(boolean isMultiUserSupported) { userManagerState.isMultiUserSupported = isMultiUserSupported; } /** * Switches the current user to {@code userHandle}. * * @param userId the integer handle of the user, where 0 is the primary user. */ public void switchUser(int userId) { if (!userManagerState.userInfoMap.containsKey(userId)) { throw new UnsupportedOperationException("Must add user before switching to it"); } ShadowProcess.setUid(userManagerState.userPidMap.get(userId)); Application application = (Application) getContext().getApplicationContext(); ShadowContextImpl shadowContext = Shadow.extract(application.getBaseContext()); shadowContext.setUserId(userId); if (RuntimeEnvironment.getApiLevel() >= R) { reflector(UserManagerReflector.class, realObject).setUserId(userId); } } /** * Creates a user with the specified name, userId and flags. * * @param id the unique id of user * @param name name of the user * @param flags 16 bits for user type. See {@link UserInfo#flags} * @return a handle to the new user */ public UserHandle addUser(int id, String name, int flags) { UserHandle userHandle = id == UserHandle.USER_SYSTEM ? Process.myUserHandle() : new UserHandle(id); // Don't override serial number set by setSerialNumberForUser() if (!userManagerState.userSerialNumbers.containsKey(id)) { // use UserHandle id as serial number unless setSerialNumberForUser() is used userManagerState.userSerialNumbers.put(id, (long) id); } // Start the user as shut down. userManagerState.userState.put(id, UserState.STATE_SHUTDOWN); // Update UserInfo regardless if was added or not userManagerState.userInfoMap.put(id, new UserInfo(id, name, flags)); if (!userManagerState.userProfilesListMap.containsKey(id)) { userManagerState.userProfilesListMap.put(id, new ArrayList<>()); // getUserProfiles() includes user's handle userManagerState.userProfilesListMap.get(id).add(new UserHandle(id)); userManagerState.userPidMap.put( id, id == UserHandle.USER_SYSTEM ? Process.myUid() : id * UserHandle.PER_USER_RANGE + ShadowProcess.getRandomApplicationUid()); } return userHandle; } /** * Returns {@code true} by default, or the value specified via {@link #setCanSwitchUser(boolean)}. */ @Implementation(minSdk = N, maxSdk = Q) protected boolean canSwitchUsers() { return getUserSwitchability() == UserManager.SWITCHABILITY_STATUS_OK; } @Implementation(minSdk = Q) protected int getUserSwitchability() { return userManagerState.userSwitchability; } /** Sets the user switchability for all users. */ public void setUserSwitchability(int switchability) { userManagerState.userSwitchability = switchability; } @Implementation(minSdk = R) protected boolean hasUserRestrictionForUser(String restrictionKey, UserHandle userHandle) { return hasUserRestriction(restrictionKey, userHandle); } private void setUserType(String userType) { UserInfo userInfo = getUserInfo(UserHandle.myUserId()); userInfo.userType = userType; } /** * Request the quiet mode. * *

If {@link #setProfileIsLocked(UserHandle, boolean)} is called with {@code true} for the * managed profile a request to disable the quiet mode will fail and return {@code false} (i.e. as * if the user refused to authenticate). Otherwise, the call will always succeed and return {@code * true}. * *

This method simply re-directs to {@link ShadowUserManager#requestQuietModeEnabled(boolean, * UserHandle)} as it already has the desired behavior irrespective of the flag's value. */ @Implementation(minSdk = R) protected boolean requestQuietModeEnabled( boolean enableQuietMode, UserHandle userHandle, int flags) { return requestQuietModeEnabled(enableQuietMode, userHandle); } @Implementation(minSdk = S) protected static boolean isHeadlessSystemUserMode() { return userManagerState.isHeadlessSystemUserMode; } /** Updates headless system user mode. */ public static void setHeadlessSystemUserMode(boolean isEnabled) { userManagerState.isHeadlessSystemUserMode = isEnabled; } @Implementation(minSdk = TIRAMISU) protected Bundle getUserRestrictions() { return getUserRestrictions(UserHandle.getUserHandleForUid(Process.myUid())); } @Implementation(minSdk = TIRAMISU) protected boolean hasUserRestrictionForUser(String restrictionKey, int userId) { Bundle bundle = getUserRestrictions(UserHandle.getUserHandleForUid(userId)); return bundle != null && bundle.getBoolean(restrictionKey); } @Resetter public static void reset() { userManagerState = new UserManagerState(); } @ForType(UserManager.class) interface UserManagerReflector { @Direct List getProfiles(int userHandle); @Direct boolean isSystemUser(); @Accessor("mUserId") void setUserId(int userId); } @Implementation(minSdk = TIRAMISU) protected boolean someUserHasAccount(String accountName, String accountType) { return userManagerState.userAccounts.contains(new Account(accountName, accountType)); } /** Setter for {@link UserManager#someUserHasAccount(String, String)}. */ public void setSomeUserHasAccount(String accountName, String accountType) { userManagerState.userAccounts.add(new Account(accountName, accountType)); } /** Removes user account set via {@link #setSomeUserHasAccount(String, String)}. */ public void removeSomeUserHasAccount(String accountName, String accountType) { userManagerState.userAccounts.remove(new Account(accountName, accountType)); } /** Sets whether or not the current user is the foreground user. */ public void setUserForeground(boolean foreground) { userManagerState.isForegroundUser = foreground; } @Implementation(minSdk = S) protected boolean isUserForeground() { return userManagerState.isForegroundUser; } @Implementation(minSdk = O) protected List getUserRestrictionSources( String restriction, UserHandle userHandle) { List sources = new ArrayList<>(); if (hasUserRestriction(restriction, userHandle)) { sources.add(new EnforcingUser(userHandle.getIdentifier(), RESTRICTION_SOURCE_SYSTEM)); } return sources; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy