Guest mode UX flow updates to user settings

- Add option in user settings to enable/disable ephemeral
  mode for guest user
- Update user settings to show exit guest and reset guest preferences
- Update user settings to show guest related preferences grouped together

Bug: 214031645, 175795666
Screenshots: go/ephemeral-guest-b-214031645-ux
Test: Manual test on sunfish, atest SystemUITests, atest SettingsRoboTests

Relands ag/16544951 after fixing post submit issues

Revert "Revert "Guest mode UX flow updates to user settings""

This reverts commit ed45e8c56a.

Change-Id: I54583f9021171ae523ff40d4f63835f1cb486e35
Merged-In: I54583f9021171ae523ff40d4f63835f1cb486e35
This commit is contained in:
Kedar Chitnis
2022-03-25 05:16:33 +00:00
parent de480154c4
commit a1fbad7dd8
12 changed files with 852 additions and 91 deletions

View File

@@ -0,0 +1,25 @@
<!--
Copyright (C) 2022 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48"
android:tint="?android:attr/colorAccent">
<path
android:fillColor="#FF000000"
android:pathData="M11.1,35.25Q14.25,33.05 17.35,31.875Q20.45,30.7 24,30.7Q27.55,30.7 30.675,31.875Q33.8,33.05 36.95,35.25Q39.15,32.55 40.075,29.8Q41,27.05 41,24Q41,16.75 36.125,11.875Q31.25,7 24,7Q16.75,7 11.875,11.875Q7,16.75 7,24Q7,27.05 7.95,29.8Q8.9,32.55 11.1,35.25ZM24,25.5Q21.1,25.5 19.125,23.525Q17.15,21.55 17.15,18.65Q17.15,15.75 19.125,13.775Q21.1,11.8 24,11.8Q26.9,11.8 28.875,13.775Q30.85,15.75 30.85,18.65Q30.85,21.55 28.875,23.525Q26.9,25.5 24,25.5ZM24,44Q19.9,44 16.25,42.425Q12.6,40.85 9.875,38.125Q7.15,35.4 5.575,31.75Q4,28.1 4,24Q4,19.85 5.575,16.225Q7.15,12.6 9.875,9.875Q12.6,7.15 16.25,5.575Q19.9,4 24,4Q28.15,4 31.775,5.575Q35.4,7.15 38.125,9.875Q40.85,12.6 42.425,16.225Q44,19.85 44,24Q44,28.1 42.425,31.75Q40.85,35.4 38.125,38.125Q35.4,40.85 31.775,42.425Q28.15,44 24,44ZM24,41Q26.75,41 29.375,40.2Q32,39.4 34.55,37.4Q32,35.6 29.35,34.65Q26.7,33.7 24,33.7Q21.3,33.7 18.65,34.65Q16,35.6 13.45,37.4Q16,39.4 18.625,40.2Q21.25,41 24,41ZM24,22.5Q25.7,22.5 26.775,21.425Q27.85,20.35 27.85,18.65Q27.85,16.95 26.775,15.875Q25.7,14.8 24,14.8Q22.3,14.8 21.225,15.875Q20.15,16.95 20.15,18.65Q20.15,20.35 21.225,21.425Q22.3,22.5 24,22.5ZM24,18.65Q24,18.65 24,18.65Q24,18.65 24,18.65Q24,18.65 24,18.65Q24,18.65 24,18.65Q24,18.65 24,18.65Q24,18.65 24,18.65Q24,18.65 24,18.65Q24,18.65 24,18.65ZM24,37.35Q24,37.35 24,37.35Q24,37.35 24,37.35Q24,37.35 24,37.35Q24,37.35 24,37.35Q24,37.35 24,37.35Q24,37.35 24,37.35Q24,37.35 24,37.35Q24,37.35 24,37.35Z"/>
</vector>

View File

@@ -0,0 +1,25 @@
<!--
Copyright (C) 2022 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#FF000000"
android:pathData="M3,13v-2h8.65L9.1,8.45 10.5,7l5,5 -5,5 -1.4,-1.45L11.65,13zM5,15v4h14L19,5L5,5v4L3,9L3,5q0,-0.825 0.587,-1.413Q4.175,3 5,3h14q0.825,0 1.413,0.587Q21,4.175 21,5v14q0,0.825 -0.587,1.413Q19.825,21 19,21L5,21q-0.825,0 -1.413,-0.587Q3,19.825 3,19v-4z"/>
</vector>

View File

@@ -0,0 +1,25 @@
<!--
Copyright (C) 2022 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#FF000000"
android:pathData="M11,20.95q-3.025,-0.375 -5.013,-2.637Q4,16.05 4,13q0,-1.65 0.65,-3.163Q5.3,8.325 6.5,7.2l1.425,1.425q-0.95,0.85 -1.438,1.975Q6,11.725 6,13q0,2.2 1.4,3.887 1.4,1.688 3.6,2.063zM13,20.95v-2q2.175,-0.4 3.587,-2.075Q18,15.2 18,13q0,-2.5 -1.75,-4.25T12,7h-0.075l1.1,1.1 -1.4,1.4 -3.5,-3.5 3.5,-3.5 1.4,1.4 -1.1,1.1L12,5q3.35,0 5.675,2.325Q20,9.65 20,13q0,3.025 -1.988,5.288Q16.026,20.55 13,20.95z"/>
</vector>

