Guest mode updates to resolve privacy concerns in guest mode
- Add API in IUserManager to allow setting ephemeral user flag
- Implement and export this API in UserManagerService and UserManager
- Set guest as ephermal by default when createGuest in UserManager is called
- Handle guest user switching in UserSwitcherController for the case
of dynamic change of ephemeral state
- Add persistant notification when in guest mode to indicate
- if guest session is new or previously used.
- if guest session will be cleared on exit or not
- Add buttons in persistant notification to reset or exit guest
- Add flags to enable/disable this feature
Bug: 214031645
Screenshots: go/ephemeral-guest-b-214031645-ux
Test: Manual test using sunfish, atest SystemUITests, atest SettingsRoboTests
Relands ag/16545010 after resolving post submit issues
Revert "Revert "Guest mode updates to resolve privacy concerns in guest mode""
This reverts commit dd5c440802078291a88e9f939e8a25348ec81315.
Change-Id: I46b8ab527bab8fe665114ed0fffbb06a59d49a77
Merged-In: I46b8ab527bab8fe665114ed0fffbb06a59d49a77
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index f4a12a5..d857244 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1857,7 +1857,6 @@
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public String getUserType();
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle);
- method public static boolean isGuestUserEphemeral();
method public static boolean isSplitSystemUser();
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo preCreateUser(@NonNull String) throws android.os.UserManager.UserOperationException;
}
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 76e9fcb..816460b 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -142,6 +142,22 @@
public static final int FLAG_PROFILE = 0x00001000;
/**
+ * Indicates that this user is created in ephemeral mode via
+ * {@link IUserManager} create user.
+ *
+ * When a user is created with {@link #FLAG_EPHEMERAL}, {@link #FLAG_EPHEMERAL_ON_CREATE}
+ * is set internally within the user manager.
+ *
+ * When {@link #FLAG_EPHEMERAL_ON_CREATE} is set {@link IUserManager.setUserEphemeral}
+ * has no effect because a user that was created ephemeral can never be made non-ephemeral.
+ *
+ * {@link #FLAG_EPHEMERAL_ON_CREATE} should NOT be set by client's of user manager
+ *
+ * @hide
+ */
+ public static final int FLAG_EPHEMERAL_ON_CREATE = 0x00002000;
+
+ /**
* @hide
*/
@IntDef(flag = true, prefix = "FLAG_", value = {
@@ -157,7 +173,8 @@
FLAG_DEMO,
FLAG_FULL,
FLAG_SYSTEM,
- FLAG_PROFILE
+ FLAG_PROFILE,
+ FLAG_EPHEMERAL_ON_CREATE
})
@Retention(RetentionPolicy.SOURCE)
public @interface UserInfoFlag {
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 3cde031..e5de3e1 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -131,4 +131,5 @@
String getUserName();
long getUserStartRealtime();
long getUserUnlockRealtime();
+ boolean setUserEphemeral(int userId, boolean enableEphemeral);
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index a64e63e..570d533 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1997,13 +1997,22 @@
* @return Whether guest user is always ephemeral
* @hide
*/
- @TestApi
- public static boolean isGuestUserEphemeral() {
+ public static boolean isGuestUserAlwaysEphemeral() {
return Resources.getSystem()
.getBoolean(com.android.internal.R.bool.config_guestUserEphemeral);
}
/**
+ * @return true, when we want to enable user manager API and UX to allow
+ * guest user ephemeral state change based on user input
+ * @hide
+ */
+ public static boolean isGuestUserAllowEphemeralStateChange() {
+ return Resources.getSystem()
+ .getBoolean(com.android.internal.R.bool.config_guestUserAllowEphemeralStateChange);
+ }
+
+ /**
* Checks whether the device is running in a headless system user mode.
*
* <p>Headless system user mode means the {@link #isSystemUser() system user} runs system
@@ -3424,6 +3433,20 @@
if (guest != null) {
Settings.Secure.putStringForUser(context.getContentResolver(),
Settings.Secure.SKIP_FIRST_USE_HINTS, "1", guest.id);
+
+ if (UserManager.isGuestUserAllowEphemeralStateChange()) {
+ // Mark guest as (changeably) ephemeral if REMOVE_GUEST_ON_EXIT is 1
+ // This is done so that a user via a UI controller can choose to
+ // make a guest as ephemeral or not.
+ // Settings.Global.REMOVE_GUEST_ON_EXIT holds the choice on what the guest state
+ // should be, with default being ephemeral.
+ boolean resetGuestOnExit = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.REMOVE_GUEST_ON_EXIT, 1) == 1;
+
+ if (resetGuestOnExit && !guest.isEphemeral()) {
+ setUserEphemeral(guest.id, true);
+ }
+ }
}
return guest;
} catch (ServiceSpecificException e) {
@@ -4941,6 +4964,31 @@
}
/**
+ * Set the user as ephemeral or non-ephemeral.
+ *
+ * If the user was initially created as ephemeral then this
+ * method has no effect and false is returned.
+ *
+ * @param userId the user's integer id
+ * @param enableEphemeral true: change user state to ephemeral,
+ * false: change user state to non-ephemeral
+ * @return true: user now has the desired ephemeral state,
+ * false: desired user ephemeral state could not be set
+ *
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS})
+ public boolean setUserEphemeral(@UserIdInt int userId, boolean enableEphemeral) {
+ try {
+ return mService.setUserEphemeral(userId, enableEphemeral);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Updates the context user's name.
*
* @param name the new name for the user
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 111c670..9647497 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10898,6 +10898,14 @@
public static final String ADD_USERS_WHEN_LOCKED = "add_users_when_locked";
/**
+ * Whether guest user should be removed on exit from guest mode.
+ * <p>
+ * Type: int
+ * @hide
+ */
+ public static final String REMOVE_GUEST_ON_EXIT = "remove_guest_on_exit";
+
+ /**
* Whether applying ramping ringer on incoming phone call ringtone.
* <p>1 = apply ramping ringer
* <p>0 = do not apply ramping ringer
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 3be4c3ed..9a9e6a2 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -74,6 +74,11 @@
public static final String SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE =
"settings_hide_second_layer_page_navigate_up_button_in_two_pane";
+ /** Flag to enable/disable guest mode UX changes as mentioned in b/214031645
+ * @hide
+ */
+ public static final String SETTINGS_GUEST_MODE_UX_CHANGES = "settings_guest_mode_ux_changes";
+
private static final Map<String, String> DEFAULT_FLAGS;
static {
@@ -100,6 +105,7 @@
DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true");
DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true");
DEFAULT_FLAGS.put(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE, "true");
+ DEFAULT_FLAGS.put(SETTINGS_GUEST_MODE_UX_CHANGES, "true");
}
private static final Set<String> PERSISTENT_FLAGS;
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 0e3840a..87f3e12 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3736,6 +3736,9 @@
"Guest" and "Reset guest". -->
<bool name="config_guestUserAutoCreated">false</bool>
+ <!-- If true, owner can change guest user ephemeral state via UI option -->
+ <bool name="config_guestUserAllowEphemeralStateChange">true</bool>
+
<!-- Enforce strong auth on boot. Setting this to false represents a security risk and should
not be ordinarily done. The only case in which this might be permissible is in a car head
unit where there are hardware mechanisms to protect the device (physical keys) and not
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a9b95da..e07326b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -394,6 +394,7 @@
<java-symbol type="bool" name="config_supportsInsecureLockScreen" />
<java-symbol type="bool" name="config_guestUserEphemeral" />
<java-symbol type="bool" name="config_guestUserAutoCreated" />
+ <java-symbol type="bool" name="config_guestUserAllowEphemeralStateChange" />
<java-symbol type="bool" name="config_localDisplaysMirrorContent" />
<java-symbol type="array" name="config_localPrivateDisplayPorts" />
<java-symbol type="integer" name="config_defaultDisplayDefaultColorMode" />
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index df2685d..8ef712a 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1438,6 +1438,44 @@
<string name="guest_remove_guest_confirm_button">Remove</string>
<!-- Status message indicating the device is in the process of resetting the guest user. [CHAR_LIMIT=NONE] -->
<string name="guest_resetting">Resetting guest\u2026</string>
+ <!-- Dialog title on action reset and restart guest [CHAR LIMIT=60] -->
+ <string name="guest_reset_and_restart_dialog_title">Reset guest session?</string>
+ <!-- Dialog message on action reset and restart guest [CHAR LIMIT=160] -->
+ <string name="guest_reset_and_restart_dialog_message">This will start a new guest
+ session and delete all apps and data from the current session</string>
+ <!-- Dialog title on action exit guest (ephemeral guest) [CHAR LIMIT=32] -->
+ <string name="guest_exit_dialog_title">Exit guest mode?</string>
+ <!-- Dialog message on action exit guest (ephemeral guest) [CHAR LIMIT=80] -->
+ <string name="guest_exit_dialog_message">This will delete
+ apps and data from the current guest session</string>
+ <!-- Dialog button on action exit guest (ephemeral guest) [CHAR LIMIT=80] -->
+ <string name="guest_exit_dialog_button">Exit</string>
+ <!-- Dialog title on action exit guest (non-ephemeral guest) [CHAR LIMIT=32] -->
+ <string name="guest_exit_dialog_title_non_ephemeral">Save guest activity?</string>
+ <!-- Dialog message on action exit guest (non-ephemeral guest) [CHAR LIMIT=80] -->
+ <string name="guest_exit_dialog_message_non_ephemeral">You can save activity from
+ the current session or delete all apps and data</string>
+ <!-- Button on guest exit, clear data (non-ephemeral guest) [CHAR LIMIT=80] -->
+ <string name="guest_exit_clear_data_button">Delete</string>
+ <!-- Button on guest exit, save data (non-ephemeral guest) [CHAR LIMIT=80] -->
+ <string name="guest_exit_save_data_button">Save</string>
+ <!-- Label for button in confirmation dialog when exiting guest user [CHAR LIMIT=35] -->
+ <string name="guest_exit_button">Exit guest mode</string>
+ <!-- Label for button in confirmation dialog when resetting guest user [CHAR LIMIT=35] -->
+ <string name="guest_reset_button">Reset guest session</string>
+ <!-- Label for guest icon in quick settings user switcher [CHAR LIMIT=35] -->
+ <string name="guest_exit_quick_settings_button">Exit guest</string>
+ <!-- Message of the notification when guest mode is entered
+ and it's a ephemeral guest [CHAR LIMIT=60] -->
+ <string name="guest_notification_ephemeral">All activity will be deleted on exit</string>
+ <!-- Message of the notification when guest mode is entered
+ and it's not a ephemeral guest and it's a first time guest login [CHAR LIMIT=60] -->
+ <string name="guest_notification_non_ephemeral">You can save or delete your activity on exit</string>
+ <!-- Message of the notification when guest mode is entered
+ and it's not a ephemeral guest and it's not a first time guest login [CHAR LIMIT=NONE] -->
+ <string name="guest_notification_non_ephemeral_non_first_login">Reset to delete session
+ activity now, or you can save or delete activity on exit</string>
+
<!-- An option in a photo selection dialog to take a new photo [CHAR LIMIT=50] -->
<string name="user_image_take_photo">Take a photo</string>
<!-- An option in a photo selection dialog to choose a pre-existing image [CHAR LIMIT=50] -->
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index d122cf5..aecd677 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -422,6 +422,7 @@
Settings.Global.RADIO_NFC,
Settings.Global.RADIO_WIFI,
Settings.Global.RADIO_WIMAX,
+ Settings.Global.REMOVE_GUEST_ON_EXIT,
Settings.Global.RECOMMENDED_NETWORK_EVALUATOR_CACHE_EXPIRY_MS,
Settings.Global.READ_EXTERNAL_STORAGE_ENFORCED_DEFAULT,
Settings.Global.RESTRICTED_NETWORKING_MODE,
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d9c8e07..0ef86ed 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -901,6 +901,23 @@
<!-- Notification when resuming an existing guest session: Action that continues with the current session [CHAR LIMIT=35] -->
<string name="guest_wipe_session_dontwipe">Yes, continue</string>
+ <!-- App name of the notification when guest mode is entered [CHAR LIMIT=35] -->
+ <string name="guest_notification_app_name">Guest mode</string>
+ <!-- Title of the notification when guest mode is entered [CHAR LIMIT=35] -->
+ <string name="guest_notification_session_active">You are in guest mode</string>
+
+ <!-- Title for add user confirmation dialog [CHAR LIMIT=30] -->
+ <string name="user_add_user_title" msgid="2108112641783146007">Add new user?</string>
+
+ <!-- Message for add user confirmation dialog - short version. [CHAR LIMIT=none] -->
+ <string name="user_add_user_message_short" msgid="1511354412249044381">When you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users. </string>
+
+ <!-- Additional message for add user confirmation dialog that is appended when current user is
+ guest and guest is ephemeral. This is to warn users that current guest session
+ would get removed after a new user is added and switched to [CHAR LIMIT=none] -->
+ <string name="user_add_user_message_guest_remove">\n\nAdding a new user will exit guest mode
+ and delete all apps and data from the current guest session.</string>
+
<!-- Title for the dialog that lets users know that the maximum allowed number of users on the device has been reached. [CHAR LIMIT=35]-->
<string name="user_limit_reached_title">User limit reached</string>
diff --git a/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java
new file mode 100644
index 0000000..fd84543
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java
@@ -0,0 +1,269 @@
+/*
+ * 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.systemui;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.UserInfo;
+import android.os.UserHandle;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.qs.QSUserSwitcherEvent;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.statusbar.policy.UserSwitcherController;
+
+import javax.inject.Inject;
+
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
+/**
+ * Manages handling of guest session persistent notification
+ * and actions to reset guest or exit guest session
+ */
+public final class GuestResetOrExitSessionReceiver extends BroadcastReceiver {
+
+ private static final String TAG = GuestResetOrExitSessionReceiver.class.getSimpleName();
+
+ /**
+ * Broadcast sent to the system when guest user needs to be reset.
+ * This is only sent to registered receivers, not manifest receivers.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_GUEST_RESET = "android.intent.action.GUEST_RESET";
+
+ /**
+ * Broadcast sent to the system when guest user needs to exit.
+ * This is only sent to registered receivers, not manifest receivers.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_GUEST_EXIT = "android.intent.action.GUEST_EXIT";
+
+ public AlertDialog mExitSessionDialog;
+ public AlertDialog mResetSessionDialog;
+ private final UserTracker mUserTracker;
+ private final BroadcastDispatcher mBroadcastDispatcher;
+ private final ResetSessionDialog.Factory mResetSessionDialogFactory;
+ private final ExitSessionDialog.Factory mExitSessionDialogFactory;
+
+ @Inject
+ public GuestResetOrExitSessionReceiver(UserTracker userTracker,
+ BroadcastDispatcher broadcastDispatcher,
+ ResetSessionDialog.Factory resetSessionDialogFactory,
+ ExitSessionDialog.Factory exitSessionDialogFactory) {
+ mUserTracker = userTracker;
+ mBroadcastDispatcher = broadcastDispatcher;
+ mResetSessionDialogFactory = resetSessionDialogFactory;
+ mExitSessionDialogFactory = exitSessionDialogFactory;
+ }
+
+ /**
+ * Register this receiver with the {@link BroadcastDispatcher}
+ */
+ public void register() {
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(ACTION_GUEST_RESET);
+ intentFilter.addAction(ACTION_GUEST_EXIT);
+ mBroadcastDispatcher.registerReceiver(this, intentFilter, null /* handler */,
+ UserHandle.SYSTEM);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ cancelResetDialog();
+ cancelExitDialog();
+
+ UserInfo currentUser = mUserTracker.getUserInfo();
+ if (!currentUser.isGuest()) {
+ return;
+ }
+
+ if (ACTION_GUEST_RESET.equals(action)) {
+ mResetSessionDialog = mResetSessionDialogFactory.create(currentUser.id);
+ mResetSessionDialog.show();
+ } else if (ACTION_GUEST_EXIT.equals(action)) {
+ mExitSessionDialog = mExitSessionDialogFactory.create(currentUser.id,
+ currentUser.isEphemeral());
+ mExitSessionDialog.show();
+ }
+ }
+
+ private void cancelResetDialog() {
+ if (mResetSessionDialog != null && mResetSessionDialog.isShowing()) {
+ mResetSessionDialog.cancel();
+ mResetSessionDialog = null;
+ }
+ }
+
+ private void cancelExitDialog() {
+ if (mExitSessionDialog != null && mExitSessionDialog.isShowing()) {
+ mExitSessionDialog.cancel();
+ mExitSessionDialog = null;
+ }
+ }
+
+ /**
+ * Dialog shown when asking for confirmation before
+ * reset and restart of guest user.
+ */
+ public static final class ResetSessionDialog extends SystemUIDialog implements
+ DialogInterface.OnClickListener {
+
+ private final UserSwitcherController mUserSwitcherController;
+ private final UiEventLogger mUiEventLogger;
+ private final int mUserId;
+
+ /** Factory class to create guest reset dialog instance */
+ @AssistedFactory
+ public interface Factory {
+ /** Create a guest reset dialog instance */
+ ResetSessionDialog create(int userId);
+ }
+
+ @AssistedInject
+ ResetSessionDialog(Context context,
+ UserSwitcherController userSwitcherController,
+ UiEventLogger uiEventLogger,
+ @Assisted int userId) {
+ super(context);
+
+ setTitle(com.android.settingslib.R.string.guest_reset_and_restart_dialog_title);
+ setMessage(context.getString(
+ com.android.settingslib.R.string.guest_reset_and_restart_dialog_message));
+ setButton(DialogInterface.BUTTON_NEUTRAL,
+ context.getString(android.R.string.cancel), this);
+ setButton(DialogInterface.BUTTON_POSITIVE,
+ context.getString(
+ com.android.settingslib.R.string.guest_reset_guest_confirm_button), this);
+ setCanceledOnTouchOutside(false);
+
+ mUserSwitcherController = userSwitcherController;
+ mUiEventLogger = uiEventLogger;
+ mUserId = userId;
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
+ mUserSwitcherController.removeGuestUser(mUserId, UserHandle.USER_NULL);
+ } else if (which == DialogInterface.BUTTON_NEUTRAL) {
+ cancel();
+ }
+ }
+ }
+
+ /**
+ * Dialog shown when asking for confirmation before
+ * exit of guest user.
+ */
+ public static final class ExitSessionDialog extends SystemUIDialog implements
+ DialogInterface.OnClickListener {
+
+ private final UserSwitcherController mUserSwitcherController;
+ private final int mUserId;
+ private boolean mIsEphemeral;
+
+ /** Factory class to create guest exit dialog instance */
+ @AssistedFactory
+ public interface Factory {
+ /** Create a guest exit dialog instance */
+ ExitSessionDialog create(int userId, boolean isEphemeral);
+ }
+
+ @AssistedInject
+ ExitSessionDialog(Context context,
+ UserSwitcherController userSwitcherController,
+ @Assisted int userId,
+ @Assisted boolean isEphemeral) {
+ super(context);
+
+ if (isEphemeral) {
+ setTitle(context.getString(
+ com.android.settingslib.R.string.guest_exit_dialog_title));
+ setMessage(context.getString(
+ com.android.settingslib.R.string.guest_exit_dialog_message));
+ setButton(DialogInterface.BUTTON_NEUTRAL,
+ context.getString(android.R.string.cancel), this);
+ setButton(DialogInterface.BUTTON_POSITIVE,
+ context.getString(
+ com.android.settingslib.R.string.guest_exit_dialog_button), this);
+ } else {
+ setTitle(context.getString(
+ com.android.settingslib
+ .R.string.guest_exit_dialog_title_non_ephemeral));
+ setMessage(context.getString(
+ com.android.settingslib
+ .R.string.guest_exit_dialog_message_non_ephemeral));
+ setButton(DialogInterface.BUTTON_NEUTRAL,
+ context.getString(android.R.string.cancel), this);
+ setButton(DialogInterface.BUTTON_NEGATIVE,
+ context.getString(
+ com.android.settingslib.R.string.guest_exit_clear_data_button), this);
+ setButton(DialogInterface.BUTTON_POSITIVE,
+ context.getString(
+ com.android.settingslib.R.string.guest_exit_save_data_button), this);
+ }
+ setCanceledOnTouchOutside(false);
+
+ mUserSwitcherController = userSwitcherController;
+ mUserId = userId;
+ mIsEphemeral = isEphemeral;
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (mIsEphemeral) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ // Ephemeral guest: exit guest, guest is removed by the system
+ // on exit, since its marked ephemeral
+ mUserSwitcherController.exitGuestUser(mUserId, UserHandle.USER_NULL, false);
+ } else if (which == DialogInterface.BUTTON_NEUTRAL) {
+ // Cancel clicked, do nothing
+ cancel();
+ }
+ } else {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ // Non-ephemeral guest: exit guest, guest is not removed by the system
+ // on exit, since its marked non-ephemeral
+ mUserSwitcherController.exitGuestUser(mUserId, UserHandle.USER_NULL, false);
+ } else if (which == DialogInterface.BUTTON_NEGATIVE) {
+ // Non-ephemeral guest: remove guest and then exit
+ mUserSwitcherController.exitGuestUser(mUserId, UserHandle.USER_NULL, true);
+ } else if (which == DialogInterface.BUTTON_NEUTRAL) {
+ // Cancel clicked, do nothing
+ cancel();
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
index 9a6020f..76a7cad 100644
--- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
@@ -35,12 +35,18 @@
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.settings.SecureSettings;
+import javax.inject.Inject;
+
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
/**
* Manages notification when a guest session is resumed.
*/
public class GuestResumeSessionReceiver extends BroadcastReceiver {
- private static final String TAG = "GuestResumeSessionReceiver";
+ private static final String TAG = GuestResumeSessionReceiver.class.getSimpleName();
@VisibleForTesting
public static final String SETTING_GUEST_HAS_LOGGED_IN = "systemui.guest_has_logged_in";
@@ -48,27 +54,31 @@
@VisibleForTesting
public AlertDialog mNewSessionDialog;
private final UserTracker mUserTracker;
- private final UserSwitcherController mUserSwitcherController;
- private final UiEventLogger mUiEventLogger;
private final SecureSettings mSecureSettings;
+ private final BroadcastDispatcher mBroadcastDispatcher;
+ private final ResetSessionDialog.Factory mResetSessionDialogFactory;
+ private final GuestSessionNotification mGuestSessionNotification;
- public GuestResumeSessionReceiver(UserSwitcherController userSwitcherController,
- UserTracker userTracker, UiEventLogger uiEventLogger,
- SecureSettings secureSettings) {
- mUserSwitcherController = userSwitcherController;
+ @Inject
+ public GuestResumeSessionReceiver(
+ UserTracker userTracker,
+ SecureSettings secureSettings,
+ BroadcastDispatcher broadcastDispatcher,
+ GuestSessionNotification guestSessionNotification,
+ ResetSessionDialog.Factory resetSessionDialogFactory) {
mUserTracker = userTracker;
- mUiEventLogger = uiEventLogger;
mSecureSettings = secureSettings;
+ mBroadcastDispatcher = broadcastDispatcher;
+ mGuestSessionNotification = guestSessionNotification;
+ mResetSessionDialogFactory = resetSessionDialogFactory;
}
/**
* Register this receiver with the {@link BroadcastDispatcher}
- *
- * @param broadcastDispatcher to register the receiver.
*/
- public void register(BroadcastDispatcher broadcastDispatcher) {
+ public void register() {
IntentFilter f = new IntentFilter(Intent.ACTION_USER_SWITCHED);
- broadcastDispatcher.registerReceiver(this, f, null /* handler */, UserHandle.SYSTEM);
+ mBroadcastDispatcher.registerReceiver(this, f, null /* handler */, UserHandle.SYSTEM);
}
@Override
@@ -89,14 +99,25 @@
return;
}
- int notFirstLogin = mSecureSettings.getIntForUser(
+ int guestLoginState = mSecureSettings.getIntForUser(
SETTING_GUEST_HAS_LOGGED_IN, 0, userId);
- if (notFirstLogin != 0) {
- mNewSessionDialog = new ResetSessionDialog(context, mUserSwitcherController,
- mUiEventLogger, userId);
+
+ if (guestLoginState == 0) {
+ // set 1 to indicate, 1st login
+ guestLoginState = 1;
+ mSecureSettings.putIntForUser(SETTING_GUEST_HAS_LOGGED_IN, guestLoginState, userId);
+ } else if (guestLoginState == 1) {
+ // set 2 to indicate, 2nd or later login
+ guestLoginState = 2;
+ mSecureSettings.putIntForUser(SETTING_GUEST_HAS_LOGGED_IN, guestLoginState, userId);
+ }
+
+ mGuestSessionNotification.createPersistentNotification(currentUser,
+ (guestLoginState <= 1));
+
+ if (guestLoginState > 1) {
+ mNewSessionDialog = mResetSessionDialogFactory.create(userId);
mNewSessionDialog.show();
- } else {
- mSecureSettings.putIntForUser(SETTING_GUEST_HAS_LOGGED_IN, 1, userId);
}
}
}
@@ -124,10 +145,19 @@
private final UiEventLogger mUiEventLogger;
private final int mUserId;
- ResetSessionDialog(Context context,
+
+ /** Factory class to create guest reset dialog instance */
+ @AssistedFactory
+ public interface Factory {
+ /** Create a guest reset dialog instance */
+ ResetSessionDialog create(int userId);
+ }
+
+ @AssistedInject
+ public ResetSessionDialog(Context context,
UserSwitcherController userSwitcherController,
UiEventLogger uiEventLogger,
- int userId) {
+ @Assisted int userId) {
super(context, false /* dismissOnDeviceLock */);
setTitle(context.getString(R.string.guest_wipe_session_title));
diff --git a/packages/SystemUI/src/com/android/systemui/GuestSessionNotification.java b/packages/SystemUI/src/com/android/systemui/GuestSessionNotification.java
new file mode 100644
index 0000000..b0eaab9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/GuestSessionNotification.java
@@ -0,0 +1,129 @@
+/*
+ * 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.systemui;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.FeatureFlagUtils;
+
+import com.android.internal.messages.nano.SystemMessageProto;
+import com.android.systemui.util.NotificationChannels;
+
+import javax.inject.Inject;
+
+/**
+ * Posts a persistent notification on entry to guest mode
+ */
+public final class GuestSessionNotification {
+
+ private static final String TAG = GuestSessionNotification.class.getSimpleName();
+
+ private final Context mContext;
+ private final NotificationManager mNotificationManager;
+
+ @Inject
+ public GuestSessionNotification(Context context,
+ NotificationManager notificationManager) {
+ mContext = context;
+ mNotificationManager = notificationManager;
+ }
+
+ private void overrideNotificationAppName(Notification.Builder notificationBuilder) {
+ final Bundle extras = new Bundle();
+ String appName = mContext.getString(R.string.guest_notification_app_name);
+
+ extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, appName);
+
+ notificationBuilder.addExtras(extras);
+ }
+
+ void createPersistentNotification(UserInfo userInfo, boolean isGuestFirstLogin) {
+ if (!FeatureFlagUtils.isEnabled(mContext,
+ FeatureFlagUtils.SETTINGS_GUEST_MODE_UX_CHANGES)
+ || !userInfo.isGuest()) {
+ // we create a persistent notification only if enabled and only for guests
+ return;
+ }
+ String contentText;
+ if (userInfo.isEphemeral()) {
+ contentText = mContext.getString(R.string.guest_notification_ephemeral);
+ } else if (isGuestFirstLogin) {
+ contentText = mContext.getString(R.string.guest_notification_non_ephemeral);
+ } else {
+ contentText = mContext.getString(
+ R.string.guest_notification_non_ephemeral_non_first_login);
+ }
+
+ final Intent guestExitIntent = new Intent(
+ GuestResetOrExitSessionReceiver.ACTION_GUEST_EXIT);
+ final Intent userSettingsIntent = new Intent(Settings.ACTION_USER_SETTINGS);
+
+ PendingIntent guestExitPendingIntent =
+ PendingIntent.getBroadcastAsUser(mContext, 0, guestExitIntent,
+ PendingIntent.FLAG_IMMUTABLE,
+ UserHandle.SYSTEM);
+
+ PendingIntent userSettingsPendingIntent =
+ PendingIntent.getActivityAsUser(mContext, 0, userSettingsIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
+ null,
+ UserHandle.of(userInfo.id));
+
+ Notification.Builder builder = new Notification.Builder(mContext,
+ NotificationChannels.ALERTS)
+ .setSmallIcon(R.drawable.ic_account_circle)
+ .setContentTitle(mContext.getString(R.string.guest_notification_session_active))
+ .setContentText(contentText)
+ .setPriority(Notification.PRIORITY_DEFAULT)
+ .setOngoing(true)
+ .setContentIntent(userSettingsPendingIntent);
+
+ // we show reset button only if this is a 2nd or later login
+ if (!isGuestFirstLogin) {
+ final Intent guestResetIntent = new Intent(
+ GuestResetOrExitSessionReceiver.ACTION_GUEST_RESET);
+
+ PendingIntent guestResetPendingIntent =
+ PendingIntent.getBroadcastAsUser(mContext, 0, guestResetIntent,
+ PendingIntent.FLAG_IMMUTABLE,
+ UserHandle.SYSTEM);
+
+ builder.addAction(R.drawable.ic_sysbar_home,
+ mContext.getString(
+ com.android.settingslib.R.string.guest_reset_guest_confirm_button),
+ guestResetPendingIntent);
+ }
+ builder.addAction(R.drawable.ic_sysbar_home,
+ mContext.getString(
+ com.android.settingslib.R.string.guest_exit_button),
+ guestExitPendingIntent);
+
+ overrideNotificationAppName(builder);
+
+ mNotificationManager.notifyAsUser(null,
+ SystemMessageProto.SystemMessage.NOTE_GUEST_SESSION,
+ builder.build(),
+ UserHandle.of(userInfo.id));
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
index 0cf3333..8ba6f1c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
@@ -18,6 +18,8 @@
import android.content.BroadcastReceiver;
+import com.android.systemui.GuestResetOrExitSessionReceiver;
+import com.android.systemui.GuestResumeSessionReceiver;
import com.android.systemui.media.dialog.MediaOutputDialogReceiver;
import com.android.systemui.people.widget.PeopleSpaceWidgetPinnedReceiver;
import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
@@ -89,4 +91,21 @@
public abstract BroadcastReceiver bindPeopleSpaceWidgetProvider(
PeopleSpaceWidgetProvider broadcastReceiver);
+ /**
+ *
+ */
+ @Binds
+ @IntoMap
+ @ClassKey(GuestResumeSessionReceiver.class)
+ public abstract BroadcastReceiver bindGuestResumeSessionReceiver(
+ GuestResumeSessionReceiver broadcastReceiver);
+
+ /**
+ *
+ */
+ @Binds
+ @IntoMap
+ @ClassKey(GuestResetOrExitSessionReceiver.class)
+ public abstract BroadcastReceiver bindGuestResetOrExitSessionReceiver(
+ GuestResetOrExitSessionReceiver broadcastReceiver);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index a3f01c2..28938d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -45,6 +45,7 @@
import android.provider.Settings;
import android.telephony.TelephonyCallback;
import android.text.TextUtils;
+import android.util.FeatureFlagUtils;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -63,6 +64,7 @@
import com.android.settingslib.users.UserCreatingDialog;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.Dumpable;
+import com.android.systemui.GuestResetOrExitSessionReceiver;
import com.android.systemui.GuestResumeSessionReceiver;
import com.android.systemui.R;
import com.android.systemui.SystemUISecondaryUserService;
@@ -120,6 +122,8 @@
private final ArrayList<WeakReference<BaseUserAdapter>> mAdapters = new ArrayList<>();
@VisibleForTesting
final GuestResumeSessionReceiver mGuestResumeSessionReceiver;
+ @VisibleForTesting
+ final GuestResetOrExitSessionReceiver mGuestResetOrExitSessionReceiver;
private final KeyguardStateController mKeyguardStateController;
private final DeviceProvisionedController mDeviceProvisionedController;
private final DevicePolicyManager mDevicePolicyManager;
@@ -185,7 +189,9 @@
InteractionJankMonitor interactionJankMonitor,
LatencyTracker latencyTracker,
DumpManager dumpManager,
- DialogLaunchAnimator dialogLaunchAnimator) {
+ DialogLaunchAnimator dialogLaunchAnimator,
+ GuestResumeSessionReceiver guestResumeSessionReceiver,
+ GuestResetOrExitSessionReceiver guestResetOrExitSessionReceiver) {
mContext = context;
mActivityManager = activityManager;
mUserTracker = userTracker;
@@ -197,14 +203,13 @@
mInteractionJankMonitor = interactionJankMonitor;
mLatencyTracker = latencyTracker;
mGlobalSettings = globalSettings;
- mGuestResumeSessionReceiver = new GuestResumeSessionReceiver(
- this, mUserTracker, mUiEventLogger, secureSettings);
+ mGuestResumeSessionReceiver = guestResumeSessionReceiver;
+ mGuestResetOrExitSessionReceiver = guestResetOrExitSessionReceiver;
mBgExecutor = bgExecutor;
mLongRunningExecutor = longRunningExecutor;
mUiExecutor = uiExecutor;
- if (!UserManager.isGuestUserEphemeral()) {
- mGuestResumeSessionReceiver.register(mBroadcastDispatcher);
- }
+ mGuestResumeSessionReceiver.register();
+ mGuestResetOrExitSessionReceiver.register();
mGuestUserAutoCreated = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_guestUserAutoCreated);
mGuestIsResetting = new AtomicBoolean();
@@ -274,6 +279,10 @@
refreshUsers(UserHandle.USER_NULL);
}
+ private static boolean isEnableGuestModeUxChanges(Context context) {
+ return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_GUEST_MODE_UX_CHANGES);
+ }
+
/**
* Refreshes users from UserManager.
*
@@ -520,20 +529,31 @@
private void onUserListItemClicked(int id, UserRecord record, DialogShower dialogShower) {
int currUserId = mUserTracker.getUserId();
+ // If switching from guest and guest is ephemeral, then follow the flow
+ // of showExitGuestDialog to remove current guest,
+ // and switch to selected user
+ UserInfo currUserInfo = mUserTracker.getUserInfo();
if (currUserId == id) {
if (record.isGuest) {
- showExitGuestDialog(id, dialogShower);
+ showExitGuestDialog(id, currUserInfo.isEphemeral(), dialogShower);
}
return;
}
- if (UserManager.isGuestUserEphemeral()) {
- // If switching from guest, we want to bring up the guest exit dialog instead of switching
- UserInfo currUserInfo = mUserManager.getUserInfo(currUserId);
- if (currUserInfo != null && currUserInfo.isGuest()) {
- showExitGuestDialog(currUserId, record.resolveId(), dialogShower);
+
+ if (currUserInfo != null && currUserInfo.isGuest()) {
+ if (isEnableGuestModeUxChanges(mContext)) {
+ showExitGuestDialog(currUserId, currUserInfo.isEphemeral(),
+ record.resolveId(), dialogShower);
return;
+ } else {
+ if (currUserInfo.isEphemeral()) {
+ showExitGuestDialog(currUserId, currUserInfo.isEphemeral(),
+ record.resolveId(), dialogShower);
+ return;
+ }
}
}
+
if (dialogShower != null) {
// If we haven't morphed into another dialog, it means we have just switched users.
// Then, dismiss the dialog.
@@ -555,7 +575,7 @@
}
}
- private void showExitGuestDialog(int id, DialogShower dialogShower) {
+ private void showExitGuestDialog(int id, boolean isGuestEphemeral, DialogShower dialogShower) {
int newId = UserHandle.USER_SYSTEM;
if (mResumeUserOnGuestLogout && mLastNonGuestUser != UserHandle.USER_SYSTEM) {
UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
@@ -563,14 +583,15 @@
newId = info.id;
}
}
- showExitGuestDialog(id, newId, dialogShower);
+ showExitGuestDialog(id, isGuestEphemeral, newId, dialogShower);
}
- private void showExitGuestDialog(int id, int targetId, DialogShower dialogShower) {
+ private void showExitGuestDialog(int id, boolean isGuestEphemeral,
+ int targetId, DialogShower dialogShower) {
if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
mExitGuestDialog.cancel();
}
- mExitGuestDialog = new ExitGuestDialog(mContext, id, targetId);
+ mExitGuestDialog = new ExitGuestDialog(mContext, id, isGuestEphemeral, targetId);
if (dialogShower != null) {
dialogShower.showDialog(mExitGuestDialog);
} else {
@@ -803,6 +824,52 @@
}
}
+ /**
+ * Exits guest user and switches to previous non-guest user. The guest must be the current
+ * user.
+ *
+ * @param guestUserId user id of the guest user to exit
+ * @param targetUserId user id of the guest user to exit, set to UserHandle.USER_NULL when
+ * target user id is not known
+ * @param forceRemoveGuestOnExit true: remove guest before switching user,
+ * false: remove guest only if its ephemeral, else keep guest
+ */
+ public void exitGuestUser(@UserIdInt int guestUserId, @UserIdInt int targetUserId,
+ boolean forceRemoveGuestOnExit) {
+ UserInfo currentUser = mUserTracker.getUserInfo();
+ if (currentUser.id != guestUserId) {
+ Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
+ + " is not current user (" + currentUser.id + ")");
+ return;
+ }
+ if (!currentUser.isGuest()) {
+ Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
+ + " is not a guest");
+ return;
+ }
+
+ int newUserId = UserHandle.USER_SYSTEM;
+ if (targetUserId == UserHandle.USER_NULL) {
+ // when target user is not specified switch to last non guest user
+ if (mResumeUserOnGuestLogout && mLastNonGuestUser != UserHandle.USER_SYSTEM) {
+ UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
+ if (info != null && info.isEnabled() && info.supportsSwitchToByUser()) {
+ newUserId = info.id;
+ }
+ }
+ } else {
+ newUserId = targetUserId;
+ }
+
+ if (currentUser.isEphemeral() || forceRemoveGuestOnExit) {
+ mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
+ removeGuestUser(currentUser.id, newUserId);
+ } else {
+ mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_SWITCH);
+ switchToUserId(newUserId);
+ }
+ }
+
private void scheduleGuestCreation() {
if (!mGuestCreationScheduled.compareAndSet(false, true)) {
return;
@@ -970,9 +1037,14 @@
public String getName(Context context, UserRecord item) {
if (item.isGuest) {
if (item.isCurrent) {
- return context.getString(mController.mGuestUserAutoCreated
+ if (isEnableGuestModeUxChanges(context)) {
+ return context.getString(
+ com.android.settingslib.R.string.guest_exit_quick_settings_button);
+ } else {
+ return context.getString(mController.mGuestUserAutoCreated
? com.android.settingslib.R.string.guest_reset_guest
: com.android.settingslib.R.string.guest_exit_guest);
+ }
} else {
if (item.info != null) {
return context.getString(com.android.internal.R.string.guest_name);
@@ -989,8 +1061,13 @@
? com.android.settingslib.R.string.guest_resetting
: com.android.internal.R.string.guest_name);
} else {
- return context.getString(
- com.android.settingslib.R.string.guest_new_guest);
+ if (isEnableGuestModeUxChanges(context)) {
+ // we always show "guest" as string, instead of "add guest"
+ return context.getString(com.android.internal.R.string.guest_name);
+ } else {
+ return context.getString(
+ com.android.settingslib.R.string.guest_new_guest);
+ }
}
}
}
@@ -1012,7 +1089,11 @@
protected static Drawable getIconDrawable(Context context, UserRecord item) {
int iconRes;
if (item.isAddUser) {
- iconRes = R.drawable.ic_account_circle_filled;
+ if (isEnableGuestModeUxChanges(context)) {
+ iconRes = R.drawable.ic_add;
+ } else {
+ iconRes = R.drawable.ic_account_circle_filled;
+ }
} else if (item.isGuest) {
iconRes = R.drawable.ic_account_circle;
} else if (item.isAddSupervisedUser) {
@@ -1157,24 +1238,58 @@
private final int mGuestId;
private final int mTargetId;
+ private final boolean mIsGuestEphemeral;
- public ExitGuestDialog(Context context, int guestId, int targetId) {
+ ExitGuestDialog(Context context, int guestId, boolean isGuestEphemeral,
+ int targetId) {
super(context);
- setTitle(mGuestUserAutoCreated
- ? com.android.settingslib.R.string.guest_reset_guest_dialog_title
- : com.android.settingslib.R.string.guest_remove_guest_dialog_title);
- setMessage(context.getString(R.string.guest_exit_guest_dialog_message));
- setButton(DialogInterface.BUTTON_NEUTRAL,
- context.getString(android.R.string.cancel), this);
- setButton(DialogInterface.BUTTON_POSITIVE,
- context.getString(mGuestUserAutoCreated
+ if (isEnableGuestModeUxChanges(context)) {
+ if (isGuestEphemeral) {
+ setTitle(context.getString(
+ com.android.settingslib.R.string.guest_exit_dialog_title));
+ setMessage(context.getString(
+ com.android.settingslib.R.string.guest_exit_dialog_message));
+ setButton(DialogInterface.BUTTON_NEUTRAL,
+ context.getString(android.R.string.cancel), this);
+ setButton(DialogInterface.BUTTON_POSITIVE,
+ context.getString(
+ com.android.settingslib.R.string.guest_exit_dialog_button), this);
+ } else {
+ setTitle(context.getString(
+ com.android.settingslib
+ .R.string.guest_exit_dialog_title_non_ephemeral));
+ setMessage(context.getString(
+ com.android.settingslib
+ .R.string.guest_exit_dialog_message_non_ephemeral));
+ setButton(DialogInterface.BUTTON_NEUTRAL,
+ context.getString(android.R.string.cancel), this);
+ setButton(DialogInterface.BUTTON_NEGATIVE,
+ context.getString(
+ com.android.settingslib.R.string.guest_exit_clear_data_button),
+ this);
+ setButton(DialogInterface.BUTTON_POSITIVE,
+ context.getString(
+ com.android.settingslib.R.string.guest_exit_save_data_button),
+ this);
+ }
+ } else {
+ setTitle(mGuestUserAutoCreated
+ ? com.android.settingslib.R.string.guest_reset_guest_dialog_title
+ : com.android.settingslib.R.string.guest_remove_guest_dialog_title);
+ setMessage(context.getString(R.string.guest_exit_guest_dialog_message));
+ setButton(DialogInterface.BUTTON_NEUTRAL,
+ context.getString(android.R.string.cancel), this);
+ setButton(DialogInterface.BUTTON_POSITIVE,
+ context.getString(mGuestUserAutoCreated
? com.android.settingslib.R.string.guest_reset_guest_confirm_button
: com.android.settingslib.R.string.guest_remove_guest_confirm_button),
- this);
+ this);
+ }
SystemUIDialog.setWindowOnTop(this, mKeyguardStateController.isShowing());
setCanceledOnTouchOutside(false);
mGuestId = guestId;
mTargetId = targetId;
+ mIsGuestEphemeral = isGuestEphemeral;
}
@Override
@@ -1184,12 +1299,40 @@
if (mFalsingManager.isFalseTap(penalty)) {
return;
}
- if (which == BUTTON_NEUTRAL) {
- cancel();
+ if (isEnableGuestModeUxChanges(getContext())) {
+ if (mIsGuestEphemeral) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ mDialogLaunchAnimator.dismissStack(this);
+ // Ephemeral guest: exit guest, guest is removed by the system
+ // on exit, since its marked ephemeral
+ exitGuestUser(mGuestId, mTargetId, false);
+ } else if (which == DialogInterface.BUTTON_NEGATIVE) {
+ // Cancel clicked, do nothing
+ cancel();
+ }
+ } else {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ mDialogLaunchAnimator.dismissStack(this);
+ // Non-ephemeral guest: exit guest, guest is not removed by the system
+ // on exit, since its marked non-ephemeral
+ exitGuestUser(mGuestId, mTargetId, false);
+ } else if (which == DialogInterface.BUTTON_NEGATIVE) {
+ mDialogLaunchAnimator.dismissStack(this);
+ // Non-ephemeral guest: remove guest and then exit
+ exitGuestUser(mGuestId, mTargetId, true);
+ } else if (which == DialogInterface.BUTTON_NEUTRAL) {
+ // Cancel clicked, do nothing
+ cancel();
+ }
+ }
} else {
- mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
- mDialogLaunchAnimator.dismissStack(this);
- removeGuestUser(mGuestId, mTargetId);
+ if (which == BUTTON_NEUTRAL) {
+ cancel();
+ } else {
+ mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
+ mDialogLaunchAnimator.dismissStack(this);
+ removeGuestUser(mGuestId, mTargetId);
+ }
}
}
}
@@ -1198,10 +1341,17 @@
final class AddUserDialog extends SystemUIDialog implements
DialogInterface.OnClickListener {
- public AddUserDialog(Context context) {
+ AddUserDialog(Context context) {
super(context);
+
setTitle(com.android.settingslib.R.string.user_add_user_title);
- setMessage(com.android.settingslib.R.string.user_add_user_message_short);
+ String message = context.getString(
+ com.android.settingslib.R.string.user_add_user_message_short);
+ UserInfo currentUser = mUserTracker.getUserInfo();
+ if (currentUser != null && currentUser.isGuest() && currentUser.isEphemeral()) {
+ message += context.getString(R.string.user_add_user_message_guest_remove);
+ }
+ setMessage(message);
setButton(DialogInterface.BUTTON_NEUTRAL,
context.getString(android.R.string.cancel), this);
setButton(DialogInterface.BUTTON_POSITIVE,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
index e3d2a29..152815f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.policy
import android.app.IActivityManager
+import android.app.NotificationManager
import android.app.admin.DevicePolicyManager
import android.content.Context
import android.content.DialogInterface
@@ -37,7 +38,9 @@
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.util.LatencyTracker
import com.android.internal.util.UserIcons
+import com.android.systemui.GuestResetOrExitSessionReceiver
import com.android.systemui.GuestResumeSessionReceiver
+import com.android.systemui.GuestSessionNotification
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogLaunchAnimator
@@ -99,6 +102,11 @@
@Mock private lateinit var threadedRenderer: ThreadedRenderer
@Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
@Mock private lateinit var globalSettings: GlobalSettings
+ @Mock private lateinit var guestSessionNotification: GuestSessionNotification
+ @Mock private lateinit var guestResetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
+ private lateinit var resetSessionDialogFactory:
+ GuestResumeSessionReceiver.ResetSessionDialog.Factory
+ private lateinit var guestResumeSessionReceiver: GuestResumeSessionReceiver
private lateinit var testableLooper: TestableLooper
private lateinit var bgExecutor: FakeExecutor
private lateinit var longRunningExecutor: FakeExecutor
@@ -130,9 +138,28 @@
com.android.internal.R.bool.config_guestUserAutoCreated, false)
mContext.addMockSystemService(Context.FACE_SERVICE, mock(FaceManager::class.java))
+ mContext.addMockSystemService(Context.NOTIFICATION_SERVICE,
+ mock(NotificationManager::class.java))
mContext.addMockSystemService(Context.FINGERPRINT_SERVICE,
mock(FingerprintManager::class.java))
+ resetSessionDialogFactory = object : GuestResumeSessionReceiver.ResetSessionDialog.Factory {
+ override fun create(userId: Int): GuestResumeSessionReceiver.ResetSessionDialog {
+ return GuestResumeSessionReceiver.ResetSessionDialog(
+ mContext,
+ mock(UserSwitcherController::class.java),
+ uiEventLogger,
+ userId
+ )
+ }
+ }
+
+ guestResumeSessionReceiver = GuestResumeSessionReceiver(userTracker,
+ secureSettings,
+ broadcastDispatcher,
+ guestSessionNotification,
+ resetSessionDialogFactory)
+
`when`(userManager.canAddMoreUsers(eq(UserManager.USER_TYPE_FULL_SECONDARY)))
.thenReturn(true)
`when`(notificationShadeWindowView.context).thenReturn(context)
@@ -195,7 +222,9 @@
interactionJankMonitor,
latencyTracker,
dumpManager,
- dialogLaunchAnimator)
+ dialogLaunchAnimator,
+ guestResumeSessionReceiver,
+ guestResetOrExitSessionReceiver)
userSwitcherController.init(notificationShadeWindowView)
}
@@ -285,7 +314,10 @@
.getButton(DialogInterface.BUTTON_POSITIVE).performClick()
testableLooper.processAllMessages()
assertEquals(1, uiEventLogger.numLogs())
- assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE.id, uiEventLogger.eventId(0))
+ assertTrue(
+ QSUserSwitcherEvent.QS_USER_GUEST_REMOVE.id == uiEventLogger.eventId(0) ||
+ QSUserSwitcherEvent.QS_USER_SWITCH.id == uiEventLogger.eventId(0)
+ )
}
@Test
@@ -347,7 +379,7 @@
userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
assertNotNull(userSwitcherController.mExitGuestDialog)
userSwitcherController.mExitGuestDialog
- .getButton(DialogInterface.BUTTON_NEGATIVE).performClick()
+ .getButton(DialogInterface.BUTTON_NEUTRAL).performClick()
testableLooper.processAllMessages()
assertEquals(0, uiEventLogger.numLogs())
}
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index dfa34bb..4ca83dd 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -286,6 +286,11 @@
// Package: android
NOTE_MTE_OVERRIDE_ENABLED = 69;
+ // Notify the user that this is a guest session with information
+ // about first login and ephemeral state
+ // Package: android
+ NOTE_GUEST_SESSION = 70;
+
// Inform the user of notification permissions changes.
// Package: android
NOTE_REVIEW_NOTIFICATION_PERMISSIONS = 71;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index cb08c79..09bc0d1 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1873,6 +1873,44 @@
}
@Override
+ public boolean setUserEphemeral(@UserIdInt int userId, boolean enableEphemeral) {
+ checkCreateUsersPermission("update ephemeral user flag");
+ UserData userToUpdate = null;
+ synchronized (mPackagesLock) {
+ synchronized (mUsersLock) {
+ final UserData userData = mUsers.get(userId);
+ if (userData == null) {
+ Slog.e(LOG_TAG, "User not found for setting ephemeral mode: u" + userId);
+ return false;
+ }
+ boolean isEphemeralUser = (userData.info.flags & UserInfo.FLAG_EPHEMERAL) != 0;
+ boolean isEphemeralOnCreateUser =
+ (userData.info.flags & UserInfo.FLAG_EPHEMERAL_ON_CREATE) != 0;
+ // when user is created in ephemeral mode via FLAG_EPHEMERAL
+ // its state cannot be changed to non ephemeral.
+ // FLAG_EPHEMERAL_ON_CREATE is used to keep track of this state
+ if (isEphemeralOnCreateUser && !enableEphemeral) {
+ Slog.e(LOG_TAG, "Failed to change user state to non-ephemeral for user "
+ + userId);
+ return false;
+ }
+ if (isEphemeralUser != enableEphemeral) {
+ if (enableEphemeral) {
+ userData.info.flags |= UserInfo.FLAG_EPHEMERAL;
+ } else {
+ userData.info.flags &= ~UserInfo.FLAG_EPHEMERAL;
+ }
+ userToUpdate = userData;
+ }
+ }
+ if (userToUpdate != null) {
+ writeUserLP(userToUpdate);
+ }
+ }
+ return true;
+ }
+
+ @Override
public void setUserIcon(@UserIdInt int userId, Bitmap bitmap) {
try {
checkManageUsersPermission("update users");
@@ -3979,6 +4017,10 @@
flags &= ~UserInfo.FLAG_EPHEMERAL;
}
+ if ((flags & UserInfo.FLAG_EPHEMERAL) != 0) {
+ flags |= UserInfo.FLAG_EPHEMERAL_ON_CREATE;
+ }
+
userInfo = new UserInfo(userId, name, null, flags, userType);
userInfo.serialNumber = mNextSerialNumber++;
userInfo.creationTime = getCreationTime();