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());