View File

@@ -7712,6 +7712,22 @@
<string name="user_exit_guest_confirm_message">All apps and data in this session will be deleted.</string>
<!-- Label for button in confirmation dialog when exiting guest session [CHAR LIMIT=35] -->
<string name="user_exit_guest_dialog_remove">Remove</string>
<!-- Title for guest category in guest mode [CHAR LIMIT=35] -->
<string name="guest_category_title">Guest (You)</string>
<!-- Title for users preference [CHAR LIMIT=35] -->
<string name="user_category_title">Users</string>
<!-- Title for users preference when in guest mode [CHAR LIMIT=35] -->
<string name="other_user_category_title">Other users</string>
<!-- Title of preference to remove guest on exit option[CHAR LIMIT=35] -->
<string name="remove_guest_on_exit">Delete guest activity</string>
<!-- Summary of preference to remove guest on exit option[CHAR LIMIT=NONE] -->
<string name="remove_guest_on_exit_summary">Delete all guest apps and data
when exiting guest mode</string>
<!-- Title of dialog shown when remove_guest_on_exit toggle is ON [CHAR LIMIT=35] -->
<string name="remove_guest_on_exit_dialog_title">Delete guest activity?</string>
<!-- Message of dialog shown when remove_guest_on_exit toggle is ON [CHAR LIMIT=NONE] -->
<string name="remove_guest_on_exit_dialog_message">Apps and data from this guest session will be
deleted now, and all future guest activity will be deleted each time you exit guest mode</string>
<!-- Title of preference to enable calling[CHAR LIMIT=40] -->
<string name="user_enable_calling">Turn on phone calls</string>

View File

@@ -26,27 +26,64 @@
settings:controller="com.android.settings.users.MultiUserTopIntroPreferenceController"/>
<PreferenceCategory
android:key="user_list"
android:title="@string/user_list_title"
android:order="10"
settings:searchable="false">
</PreferenceCategory>
android:key="guest_category"
android:title="@string/guest_category_title"
android:order="2"
settings:searchable="false"/>
<com.android.settingslib.RestrictedPreference
android:key="guest_add"
android:title="@string/guest_new_guest"
android:order="15"/>
<Preference
android:key="guest_exit"
android:title="@string/guest_exit_button"
android:icon="@drawable/ic_guest_exit"
android:order="3"/>
<Preference
android:key="guest_reset"
android:title="@string/guest_reset_button"
android:icon="@drawable/ic_guest_reset"
android:order="4"/>
<Preference
android:key="guest_info"
android:icon="@drawable/ic_info"
android:order="5"
android:selectable="false"/>
<PreferenceCategory
android:key="user_list"
android:title="@string/user_category_title"
android:order="10"
settings:searchable="false"/>
<com.android.settingslib.RestrictedPreference
android:key="user_add"
android:title="@string/user_add_user_or_profile_menu"
android:icon="@drawable/ic_add_40dp"
android:order="20"/>
<com.android.settingslib.RestrictedPreference
android:key="supervised_user_add"
android:title="@*android:string/supervised_user_creation_label"
android:icon="@drawable/ic_add_40dp"
android:order="25"/>
<PreferenceCategory
android:key="guest_user_category"
android:title="@*android:string/guest_name"
android:order="50"/>
<com.android.settingslib.RestrictedPreference
android:key="guest_add"
android:title="@string/guest_new_guest"
android:icon="@drawable/ic_add_40dp"
android:order="55"/>
<com.android.settingslib.RestrictedSwitchPreference
android:key="remove_guest_on_exit"
android:title="@string/remove_guest_on_exit"
android:summary="@string/remove_guest_on_exit_summary"
android:order="60"/>
<com.android.settingslib.RestrictedSwitchPreference
android:key="user_settings_add_users_when_locked"
android:title="@string/user_add_on_lockscreen_menu"

View File

