Files
app_Settings/src/com/android/settings/users/UserSettings.java
João Victor Mendes Freire 8b14ffb876 Fix user avatar not being set on rotation
This commit fixes the issue where the user avatar was not being colored
(using only the gray one) when the device was rotated. Additionally, it
fixes a race condition issue of the avatar color sometimes being
different than the selected one.

Bug: 261035066
Test: manually tested && atest UserSettingsComponentTest
Change-Id: I739fa8fd5d8717b602d7ba561d9c728ce622ff10
2022-12-05 20:05:05 +00:00

1753 lines
73 KiB
Java

/*
* Copyright (C) 2012 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 com.android.settings.users;
import static com.android.settingslib.Utils.getColorAttrDefaultColor;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.Dialog;
import android.app.admin.DevicePolicyManager;
import android.app.settings.SettingsEnums;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BlendMode;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.ContactsContract;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.WindowManagerGlobal;
import android.widget.SimpleAdapter;
import android.widget.Toast;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
import com.android.internal.util.UserIcons;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.Utils;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.password.ChooseLockGeneric;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.widget.MainSwitchBarController;
import com.android.settings.widget.SettingsMainSwitchBar;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.RestrictedPreference;
import com.android.settingslib.drawable.CircleFramedDrawable;
import com.android.settingslib.search.SearchIndexable;
import com.android.settingslib.search.SearchIndexableRaw;
import com.android.settingslib.users.EditUserInfoController;
import com.android.settingslib.users.UserCreatingDialog;
import com.android.settingslib.utils.ThreadUtils;
import com.google.android.setupcompat.util.WizardManagerHelper;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
/**
* Screen that manages the list of users on the device.
* Secondary users and a guest user can be created if there is no restriction.
*
* The first user in the list is always the current user.
* Owner is the primary user.
*/
@SearchIndexable
public class UserSettings extends SettingsPreferenceFragment
implements Preference.OnPreferenceClickListener,
MultiUserSwitchBarController.OnMultiUserSwitchChangedListener,
DialogInterface.OnDismissListener {
private static final String TAG = "UserSettings";
/** UserId of the user being removed */
private static final String SAVE_REMOVING_USER = "removing_user";
private static final String KEY_USER_LIST = "user_list";
private static final String KEY_USER_ME = "user_me";
private static final String KEY_USER_GUEST = "user_guest";
private static final String KEY_ADD_GUEST = "guest_add";
private static final String KEY_ADD_USER = "user_add";
private static final String KEY_ADD_SUPERVISED_USER = "supervised_user_add";
private static final String KEY_ADD_USER_WHEN_LOCKED = "user_settings_add_users_when_locked";
private static final String KEY_ENABLE_GUEST_TELEPHONY = "enable_guest_calling";
private static final String KEY_MULTIUSER_TOP_INTRO = "multiuser_top_intro";
private static final String KEY_TIMEOUT_TO_DOCK_USER = "timeout_to_dock_user_preference";
private static final String KEY_GUEST_CATEGORY = "guest_category";
private static final String KEY_GUEST_RESET = "guest_reset";
private static final String KEY_GUEST_EXIT = "guest_exit";
private static final String KEY_REMOVE_GUEST_ON_EXIT = "remove_guest_on_exit";
private static final String KEY_GUEST_USER_CATEGORY = "guest_user_category";
private static final String KEY_ALLOW_MULTIPLE_USERS = "allow_multiple_users";
private static final String SETTING_GUEST_HAS_LOGGED_IN = "systemui.guest_has_logged_in";
private static final int MENU_REMOVE_USER = Menu.FIRST;
private static final IntentFilter USER_REMOVED_INTENT_FILTER;
private static final int DIALOG_CONFIRM_REMOVE = 1;
private static final int DIALOG_ADD_USER = 2;
// Dialogs with id 3 and 4 got removed
private static final int DIALOG_USER_CANNOT_MANAGE = 5;
private static final int DIALOG_CHOOSE_USER_TYPE = 6;
private static final int DIALOG_NEED_LOCKSCREEN = 7;
private static final int DIALOG_CONFIRM_REMOVE_GUEST = 8;
private static final int DIALOG_USER_PROFILE_EDITOR = 9;
private static final int DIALOG_USER_PROFILE_EDITOR_ADD_USER = 10;
private static final int DIALOG_USER_PROFILE_EDITOR_ADD_RESTRICTED_PROFILE = 11;
private static final int DIALOG_CONFIRM_REMOVE_GUEST_WITH_AUTO_CREATE = 12;
private static final int DIALOG_CONFIRM_RESET_AND_RESTART_GUEST = 13;
private static final int DIALOG_CONFIRM_EXIT_GUEST_EPHEMERAL = 14;
private static final int DIALOG_CONFIRM_EXIT_GUEST_NON_EPHEMERAL = 15;
private static final int MESSAGE_UPDATE_LIST = 1;
private static final int MESSAGE_USER_CREATED = 2;
static final int MESSAGE_REMOVE_GUEST_ON_EXIT_CONTROLLER_GUEST_REMOVED = 3;
private static final int USER_TYPE_USER = 1;
private static final int USER_TYPE_RESTRICTED_PROFILE = 2;
private static final int REQUEST_CHOOSE_LOCK = 10;
private static final int REQUEST_EDIT_GUEST = 11;
static final int RESULT_GUEST_REMOVED = 100;
private static final String KEY_ADD_USER_LONG_MESSAGE_DISPLAYED =
"key_add_user_long_message_displayed";
private static final String KEY_TITLE = "title";
private static final String KEY_SUMMARY = "summary";
private static final String EXTRA_OPEN_DIALOG_USER_PROFILE_EDITOR =
"EXTRA_OPEN_DIALOG_USER_PROFILE_EDITOR";
static {
USER_REMOVED_INTENT_FILTER = new IntentFilter(Intent.ACTION_USER_REMOVED);
USER_REMOVED_INTENT_FILTER.addAction(Intent.ACTION_USER_INFO_CHANGED);
}
@VisibleForTesting
PreferenceGroup mUserListCategory;
@VisibleForTesting
PreferenceGroup mGuestUserCategory;
@VisibleForTesting
PreferenceGroup mGuestCategory;
@VisibleForTesting
Preference mGuestResetPreference;
@VisibleForTesting
Preference mGuestExitPreference;
@VisibleForTesting
UserPreference mMePreference;
@VisibleForTesting
RestrictedPreference mAddGuest;
@VisibleForTesting
RestrictedPreference mAddUser;
@VisibleForTesting
RestrictedPreference mAddSupervisedUser;
@VisibleForTesting
SparseArray<Bitmap> mUserIcons = new SparseArray<>();
private int mRemovingUserId = -1;
private boolean mAddingUser;
private boolean mGuestUserAutoCreated;
private String mConfigSupervisedUserCreationPackage;
private String mAddingUserName;
private UserCapabilities mUserCaps;
private boolean mShouldUpdateUserList = true;
private final Object mUserLock = new Object();
private UserManager mUserManager;
private static SparseArray<Bitmap> sDarkDefaultUserBitmapCache = new SparseArray<>();
private MultiUserSwitchBarController mSwitchBarController;
private EditUserInfoController mEditUserInfoController =
new EditUserInfoController(Utils.FILE_PROVIDER_AUTHORITY);
private AddUserWhenLockedPreferenceController mAddUserWhenLockedPreferenceController;
private GuestTelephonyPreferenceController mGuestTelephonyPreferenceController;
private RemoveGuestOnExitPreferenceController mRemoveGuestOnExitPreferenceController;
private MultiUserTopIntroPreferenceController mMultiUserTopIntroPreferenceController;
private TimeoutToDockUserPreferenceController mTimeoutToDockUserPreferenceController;
private UserCreatingDialog mUserCreatingDialog;
private final AtomicBoolean mGuestCreationScheduled = new AtomicBoolean();
private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
private CharSequence mPendingUserName;
private Drawable mPendingUserIcon;
// A place to cache the generated default avatar
private Drawable mDefaultIconDrawable;
// TODO: Replace current Handler solution to something that doesn't leak memory and works
// TODO: during a configuration change
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_UPDATE_LIST:
updateUserList();
break;
case MESSAGE_REMOVE_GUEST_ON_EXIT_CONTROLLER_GUEST_REMOVED:
updateUserList();
if (mGuestUserAutoCreated) {
scheduleGuestCreation();
}
break;
}
}
};
private BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_USER_REMOVED)) {
mRemovingUserId = -1;
} else if (intent.getAction().equals(Intent.ACTION_USER_INFO_CHANGED)) {
int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
if (userHandle != -1) {
mUserIcons.remove(userHandle);
}
}
mHandler.sendEmptyMessage(MESSAGE_UPDATE_LIST);
}
};
@Override
public int getMetricsCategory() {
return SettingsEnums.USER;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Assume we are in a SettingsActivity. This is only safe because we currently use
// SettingsActivity as base for all preference fragments.
final SettingsActivity activity = (SettingsActivity) getActivity();
final SettingsMainSwitchBar switchBar = activity.getSwitchBar();
switchBar.setTitle(getContext().getString(R.string.multiple_users_main_switch_title));
if (isCurrentUserAdmin()) {
switchBar.show();
} else {
switchBar.hide();
}
mSwitchBarController = new MultiUserSwitchBarController(activity,
new MainSwitchBarController(switchBar), this /* listener */);
getSettingsLifecycle().addObserver(mSwitchBarController);
boolean openUserEditDialog = getIntent().getBooleanExtra(
EXTRA_OPEN_DIALOG_USER_PROFILE_EDITOR, false);
if (switchBar.isChecked() && openUserEditDialog) {
showDialog(DIALOG_USER_PROFILE_EDITOR);
}
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
addPreferencesFromResource(R.xml.user_settings);
final Activity activity = getActivity();
if (!WizardManagerHelper.isDeviceProvisioned(activity)) {
activity.finish();
return;
}
mGuestUserAutoCreated = getPrefContext().getResources().getBoolean(
com.android.internal.R.bool.config_guestUserAutoCreated);
mAddUserWhenLockedPreferenceController = new AddUserWhenLockedPreferenceController(
activity, KEY_ADD_USER_WHEN_LOCKED);
mGuestTelephonyPreferenceController = new GuestTelephonyPreferenceController(
activity, KEY_ENABLE_GUEST_TELEPHONY);
mRemoveGuestOnExitPreferenceController = new RemoveGuestOnExitPreferenceController(
activity, KEY_REMOVE_GUEST_ON_EXIT, this, mHandler);
mMultiUserTopIntroPreferenceController = new MultiUserTopIntroPreferenceController(activity,
KEY_MULTIUSER_TOP_INTRO);
mTimeoutToDockUserPreferenceController = new TimeoutToDockUserPreferenceController(
activity, KEY_TIMEOUT_TO_DOCK_USER);
final PreferenceScreen screen = getPreferenceScreen();
mAddUserWhenLockedPreferenceController.displayPreference(screen);
mGuestTelephonyPreferenceController.displayPreference(screen);
mRemoveGuestOnExitPreferenceController.displayPreference(screen);
mMultiUserTopIntroPreferenceController.displayPreference(screen);
mTimeoutToDockUserPreferenceController.displayPreference(screen);
screen.findPreference(mAddUserWhenLockedPreferenceController.getPreferenceKey())
.setOnPreferenceChangeListener(mAddUserWhenLockedPreferenceController);
screen.findPreference(mGuestTelephonyPreferenceController.getPreferenceKey())
.setOnPreferenceChangeListener(mGuestTelephonyPreferenceController);
screen.findPreference(mRemoveGuestOnExitPreferenceController.getPreferenceKey())
.setOnPreferenceChangeListener(mRemoveGuestOnExitPreferenceController);
if (icicle != null) {
if (icicle.containsKey(SAVE_REMOVING_USER)) {
mRemovingUserId = icicle.getInt(SAVE_REMOVING_USER);
}
mEditUserInfoController.onRestoreInstanceState(icicle);
}
mUserCaps = UserCapabilities.create(activity);
mUserManager = (UserManager) activity.getSystemService(Context.USER_SERVICE);
if (!mUserCaps.mEnabled) {
return;
}
final int myUserId = UserHandle.myUserId();
mUserListCategory = (PreferenceGroup) findPreference(KEY_USER_LIST);
mMePreference = new UserPreference(getPrefContext(), null /* attrs */, myUserId);
mMePreference.setKey(KEY_USER_ME);
mMePreference.setOnPreferenceClickListener(this);
if (isCurrentUserAdmin()) {
mMePreference.setSummary(R.string.user_admin);
}
mGuestCategory = findPreference(KEY_GUEST_CATEGORY);
mGuestResetPreference = findPreference(KEY_GUEST_RESET);
mGuestResetPreference.setOnPreferenceClickListener(this);
mGuestExitPreference = findPreference(KEY_GUEST_EXIT);
mGuestExitPreference.setOnPreferenceClickListener(this);
mGuestUserCategory = findPreference(KEY_GUEST_USER_CATEGORY);
mAddGuest = findPreference(KEY_ADD_GUEST);
mAddGuest.setOnPreferenceClickListener(this);
mAddUser = findPreference(KEY_ADD_USER);
if (!mUserCaps.mCanAddRestrictedProfile) {
// Label should only mention adding a "user", not a "profile"
mAddUser.setTitle(com.android.settingslib.R.string.user_add_user);
}
mAddUser.setOnPreferenceClickListener(this);
setConfigSupervisedUserCreationPackage();
mAddSupervisedUser = findPreference(KEY_ADD_SUPERVISED_USER);
mAddSupervisedUser.setOnPreferenceClickListener(this);
activity.registerReceiverAsUser(
mUserChangeReceiver, UserHandle.ALL, USER_REMOVED_INTENT_FILTER, null, mHandler,
Context.RECEIVER_EXPORTED_UNAUDITED);
updateUI();
mShouldUpdateUserList = false;
}
@Override
public void onResume() {
super.onResume();
if (!mUserCaps.mEnabled) {
return;
}
final PreferenceScreen screen = getPreferenceScreen();
mAddUserWhenLockedPreferenceController.updateState(screen.findPreference(
mAddUserWhenLockedPreferenceController.getPreferenceKey()));
mGuestTelephonyPreferenceController.updateState(screen.findPreference(
mGuestTelephonyPreferenceController.getPreferenceKey()));
mTimeoutToDockUserPreferenceController.updateState(screen.findPreference(
mTimeoutToDockUserPreferenceController.getPreferenceKey()));
mRemoveGuestOnExitPreferenceController.updateState(screen.findPreference(
mRemoveGuestOnExitPreferenceController.getPreferenceKey()));
if (mShouldUpdateUserList) {
updateUI();
}
}
@Override
public void onPause() {
mShouldUpdateUserList = true;
super.onPause();
}
@Override
public void onDestroy() {
super.onDestroy();
if (mUserCaps == null || !mUserCaps.mEnabled) {
return;
}
getActivity().unregisterReceiver(mUserChangeReceiver);
}
@Override
public void onSaveInstanceState(Bundle outState) {
mEditUserInfoController.onSaveInstanceState(outState);
outState.putInt(SAVE_REMOVING_USER, mRemovingUserId);
super.onSaveInstanceState(outState);
}
@Override
public void startActivityForResult(Intent intent, int requestCode) {
mEditUserInfoController.startingActivityForResult();
super.startActivityForResult(intent, requestCode);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
int pos = 0;
if (!isCurrentUserAdmin() && canSwitchUserNow() && !isCurrentUserGuest()) {
String nickname = mUserManager.getUserName();
MenuItem removeThisUser = menu.add(0, MENU_REMOVE_USER, pos++,
getResources().getString(R.string.user_remove_user_menu, nickname));
removeThisUser.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
final EnforcedAdmin disallowRemoveUserAdmin =
RestrictedLockUtilsInternal.checkIfRestrictionEnforced(getContext(),
UserManager.DISALLOW_REMOVE_USER, UserHandle.myUserId());
RestrictedLockUtilsInternal.setMenuItemAsDisabledByAdmin(getContext(), removeThisUser,
disallowRemoveUserAdmin);
}
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
final int itemId = item.getItemId();
if (itemId == MENU_REMOVE_USER) {
onRemoveUserClicked(UserHandle.myUserId());
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
@Override
public void onMultiUserSwitchChanged(boolean newState) {
updateUI();
}
private void updateUI() {
mUserCaps.updateAddUserCapabilities(getActivity());
loadProfile();
updateUserList();
}
/**
* Loads profile information for the current user.
*/
private void loadProfile() {
if (isCurrentUserGuest()) {
// No need to load profile information
mMePreference.setIcon(getEncircledDefaultIcon());
mMePreference.setTitle(mGuestUserAutoCreated
? com.android.settingslib.R.string.guest_reset_guest
: com.android.settingslib.R.string.guest_exit_guest);
mMePreference.setSelectable(true);
// removing a guest will result in switching back to the admin user
mMePreference.setEnabled(canSwitchUserNow());
return;
}
new AsyncTask<Void, Void, String>() {
@Override
protected void onPostExecute(String result) {
finishLoadProfile(result);
}
@Override
protected String doInBackground(Void... values) {
UserInfo user = mUserManager.getUserInfo(UserHandle.myUserId());
if (user.iconPath == null || user.iconPath.equals("")) {
// Assign profile photo.
copyMeProfilePhoto(getActivity(), user);
}
return user.name;
}
}.execute();
}
private void finishLoadProfile(String profileName) {
if (getActivity() == null) {
return;
}
mMePreference.setTitle(getString(R.string.user_you, profileName));
int myUserId = UserHandle.myUserId();
Bitmap b = mUserManager.getUserIcon(myUserId);
if (b != null) {
mMePreference.setIcon(encircleUserIcon(b));
mUserIcons.put(myUserId, b);
}
}
private boolean hasLockscreenSecurity() {
LockPatternUtils lpu = new LockPatternUtils(getActivity());
return lpu.isSecure(UserHandle.myUserId());
}
private void launchChooseLockscreen() {
Intent chooseLockIntent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
chooseLockIntent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS,
true);
startActivityForResult(chooseLockIntent, REQUEST_CHOOSE_LOCK);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CHOOSE_LOCK) {
if (resultCode != Activity.RESULT_CANCELED && hasLockscreenSecurity()) {
addUserNow(USER_TYPE_RESTRICTED_PROFILE);
}
} else if (mGuestUserAutoCreated && requestCode == REQUEST_EDIT_GUEST
&& resultCode == RESULT_GUEST_REMOVED) {
scheduleGuestCreation();
} else {
mEditUserInfoController.onActivityResult(requestCode, resultCode, data);
}
}
private void onAddUserClicked(int userType) {
synchronized (mUserLock) {
if (mRemovingUserId == -1 && !mAddingUser) {
switch (userType) {
case USER_TYPE_USER:
showDialog(DIALOG_ADD_USER);
break;
case USER_TYPE_RESTRICTED_PROFILE:
if (hasLockscreenSecurity()) {
showDialog(DIALOG_USER_PROFILE_EDITOR_ADD_RESTRICTED_PROFILE);
} else {
showDialog(DIALOG_NEED_LOCKSCREEN);
}
break;
}
}
}
}
private void onAddSupervisedUserClicked() {
final Intent intent = new Intent()
.setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
.setPackage(mConfigSupervisedUserCreationPackage)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
private void onAddGuestClicked() {
Context context = getContext();
final UserCreatingDialog guestCreatingDialog =
new UserCreatingDialog(getActivity(), /* isGuest= */ true);
guestCreatingDialog.show();
ThreadUtils.postOnBackgroundThread(() -> {
mMetricsFeatureProvider.action(getActivity(), SettingsEnums.ACTION_USER_GUEST_ADD);
Trace.beginSection("UserSettings.addGuest");
final UserInfo guest = mUserManager.createGuest(context);
Trace.endSection();
ThreadUtils.postOnMainThread(() -> {
guestCreatingDialog.dismiss();
if (guest == null) {
Toast.makeText(context,
com.android.settingslib.R.string.add_guest_failed,
Toast.LENGTH_SHORT).show();
return;
}
openUserDetails(guest, true, context);
});
});
}
private void onRemoveUserClicked(int userId) {
synchronized (mUserLock) {
if (mRemovingUserId == -1 && !mAddingUser) {
mRemovingUserId = userId;
showDialog(DIALOG_CONFIRM_REMOVE);
}
}
}
private void onUserCreated(UserInfo userInfo, Context context) {
hideUserCreatingDialog();
mAddingUser = false;
openUserDetails(userInfo, true, context);
}
private void hideUserCreatingDialog() {
if (mUserCreatingDialog != null && mUserCreatingDialog.isShowing()) {
mUserCreatingDialog.dismiss();
}
}
private void onUserCreationFailed() {
Toast.makeText(getContext(),
com.android.settingslib.R.string.add_user_failed,
Toast.LENGTH_SHORT).show();
hideUserCreatingDialog();
}
private void openUserDetails(UserInfo userInfo, boolean newUser) {
openUserDetails(userInfo, newUser, getContext());
}
private void openUserDetails(UserInfo userInfo, boolean newUser, Context context) {
// to prevent a crash when config changes during user creation,
// we simply ignore this redirection step
if (context == null) {
return;
}
Bundle extras = new Bundle();
extras.putInt(UserDetailsSettings.EXTRA_USER_ID, userInfo.id);
extras.putBoolean(AppRestrictionsFragment.EXTRA_NEW_USER, newUser);
SubSettingLauncher launcher = new SubSettingLauncher(context)
.setDestination(UserDetailsSettings.class.getName())
.setArguments(extras)
.setTitleText(userInfo.name)
.setSourceMetricsCategory(getMetricsCategory());
if (mGuestUserAutoCreated && userInfo.isGuest()) {
launcher.setResultListener(this, REQUEST_EDIT_GUEST);
}
launcher.launch();
}
@Override
public void onDialogShowing() {
super.onDialogShowing();
setOnDismissListener(this);
}
@Override
public Dialog onCreateDialog(int dialogId) {
Context context = getActivity();
if (context == null) {
return null;
}
switch (dialogId) {
case DIALOG_CONFIRM_REMOVE: {
Dialog dlg =
UserDialogs.createRemoveDialog(getActivity(), mRemovingUserId,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
removeUserNow();
}
}
);
return dlg;
}
case DIALOG_USER_CANNOT_MANAGE:
return new AlertDialog.Builder(context)
.setMessage(R.string.user_cannot_manage_message)
.setPositiveButton(android.R.string.ok, null)
.create();
case DIALOG_ADD_USER: {
final SharedPreferences preferences = getActivity().getPreferences(
Context.MODE_PRIVATE);
final boolean longMessageDisplayed = preferences.getBoolean(
KEY_ADD_USER_LONG_MESSAGE_DISPLAYED, false);
final int messageResId = longMessageDisplayed
? com.android.settingslib.R.string.user_add_user_message_short
: com.android.settingslib.R.string.user_add_user_message_long;
Dialog dlg = new AlertDialog.Builder(context)
.setTitle(com.android.settingslib.R.string.user_add_user_title)
.setMessage(messageResId)
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
showDialog(DIALOG_USER_PROFILE_EDITOR_ADD_USER);
if (!longMessageDisplayed) {
preferences.edit().putBoolean(
KEY_ADD_USER_LONG_MESSAGE_DISPLAYED,
true).apply();
}
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
return dlg;
}
case DIALOG_CHOOSE_USER_TYPE: {
List<HashMap<String, String>> data = new ArrayList<HashMap<String, String>>();
HashMap<String, String> addUserItem = new HashMap<String, String>();
addUserItem.put(KEY_TITLE, getString(
com.android.settingslib.R.string.user_add_user_item_title));
addUserItem.put(KEY_SUMMARY, getString(
com.android.settingslib.R.string.user_add_user_item_summary));
HashMap<String, String> addProfileItem = new HashMap<String, String>();
addProfileItem.put(KEY_TITLE, getString(
com.android.settingslib.R.string.user_add_profile_item_title));
addProfileItem.put(KEY_SUMMARY, getString(
com.android.settingslib.R.string.user_add_profile_item_summary));
data.add(addUserItem);
data.add(addProfileItem);
AlertDialog.Builder builder = new AlertDialog.Builder(context);
SimpleAdapter adapter = new SimpleAdapter(builder.getContext(),
data, R.layout.two_line_list_item,
new String[]{KEY_TITLE, KEY_SUMMARY},
new int[]{R.id.title, R.id.summary});
builder.setTitle(com.android.settingslib.R.string.user_add_user_type_title);
builder.setAdapter(adapter,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
onAddUserClicked(which == 0
? USER_TYPE_USER
: USER_TYPE_RESTRICTED_PROFILE);
}
});
return builder.create();
}
case DIALOG_NEED_LOCKSCREEN: {
Dialog dlg = new AlertDialog.Builder(context)
.setMessage(com.android.settingslib.R.string.user_need_lock_message)
.setPositiveButton(com.android.settingslib.R.string.user_set_lock_button,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
launchChooseLockscreen();
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
return dlg;
}
case DIALOG_CONFIRM_REMOVE_GUEST: {
Dialog dlg = new AlertDialog.Builder(context)
.setTitle(com.android.settingslib.R.string.guest_remove_guest_dialog_title)
.setMessage(R.string.user_exit_guest_confirm_message)
.setPositiveButton(R.string.user_exit_guest_dialog_remove,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
clearAndExitGuest();
}
})
.setNegativeButton(android.R.string.cancel, null)
.create();
return dlg;
}
case DIALOG_CONFIRM_EXIT_GUEST_EPHEMERAL: {
Dialog dlg = new AlertDialog.Builder(context)
.setTitle(com.android.settingslib.R.string.guest_exit_dialog_title)
.setMessage(com.android.settingslib.R.string.guest_exit_dialog_message)
.setPositiveButton(
com.android.settingslib.R.string.guest_exit_dialog_button,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
clearAndExitGuest();
}
})
.setNeutralButton(android.R.string.cancel, null)
.create();
return dlg;
}
case DIALOG_CONFIRM_EXIT_GUEST_NON_EPHEMERAL: {
Dialog dlg = new AlertDialog.Builder(context)
.setTitle(
com.android.settingslib.R.string.guest_exit_dialog_title_non_ephemeral)
.setMessage(
com.android.settingslib
.R.string.guest_exit_dialog_message_non_ephemeral)
.setPositiveButton(
com.android.settingslib.R.string.guest_exit_save_data_button,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
exitGuest();
}
})
.setNegativeButton(
com.android.settingslib.R.string.guest_exit_clear_data_button,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
clearAndExitGuest();
}
})
.setNeutralButton(android.R.string.cancel, null)
.create();
return dlg;
}
case DIALOG_USER_PROFILE_EDITOR: {
return buildEditCurrentUserDialog();
}
case DIALOG_USER_PROFILE_EDITOR_ADD_USER: {
synchronized (mUserLock) {
mPendingUserName = getString(
com.android.settingslib.R.string.user_new_user_name);
mPendingUserIcon = null;
}
return buildAddUserDialog(USER_TYPE_USER);
}
case DIALOG_USER_PROFILE_EDITOR_ADD_RESTRICTED_PROFILE: {
synchronized (mUserLock) {
mPendingUserName = getString(
com.android.settingslib.R.string.user_new_profile_name);
mPendingUserIcon = null;
}
return buildAddUserDialog(USER_TYPE_RESTRICTED_PROFILE);
}
case DIALOG_CONFIRM_REMOVE_GUEST_WITH_AUTO_CREATE: {
return UserDialogs.createResetGuestDialog(getActivity(),
(dialog, which) -> clearAndExitGuest());
}
case DIALOG_CONFIRM_RESET_AND_RESTART_GUEST: {
Dialog dlg = new AlertDialog.Builder(context)
.setTitle(
com.android.settingslib.R.string.guest_reset_and_restart_dialog_title)
.setMessage(
com.android.settingslib.R.string.guest_reset_and_restart_dialog_message)
.setPositiveButton(
com.android.settingslib.R.string.guest_reset_guest_confirm_button,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
resetAndRestartGuest();
}
})
.setNeutralButton(android.R.string.cancel, null)
.create();
return dlg;
}
default:
return null;
}
}
private Dialog buildEditCurrentUserDialog() {
final Activity activity = getActivity();
if (activity == null) {
return null;
}
UserInfo user = mUserManager.getUserInfo(Process.myUserHandle().getIdentifier());
Drawable userIcon = Utils.getUserIcon(activity, mUserManager, user);
return mEditUserInfoController.createDialog(
activity,
this::startActivityForResult,
userIcon,
user.name,
getString(com.android.settingslib.R.string.profile_info_settings_title),
(newUserName, newUserIcon) -> {
if (newUserIcon != userIcon) {
ThreadUtils.postOnBackgroundThread(() ->
mUserManager.setUserIcon(user.id,
UserIcons.convertToBitmapAtUserIconSize(
activity.getResources(), newUserIcon)));
mMePreference.setIcon(newUserIcon);
}
if (!TextUtils.isEmpty(newUserName) && !newUserName.equals(user.name)) {
mMePreference.setTitle(newUserName);
mUserManager.setUserName(user.id, newUserName);
}
}, null);
}
private Dialog buildAddUserDialog(int userType) {
Dialog d;
synchronized (mUserLock) {
d = mEditUserInfoController.createDialog(
getActivity(),
this::startActivityForResult,
null,
mPendingUserName.toString(),
getString(userType == USER_TYPE_USER
? com.android.settingslib.R.string.user_info_settings_title
: com.android.settingslib.R.string.profile_info_settings_title),
(userName, userIcon) -> {
mPendingUserIcon = userIcon;
mPendingUserName = userName;
addUserNow(userType);
},
() -> {
synchronized (mUserLock) {
mPendingUserIcon = null;
mPendingUserName = null;
}
}
);
}
return d;
}
@Override
public int getDialogMetricsCategory(int dialogId) {
switch (dialogId) {
case DIALOG_CONFIRM_REMOVE:
return SettingsEnums.DIALOG_USER_REMOVE;
case DIALOG_USER_CANNOT_MANAGE:
return SettingsEnums.DIALOG_USER_CANNOT_MANAGE;
case DIALOG_ADD_USER:
return SettingsEnums.DIALOG_USER_ADD;
case DIALOG_CHOOSE_USER_TYPE:
return SettingsEnums.DIALOG_USER_CHOOSE_TYPE;
case DIALOG_NEED_LOCKSCREEN:
return SettingsEnums.DIALOG_USER_NEED_LOCKSCREEN;
case DIALOG_CONFIRM_REMOVE_GUEST:
case DIALOG_CONFIRM_REMOVE_GUEST_WITH_AUTO_CREATE:
case DIALOG_CONFIRM_EXIT_GUEST_EPHEMERAL:
case DIALOG_CONFIRM_EXIT_GUEST_NON_EPHEMERAL:
case DIALOG_CONFIRM_RESET_AND_RESTART_GUEST:
return SettingsEnums.DIALOG_USER_CONFIRM_EXIT_GUEST;
case DIALOG_USER_PROFILE_EDITOR:
case DIALOG_USER_PROFILE_EDITOR_ADD_USER:
case DIALOG_USER_PROFILE_EDITOR_ADD_RESTRICTED_PROFILE:
return SettingsEnums.DIALOG_USER_EDIT_PROFILE;
default:
return 0;
}
}
private void removeUserNow() {
if (mRemovingUserId == UserHandle.myUserId()) {
removeThisUser();
} else {
ThreadUtils.postOnBackgroundThread(new Runnable() {
@Override
public void run() {
synchronized (mUserLock) {
mUserManager.removeUser(mRemovingUserId);
mHandler.sendEmptyMessage(MESSAGE_UPDATE_LIST);
}
}
});
}
}
private void removeThisUser() {
if (!canSwitchUserNow()) {
Log.w(TAG, "Cannot remove current user when switching is disabled");
return;
}
try {
getContext().getSystemService(UserManager.class)
.removeUserWhenPossible(UserHandle.of(UserHandle.myUserId()),
/* overrideDevicePolicy= */ false);
ActivityManager.getService().switchUser(UserHandle.USER_SYSTEM);
} catch (RemoteException re) {
Log.e(TAG, "Unable to remove self user");
}
}
private void switchToUserId(int userId) {
if (!canSwitchUserNow()) {
Log.w(TAG, "Cannot switch current user when switching is disabled");
return;
}
try {
ActivityManager.getService().switchUser(userId);
} catch (RemoteException re) {
Log.e(TAG, "Unable to switch user");
}
}
private void addUserNow(final int userType) {
Trace.beginAsyncSection("UserSettings.addUserNow", 0);
synchronized (mUserLock) {
mAddingUser = true;
mAddingUserName = userType == USER_TYPE_USER
? (mPendingUserName != null ? mPendingUserName.toString()
: getString(R.string.user_new_user_name))
: (mPendingUserName != null ? mPendingUserName.toString()
: getString(R.string.user_new_profile_name));
}
mUserCreatingDialog = new UserCreatingDialog(getActivity());
mUserCreatingDialog.show();
createUser(userType, mAddingUserName);
}
@VisibleForTesting
void createUser(final int userType, String userName) {
Context context = getContext();
Resources resources = getResources();
final Drawable selectedUserIcon = mPendingUserIcon;
Future<?> unusedCreateUserFuture = ThreadUtils.postOnBackgroundThread(() -> {
UserInfo user;
if (userType == USER_TYPE_USER) {
user = mUserManager.createUser(
userName,
mUserManager.USER_TYPE_FULL_SECONDARY,
0);
} else {
user = mUserManager.createRestrictedProfile(userName);
}
ThreadUtils.postOnMainThread(() -> {
if (user == null) {
mAddingUser = false;
mPendingUserIcon = null;
mPendingUserName = null;
onUserCreationFailed();
return;
}
Future<?> unusedSettingIconFuture = ThreadUtils.postOnBackgroundThread(() -> {
Drawable newUserIcon = selectedUserIcon;
if (newUserIcon == null) {
newUserIcon = UserIcons.getDefaultUserIcon(resources, user.id, false);
}
mUserManager.setUserIcon(
user.id, UserIcons.convertToBitmapAtUserIconSize(
resources, newUserIcon));
});
mPendingUserIcon = null;
mPendingUserName = null;
onUserCreated(user, context);
});
});
}
/**
* Erase the current user (guest) and switch to another user.
*/
@VisibleForTesting
void clearAndExitGuest() {
// Just to be safe
if (!isCurrentUserGuest()) {
return;
}
mMetricsFeatureProvider.action(getActivity(),
SettingsEnums.ACTION_USER_GUEST_EXIT_CONFIRMED);
int guestUserId = UserHandle.myUserId();
// Using markGuestForDeletion allows us to create a new guest before this one is
// fully removed.
boolean marked = mUserManager.markGuestForDeletion(guestUserId);
if (!marked) {
Log.w(TAG, "Couldn't mark the guest for deletion for user " + guestUserId);
return;
}
removeThisUser();
if (mGuestUserAutoCreated) {
scheduleGuestCreation();
}
}
/**
* Switch to another user.
*/
private void exitGuest() {
// Just to be safe
if (!isCurrentUserGuest()) {
return;
}
mMetricsFeatureProvider.action(getActivity(),
SettingsEnums.ACTION_USER_GUEST_EXIT_CONFIRMED);
switchToUserId(UserHandle.USER_SYSTEM);
}
private int createGuest() {
UserInfo guest;
Context context = getPrefContext();
try {
guest = mUserManager.createGuest(context);
} catch (UserManager.UserOperationException e) {
Log.e(TAG, "Couldn't create guest user", e);
return UserHandle.USER_NULL;
}
if (guest == null) {
Log.e(TAG, "Couldn't create guest, most likely because there already exists one");
return UserHandle.USER_NULL;
}
return guest.id;
}
/**
* Remove current guest and start a new guest session
*/
private void resetAndRestartGuest() {
// Just to be safe
if (!isCurrentUserGuest()) {
return;
}
int oldGuestUserId = UserHandle.myUserId();
// Using markGuestForDeletion allows us to create a new guest before this one is
// fully removed.
boolean marked = mUserManager.markGuestForDeletion(oldGuestUserId);
if (!marked) {
Log.w(TAG, "Couldn't mark the guest for deletion for user " + oldGuestUserId);
return;
}
try {
// Create a new guest in the foreground, and then immediately switch to it
int newGuestUserId = createGuest();
if (newGuestUserId == UserHandle.USER_NULL) {
Log.e(TAG, "Could not create new guest, switching back to system user");
switchToUserId(UserHandle.USER_SYSTEM);
mUserManager.removeUser(oldGuestUserId);
WindowManagerGlobal.getWindowManagerService().lockNow(/* options= */ null);
return;
}
switchToUserId(newGuestUserId);
mUserManager.removeUser(oldGuestUserId);
} catch (RemoteException e) {
Log.e(TAG, "Couldn't remove guest because ActivityManager or WindowManager is dead");
return;
}
}
/**
* Create a guest user in the background
*/
@VisibleForTesting
void scheduleGuestCreation() {
// TODO(b/191067027): Move guest recreation to system_server
if (mGuestCreationScheduled.compareAndSet(/* expect= */ false, /* update= */ true)) {
// Once mGuestCreationScheduled=true, mAddGuest needs to be updated so that it shows
// "Resetting guest..."
mHandler.sendEmptyMessage(MESSAGE_UPDATE_LIST);
mExecutor.execute(() -> {
UserInfo guest = mUserManager.createGuest(getContext());
mGuestCreationScheduled.set(false);
if (guest == null) {
Log.e(TAG, "Unable to automatically recreate guest user");
}
// The list needs to be updated whether or not guest creation worked. If guest
// creation failed, the list needs to update so that "Add guest" is displayed.
// Otherwise, the UX could be stuck in a state where there is no way to switch to
// the guest user (e.g. Guest would not be selectable, and it would be stuck
// saying "Resetting guest...")
mHandler.sendEmptyMessage(MESSAGE_UPDATE_LIST);
});
}
}
@VisibleForTesting
void updateUserList() {
final Context context = getActivity();
if (context == null) {
return;
}
List<UserInfo> users;
if (mUserCaps.mUserSwitcherEnabled) {
// Only users that can be switched to should show up here.
// e.g. Managed profiles appear under Accounts Settings instead
users = mUserManager.getAliveUsers().stream()
.filter(UserInfo::supportsSwitchToByUser)
.collect(Collectors.toList());
} else {
// Only current user will be displayed in case of multi-user switch is disabled
users = List.of(mUserManager.getUserInfo(context.getUserId()));
}
final ArrayList<Integer> missingIcons = new ArrayList<>();
final ArrayList<UserPreference> userPreferences = new ArrayList<>();
// mMePreference shows a icon for current user. However when current user is a guest, we
// don't show the guest user icon, instead we show two preferences for guest user to
// exit and reset itself. Hence we don't add mMePreference, i.e. guest user to the
// list of users visible in the UI.
if (!isCurrentUserGuest()) {
userPreferences.add(mMePreference);
}
boolean canOpenUserDetails =
isCurrentUserAdmin() || (canSwitchUserNow() && !mUserCaps.mDisallowSwitchUser);
for (UserInfo user : users) {
if (user.isGuest()) {
// Guest user is added to guest category via updateGuestCategory
// and not to user list so skip guest here
continue;
}
UserPreference pref;
if (user.id == UserHandle.myUserId()) {
pref = mMePreference;
} else {
pref = new UserPreference(getPrefContext(), null, user.id);
pref.setTitle(user.name);
userPreferences.add(pref);
pref.setOnPreferenceClickListener(this);
pref.setEnabled(canOpenUserDetails);
pref.setSelectable(true);
pref.setKey("id=" + user.id);
if (user.isAdmin()) {
pref.setSummary(R.string.user_admin);
}
}
if (pref == null) {
continue;
}
if (user.id != UserHandle.myUserId() && !user.isGuest() && !user.isInitialized()) {
// sometimes after creating a guest the initialized flag isn't immediately set
// and we don't want to show "Not set up" summary for them
if (user.isRestricted()) {
pref.setSummary(R.string.user_summary_restricted_not_set_up);
} else {
pref.setSummary(R.string.user_summary_not_set_up);
// Disallow setting up user which results in user switching when the
// restriction is set.
pref.setEnabled(!mUserCaps.mDisallowSwitchUser && canSwitchUserNow());
}
} else if (user.isRestricted()) {
pref.setSummary(R.string.user_summary_restricted_profile);
}
if (user.iconPath != null) {
if (mUserIcons.get(user.id) == null) {
// Icon not loaded yet, print a placeholder
missingIcons.add(user.id);
pref.setIcon(getEncircledDefaultIcon());
} else {
setPhotoId(pref, user);
}
} else {
// Icon not available yet, print a placeholder
pref.setIcon(getEncircledDefaultIcon());
}
}
// Add a temporary entry for the user being created
if (mAddingUser) {
UserPreference pref = new UserPreference(getPrefContext(), null,
UserPreference.USERID_UNKNOWN);
pref.setEnabled(false);
pref.setTitle(mAddingUserName);
pref.setIcon(getEncircledDefaultIcon());
userPreferences.add(pref);
}
// Sort list of users by serialNum
Collections.sort(userPreferences, UserPreference.SERIAL_NUMBER_COMPARATOR);
getActivity().invalidateOptionsMenu();
// Load the icons
if (missingIcons.size() > 0) {
loadIconsAsync(missingIcons);
}
// If restricted profiles are supported, mUserListCategory will have a special title
if (mUserCaps.mCanAddRestrictedProfile) {
mUserListCategory.setTitle(R.string.user_list_title);
} else if (isCurrentUserGuest()) {
mUserListCategory.setTitle(R.string.other_user_category_title);
} else {
mUserListCategory.setTitle(R.string.user_category_title);
}
// Remove everything from mUserListCategory and add new users.
mUserListCategory.removeAll();
final Preference addUserOnLockScreen = getPreferenceScreen().findPreference(
mAddUserWhenLockedPreferenceController.getPreferenceKey());
mAddUserWhenLockedPreferenceController.updateState(addUserOnLockScreen);
final Preference guestCallPreference = getPreferenceScreen().findPreference(
mGuestTelephonyPreferenceController.getPreferenceKey());
mGuestTelephonyPreferenceController.updateState(guestCallPreference);
final Preference multiUserTopIntroPreference = getPreferenceScreen().findPreference(
mMultiUserTopIntroPreferenceController.getPreferenceKey());
mMultiUserTopIntroPreferenceController.updateState(multiUserTopIntroPreference);
updateGuestPreferences();
updateGuestCategory(context, users);
updateAddUser(context);
updateAddSupervisedUser(context);
for (UserPreference userPreference : userPreferences) {
userPreference.setOrder(Preference.DEFAULT_ORDER);
mUserListCategory.addPreference(userPreference);
}
}
@VisibleForTesting
void setConfigSupervisedUserCreationPackage() {
mConfigSupervisedUserCreationPackage = getPrefContext().getString(
com.android.internal.R.string.config_supervisedUserCreationPackage);
}
private boolean isCurrentUserGuest() {
return mUserCaps.mIsGuest;
}
private boolean isCurrentUserAdmin() {
return mUserCaps.mIsAdmin;
}
private boolean canSwitchUserNow() {
return mUserManager.getUserSwitchability() == UserManager.SWITCHABILITY_STATUS_OK;
}
private void updateGuestPreferences() {
// reset guest and exit guest preferences are shown only in guest mode.
// For all other users these are not visible.
mGuestCategory.setVisible(false);
mGuestResetPreference.setVisible(false);
mGuestExitPreference.setVisible(false);
if (!isCurrentUserGuest()) {
return;
}
mGuestCategory.setVisible(true);
mGuestExitPreference.setVisible(true);
mGuestResetPreference.setVisible(true);
boolean isGuestFirstLogin = Settings.Secure.getIntForUser(
getContext().getContentResolver(),
SETTING_GUEST_HAS_LOGGED_IN,
0,
UserHandle.myUserId()) <= 1;
String guestExitSummary;
if (mUserCaps.mIsEphemeral) {
guestExitSummary = getContext().getString(
R.string.guest_notification_ephemeral);
} else if (isGuestFirstLogin) {
guestExitSummary = getContext().getString(
R.string.guest_notification_non_ephemeral);
} else {
guestExitSummary = getContext().getString(
R.string.guest_notification_non_ephemeral_non_first_login);
}
mGuestExitPreference.setSummary(guestExitSummary);
}
private void updateGuestCategory(Context context, List<UserInfo> users) {
// show guest category title and related guest preferences
// - if guest is created, then show guest user preference
// - if guest is not created and its allowed to create guest,
// then show "add guest" preference
// - if allowed, show "reset guest on exit" preference
// - if there is nothing to show, then make the guest category as not visible
// - guest category is not visible for guest user.
UserPreference pref = null;
boolean isGuestAlreadyCreated = false;
boolean canOpenUserDetails =
isCurrentUserAdmin() || (canSwitchUserNow() && !mUserCaps.mDisallowSwitchUser);
mGuestUserCategory.removeAll();
mGuestUserCategory.setVisible(false);
for (UserInfo user : users) {
if (!user.isGuest() || !user.isEnabled()) {
// Only look at enabled, guest users
continue;
}
final Context prefContext = getPrefContext();
pref = new UserPreference(prefContext, null, user.id);
pref.setTitle(user.name);
pref.setOnPreferenceClickListener(this);
pref.setEnabled(canOpenUserDetails);
pref.setSelectable(true);
Drawable icon = getContext().getDrawable(R.drawable.ic_account_circle_outline);
icon.setTint(
getColorAttrDefaultColor(getContext(), android.R.attr.colorControlNormal));
pref.setIcon(encircleUserIcon(
UserIcons.convertToBitmapAtUserIconSize(
getContext().getResources(), icon)));
pref.setKey(KEY_USER_GUEST);
pref.setOrder(Preference.DEFAULT_ORDER);
if (mUserCaps.mDisallowSwitchUser) {
pref.setDisabledByAdmin(
RestrictedLockUtilsInternal.getDeviceOwner(context));
} else {
pref.setDisabledByAdmin(null);
}
if (mUserCaps.mUserSwitcherEnabled) {
mGuestUserCategory.addPreference(pref);
// guest user preference is shown hence also make guest category visible
mGuestUserCategory.setVisible(true);
}
isGuestAlreadyCreated = true;
}
boolean isVisible = updateAddGuestPreference(context, isGuestAlreadyCreated);
if (isVisible) {
// "add guest" preference is shown hence also make guest category visible
mGuestUserCategory.setVisible(true);
}
final Preference removeGuestOnExit = getPreferenceScreen().findPreference(
mRemoveGuestOnExitPreferenceController.getPreferenceKey());
mRemoveGuestOnExitPreferenceController.updateState(removeGuestOnExit);
if (mRemoveGuestOnExitPreferenceController.isAvailable()) {
// "reset guest on exit" preference is shown hence also make guest category visible
mGuestUserCategory.setVisible(true);
}
if (isCurrentUserGuest()) {
// guest category is not visible for guest user.
mGuestUserCategory.setVisible(false);
}
}
private boolean updateAddGuestPreference(Context context, boolean isGuestAlreadyCreated) {
boolean isVisible = false;
if (!isGuestAlreadyCreated && mUserCaps.mCanAddGuest
&& mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_GUEST)
&& WizardManagerHelper.isDeviceProvisioned(context)
&& mUserCaps.mUserSwitcherEnabled) {
Drawable icon = context.getDrawable(R.drawable.ic_account_circle);
mAddGuest.setIcon(centerAndTint(icon));
isVisible = true;
mAddGuest.setVisible(true);
mAddGuest.setSelectable(true);
if (mGuestUserAutoCreated && mGuestCreationScheduled.get()) {
mAddGuest.setTitle(com.android.internal.R.string.guest_name);
mAddGuest.setSummary(R.string.guest_resetting);
mAddGuest.setEnabled(false);
} else {
mAddGuest.setTitle(com.android.settingslib.R.string.guest_new_guest);
mAddGuest.setEnabled(canSwitchUserNow());
}
} else {
mAddGuest.setVisible(false);
}
return isVisible;
}
private void updateAddUser(Context context) {
updateAddUserCommon(context, mAddUser, mUserCaps.mCanAddRestrictedProfile);
Drawable icon = context.getDrawable(R.drawable.ic_account_circle_filled);
mAddUser.setIcon(centerAndTint(icon));
}
private void updateAddSupervisedUser(Context context) {
if (!TextUtils.isEmpty(mConfigSupervisedUserCreationPackage)) {
updateAddUserCommon(context, mAddSupervisedUser, false);
Drawable icon = context.getDrawable(R.drawable.ic_add_supervised_user);
mAddSupervisedUser.setIcon(centerAndTint(icon));
} else {
mAddSupervisedUser.setVisible(false);
}
}
private void updateAddUserCommon(Context context, RestrictedPreference addUser,
boolean canAddRestrictedProfile) {
if ((mUserCaps.mCanAddUser && !mUserCaps.mDisallowAddUserSetByAdmin)
&& WizardManagerHelper.isDeviceProvisioned(context)
&& mUserCaps.mUserSwitcherEnabled) {
addUser.setVisible(true);
addUser.setSelectable(true);
final boolean canAddMoreUsers =
mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY)
|| (canAddRestrictedProfile
&& mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_RESTRICTED));
addUser.setEnabled(canAddMoreUsers && !mAddingUser && canSwitchUserNow());
if (!canAddMoreUsers) {
addUser.setSummary(getString(R.string.user_add_max_count));
} else {
addUser.setSummary(null);
}
if (addUser.isEnabled()) {
addUser.setDisabledByAdmin(
mUserCaps.mDisallowAddUser ? mUserCaps.mEnforcedAdmin : null);
}
} else {
addUser.setVisible(false);
}
}
private Drawable centerAndTint(Drawable icon) {
icon.setTintBlendMode(BlendMode.SRC_IN);
icon.setTint(getColorAttrDefaultColor(getContext(), android.R.attr.textColorPrimary));
Drawable bg = getContext().getDrawable(R.drawable.user_avatar_bg).mutate();
LayerDrawable ld = new LayerDrawable(new Drawable[] {bg, icon});
int size = getContext().getResources().getDimensionPixelSize(
R.dimen.multiple_users_avatar_size);
int bgSize = getContext().getResources().getDimensionPixelSize(
R.dimen.multiple_users_user_icon_size);
ld.setLayerSize(1, size, size);
ld.setLayerSize(0, bgSize, bgSize);
ld.setLayerGravity(1, Gravity.CENTER);
return ld;
}
/**
* @return number of non-guest non-managed users
*/
@VisibleForTesting
int getRealUsersCount() {
return (int) mUserManager.getUsers()
.stream()
.filter(user -> !user.isGuest() && !user.isProfile())
.count();
}
private void loadIconsAsync(List<Integer> missingIcons) {
new AsyncTask<List<Integer>, Void, Void>() {
@Override
protected void onPostExecute(Void result) {
updateUserList();
}
@Override
protected Void doInBackground(List<Integer>... values) {
for (int userId : values[0]) {
Bitmap bitmap = mUserManager.getUserIcon(userId);
if (bitmap == null) {
bitmap = getDefaultUserIconAsBitmap(getContext().getResources(), userId);
}
mUserIcons.append(userId, bitmap);
}
return null;
}
}.execute(missingIcons);
}
private Drawable getEncircledDefaultIcon() {
if (mDefaultIconDrawable == null) {
mDefaultIconDrawable = encircleUserIcon(
getDefaultUserIconAsBitmap(getContext().getResources(), UserHandle.USER_NULL));
}
return mDefaultIconDrawable;
}
private void setPhotoId(Preference pref, UserInfo user) {
Bitmap bitmap = mUserIcons.get(user.id);
if (bitmap != null) {
pref.setIcon(encircleUserIcon(bitmap));
}
}
@Override
public boolean onPreferenceClick(Preference pref) {
if (isCurrentUserGuest()) {
if (mGuestResetPreference != null && pref == mGuestResetPreference) {
showDialog(DIALOG_CONFIRM_RESET_AND_RESTART_GUEST);
return true;
}
if (mGuestExitPreference != null && pref == mGuestExitPreference) {
if (mUserCaps.mIsEphemeral) {
showDialog(DIALOG_CONFIRM_EXIT_GUEST_EPHEMERAL);
} else {
showDialog(DIALOG_CONFIRM_EXIT_GUEST_NON_EPHEMERAL);
}
return true;
}
}
if (pref == mMePreference) {
if (!isCurrentUserGuest()) {
showDialog(DIALOG_USER_PROFILE_EDITOR);
return true;
}
} else if (pref instanceof UserPreference) {
UserInfo userInfo = mUserManager.getUserInfo(((UserPreference) pref).getUserId());
openUserDetails(userInfo, false);
return true;
} else if (pref == mAddUser) {
// If we allow both types, show a picker, otherwise directly go to
// flow for full user.
if (mUserCaps.mCanAddRestrictedProfile) {
showDialog(DIALOG_CHOOSE_USER_TYPE);
} else {
onAddUserClicked(USER_TYPE_USER);
}
return true;
} else if (pref == mAddSupervisedUser) {
mMetricsFeatureProvider.action(getActivity(), SettingsEnums.ACTION_USER_SUPERVISED_ADD);
Trace.beginSection("UserSettings.addSupervisedUser");
onAddSupervisedUserClicked();
Trace.endSection();
return true;
} else if (pref == mAddGuest) {
mAddGuest.setEnabled(false); // prevent multiple tap issue
onAddGuestClicked();
return true;
}
return false;
}
private Drawable encircleUserIcon(Bitmap icon) {
return new CircleFramedDrawable(
icon,
getActivity().getResources().getDimensionPixelSize(
R.dimen.multiple_users_user_icon_size));
}
@Override
public void onDismiss(DialogInterface dialog) {
synchronized (mUserLock) {
mRemovingUserId = -1;
updateUserList();
}
}
@Override
public int getHelpResource() {
return R.string.help_url_users;
}
/**
* Returns a default user icon (as a {@link Bitmap}) for the given user.
*
* Note that for guest users, you should pass in {@code UserHandle.USER_NULL}.
*
* @param resources resources object to fetch the user icon.
* @param userId the user id or {@code UserHandle.USER_NULL} for a non-user specific icon
*/
private static Bitmap getDefaultUserIconAsBitmap(Resources resources, int userId) {
Bitmap bitmap = null;
// Try finding the corresponding bitmap in the dark bitmap cache
bitmap = sDarkDefaultUserBitmapCache.get(userId);
if (bitmap == null) {
bitmap = UserIcons.convertToBitmapAtUserIconSize(resources,
UserIcons.getDefaultUserIcon(resources, userId, false));
// Save it to cache
sDarkDefaultUserBitmapCache.put(userId, bitmap);
}
return bitmap;
}
/**
* Assign the default photo to user with {@paramref userId}
*
* @param context used to get the {@link UserManager}
* @param userId used to get the icon bitmap
* @return true if assign photo successfully, false if failed
*/
@VisibleForTesting
static boolean assignDefaultPhoto(Context context, int userId) {
if (context == null) {
return false;
}
UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
Bitmap bitmap = getDefaultUserIconAsBitmap(context.getResources(), userId);
um.setUserIcon(userId, bitmap);
return true;
}
@WorkerThread
static void copyMeProfilePhoto(Context context, UserInfo user) {
Uri contactUri = ContactsContract.Profile.CONTENT_URI;
int userId = user != null ? user.id : UserHandle.myUserId();
InputStream avatarDataStream = ContactsContract.Contacts.openContactPhotoInputStream(
context.getContentResolver(),
contactUri, true);
// If there's no profile photo, assign a default avatar
if (avatarDataStream == null) {
assignDefaultPhoto(context, userId);
return;
}
UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
Bitmap decodedIcon = BitmapFactory.decodeStream(avatarDataStream);
CircleFramedDrawable drawable = CircleFramedDrawable.getInstance(context, decodedIcon);
Bitmap icon = UserIcons.convertToBitmapAtUserIconSize(context.getResources(), drawable);
um.setUserIcon(userId, icon);
try {
avatarDataStream.close();
} catch (IOException ioe) {
}
}
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.user_settings) {
@Override
protected boolean isPageSearchEnabled(Context context) {
final UserCapabilities userCaps = UserCapabilities.create(context);
return userCaps.mEnabled;
}
@Override
public List<SearchIndexableRaw> getRawDataToIndex(Context context,
boolean enabled) {
final List<SearchIndexableRaw> rawData = new ArrayList<>();
SearchIndexableRaw allowMultipleUsersResult = new SearchIndexableRaw(context);
allowMultipleUsersResult.key = KEY_ALLOW_MULTIPLE_USERS;
allowMultipleUsersResult.title =
context.getString(R.string.multiple_users_main_switch_title);
allowMultipleUsersResult.keywords =
context.getString(R.string.multiple_users_main_switch_keywords);
allowMultipleUsersResult.screenTitle =
context.getString(R.string.user_settings_title);
allowMultipleUsersResult.className =
MultiUserSwitchBarController.class.getName();
rawData.add(allowMultipleUsersResult);
return rawData;
}
@Override
public List<String> getNonIndexableKeysFromXml(Context context, int xmlResId,
boolean suppressAllPage) {
final List<String> niks = super.getNonIndexableKeysFromXml(context, xmlResId,
suppressAllPage);
AddUserWhenLockedPreferenceController controller =
new AddUserWhenLockedPreferenceController(
context, KEY_ADD_USER_WHEN_LOCKED);
controller.updateNonIndexableKeys(niks);
new AutoSyncDataPreferenceController(context, null /* parent */)
.updateNonIndexableKeys(niks);
new AutoSyncPersonalDataPreferenceController(context, null /* parent */)
.updateNonIndexableKeys(niks);
new AutoSyncWorkDataPreferenceController(context, null /* parent */)
.updateNonIndexableKeys(niks);
return niks;
}
};
}