| /* |
| * 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.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.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_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 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"; |
| |
| 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 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_USER_CREATED: |
| onUserCreated(msg.arg1); |
| 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 (mUserCaps.mIsAdmin) { |
| switchBar.show(); |
| } else { |
| switchBar.hide(); |
| } |
| mSwitchBarController = new MultiUserSwitchBarController(activity, |
| new MainSwitchBarController(switchBar), this /* listener */); |
| getSettingsLifecycle().addObserver(mSwitchBarController); |
| } |
| |
| @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); |
| |
| 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); |
| mRemoveGuestOnExitPreferenceController.displayPreference(screen); |
| mMultiUserTopIntroPreferenceController.displayPreference(screen); |
| mTimeoutToDockUserPreferenceController.displayPreference(screen); |
| |
| screen.findPreference(mAddUserWhenLockedPreferenceController.getPreferenceKey()) |
| .setOnPreferenceChangeListener(mAddUserWhenLockedPreferenceController); |
| |
| 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 (mUserCaps.mIsAdmin) { |
| 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())); |
| 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; |
| // TODO(b/191509236): The menu item does not need to be accessible for guest users, |
| // regardless of mGuestUserAutoCreated |
| if (!mUserCaps.mIsAdmin && canSwitchUserNow() && !(isCurrentUserGuest() |
| && mGuestUserAutoCreated)) { |
| 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() { |
| 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(getContext()); |
| Trace.endSection(); |
| |
| ThreadUtils.postOnMainThread(() -> { |
| guestCreatingDialog.dismiss(); |
| if (guest == null) { |
| Toast.makeText(getContext(), |
| com.android.settingslib.R.string.add_guest_failed, |
| Toast.LENGTH_SHORT).show(); |
| return; |
| } |
| openUserDetails(guest, true); |
| }); |
| }); |
| } |
| |
| private void onRemoveUserClicked(int userId) { |
| synchronized (mUserLock) { |
| if (mRemovingUserId == -1 && !mAddingUser) { |
| mRemovingUserId = userId; |
| showDialog(DIALOG_CONFIRM_REMOVE); |
| } |
| } |
| } |
| |
| private void onUserCreated(int userId) { |
| hideUserCreatingDialog(); |
| // prevent crash when config changes during user creation |
| if (getContext() == null) { |
| return; |
| } |
| mAddingUser = false; |
| UserInfo userInfo = mUserManager.getUserInfo(userId); |
| openUserDetails(userInfo, true); |
| } |
| |
| 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) { |
| Bundle extras = new Bundle(); |
| extras.putInt(UserDetailsSettings.EXTRA_USER_ID, userInfo.id); |
| extras.putBoolean(AppRestrictionsFragment.EXTRA_NEW_USER, newUser); |
| |
| SubSettingLauncher launcher = new SubSettingLauncher(getContext()) |
| .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(); |
| ThreadUtils.postOnBackgroundThread(new AddUserNowImpl(userType, mAddingUserName)); |
| } |
| |
| @VisibleForTesting |
| class AddUserNowImpl implements Runnable{ |
| int mUserType; |
| String mImplAddUserName; |
| |
| AddUserNowImpl(final int userType, final String addUserName) { |
| mUserType = userType; |
| mImplAddUserName = addUserName; |
| } |
| |
| @Override |
| public void run() { |
| runAddUser(); |
| Trace.endAsyncSection("UserSettings.addUserNow", 0); |
| } |
| |
| private void runAddUser() { |
| UserInfo user; |
| String username; |
| |
| synchronized (mUserLock) { |
| username = mImplAddUserName; |
| } |
| |
| // Could take a few seconds |
| if (mUserType == USER_TYPE_USER) { |
| user = mUserManager.createUser(username, 0); |
| } else { |
| user = mUserManager.createRestrictedProfile(username); |
| } |
| |
| synchronized (mUserLock) { |
| if (user == null) { |
| mAddingUser = false; |
| mPendingUserIcon = null; |
| mPendingUserName = null; |
| ThreadUtils.postOnMainThread(() -> onUserCreationFailed()); |
| return; |
| } |
| |
| Drawable newUserIcon = mPendingUserIcon; |
| if (newUserIcon == null) { |
| newUserIcon = UserIcons.getDefaultUserIcon(getResources(), user.id, false); |
| } |
| mUserManager.setUserIcon( |
| user.id, UserIcons.convertToBitmapAtUserIconSize( |
| getResources(), newUserIcon)); |
| |
| if (mUserType == USER_TYPE_USER) { |
| mHandler.sendEmptyMessage(MESSAGE_UPDATE_LIST); |
| } |
| |
| mHandler.sendMessage(mHandler.obtainMessage( |
| MESSAGE_USER_CREATED, user.id, user.serialNumber)); |
| |
| mPendingUserIcon = null; |
| mPendingUserName = null; |
| } |
| } |
| }; |
| |
| /** |
| * 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; |
| } |
| final List<UserInfo> users = mUserManager.getAliveUsers() |
| // Only users that can be switched to should show up here. |
| // e.g. Managed profiles appear under Accounts Settings instead |
| .stream().filter(UserInfo::supportsSwitchToByUser) |
| .collect(Collectors.toList()); |
| 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 (!mUserCaps.mIsGuest) { |
| userPreferences.add(mMePreference); |
| } |
| |
| boolean canOpenUserDetails = |
| mUserCaps.mIsAdmin || (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(); |
| |
| // If multi-user is disabled, just show top info and return. |
| final Preference addUserOnLockScreen = getPreferenceScreen().findPreference( |
| mAddUserWhenLockedPreferenceController.getPreferenceKey()); |
| mAddUserWhenLockedPreferenceController.updateState(addUserOnLockScreen); |
| |
| final Preference multiUserTopIntroPrefence = getPreferenceScreen().findPreference( |
| mMultiUserTopIntroPreferenceController.getPreferenceKey()); |
| mMultiUserTopIntroPreferenceController.updateState(multiUserTopIntroPrefence); |
| mUserListCategory.setVisible(mUserCaps.mUserSwitcherEnabled); |
| updateGuestPreferences(); |
| updateGuestCategory(context, users); |
| updateAddUser(context); |
| updateAddSupervisedUser(context); |
| |
| if (!mUserCaps.mUserSwitcherEnabled) { |
| return; |
| } |
| |
| 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 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 = |
| mUserCaps.mIsAdmin || (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 (mUserCaps.mIsGuest) { |
| // 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<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; |
| } |
| }; |
| } |