Merge changes from topic "gm_bg"

* changes:
  Fix garage mode ControllerTest
  Re-start background user when starting garage mode
diff --git a/car_product/overlay/frameworks/base/core/res/res/values/config.xml b/car_product/overlay/frameworks/base/core/res/res/values/config.xml
index 998c47a..067d9d0 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values/config.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values/config.xml
@@ -22,8 +22,13 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Enable multi-user. -->
     <bool name="config_enableMultiUserUI">true</bool>
-    <!-- Maximum number of users we allow to be running at a time -->
-    <integer name="config_multiuserMaxRunningUsers">2</integer>
+    <!-- Maximum number of users we allow to be running at a time.
+         For automotive, background user will be immediately stopped upon user switching but
+         up to this many users can be running in garage mode.
+         3 = headless user 0 + two primary users or 1 primary + 1 guest -->
+    <integer name="config_multiuserMaxRunningUsers">3</integer>
+    <!-- Use delay loccking mode always for automotive -->
+    <bool name="config_multiuserDelayUserDataLocking">true</bool>
     <!-- If true, all guest users created on the device will be ephemeral. -->
     <bool name="config_guestUserEphemeral">true</bool>
     <!-- Car Mode -->
diff --git a/service/src/com/android/car/garagemode/GarageMode.java b/service/src/com/android/car/garagemode/GarageMode.java
index 98bb41b..2a59222 100644
--- a/service/src/com/android/car/garagemode/GarageMode.java
+++ b/service/src/com/android/car/garagemode/GarageMode.java
@@ -21,7 +21,13 @@
 import android.app.job.JobSnapshot;
 import android.content.Intent;
 import android.os.Handler;
+import android.os.UserHandle;
+import android.util.ArraySet;
 
+import com.android.car.CarLocalServices;
+import com.android.car.user.CarUserService;
+
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
 
@@ -49,23 +55,17 @@
 
     static final long JOB_SNAPSHOT_INITIAL_UPDATE_MS = 10000; // 10 seconds
     static final long JOB_SNAPSHOT_UPDATE_FREQUENCY_MS = 1000; // 1 second
+    static final long USER_STOP_CHECK_INTERVAL = 10000; // 10 secs
 
     private final Controller mController;
 
     private boolean mGarageModeActive;
     private JobScheduler mJobScheduler;
     private Handler mHandler;
