blob: 4a69f0b7383a29f9460296a614f05e315f138d6a [file] [log] [blame]
/*
* 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.user;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.car.settings.CarSettings;
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.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import com.android.internal.util.UserIcons;
import java.util.Iterator;
import java.util.List;
/**
* 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.
*
* <p>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 class CarUserManagerHelper {
private static final String TAG = "CarUserManagerHelper";
private static final String HEADLESS_SYSTEM_USER = "android.car.systemuser.headless";
private final Context mContext;
private final UserManager mUserManager;
private final ActivityManager mActivityManager;
private Bitmap mDefaultGuestUserIcon;
private OnUsersUpdateListener mUpdateListener;
private final BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
mUpdateListener.onUsersUpdate();
}
};
public CarUserManagerHelper(Context context) {
mContext = context.getApplicationContext();
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
}
/**
* Registers a listener for updates to all users - removing, adding users or changing user info.
*
* <p> Best practise is to keep one listener per helper.
*
* @param listener Instance of {@link OnUsersUpdateListener}.
*/
public void registerOnUsersUpdateListener(OnUsersUpdateListener listener) {
if (mUpdateListener != null) {
unregisterOnUsersUpdateListener();
}
mUpdateListener = listener;
registerReceiver();
}
/**
* Unregisters on user update listener by unregistering {@code BroadcastReceiver}.
*/
public void unregisterOnUsersUpdateListener() {
unregisterReceiver();
}
/**
* Returns {@code true} if the system is in the headless user 0 model.
*
* @return {@boolean true} if headless system user.
*/
public boolean isHeadlessSystemUser() {
return SystemProperties.getBoolean(HEADLESS_SYSTEM_USER, false);
}
/**
* 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.
*
* <p>Differentiation between foreground user and current process user is relevant for
* multi-user deployments.
*
* <p>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 foreground users on the system that are not currently running as
* the foreground user.
*
* @return List of {@code UserInfo} for each user that is not the foreground user.
*/
public List<UserInfo> getAllSwitchableUsers() {
if (isHeadlessSystemUser()) {
return getAllUsersExceptSystemUserAndSpecifiedUser(getCurrentForegroundUserId());
} else {
return getAllUsersExceptSpecifiedUser(getCurrentForegroundUserId());
}
}
/**
* Gets all the users that can be brought to the foreground on the system.
*
* @return List of {@code UserInfo} for users that associated with a real person.
*/
public List<UserInfo> getAllUsers() {
if (isHeadlessSystemUser()) {
return getAllUsersExceptSystemUserAndSpecifiedUser(UserHandle.USER_SYSTEM);
} else {
return mUserManager.getUsers(/* excludeDying= */true);
}
}
/**
* Get all the users except the one with userId passed in.
*
* @param userId of the user not to be returned.
* @return All users other than user with userId.
*/
private List<UserInfo> getAllUsersExceptSpecifiedUser(int userId) {
List<UserInfo> users = mUserManager.getUsers(/* excludeDying= */true);
for (Iterator<UserInfo> iterator = users.iterator(); iterator.hasNext(); ) {
UserInfo userInfo = iterator.next();
if (userInfo.id == userId) {
// Remove user with userId from the list.
iterator.remove();
}
}
return users;
}
/**
* Get all the users except system user and the one with userId passed in.
*
* @param userId of the user not to be returned.
* @return All users other than system user and user with userId.
*/
private List<UserInfo> getAllUsersExceptSystemUserAndSpecifiedUser(int userId) {
List<UserInfo> users = mUserManager.getUsers(/* excludeDying= */true);
for (Iterator<UserInfo> iterator = users.iterator(); iterator.hasNext(); ) {
UserInfo userInfo = iterator.next();
if (userInfo.id == userId || userInfo.id == UserHandle.USER_SYSTEM) {
// Remove user with userId from the list.
iterator.remove();
}
}
return users;
}
// User information accessors
/**
* Checks whether the user is system user.
*
* @param userInfo User to check against system user.
* @return {@code true} if system user, {@code false} otherwise.
*/
public boolean isSystemUser(UserInfo userInfo) {
return userInfo.id == UserHandle.USER_SYSTEM;
}
/**
* Checks whether the user is default user.
*
* @param userInfo User to check against system user.
* @return {@code true} if is default user, {@code false} otherwise.
*/
public boolean isDefaultUser(UserInfo userInfo) {
return userInfo.id == CarSettings.DEFAULT_USER_ID_TO_BOOT_INTO;
}
/**
* Checks whether passed in user is the foreground user.
*
* @param userInfo User to check.
* @return {@code true} if foreground user, {@code false} otherwise.
*/
public boolean isForegroundUser(UserInfo userInfo) {
return getCurrentForegroundUserId() == userInfo.id;
}
/**
* Checks whether passed in user is the user that's running the current process.
*
* @param userInfo User to check.
* @return {@code true} if user running the process, {@code false} otherwise.
*/
public boolean isCurrentProcessUser(UserInfo userInfo) {
return getCurrentProcessUserId() == userInfo.id;
}
// Foreground user information accessors.
/**
* Checks if the foreground user is a guest user.
*/
public boolean isForegroundUserGuest() {
return getCurrentForegroundUserInfo().isGuest();
}
/**
* Returns whether this user can be removed from the system.
*
* @param userInfo User to be removed
* @return {@code true} if they can be removed, {@code false} otherwise.
*/
public boolean canUserBeRemoved(UserInfo userInfo) {
return !isSystemUser(userInfo);
}
/**
* Return whether the foreground user has a restriction.
*
* @param restriction Restriction to check. Should be a UserManager.* restriction.
* @return Whether that restriction exists for the foreground user.
*/
public boolean foregroundUserHasUserRestriction(String restriction) {
return mUserManager.hasUserRestriction(
restriction, getCurrentForegroundUserInfo().getUserHandle());
}
/**
* Checks if the foreground user can add new users.
*/
public boolean canForegroundUserAddUsers() {
return !foregroundUserHasUserRestriction(UserManager.DISALLOW_ADD_USER);
}
// 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 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);
}
/**
* Checks if the user running the current process is allowed to switch to another user.
*/
public boolean canCurrentProcessSwitchUsers() {
return !isCurrentProcessUserHasRestriction(UserManager.DISALLOW_USER_SWITCH);
}
/**
* Creates a new user on the system, the created user would be granted admin role.
*
* @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) {
UserInfo user = mUserManager.createUser(userName, UserInfo.FLAG_ADMIN);
if (user == null) {
// Couldn't create user, most likely because there are too many, but we haven't
// been able to reload the list yet.
Log.w(TAG, "can't create admin user.");
return null;
}
assignDefaultIcon(user);
return user;
}
/**
* Creates a new restricted user on the system.
*
* @param userName Name to give to the newly created user.
* @return Newly created restricted 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, but we haven't
// been able to reload the list yet.
Log.w(TAG, "can't create non-admin user.");
return null;
}
assignDefaultIcon(user);
return user;
}
/**
* 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.
*
* @param userInfo User to be removed
* @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;
}
// Not allow to delete the default user for now. Since default user is the one to
// boot into.
if (isHeadlessSystemUser() && isDefaultUser(userInfo)) {
Log.w(TAG, "User " + userInfo.id + " is the default user, could not be removed.");
return false;
}
if (userInfo.id == getCurrentForegroundUserId()) {
startNewGuestSession(guestUserName);
}
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;
}
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) {
if (userInfo.id == getCurrentForegroundUserId()) {
return false;
}
return switchToUserId(userInfo.id);
}
/**
* Creates a new guest session and switches into the guest session.
*
* @param guestName Username for the guest user.
* @return {@code true} if switch to guest user succeed.
*/
public boolean startNewGuestSession(String guestName) {
UserInfo guest = mUserManager.createGuest(mContext, guestName);
if (guest == null) {
// Couldn't create user, most likely because there are too many, but we haven't
// been able to reload the list yet.
Log.w(TAG, "can't create user.");
return false;
}
assignDefaultIcon(guest);
return switchToUserId(guest.id);
}
/**
* 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);
}
/**
* 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();
}
}