@@ -0,0 +1,246 @@
/*
* Copyright (C) 2022 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 android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.UserManager;
import android.provider.Settings;
import android.util.FeatureFlagUtils;
import android.util.Log;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settingslib.RestrictedSwitchPreference;
/**
* Controller to control the preference toggle for "remove guest on exit"
*
* Note, class is not 'final' since we need to mock it for unit tests
*/
public class RemoveGuestOnExitPreferenceController extends BasePreferenceController
implements Preference.OnPreferenceChangeListener {
private static final String TAG = RemoveGuestOnExitPreferenceController.class.getSimpleName();
private static final String TAG_CONFIRM_GUEST_REMOVE = "confirmGuestRemove";
private static final int REMOVE_GUEST_ON_EXIT_DEFAULT = 1;
private final UserCapabilities mUserCaps;
private final UserManager mUserManager;
private final Fragment mParentFragment;
private final Handler mHandler;
public RemoveGuestOnExitPreferenceController(Context context, String key,
Fragment parent, Handler handler) {
super(context, key);
mUserCaps = UserCapabilities.create(context);
mUserManager = context.getSystemService(UserManager.class);
mParentFragment = parent;
mHandler = handler;
}
@Override
public void updateState(Preference preference) {
mUserCaps.updateAddUserCapabilities(mContext);
final RestrictedSwitchPreference restrictedSwitchPreference =
(RestrictedSwitchPreference) preference;
restrictedSwitchPreference.setChecked(isChecked());
if (!isAvailable()) {
restrictedSwitchPreference.setVisible(false);
} else {
restrictedSwitchPreference.setDisabledByAdmin(
mUserCaps.disallowAddUser() ? mUserCaps.getEnforcedAdmin() : null);
restrictedSwitchPreference.setVisible(mUserCaps.mUserSwitcherEnabled);
}
}
@Override
public int getAvailabilityStatus() {
// if guest is forced to be ephemeral via config_guestUserEphemeral
// then disable this controller
// also disable this controller for non-admin users
// also disable when config_guestUserAllowEphemeralStateChange is false
if (mUserManager.isGuestUserAlwaysEphemeral()
|| !UserManager.isGuestUserAllowEphemeralStateChange()
|| !mUserCaps.isAdmin()
|| mUserCaps.disallowAddUser()
|| mUserCaps.disallowAddUserSetByAdmin()
|| !FeatureFlagUtils.isEnabled(mContext,
FeatureFlagUtils.SETTINGS_GUEST_MODE_UX_CHANGES)) {
return DISABLED_FOR_USER;
} else {
return mUserCaps.mUserSwitcherEnabled ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
}
}
private boolean isChecked() {
return Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.REMOVE_GUEST_ON_EXIT, REMOVE_GUEST_ON_EXIT_DEFAULT) != 0;
}
private static boolean setChecked(Context context, boolean isChecked) {
Settings.Global.putInt(context.getContentResolver(),
Settings.Global.REMOVE_GUEST_ON_EXIT, isChecked ? 1 : 0);
return true;
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
boolean enable = (boolean) newValue;
UserInfo guestInfo = mUserManager.findCurrentGuestUser();
// no guest do the setting and return
// guest ephemeral state will take effect on guest create
if (guestInfo == null) {
return setChecked(mContext, enable);
}
// if guest has never been initialized or started
// we can change guest ephemeral state
if (!guestInfo.isInitialized()) {
boolean isSuccess = mUserManager.setUserEphemeral(guestInfo.id, enable);
if (isSuccess) {
return setChecked(mContext, enable);
} else {
Log.w(TAG, "Unused guest, id=" + guestInfo.id
+ ". Mark ephemeral as " + enable + " failed !!!");
return false;
}
}
// if guest has been used before and is not ephemeral
// but now we are making reset guest on exit preference as enabled
// then show confirmation dialog box and remove this guest if confirmed by user
if (guestInfo.isInitialized() && !guestInfo.isEphemeral() && enable) {
ConfirmGuestRemoveFragment.show(mParentFragment,
mHandler,
enable,
guestInfo.id,
(RestrictedSwitchPreference) preference);
return false;
}
// all other cases, there should be none, don't change state
return false;
}
/**
* Dialog to confirm guest removal on toggle clicked set to true
*
* Fragment must be a public static class to be properly recreated from instance state
* else we will get "AndroidRuntime: java.lang.IllegalStateException"
*/
public static final class ConfirmGuestRemoveFragment extends InstrumentedDialogFragment
implements DialogInterface.OnClickListener {
private static final String TAG = ConfirmGuestRemoveFragment.class.getSimpleName();
private static final String SAVE_ENABLING = "enabling";
private static final String SAVE_GUEST_USER_ID = "guestUserId";
private boolean mEnabling;
private int mGuestUserId;
private RestrictedSwitchPreference mPreference;
private Handler mHandler;
private static void show(Fragment parent,
Handler handler,
boolean enabling, int guestUserId,
RestrictedSwitchPreference preference) {
if (!parent.isAdded()) return;
final ConfirmGuestRemoveFragment dialog = new ConfirmGuestRemoveFragment();
dialog.mHandler = handler;
dialog.mEnabling = enabling;
dialog.mGuestUserId = guestUserId;
dialog.setTargetFragment(parent, 0);
dialog.mPreference = preference;
dialog.show(parent.getFragmentManager(), TAG_CONFIRM_GUEST_REMOVE);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Context context = getActivity();
if (savedInstanceState != null) {
mEnabling = savedInstanceState.getBoolean(SAVE_ENABLING);
mGuestUserId = savedInstanceState.getInt(SAVE_GUEST_USER_ID);
}
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.remove_guest_on_exit_dialog_title);
builder.setMessage(R.string.remove_guest_on_exit_dialog_message);
builder.setPositiveButton(
com.android.settingslib.R.string.guest_exit_clear_data_button, this);
builder.setNegativeButton(android.R.string.cancel, null);
return builder.create();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(SAVE_ENABLING, mEnabling);
outState.putInt(SAVE_GUEST_USER_ID, mGuestUserId);
}
@Override
public int getMetricsCategory() {
return SettingsEnums.DIALOG_USER_REMOVE;
}
@Override
public void onClick(DialogInterface dialog, int which) {
if (which != DialogInterface.BUTTON_POSITIVE) {
return;
}
UserManager userManager = getContext().getSystemService(UserManager.class);
if (userManager == null) {
Log.e(TAG, "Unable to get user manager service");
return;
}
UserInfo guestUserInfo = userManager.getUserInfo(mGuestUserId);
// only do action for guests and when enabling the preference
if (guestUserInfo == null || !guestUserInfo.isGuest() || !mEnabling) {
Log.w(TAG, "Removing guest user ... failed, id=" + mGuestUserId);
return;
}
if (mPreference != null) {
// Using markGuestForDeletion allows us to create a new guest before this one is
// fully removed.
boolean isSuccess = userManager.markGuestForDeletion(guestUserInfo.id);
if (!isSuccess) {
Log.w(TAG, "Couldn't mark the guest for deletion for user "
+ guestUserInfo.id);
return;
}
userManager.removeUser(guestUserInfo.id);
if (setChecked(getContext(), mEnabling)) {
mPreference.setChecked(mEnabling);
mHandler.sendEmptyMessage(
UserSettings
.MESSAGE_REMOVE_GUEST_ON_EXIT_CONTROLLER_GUEST_REMOVED);
}
}
}
}
}