-    private Runnable mRunnable;
-    private CompletableFuture<Void> mFuture;
-
-    GarageMode(Controller controller) {
-        mGarageModeActive = false;
-        mController = controller;
-        mJobScheduler = controller.getJobSchedulerService();
-        mHandler = controller.getHandler();
-
-        mRunnable = () -> {
-            int numberRunning = numberOfIdleJobsRunning();
+    private Runnable mRunnable = new Runnable() {
+        @Override
+        public void run() {
+            int numberRunning = numberOfJobsRunning();
             if (numberRunning > 0) {
                 LOG.d("" + numberRunning + " jobs are still running. Need to wait more ...");
                 mHandler.postDelayed(mRunnable, JOB_SNAPSHOT_UPDATE_FREQUENCY_MS);
@@ -73,7 +73,54 @@
                 LOG.d("No jobs are currently running.");
                 finish();
             }
-        };
+        }
+    };
+
+    private final Runnable mStopUserCheckRunnable = new Runnable() {
+        @Override
+        public void run() {
+            int userToStop = UserHandle.USER_SYSTEM; // BG user never becomes system user.
+            int remainingUsersToStop = 0;
+            synchronized (this) {
+                remainingUsersToStop = mStartedBackgroundUsers.size();
+                if (remainingUsersToStop > 0) {
+                    userToStop = mStartedBackgroundUsers.valueAt(0);
+                } else {
+                    return;
+                }
+            }
+            if (numberOfJobsRunning() == 0) { // all jobs done or stopped.
+                // Keep user until job scheduling is stopped. Otherwise, it can crash jobs.
+                if (userToStop != UserHandle.USER_SYSTEM) {
+                    CarLocalServices.getService(CarUserService.class).stopBackgroundUser(
+                            userToStop);
+                    LOG.i("Stopping background user:" + userToStop + " remaining users:"
+                            + (remainingUsersToStop - 1));
+                }
+                synchronized (this) {
+                    mStartedBackgroundUsers.remove(userToStop);
+                    if (mStartedBackgroundUsers.size() == 0) {
+                        LOG.i("all background users stopped");
+                        return;
+                    }
+                }
+            } else {
+                LOG.i("Waiting for jobs to finish, remaining users:" + remainingUsersToStop);
+            }
+            // Poll again
+            mHandler.postDelayed(mStopUserCheckRunnable, USER_STOP_CHECK_INTERVAL);
+        }
+    };
+
+
+    private CompletableFuture<Void> mFuture;
+    private ArraySet<Integer> mStartedBackgroundUsers = new ArraySet<>();
+
+    GarageMode(Controller controller) {
+        mGarageModeActive = false;
+        mController = controller;
+        mJobScheduler = controller.getJobSchedulerService();
+        mHandler = controller.getHandler();
     }
 
     boolean isGarageModeActive() {
@@ -88,16 +135,26 @@
         updateFuture(future);
         broadcastSignalToJobSchedulerTo(true);
         startMonitoringThread();
+        ArrayList<Integer> startedUsers =
+                CarLocalServices.getService(CarUserService.class).startAllBackgroundUsers();
+        synchronized (this) {
+            for (Integer user : startedUsers) {
+                mStartedBackgroundUsers.add(user);
+            }
+        }
     }
 
     synchronized void cancel() {
+        broadcastSignalToJobSchedulerTo(false);
         if (mFuture != null && !mFuture.isDone()) {
             mFuture.cancel(true);
         }
         mFuture = null;
+        startBackgroundUserStopping();
     }
 
     synchronized void finish() {
+        broadcastSignalToJobSchedulerTo(false);
         mController.scheduleNextWakeup();
         synchronized (this) {
             if (mFuture != null && !mFuture.isDone()) {
@@ -105,6 +162,7 @@
             }
             mFuture = null;
         }
+        startBackgroundUserStopping();
     }
 
     private void cleanupGarageMode() {
@@ -112,8 +170,17 @@
         synchronized (this) {
             mGarageModeActive = false;
         }
-        broadcastSignalToJobSchedulerTo(false);
         stopMonitoringThread();
+        mHandler.removeCallbacks(mRunnable);
+        startBackgroundUserStopping();
+    }
+
+    private void startBackgroundUserStopping() {
+        synchronized (this) {
+            if (mStartedBackgroundUsers.size() > 0) {
+                mHandler.postDelayed(mStopUserCheckRunnable, USER_STOP_CHECK_INTERVAL);
+            }
+        }
     }
 
     private void updateFuture(CompletableFuture<Void> future) {
@@ -151,7 +218,7 @@
         mHandler.removeCallbacks(mRunnable);
     }
 
-    private int numberOfIdleJobsRunning() {
+    private int numberOfJobsRunning() {
         List<JobInfo> startedJobs = mJobScheduler.getStartedJobs();
         int count = 0;
         for (JobSnapshot snap : mJobScheduler.getAllJobSnapshots()) {
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index 258d80a..a1550da 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -17,6 +17,8 @@
 package com.android.car.user;
 
 import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.IActivityManager;
 import android.car.settings.CarSettings;
 import android.car.userlib.CarUserManagerHelper;
 import android.content.BroadcastReceiver;
@@ -24,6 +26,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.location.LocationManager;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -47,12 +50,15 @@
     private static final String TAG = "CarUserService";
     private final Context mContext;
     private final CarUserManagerHelper mCarUserManagerHelper;
+    private final IActivityManager mAm;
 
     private final Object mLock = new Object();
     @GuardedBy("mLock")
     private boolean mUser0Unlocked;
     @GuardedBy("mLock")
     private final ArrayList<Runnable> mUser0UnlockTasks = new ArrayList<>();
+    @GuardedBy("mLock")
+    private final ArrayList<Integer> mLastUnlockedUsers = new ArrayList<>();
 
     public CarUserService(
                 @Nullable Context context, @Nullable CarUserManagerHelper carUserManagerHelper) {
@@ -61,6 +67,7 @@
         }
         mContext = context;
         mCarUserManagerHelper = carUserManagerHelper;
+        mAm = ActivityManager.getService();
     }
 
     @Override
@@ -92,10 +99,14 @@
         writer.println(TAG);
         writer.println("Context: " + mContext);
         boolean user0Unlocked;
+        ArrayList<Integer> lastUnlockedUsers;
         synchronized (mLock) {
             user0Unlocked = mUser0Unlocked;
+            lastUnlockedUsers = new ArrayList<>(mLastUnlockedUsers);
+
         }
         writer.println("User0Unlocked: " + user0Unlocked);
+        writer.println("LastUnlockedUsers:" + lastUnlockedUsers);
     }
 
     @Override
@@ -131,11 +142,15 @@
      * @param unlocked unlocked (=true) or locked (=false)
      */
     public void setUserLockStatus(int userHandle, boolean unlocked) {
-        if (userHandle != UserHandle.USER_SYSTEM) {
-            return;
-        }
         ArrayList<Runnable> tasks = null;
         synchronized (mLock) {
+            if (userHandle != UserHandle.USER_SYSTEM && unlocked
+                    && mCarUserManagerHelper.isPersistentUser(userHandle)) {
+                Integer user = userHandle;
+                mLastUnlockedUsers.remove(user);
+                mLastUnlockedUsers.add(0, user);
+                return;
+            }
             // Assumes that car service need to do it only once during boot-up
             if (unlocked && !mUser0Unlocked) {
                 tasks = new ArrayList<>(mUser0UnlockTasks);
@@ -152,6 +167,54 @@
     }
 
     /**
+     * Start all background users that were active in system.
+     * @return list of background users started successfully.
+     */
+    public ArrayList<Integer> startAllBackgroundUsers() {
+        ArrayList<Integer> users;
+        synchronized (mLock) {
+            users = new ArrayList<>(mLastUnlockedUsers);
+        }
+        ArrayList<Integer> startedUsers = new ArrayList<>();
+        for (Integer user : users) {
+            if (user == mCarUserManagerHelper.getCurrentForegroundUserId()) {
+                continue;
+            }
+            try {
+                if (mAm.startUserInBackground(user)) {
+                    if (mAm.unlockUser(user, null, null, null)) {
+                        startedUsers.add(user);
+                    }
+                }
+            } catch (RemoteException e) {
+                // ignore
+            }
+        }
+        return startedUsers;
+    }
+
+    /**
+     * Stop all background users that were active in system.
+     * @return true if stopping succeeds.
+     */
+    public boolean stopBackgroundUser(int userId) {
+        if (userId == mCarUserManagerHelper.getCurrentForegroundUserId()) {
+            Log.i(TAG, "stopBackgroundUser, already a fg user:" + userId);
+            return false;
+        }
+        try {
+            int r = mAm.stopUser(userId, true, null);
+            if (r != ActivityManager.USER_OP_SUCCESS) {
+                Log.i(TAG, "stopBackgroundUser failed, user:" + userId + " err:" + r);
+                return false;
+            }
+        } catch (RemoteException e) {
+            // ignore
+        }
+        return true;
+    }
+
+    /**
      * Run give runnable when user 0 is unlocked. If user 0 is already unlocked, it is
      * run inside this call.
      * @param r Runnable to run.
diff --git a/tests/carservice_test/src/com/android/car/garagemode/ControllerTest.java b/tests/carservice_test/src/com/android/car/garagemode/ControllerTest.java
index cf184c1..c042e05 100644
--- a/tests/carservice_test/src/com/android/car/garagemode/ControllerTest.java
+++ b/tests/carservice_test/src/com/android/car/garagemode/ControllerTest.java
@@ -24,6 +24,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
@@ -38,14 +39,20 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.car.CarLocalServices;
+import com.android.car.user.CarUserService;
+
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
 
@@ -59,6 +66,8 @@
     @Mock private Handler mHandlerMock;
     @Mock private Car mCarMock;
     @Mock private CarPowerManager mCarPowerManagerMock;
+    @Mock private CarUserService mCarUserServiceMock;
+    private CarUserService mCarUserServiceOriginal;
     @Captor private ArgumentCaptor<Intent> mIntentCaptor;
     @Captor private ArgumentCaptor<Integer> mIntegerCaptor;
 
@@ -90,6 +99,16 @@
                 mCarMock);
         mController.setCarPowerManager(mCarPowerManagerMock);
         mFuture = new CompletableFuture<>();
+        mCarUserServiceOriginal = CarLocalServices.getService(CarUserService.class);
+        CarLocalServices.removeServiceForTest(CarUserService.class);
+        CarLocalServices.addService(CarUserService.class, mCarUserServiceMock);
+        doReturn(new ArrayList<Integer>()).when(mCarUserServiceMock).startAllBackgroundUsers();
+    }
+
+    @After
+    public void tearDown() {
+        CarLocalServices.removeServiceForTest(CarUserService.class);
+        CarLocalServices.addService(CarUserService.class, mCarUserServiceOriginal);
     }
 
     @Test
@@ -111,7 +130,7 @@
         assertThat(mController.isGarageModeActive()).isFalse();
 
         // Verify that monitoring thread has stopped
-        verify(mHandlerMock).removeCallbacks(any(Runnable.class));
+        verify(mHandlerMock, Mockito.atLeastOnce()).removeCallbacks(any(Runnable.class));
 
         // Verify that OFF signal broadcasted to JobScheduler
         verify(mContextMock, times(2)).sendBroadcast(mIntentCaptor.capture());