Changed STR resume behavior when current user is an ephemeral guest.

Previously, when the system resumed from suspend and the previous user was an
ephemeral guest, the system would switch to the first non-persistent user (which
is not a guest). The main reason for this restriction is privacy - the vehicle
could have been suspended for hours and the (human) user "resuming" it might not
be the same person that "suspended" it.

This CL changes that behavior - while preserving the privacy - by automatically
creating (and switching to) a new guest user in this scenario

Here's how to test it:

1.Create a guest user (say, 11)
2.Switch to that user (adb shell am switch-user 11)
3.Suspend (adb shell dumpsys car_service suspend)
4.Wait until garage mode is done (watch for logcat CAR.POWER)
5.Resume (adb shell dumpsys car_service resume)
6.Notice a new guest was created and switched to

Test: manual verification (see above); new unit tests will be added later.
Test: atest CarServiceUnitTest:com.android.car.CarPowerManagementServiceTest

Bug: 146020398
Bug: 146380030
Bug: 145027829
Bug: 147771696

Change-Id: Icb486877385985cdaab50914e3bbf4371685f410
Merged-In: Icb486877385985cdaab50914e3bbf4371685f410
(cherry picked from commit 80a47e10307e73c4dc02d59d1ccb453944f08965)
(cherry picked from commit 15dd57ade16be5f5c0d6ac1fb193b421db9960a9)
(cherry picked from commit f45ee506b65fdbc699949fa71f0cd19ed908b07e)
diff --git a/service/src/com/android/car/CarPowerManagementService.java b/service/src/com/android/car/CarPowerManagementService.java
index 401a42d..283a684 100644
--- a/service/src/com/android/car/CarPowerManagementService.java
+++ b/service/src/com/android/car/CarPowerManagementService.java
@@ -15,12 +15,16 @@
  */
 package com.android.car;
 
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
 import android.car.Car;
 import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
 import android.car.hardware.power.ICarPower;
 import android.car.hardware.power.ICarPowerStateListener;
 import android.car.userlib.CarUserManagerHelper;
 import android.content.Context;
+import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.hardware.automotive.vehicle.V2_0.VehicleApPowerStateReq;
 import android.os.Build;
@@ -34,6 +38,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.Log;
 
 import com.android.car.hal.PowerHalService;
@@ -45,6 +50,7 @@
 import java.io.PrintWriter;
 import java.util.HashSet;
 import java.util.LinkedList;
+import java.util.List;
 import java.util.Set;
 import java.util.Timer;
 import java.util.TimerTask;
@@ -102,8 +108,8 @@
     @GuardedBy("mLock")
     private boolean mIsResuming;
     private final boolean mDisableUserSwitchDuringResume;
-
     private final CarUserManagerHelper mCarUserManagerHelper;
+    private final UserManager mUserManager;    // CarUserManagerHelper is deprecated...
 
     // TODO:  Make this OEM configurable.
     private static final int SHUTDOWN_POLLING_INTERVAL_MS = 2000;
@@ -132,16 +138,19 @@
 
     public CarPowerManagementService(Context context, PowerHalService powerHal,
             SystemInterface systemInterface, CarUserManagerHelper carUserManagerHelper) {
-        this(context, context.getResources(), powerHal, systemInterface, carUserManagerHelper);
+        this(context, context.getResources(), powerHal, systemInterface, carUserManagerHelper,
+                UserManager.get(context));
     }
 
     @VisibleForTesting
     CarPowerManagementService(Context context, Resources resources, PowerHalService powerHal,
-            SystemInterface systemInterface, CarUserManagerHelper carUserManagerHelper) {
+            SystemInterface systemInterface, CarUserManagerHelper carUserManagerHelper,
+            UserManager userManager) {
         mContext = context;
         mHal = powerHal;
         mSystemInterface = systemInterface;
         mCarUserManagerHelper = carUserManagerHelper;
+        mUserManager = userManager;
         mDisableUserSwitchDuringResume = resources
                 .getBoolean(R.bool.config_disableUserSwitchDuringResume);
         sShutdownPrepareTimeMs = resources.getInteger(
@@ -155,6 +164,7 @@
         }
     }
 
