/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.car.userlib; import android.Manifest; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.UserInfo; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.sysprop.CarProperties; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.RoSystemProperties; import com.android.internal.util.UserIcons; import com.google.android.collect.Sets; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Set; /** * Helper class for {@link UserManager}, this is meant to be used by builds that support * Multi-user model with headless user 0. User 0 is not associated with a real person, and * can not be brought to foreground. * *
This class provides method for user management, including creating, removing, adding
* and switching users. Methods related to get users will exclude system user by default.
*
* @hide
*/
public final class CarUserManagerHelper {
private static final String TAG = "CarUserManagerHelper";
private static final int BOOT_USER_NOT_FOUND = -1;
/**
* Default set of restrictions for Non-Admin users.
*/
private static final Set Restrictions are written to disk and persistent across boots.
*/
public void initDefaultGuestRestrictions() {
Bundle defaultGuestRestrictions = new Bundle();
for (String restriction : DEFAULT_GUEST_RESTRICTIONS) {
defaultGuestRestrictions.putBoolean(restriction, true);
}
mUserManager.setDefaultGuestRestrictions(defaultGuestRestrictions);
}
/**
* Returns {@code true} if the system is in the headless user 0 model.
*
* @return {@boolean true} if headless system user.
*/
public boolean isHeadlessSystemUser() {
return RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER;
}
/**
* Gets UserInfo for the system user.
*
* @return {@link UserInfo} for the system user.
*/
public UserInfo getSystemUserInfo() {
return mUserManager.getUserInfo(UserHandle.USER_SYSTEM);
}
/**
* Gets UserInfo for the current foreground user.
*
* Concept of foreground user is relevant for the multi-user deployment. Foreground user
* corresponds to the currently "logged in" user.
*
* @return {@link UserInfo} for the foreground user.
*/
public UserInfo getCurrentForegroundUserInfo() {
return mUserManager.getUserInfo(getCurrentForegroundUserId());
}
/**
* @return Id of the current foreground user.
*/
public int getCurrentForegroundUserId() {
return mActivityManager.getCurrentUser();
}
/**
* Gets UserInfo for the user running the caller process.
*
* Differentiation between foreground user and current process user is relevant for
* multi-user deployments.
*
* Some multi-user aware components (like SystemUI) needs to run a singleton component
* in system user. Current process user is always the same for that component, even when
* the foreground user changes.
*
* @return {@link UserInfo} for the user running the current process.
*/
public UserInfo getCurrentProcessUserInfo() {
return mUserManager.getUserInfo(getCurrentProcessUserId());
}
/**
* @return Id for the user running the current process.
*/
public int getCurrentProcessUserId() {
return UserHandle.myUserId();
}
/**
* Gets all the existing users on the system that are not currently running as
* the foreground user.
* These are all the users that can be switched to from the foreground user.
*
* @return List of {@code UserInfo} for each user that is not the foreground user.
*/
public List It excludes system user in headless system user model.
*
* @return Maximum number of users that can be present on the device.
*/
public int getMaxSupportedUsers() {
if (isHeadlessSystemUser()) {
return mTestableFrameworkWrapper.userManagerGetMaxSupportedUsers() - 1;
}
return mTestableFrameworkWrapper.userManagerGetMaxSupportedUsers();
}
/**
* Get the maximum number of real (non-guest, non-managed profile) users that can be created on
* the device. This is a dynamic value and it decreases with the increase of the number of
* managed profiles on the device.
*
* It excludes system user in headless system user model.
*
* @return Maximum number of real users that can be created.
*/
public int getMaxSupportedRealUsers() {
return getMaxSupportedUsers() - getManagedProfilesCount();
}
/**
* Returns true if the maximum number of users on the device has been reached, false otherwise.
*/
public boolean isUserLimitReached() {
int countNonGuestUsers = getAllUsersExceptGuests().size();
int maxSupportedUsers = getMaxSupportedUsers();
if (countNonGuestUsers > maxSupportedUsers) {
Log.e(TAG, "There are more users on the device than allowed.");
return true;
}
return getAllUsersExceptGuests().size() == maxSupportedUsers;
}
private int getManagedProfilesCount() {
List For instance switching users is not allowed if the current user is in a phone call,
* or {@link #{UserManager.DISALLOW_USER_SWITCH} is set.
*/
public boolean canForegroundUserSwitchUsers() {
boolean inIdleCallState = TelephonyManager.getDefault().getCallState()
== TelephonyManager.CALL_STATE_IDLE;
boolean disallowUserSwitching =
foregroundUserHasUserRestriction(UserManager.DISALLOW_USER_SWITCH);
return (inIdleCallState && !disallowUserSwitching);
}
// Current process user information accessors
/**
* Checks whether this process is running under the system user.
*/
public boolean isCurrentProcessSystemUser() {
return mUserManager.isSystemUser();
}
/**
* Checks if the calling app is running in a demo user.
*/
public boolean isCurrentProcessDemoUser() {
return mUserManager.isDemoUser();
}
/**
* Checks if the calling app is running as an admin user.
*/
public boolean isCurrentProcessAdminUser() {
return mUserManager.isAdminUser();
}
/**
* Checks if the calling app is running as a guest user.
*/
public boolean isCurrentProcessGuestUser() {
return mUserManager.isGuestUser();
}
/**
* Check is the calling app is running as a restricted profile user (ie. a LinkedUser).
* Restricted profiles are only available when {@link #isHeadlessSystemUser()} is false.
*/
public boolean isCurrentProcessRestrictedProfileUser() {
return mUserManager.isRestrictedProfile();
}
// Current process user restriction accessors
/**
* Return whether the user running the current process has a restriction.
*
* @param restriction Restriction to check. Should be a UserManager.* restriction.
* @return Whether that restriction exists for the user running the process.
*/
public boolean isCurrentProcessUserHasRestriction(String restriction) {
return mUserManager.hasUserRestriction(restriction);
}
/**
* Checks if the current process user can modify accounts. Demo and Guest users cannot modify
* accounts even if the DISALLOW_MODIFY_ACCOUNTS restriction is not applied.
*/
public boolean canCurrentProcessModifyAccounts() {
return !isCurrentProcessUserHasRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS)
&& !isCurrentProcessDemoUser()
&& !isCurrentProcessGuestUser();
}
/**
* Checks if the user running the current process can add new users.
*/
public boolean canCurrentProcessAddUsers() {
return !isCurrentProcessUserHasRestriction(UserManager.DISALLOW_ADD_USER);
}
/**
* Checks if the user running the current process can remove users.
*/
public boolean canCurrentProcessRemoveUsers() {
return !isCurrentProcessUserHasRestriction(UserManager.DISALLOW_REMOVE_USER);
}
/**
* Returns whether the current process user can switch to other users.
*
* For instance switching users is not allowed if the user is in a phone call,
* or {@link #{UserManager.DISALLOW_USER_SWITCH} is set.
*/
public boolean canCurrentProcessSwitchUsers() {
boolean inIdleCallState = TelephonyManager.getDefault().getCallState()
== TelephonyManager.CALL_STATE_IDLE;
boolean disallowUserSwitching =
isCurrentProcessUserHasRestriction(UserManager.DISALLOW_USER_SWITCH);
return (inIdleCallState && !disallowUserSwitching);
}
/**
* Grants admin permissions to the user.
*
* @param user User to be upgraded to Admin status.
*/
@RequiresPermission(allOf = {
Manifest.permission.INTERACT_ACROSS_USERS_FULL,
Manifest.permission.MANAGE_USERS
})
public void grantAdminPermissions(UserInfo user) {
if (!isCurrentProcessAdminUser()) {
Log.w(TAG, "Only admin users can assign admin permissions.");
return;
}
mUserManager.setUserAdmin(user.id);
// Remove restrictions imposed on non-admins.
setDefaultNonAdminRestrictions(user, /* enable= */ false);
setOptionalNonAdminRestrictions(user, /* enable= */ false);
}
/**
* Creates a new user on the system with a default user name. This user name is set during
* constrution. The created user would be granted admin role. Only admins can create other
* admins.
*
* @return Newly created admin user, null if failed to create a user.
*/
@Nullable
public UserInfo createNewAdminUser() {
return createNewAdminUser(getDefaultAdminName());
}
/**
* Creates a new user on the system, the created user would be granted admin role.
* Only admins can create other admins.
*
* @param userName Name to give to the newly created user.
* @return Newly created admin user, null if failed to create a user.
*/
@Nullable
public UserInfo createNewAdminUser(String userName) {
if (!(isCurrentProcessAdminUser() || isCurrentProcessSystemUser())) {
// Only Admins or System user can create other privileged users.
Log.e(TAG, "Only admin users and system user can create other admins.");
return null;
}
UserInfo user = mUserManager.createUser(userName, UserInfo.FLAG_ADMIN);
if (user == null) {
// Couldn't create user, most likely because there are too many.
Log.w(TAG, "can't create admin user.");
return null;
}
assignDefaultIcon(user);
return user;
}
/**
* Creates a new non-admin user on the system.
*
* @param userName Name to give to the newly created user.
* @return Newly created non-admin user, null if failed to create a user.
*/
@Nullable
public UserInfo createNewNonAdminUser(String userName) {
UserInfo user = mUserManager.createUser(userName, 0);
if (user == null) {
// Couldn't create user, most likely because there are too many.
Log.w(TAG, "can't create non-admin user.");
return null;
}
setDefaultNonAdminRestrictions(user, /* enable= */ true);
// Each non-admin has sms and outgoing call restrictions applied by the UserManager on
// creation. We want to enable these permissions by default in the car.
setUserRestriction(user, UserManager.DISALLOW_SMS, /* enable= */ false);
setUserRestriction(user, UserManager.DISALLOW_OUTGOING_CALLS, /* enable= */ false);
assignDefaultIcon(user);
return user;
}
/**
* Sets the values of default Non-Admin restrictions to the passed in value.
*
* @param userInfo User to set restrictions on.
* @param enable If true, restriction is ON, If false, restriction is OFF.
*/
private void setDefaultNonAdminRestrictions(UserInfo userInfo, boolean enable) {
for (String restriction : DEFAULT_NON_ADMIN_RESTRICTIONS) {
setUserRestriction(userInfo, restriction, enable);
}
}
/**
* Sets the values of settings controllable restrictions to the passed in value.
*
* @param userInfo User to set restrictions on.
* @param enable If true, restriction is ON, If false, restriction is OFF.
*/
private void setOptionalNonAdminRestrictions(UserInfo userInfo, boolean enable) {
for (String restriction : OPTIONAL_NON_ADMIN_RESTRICTIONS) {
setUserRestriction(userInfo, restriction, enable);
}
}
/**
* Sets the value of the specified restriction for the specified user.
*
* @param userInfo the user whose restriction is to be changed
* @param restriction the key of the restriction
* @param enable the value for the restriction. if true, turns the restriction ON, if false,
* turns the restriction OFF.
*/
public void setUserRestriction(UserInfo userInfo, String restriction, boolean enable) {
UserHandle userHandle = UserHandle.of(userInfo.id);
mUserManager.setUserRestriction(restriction, enable, userHandle);
}
/**
* Tries to remove the user that's passed in. System user cannot be removed.
* If the user to be removed is user currently running the process,
* it switches to the guest user first, and then removes the user.
* If the user being removed is the last admin user, this will create a new admin user.
*
* @param userInfo User to be removed
* @param guestUserName User name to use for the guest user if we need to switch to it
* @return {@code true} if user is successfully removed, {@code false} otherwise.
*/
public boolean removeUser(UserInfo userInfo, String guestUserName) {
if (isSystemUser(userInfo)) {
Log.w(TAG, "User " + userInfo.id + " is system user, could not be removed.");
return false;
}
// Try to create a new admin before deleting the current one.
if (userInfo.isAdmin() && getAllAdminUsers().size() <= 1) {
return removeLastAdmin(userInfo);
}
if (!isCurrentProcessAdminUser() && !isCurrentProcessUser(userInfo)) {
// If the caller is non-admin, they can only delete themselves.
Log.e(TAG, "Non-admins cannot remove other users.");
return false;
}
if (userInfo.id == getCurrentForegroundUserId()) {
if (!canCurrentProcessSwitchUsers()) {
// If we can't switch to a different user, we can't exit this one and therefore
// can't delete it.
Log.w(TAG, "User switching is not allowed. Current user cannot be deleted");
return false;
}
startGuestSession(guestUserName);
}
return mUserManager.removeUser(userInfo.id);
}
private boolean removeLastAdmin(UserInfo userInfo) {
if (Log.isLoggable(TAG, Log.INFO)) {
Log.i(TAG, "User " + userInfo.id
+ " is the last admin user on device. Creating a new admin.");
}
UserInfo newAdmin = createNewAdminUser(getDefaultAdminName());
if (newAdmin == null) {
Log.w(TAG, "Couldn't create another admin, cannot delete current user.");
return false;
}
switchToUser(newAdmin);
return mUserManager.removeUser(userInfo.id);
}
/**
* Switches (logs in) to another user given user id.
*
* @param id User id to switch to.
* @return {@code true} if user switching succeed.
*/
public boolean switchToUserId(int id) {
if (id == UserHandle.USER_SYSTEM && isHeadlessSystemUser()) {
// System User doesn't associate with real person, can not be switched to.
return false;
}
if (!canCurrentProcessSwitchUsers()) {
return false;
}
if (id == getCurrentForegroundUserId()) {
return false;
}
return mActivityManager.switchUser(id);
}
/**
* Switches (logs in) to another user.
*
* @param userInfo User to switch to.
* @return {@code true} if user switching succeed.
*/
public boolean switchToUser(UserInfo userInfo) {
return switchToUserId(userInfo.id);
}
/**
* Creates a new guest or finds the existing one, and switches into it.
*
* @param guestName Username for the guest user.
* @return {@code true} if switch to guest user succeed.
*/
public boolean startGuestSession(String guestName) {
UserInfo guest = createNewOrFindExistingGuest(guestName);
if (guest == null) {
return false;
}
return switchToUserId(guest.id);
}
/**
* Creates and returns a new guest user or returns the existing one.
* Returns null if it fails to create a new guest.
*
* @param guestName Username for guest if new guest is being created.
*/
@Nullable
public UserInfo createNewOrFindExistingGuest(String guestName) {
// CreateGuest will return null if a guest already exists.
UserInfo newGuest = mUserManager.createGuest(mContext, guestName);
if (newGuest != null) {
assignDefaultIcon(newGuest);
return newGuest;
}
UserInfo existingGuest = findExistingGuestUser();
if (existingGuest == null) {
// Most likely a guest got removed just before we tried to look for it.
Log.w(TAG, "Couldn't create a new guest and couldn't find an existing one.");
}
return existingGuest;
}
/**
* Returns UserInfo for the existing guest user, or null if there are no guests on the device.
*/
@Nullable
private UserInfo findExistingGuestUser() {
for (UserInfo userInfo : getAllUsers()) {
if (userInfo.isGuest() && !userInfo.guestToRemove) {
return userInfo;
}
}
return null;
}
/**
* Gets a bitmap representing the user's default avatar.
*
* @param userInfo User whose avatar should be returned.
* @return Default user icon
*/
public Bitmap getUserDefaultIcon(UserInfo userInfo) {
return UserIcons.convertToBitmap(
UserIcons.getDefaultUserIcon(mContext.getResources(), userInfo.id, false));
}
/**
* Gets a bitmap representing the default icon for a Guest user.
*
* @return Default guest user icon
*/
public Bitmap getGuestDefaultIcon() {
if (mDefaultGuestUserIcon == null) {
mDefaultGuestUserIcon = UserIcons.convertToBitmap(UserIcons.getDefaultUserIcon(
mContext.getResources(), UserHandle.USER_NULL, false));
}
return mDefaultGuestUserIcon;
}
/**
* Gets an icon for the user.
*
* @param userInfo User for which we want to get the icon.
* @return a Bitmap for the icon
*/
public Bitmap getUserIcon(UserInfo userInfo) {
Bitmap picture = mUserManager.getUserIcon(userInfo.id);
if (picture == null) {
return assignDefaultIcon(userInfo);
}
return picture;
}
/**
* Method for scaling a Bitmap icon to a desirable size.
*
* @param icon Bitmap to scale.
* @param desiredSize Wanted size for the icon.
* @return Drawable for the icon, scaled to the new size.
*/
public Drawable scaleUserIcon(Bitmap icon, int desiredSize) {
Bitmap scaledIcon = Bitmap.createScaledBitmap(
icon, desiredSize, desiredSize, true /* filter */);
return new BitmapDrawable(mContext.getResources(), scaledIcon);
}
/**
* Sets new Username for the user.
*
* @param user User whose name should be changed.
* @param name New username.
*/
public void setUserName(UserInfo user, String name) {
mUserManager.setUserName(user.id, name);
}
private void registerReceiver() {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_USER_ADDED);
filter.addAction(Intent.ACTION_USER_INFO_CHANGED);
filter.addAction(Intent.ACTION_USER_SWITCHED);
filter.addAction(Intent.ACTION_USER_STOPPED);
filter.addAction(Intent.ACTION_USER_UNLOCKED);
mContext.registerReceiverAsUser(mUserChangeReceiver, UserHandle.ALL, filter, null, null);
}
// Assigns a default icon to a user according to the user's id.
private Bitmap assignDefaultIcon(UserInfo userInfo) {
Bitmap bitmap = userInfo.isGuest()
? getGuestDefaultIcon() : getUserDefaultIcon(userInfo);
mUserManager.setUserIcon(userInfo.id, bitmap);
return bitmap;
}
private void unregisterReceiver() {
mContext.unregisterReceiver(mUserChangeReceiver);
}
private String getDefaultAdminName() {
if (TextUtils.isEmpty(mDefaultAdminName)) {
mDefaultAdminName = mContext.getString(com.android.internal.R.string.owner_name);
}
return mDefaultAdminName;
}
@VisibleForTesting
void setDefaultAdminName(String defaultAdminName) {
mDefaultAdminName = defaultAdminName;
}
/**
* Interface for listeners that want to register for receiving updates to changes to the users
* on the system including removing and adding users, and changing user info.
*/
public interface OnUsersUpdateListener {
/**
* Method that will get called when users list has been changed.
*/
void onUsersUpdate();
}
}
*
*
* If any step fails to retrieve the stored id or the retrieved id does not exist on device,
* then it will move onto the next step.
*
* @return user id of the initial user to boot into on the device.
*/
@SystemApi
public int getInitialUser() {
List