View File

@@ -34,6 +34,7 @@ public class UserCapabilities {
boolean mCanAddRestrictedProfile;
boolean mIsAdmin;
boolean mIsGuest;
boolean mIsEphemeral;
boolean mUserSwitcherEnabled;
boolean mCanAddGuest;
boolean mDisallowAddUser;
@@ -56,6 +57,7 @@ public class UserCapabilities {
final UserInfo myUserInfo = userManager.getUserInfo(UserHandle.myUserId());
caps.mIsGuest = myUserInfo.isGuest();
caps.mIsAdmin = myUserInfo.isAdmin();
caps.mIsEphemeral = myUserInfo.isEphemeral();
DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
Context.DEVICE_POLICY_SERVICE);

View File

@@ -73,6 +73,7 @@ public class UserDetailsSettings extends SettingsPreferenceFragment
private static final int DIALOG_CONFIRM_ENABLE_CALLING_AND_SMS = 3;
private static final int DIALOG_SETUP_USER = 4;
private static final int DIALOG_CONFIRM_RESET_GUEST = 5;
private static final int DIALOG_CONFIRM_RESET_GUEST_AND_SWITCH_USER = 6;
/** Whether to enable the app_copying fragment. */
private static final boolean SHOW_APP_COPYING_PREF = false;
@@ -142,6 +143,11 @@ public class UserDetailsSettings extends SettingsPreferenceFragment
if (canSwitchUserNow()) {
if (shouldShowSetupPromptDialog()) {
showDialog(DIALOG_SETUP_USER);
} else if (mUserCaps.mIsGuest && mUserCaps.mIsEphemeral) {
// if we are switching away from a ephemeral guest then,
// show a dialog that guest user will be reset and then switch
// the user
showDialog(DIALOG_CONFIRM_RESET_GUEST_AND_SWITCH_USER);
} else {
switchUser();
}
@@ -173,6 +179,7 @@ public class UserDetailsSettings extends SettingsPreferenceFragment
switch (dialogId) {
case DIALOG_CONFIRM_REMOVE:
case DIALOG_CONFIRM_RESET_GUEST:
case DIALOG_CONFIRM_RESET_GUEST_AND_SWITCH_USER:
return SettingsEnums.DIALOG_USER_REMOVE;
case DIALOG_CONFIRM_ENABLE_CALLING:
return SettingsEnums.DIALOG_USER_ENABLE_CALLING;
@@ -216,6 +223,14 @@ public class UserDetailsSettings extends SettingsPreferenceFragment
return UserDialogs.createRemoveGuestDialog(getActivity(),
(dialog, which) -> resetGuest());
}
case DIALOG_CONFIRM_RESET_GUEST_AND_SWITCH_USER:
if (mGuestUserAutoCreated) {
return UserDialogs.createResetGuestDialog(getActivity(),
(dialog, which) -> switchUser());
} else {
return UserDialogs.createRemoveGuestDialog(getActivity(),
(dialog, which) -> switchUser());
}
}
throw new IllegalArgumentException("Unsupported dialogId " + dialogId);
}
@@ -361,6 +376,16 @@ public class UserDetailsSettings extends SettingsPreferenceFragment
if (mUserInfo.isGuest()) {
mMetricsFeatureProvider.action(getActivity(), SettingsEnums.ACTION_SWITCH_TO_GUEST);
}
if (mUserCaps.mIsGuest && mUserCaps.mIsEphemeral) {
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;
}
}
ActivityManager.getService().switchUser(mUserInfo.id);
} catch (RemoteException re) {
Log.e(TAG, "Error while switching to other user.");

View File

@@ -189,7 +189,7 @@ public final class UserDialogs {
DialogInterface.OnClickListener onConfirmListener) {
return new AlertDialog.Builder(context)
.setTitle(com.android.settingslib.R.string.guest_reset_guest_dialog_title)
.setMessage(R.string.user_exit_guest_confirm_message)
.setMessage(com.android.settingslib.R.string.guest_exit_dialog_message)
.setPositiveButton(
com.android.settingslib.R.string.guest_reset_guest_confirm_button,
onConfirmListener)

View File

@@ -47,13 +47,16 @@ 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.FeatureFlagUtils;
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;
@@ -95,6 +98,7 @@ 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.
@@ -123,6 +127,14 @@ public class UserSettings extends SettingsPreferenceFragment
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_USER_ZERO = "timeout_to_user_zero_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_GUEST_INFO = "guest_info";
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;
@@ -134,14 +146,18 @@ public class UserSettings extends SettingsPreferenceFragment
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_EXIT_GUEST = 8;
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_RESET_GUEST = 12;
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;
@@ -165,6 +181,16 @@ public class UserSettings extends SettingsPreferenceFragment
@VisibleForTesting
PreferenceGroup mUserListCategory;
@VisibleForTesting
PreferenceGroup mGuestUserCategory;
@VisibleForTesting
PreferenceGroup mGuestCategory;
@VisibleForTesting
Preference mGuestResetPreference;
@VisibleForTesting
Preference mGuestExitPreference;
@VisibleForTesting
Preference mGuestInfoPreference;
@VisibleForTesting
UserPreference mMePreference;
@VisibleForTesting
RestrictedPreference mAddGuest;
@@ -189,6 +215,7 @@ public class UserSettings extends SettingsPreferenceFragment
private EditUserInfoController mEditUserInfoController =
new EditUserInfoController(Utils.FILE_PROVIDER_AUTHORITY);
private AddUserWhenLockedPreferenceController mAddUserWhenLockedPreferenceController;
private RemoveGuestOnExitPreferenceController mRemoveGuestOnExitPreferenceController;
private MultiUserTopIntroPreferenceController mMultiUserTopIntroPreferenceController;
private TimeoutToUserZeroPreferenceController mTimeoutToUserZeroPreferenceController;
private UserCreatingDialog mUserCreatingDialog;
@@ -213,6 +240,12 @@ public class UserSettings extends SettingsPreferenceFragment
case MESSAGE_USER_CREATED:
onUserCreated(msg.arg1);
break;
case MESSAGE_REMOVE_GUEST_ON_EXIT_CONTROLLER_GUEST_REMOVED:
updateUserList();
if (mGuestUserAutoCreated) {
scheduleGuestCreation();
}
break;
}
}
};
@@ -245,7 +278,11 @@ public class UserSettings extends SettingsPreferenceFragment
final SettingsActivity activity = (SettingsActivity) getActivity();
final SettingsMainSwitchBar switchBar = activity.getSwitchBar();
switchBar.setTitle(getContext().getString(R.string.multiple_users_main_switch_title));
switchBar.show();
if (mUserCaps.mIsAdmin) {
switchBar.show();
} else {
switchBar.hide();
}
mSwitchBarController = new MultiUserSwitchBarController(activity,
new MainSwitchBarController(switchBar), this /* listener */);
getSettingsLifecycle().addObserver(mSwitchBarController);
@@ -267,6 +304,9 @@ public class UserSettings extends SettingsPreferenceFragment
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);
@@ -275,12 +315,16 @@ public class UserSettings extends SettingsPreferenceFragment
final PreferenceScreen screen = getPreferenceScreen();
mAddUserWhenLockedPreferenceController.displayPreference(screen);
mRemoveGuestOnExitPreferenceController.displayPreference(screen);
mMultiUserTopIntroPreferenceController.displayPreference(screen);
mTimeoutToUserZeroPreferenceController.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);
@@ -304,6 +348,18 @@ public class UserSettings extends SettingsPreferenceFragment
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);
mGuestInfoPreference = findPreference(KEY_GUEST_INFO);
mGuestUserCategory = findPreference(KEY_GUEST_USER_CATEGORY);
mAddGuest = findPreference(KEY_ADD_GUEST);
mAddGuest.setOnPreferenceClickListener(this);
@@ -339,7 +395,8 @@ public class UserSettings extends SettingsPreferenceFragment
mAddUserWhenLockedPreferenceController.getPreferenceKey()));
mTimeoutToUserZeroPreferenceController.updateState(screen.findPreference(
mTimeoutToUserZeroPreferenceController.getPreferenceKey()));
mRemoveGuestOnExitPreferenceController.updateState(screen.findPreference(
mRemoveGuestOnExitPreferenceController.getPreferenceKey()));
if (mShouldUpdateUserList) {
updateUI();
}
@@ -418,6 +475,11 @@ public class UserSettings extends SettingsPreferenceFragment
updateUserList();
}
private boolean isEnableGuestModeUxChanges() {
return FeatureFlagUtils.isEnabled(getContext(),
FeatureFlagUtils.SETTINGS_GUEST_MODE_UX_CHANGES);
}
/**
* Loads profile information for the current user.
*/
@@ -702,7 +764,7 @@ public class UserSettings extends SettingsPreferenceFragment
.create();
return dlg;
}
case DIALOG_CONFIRM_EXIT_GUEST: {
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)
@@ -710,13 +772,56 @@ public class UserSettings extends SettingsPreferenceFragment
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
exitGuest();
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();
}
@@ -736,14 +841,27 @@ public class UserSettings extends SettingsPreferenceFragment
}
return buildAddUserDialog(USER_TYPE_RESTRICTED_PROFILE);
}
case DIALOG_CONFIRM_RESET_GUEST: {
if (mGuestUserAutoCreated) {
return UserDialogs.createResetGuestDialog(getActivity(),
(dialog, which) -> resetGuest());
} else {
return UserDialogs.createRemoveGuestDialog(getActivity(),
(dialog, which) -> resetGuest());
}
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;
@@ -821,8 +939,11 @@ public class UserSettings extends SettingsPreferenceFragment
return SettingsEnums.DIALOG_USER_CHOOSE_TYPE;
case DIALOG_NEED_LOCKSCREEN:
return SettingsEnums.DIALOG_USER_NEED_LOCKSCREEN;
case DIALOG_CONFIRM_EXIT_GUEST:
case DIALOG_CONFIRM_RESET_GUEST:
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:
@@ -864,6 +985,18 @@ public class UserSettings extends SettingsPreferenceFragment
}
}
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) {
@@ -945,36 +1078,91 @@ public class UserSettings extends SettingsPreferenceFragment
* Erase the current user (guest) and switch to another user.
*/
@VisibleForTesting
void exitGuest() {
void clearAndExitGuest() {
// Just to be safe
if (!isCurrentUserGuest()) {
return;
}
mMetricsFeatureProvider.action(getActivity(),
SettingsEnums.ACTION_USER_GUEST_EXIT_CONFIRMED);
removeThisUser();
}
/**
* Erase the current user (assuming it is a guest user), and create a new one in the background
*/
@VisibleForTesting
void resetGuest() {
// Just to be safe
if (!isCurrentUserGuest()) {
return;
}
int guestUserId = UserHandle.myUserId();
// Using markGuestForDeletion allows us to create a new guest before this one is
// fully removed. This could happen if someone calls scheduleGuestCreation()
// immediately after calling this method.
// fully removed.
boolean marked = mUserManager.markGuestForDeletion(guestUserId);
if (!marked) {
Log.w(TAG, "Couldn't mark the guest for deletion for user " + guestUserId);
return;
}
exitGuest();
scheduleGuestCreation();
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;
}
}
/**
@@ -1009,18 +1197,28 @@ public class UserSettings extends SettingsPreferenceFragment
if (context == null) {
return;
}
final List<UserInfo> users = mUserManager.getAliveUsers();
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<>();
userPreferences.add(mMePreference);
// 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.supportsSwitchToByUser()) {
// Only users that can be switched to should show up here.
// e.g. Managed profiles appear under Accounts Settings instead
if (user.isGuest()) {
// Guest user is added to guest category via updateGuestCategory
// and not to user list so skip guest here
continue;
}
UserPreference pref;
@@ -1033,21 +1231,9 @@ public class UserSettings extends SettingsPreferenceFragment
pref.setOnPreferenceClickListener(this);
pref.setEnabled(canOpenUserDetails);
pref.setSelectable(true);
if (user.isGuest()) {
pref.setIcon(getEncircledDefaultIcon());
pref.setKey(KEY_USER_GUEST);
if (mUserCaps.mDisallowSwitchUser) {
pref.setDisabledByAdmin(
RestrictedLockUtilsInternal.getDeviceOwner(context));
} else {
pref.setDisabledByAdmin(null);
}
} else {
pref.setKey("id=" + user.id);
if (user.isAdmin()) {
pref.setSummary(R.string.user_admin);
}
pref.setKey("id=" + user.id);
if (user.isAdmin()) {
pref.setSummary(R.string.user_admin);
}
}
if (pref == null) {
@@ -1102,12 +1288,13 @@ public class UserSettings extends SettingsPreferenceFragment
loadIconsAsync(missingIcons);
}
// If profiles are supported, mUserListCategory will have a special title
// 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(null);
mUserListCategory.setLayoutResource(R.layout.empty_view);
mUserListCategory.setTitle(R.string.user_category_title);
}
// Remove everything from mUserListCategory and add new users.
@@ -1122,8 +1309,8 @@ public class UserSettings extends SettingsPreferenceFragment
mMultiUserTopIntroPreferenceController.getPreferenceKey());
mMultiUserTopIntroPreferenceController.updateState(multiUserTopIntroPrefence);
mUserListCategory.setVisible(mUserCaps.mUserSwitcherEnabled);
updateAddGuest(context, users.stream().anyMatch(UserInfo::isGuest));
updateGuestPreferences();
updateGuestCategory(context, users);
updateAddUser(context);
updateAddSupervisedUser(context);
@@ -1152,14 +1339,127 @@ public class UserSettings extends SettingsPreferenceFragment
return mUserManager.getUserSwitchability() == UserManager.SWITCHABILITY_STATUS_OK;
}
private void updateAddGuest(Context context, boolean isGuestAlreadyCreated) {
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);
mGuestInfoPreference.setVisible(false);
if (!isCurrentUserGuest()) {
return;
}
mGuestCategory.setVisible(true);
mGuestExitPreference.setVisible(true);
if (isEnableGuestModeUxChanges()) {
mGuestResetPreference.setVisible(true);
mGuestInfoPreference.setVisible(true);
boolean isGuestFirstLogin = Settings.Secure.getIntForUser(
getContext().getContentResolver(),
SETTING_GUEST_HAS_LOGGED_IN,
0,
UserHandle.myUserId()) <= 1;
String guestInfoText;
if (mUserCaps.mIsEphemeral) {
guestInfoText = getContext().getString(
R.string.guest_notification_ephemeral);
} else if (isGuestFirstLogin) {
guestInfoText = getContext().getString(
R.string.guest_notification_non_ephemeral);
} else {
guestInfoText = getContext().getString(
R.string.guest_notification_non_ephemeral_non_first_login);
}
mGuestInfoPreference.setSummary(guestInfoText);
} else {
mGuestExitPreference.setIcon(getEncircledDefaultIcon());
mGuestExitPreference.setTitle(
mGuestUserAutoCreated
? com.android.settingslib.R.string.guest_reset_guest
: com.android.settingslib.R.string.guest_exit_guest);
}
}
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);
if (isEnableGuestModeUxChanges()) {
pref.setIcon(getContext().getDrawable(R.drawable.ic_account_circle));
} else {
pref.setIcon(getEncircledDefaultIcon());
}
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) {
isVisible = true;
mAddGuest.setVisible(true);
Drawable icon = context.getDrawable(R.drawable.ic_account_circle);
mAddGuest.setIcon(centerAndTint(icon));
// when isEnableGuestModeUxChanges() is true, the icon is set via the layout xml
// In com.android.settings.users.UserSettingsTest
// we disable the check for setIcon being called
if (!isEnableGuestModeUxChanges()) {
Drawable icon = context.getDrawable(R.drawable.ic_account_circle);
mAddGuest.setIcon(centerAndTint(icon));
}
mAddGuest.setSelectable(true);
if (mGuestUserAutoCreated && mGuestCreationScheduled.get()) {
mAddGuest.setTitle(com.android.internal.R.string.guest_name);
@@ -1172,19 +1472,26 @@ public class UserSettings extends SettingsPreferenceFragment
} 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));
// when isEnableGuestModeUxChanges() is true, the icon is set via the layout xml
if (!isEnableGuestModeUxChanges()) {
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));
// when isEnableGuestModeUxChanges() is true, the icon is set via the layout xml
if (!isEnableGuestModeUxChanges()) {
Drawable icon = context.getDrawable(R.drawable.ic_add_supervised_user);
mAddSupervisedUser.setIcon(centerAndTint(icon));
}
} else {
mAddSupervisedUser.setVisible(false);
}
@@ -1280,17 +1587,36 @@ public class UserSettings extends SettingsPreferenceFragment
@Override
public boolean onPreferenceClick(Preference pref) {
if (pref == mMePreference) {
if (isCurrentUserGuest()) {
if (mGuestUserAutoCreated) {
showDialog(DIALOG_CONFIRM_RESET_GUEST);
} else {
showDialog(DIALOG_CONFIRM_EXIT_GUEST);
if (isCurrentUserGuest()) {
if (isEnableGuestModeUxChanges()) {
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;
}
} else {
showDialog(DIALOG_USER_PROFILE_EDITOR);
if (mGuestExitPreference != null && pref == mGuestExitPreference) {
if (mGuestUserAutoCreated) {
showDialog(DIALOG_CONFIRM_REMOVE_GUEST_WITH_AUTO_CREATE);
} else {
showDialog(DIALOG_CONFIRM_REMOVE_GUEST);
}
return true;
}
}
}
if (pref == mMePreference) {
if (!isCurrentUserGuest()) {
showDialog(DIALOG_USER_PROFILE_EDITOR);
return true;
}
return true;
} else if (pref instanceof UserPreference) {
UserInfo userInfo = mUserManager.getUserInfo(((UserPreference) pref).getUserId());
openUserDetails(userInfo, false);

View File

@@ -226,4 +226,32 @@ public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager
return new UserInfo(PRIMARY_USER_ID, null, null,
UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
}
protected boolean setUserEphemeral(@UserIdInt int userId, boolean enableEphemeral) {
UserInfo userInfo = mUserProfileInfos.stream()
.filter(user -> user.id == userId)
.findFirst()
.orElse(super.getUserInfo(userId));
boolean isSuccess = false;
boolean isEphemeralUser =
(userInfo.flags & UserInfo.FLAG_EPHEMERAL) != 0;
boolean isEphemeralOnCreateUser =
(userInfo.flags & UserInfo.FLAG_EPHEMERAL_ON_CREATE)
!= 0;
// when user is created in ephemeral mode via FLAG_EPHEMERAL
// its state cannot be changed.
// FLAG_EPHEMERAL_ON_CREATE is used to keep track of this state
if (!isEphemeralOnCreateUser) {
isSuccess = true;
if (isEphemeralUser != enableEphemeral) {
if (enableEphemeral) {
userInfo.flags |= UserInfo.FLAG_EPHEMERAL;
} else {
userInfo.flags &= ~UserInfo.FLAG_EPHEMERAL;
}
}
}
return isSuccess;
}
}

View File

@@ -56,6 +56,7 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import androidx.fragment.app.FragmentActivity;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
@@ -152,6 +153,8 @@ public class UserSettingsTest {
ReflectionHelpers.setField(mFragment, "mDefaultIconDrawable", mDefaultIconDrawable);
ReflectionHelpers.setField(mFragment, "mAddingUser", false);
ReflectionHelpers.setField(mFragment, "mMetricsFeatureProvider", mMetricsFeatureProvider);
ReflectionHelpers.setField(mFragment, "mRemoveGuestOnExitPreferenceController",
mock(RemoveGuestOnExitPreferenceController.class));
doReturn(mUserManager).when(mActivity).getSystemService(UserManager.class);
doReturn(mPackageManager).when(mActivity).getPackageManager();
@@ -178,6 +181,11 @@ public class UserSettingsTest {
mFragment.mAddSupervisedUser = mAddSupervisedUserPreference;
mFragment.mAddGuest = mAddGuestPreference;
mFragment.mUserListCategory = mock(PreferenceCategory.class);
mFragment.mGuestUserCategory = mock(PreferenceCategory.class);
mFragment.mGuestCategory = mock(PreferenceCategory.class);
mFragment.mGuestResetPreference = mock(Preference.class);
mFragment.mGuestExitPreference = mock(Preference.class);
mFragment.mGuestInfoPreference = mock(Preference.class);
}
@After
@@ -219,7 +227,7 @@ public class UserSettingsTest {
@Test
public void testExitGuest_ShouldLogAction() {
mUserCapabilities.mIsGuest = true;
mFragment.exitGuest();
mFragment.clearAndExitGuest();
verify(mMetricsFeatureProvider).action(any(),
eq(SettingsEnums.ACTION_USER_GUEST_EXIT_CONFIRMED));
}
@@ -227,7 +235,7 @@ public class UserSettingsTest {
@Test
public void testExitGuestWhenNotGuest_ShouldNotLogAction() {
mUserCapabilities.mIsGuest = false;
mFragment.exitGuest();
mFragment.clearAndExitGuest();
verify(mMetricsFeatureProvider, never()).action(any(),
eq(SettingsEnums.ACTION_USER_GUEST_EXIT_CONFIRMED));
}
@@ -323,7 +331,6 @@ public class UserSettingsTest {
verify(mAddGuestPreference).setVisible(true);
verify(mAddGuestPreference).setEnabled(true);
verify(mAddGuestPreference).setIcon(any(Drawable.class));
verify(mAddGuestPreference).setSelectable(true);
}
@@ -371,7 +378,6 @@ public class UserSettingsTest {
verify(mAddGuestPreference).setVisible(true);
verify(mAddGuestPreference).setEnabled(false);
verify(mAddGuestPreference).setIcon(any(Drawable.class));
verify(mAddGuestPreference).setSelectable(true);
}
@@ -473,9 +479,9 @@ public class UserSettingsTest {
mFragment.updateUserList();
ArgumentCaptor<UserPreference> captor = ArgumentCaptor.forClass(UserPreference.class);
verify(mFragment.mUserListCategory, times(2))
verify(mFragment.mGuestUserCategory, times(1))
.addPreference(captor.capture());
UserPreference guestPref = captor.getAllValues().get(1);
UserPreference guestPref = captor.getAllValues().get(0);
assertThat(guestPref.getUserId()).isEqualTo(INACTIVE_GUEST_USER_ID);
assertThat(guestPref.getTitle()).isEqualTo("Guest");
assertThat(guestPref.getIcon()).isNotNull();
@@ -595,9 +601,9 @@ public class UserSettingsTest {
mFragment.updateUserList();
ArgumentCaptor<UserPreference> captor = ArgumentCaptor.forClass(UserPreference.class);
verify(mFragment.mUserListCategory, times(2))
verify(mFragment.mGuestUserCategory, times(1))
.addPreference(captor.capture());
UserPreference userPref = captor.getAllValues().get(1);
UserPreference userPref = captor.getAllValues().get(0);
assertThat(userPref.getUserId()).isEqualTo(INACTIVE_GUEST_USER_ID);
assertThat(userPref.getSummary()).isNull();
}