+    // TODO: remove?
     /**
      * Create a dummy instance for unit testing purpose only. Instance constructed in this way
      * is not safe as members expected to be non-null are null.
@@ -167,6 +177,7 @@
         mHandlerThread = null;
         mHandler = new PowerHandler(Looper.getMainLooper());
         mCarUserManagerHelper = null;
+        mUserManager = null;
         mDisableUserSwitchDuringResume = true;
     }
 
@@ -236,7 +247,8 @@
         writer.print(",mNextWakeupSec:" + mNextWakeupSec);
         writer.print(",mShutdownOnNextSuspend:" + mShutdownOnNextSuspend);
         writer.print(",mShutdownOnFinish:" + mShutdownOnFinish);
-        writer.println(",sShutdownPrepareTimeMs:" + sShutdownPrepareTimeMs);
+        writer.print(",sShutdownPrepareTimeMs:" + sShutdownPrepareTimeMs);
+        writer.println(",mDisableUserSwitchDuringResume:" + mDisableUserSwitchDuringResume);
     }
 
     @Override
@@ -363,17 +375,109 @@
             }
         }
 
-        if (allowUserSwitch) {
-            int targetUserId = mCarUserManagerHelper.getInitialUser();
-            if (targetUserId != UserHandle.USER_SYSTEM
-                    && targetUserId != mCarUserManagerHelper.getCurrentForegroundUserId()) {
-                Log.i(CarLog.TAG_POWER, "Desired user changed, switching to user:" + targetUserId);
-                mCarUserManagerHelper.switchToUserId(targetUserId);
-            }
-        }
         mSystemInterface.setDisplayState(true);
         sendPowerManagerEvent(CarPowerStateListener.ON);
         mHal.sendOn();
+
+        try {
+            switchUserOnResumeIfNecessary(allowUserSwitch);
+        } catch (Exception e) {
+            Log.e(CarLog.TAG_POWER, "Could not switch user on resume: " + e);
+        }
+    }
+
+    private void switchUserOnResumeIfNecessary(boolean allowSwitching) {
+        int targetUserId = mCarUserManagerHelper.getInitialUser();
+        if (targetUserId == UserHandle.USER_SYSTEM) {
+            // API explicitly say it doesn't return USER_SYSTEM
+            Log.wtf(CarLog.TAG_POWER, "getInitialUser() returned system user");
+            return;
+        }
+        int currentUserId = ActivityManager.getCurrentUser();
+        UserInfo targetUserInfo = mUserManager.getUserInfo(targetUserId);
+        boolean isTargetPersistent = !targetUserInfo.isEphemeral();
+        boolean isTargetGuest = targetUserInfo.isGuest();
+        Log.d(CarLog.TAG_POWER, "getTargetUserId(): current=" + currentUserId
+                + ", target=" + targetUserInfo.toFullString()
+                + ", isTargetPersistent=" + isTargetPersistent + ", isTargetGuest=" + isTargetGuest
+                + ", allowSwitching: " + allowSwitching);
+
+        if (isTargetPersistent && !isTargetGuest) {
+            if (!allowSwitching) {
+                Log.d(CarLog.TAG_POWER, "Not switching to " + targetUserId
+                        + " because it's not allowed");
+                return;
+            }
+            if (currentUserId == targetUserId) {
+                Log.v(CarLog.TAG_POWER, "no need to switch to (same user) " + currentUserId);
+                return;
+            }
+            // All good - switch to the requested user
+            switchToUser(currentUserId, targetUserId, /* reason= */ null);
+            return;
+        }
+
+        if (!isTargetGuest) {
+            // Shouldn't happen (unless OEM is explicitly creating ephemeral users, which
+            // doesn't make much sense), but it doesn't hurt to log...
+            Log.w(CarLog.TAG_POWER, "target user is ephemeral but not a guest: "
+                    + targetUserInfo.toFullString());
+            if (allowSwitching) {
+                switchToUser(currentUserId, targetUserId, /* reason= */ null);
+            }
+            return;
+        } else if (isTargetPersistent) {
+            // Shouldn't happen neither, but it's not a big deal (guest will be replaced below
+            // anyway), but it's worth logging as well...
+            Log.w(CarLog.TAG_POWER, "target user is a non-ephemeral guest: "
+                    + targetUserInfo.toFullString());
+        }
+
+        // At this point, target user is a guest - we cannot resume into an ephemeral guest for
+        // privacy reasons, so we need to create a new guest and switch to it (even if the OEM
+        // doesn't allow switching)
+
+
+        boolean marked = mUserManager.markGuestForDeletion(targetUserId);
+        if (!marked) {
+            Log.w(CarLog.TAG_POWER, "Could not mark guest user " + targetUserId + " for deletion");
+            return;
+        }
+
+        UserInfo newGuest = mUserManager.createGuest(mContext, targetUserInfo.name);
+
+        if (newGuest != null) {
+            switchToUser(currentUserId, newGuest.id, "Created new guest");
+            Log.d(CarLog.TAG_POWER, "Removing previous guest " + targetUserId);
+            mUserManager.removeUser(targetUserId);
+        } else {
+            Log.wtf(CarLog.TAG_POWER, "Could not create new guest");
+        }
+    }
+
+    private void switchToUser(@UserIdInt int fromUser, @UserIdInt int toUser,
+            @Nullable String reason) {
+        StringBuilder message = new StringBuilder();
+        if (reason == null) {
+            message.append("Desired user changed");
+        } else {
+            message.append(reason);
+        }
+        message.append(", switching from ").append(fromUser).append(" to ").append(toUser);
+        Log.i(CarLog.TAG_POWER, message.toString());
+        mCarUserManagerHelper.switchToUserId(toUser);
+    }
+
+    // TODO(b/146020398): test scenarios that return it
+    private int getFirstSwitchableUser() {
+        List<UserInfo> allUsers = mUserManager.getUsers();
+        for (UserInfo user : allUsers) {
+            if (user.id != UserHandle.USER_SYSTEM) {
+                return user.id;
+            }
+        }
+        Log.wtf(CarLog.TAG_POWER, "no switchable user: " + allUsers);
+        return UserHandle.USER_NULL;
     }
 
     private void handleShutdownPrepare(CpmsState newState) {
diff --git a/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java b/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java
index b2777d3..78663f1 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java
@@ -165,7 +165,7 @@
                 + ", maxGarageModeRunningDurationInSecs="
                 + mResources.getInteger(R.integer.maxGarageModeRunningDurationInSecs));
         mService = new CarPowerManagementService(mContext, mResources, mPowerHal,
-                mSystemInterface, mCarUserManagerHelper);
+                mSystemInterface, mCarUserManagerHelper, mUserManager);
         mService.init();
         CarPowerManagementService.setShutdownPrepareTimeout(0);
         mPowerHal.setSignalListener(mPowerSignalListener);
diff --git a/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java b/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java
index 72332d9..1dc68a6 100644
--- a/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java
+++ b/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java
@@ -231,7 +231,8 @@
      */
     @SystemApi
     public int getInitialUser() {
-        List<Integer> allUsers = userInfoListToUserIdList(getAllPersistentUsers());
+        // TODO(b/146020398): update tests cases, as it used to return persistent users only
+        List<Integer> allUsers = userInfoListToUserIdList(getAllUsers());
 
         int bootUserOverride = mTestableFrameworkWrapper.getBootUserOverrideId(BOOT_USER_NOT_FOUND);