DO NOT MERGE - Merge Android 10 into master
Bug: 139893257
Change-Id: I9b0fdb070405caae7b7a983d90bbea98374b01b5
diff --git a/Android.bp b/Android.bp
index 695f0b6..0f805d2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,17 +1,18 @@
java_library {
name: "car-frameworks-service",
installable: true,
+ libs: ["services"],
+ //LOCAL_PACKAGE_NAME := CarFrameworkService
+ required: ["libcar-framework-service-jni"],
srcs: [
"src/**/*.java",
"src/com/android/internal/car/ICarServiceHelper.aidl",
],
- required: ["libcar-framework-service-jni"],
- libs: ["services"],
+ static_libs: ["android.car.userlib"],
}
cc_library_shared {
name: "libcar-framework-service-jni",
- srcs: ["src/jni/com_android_internal_car_CarServiceHelperService.cpp"],
shared_libs: [
"libandroid_runtime",
"libhidlbase",
@@ -20,4 +21,5 @@
"libsuspend",
"libutils",
],
+ srcs: ["src/jni/com_android_internal_car_CarServiceHelperService.cpp"],
}
diff --git a/OWNERS b/OWNERS
index 563e6ae..857edb5 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,4 +1,4 @@
-# Default code reviewers picked from top 3 or more developers.
-# Please update this list if you find better candidates.
-spaik@google.com
+keunyoung@google.com
+randolphs@google.com
+sgurun@google.com
yizheng@google.com
diff --git a/src/com/android/internal/car/CarServiceHelperService.java b/src/com/android/internal/car/CarServiceHelperService.java
index 6ed2492..f8842d4 100644
--- a/src/com/android/internal/car/CarServiceHelperService.java
+++ b/src/com/android/internal/car/CarServiceHelperService.java
@@ -16,49 +16,85 @@
package com.android.internal.car;
+import android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.app.admin.DevicePolicyManager;
+import android.car.userlib.CarUserManagerHelper;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.content.pm.UserInfo;
+import android.hidl.manager.V1_0.IServiceManager;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
+import android.os.Process;
import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.os.Trace;
import android.os.UserHandle;
import android.util.Slog;
+import android.util.TimingsTraceLog;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.SystemService;
-import com.android.internal.car.ICarServiceHelper;
+import com.android.server.Watchdog;
+import com.android.server.am.ActivityManagerService;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
/**
* System service side companion service for CarService.
* Starts car service and provide necessary API for CarService. Only for car product.
*/
public class CarServiceHelperService extends SystemService {
+ // Place holder for user name of the first user created.
private static final String TAG = "CarServiceHelper";
+ private static final boolean DBG = true;
private static final String CAR_SERVICE_INTERFACE = "android.car.ICar";
+ // These numbers should match with binder call order of
+ // packages/services/Car/car-lib/src/android/car/ICar.aidl
+ private static final int ICAR_CALL_SET_CAR_SERVICE_HELPER = 0;
+ private static final int ICAR_CALL_SET_USER_UNLOCK_STATUS = 1;
+ private static final int ICAR_CALL_SET_SWITCH_USER = 2;
+
+ private static final String PROP_RESTART_RUNTIME = "ro.car.recovery.restart_runtime.enabled";
+
+ private static final List<String> CAR_HAL_INTERFACES_OF_INTEREST = Arrays.asList(
+ "android.hardware.automotive.vehicle@2.0::IVehicle",
+ "android.hardware.automotive.audiocontrol@1.0::IAudioControl"
+ );
+
+ @GuardedBy("mLock")
+ private int mLastSwitchedUser = UserHandle.USER_NULL;
+
private final ICarServiceHelperImpl mHelper = new ICarServiceHelperImpl();
private final Context mContext;
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
private IBinder mCarService;
+ @GuardedBy("mLock")
+ private boolean mSystemBootCompleted;
+ @GuardedBy("mLock")
+ private final HashMap<Integer, Boolean> mUserUnlockedStatus = new HashMap<>();
+ private final CarUserManagerHelper mCarUserManagerHelper;
private final ServiceConnection mCarServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
- Slog.i(TAG, "**CarService connected**");
- mCarService = iBinder;
- // Cannot depend on ICar which is defined in CarService, so handle binder call directly
- // instead.
- // void setCarServiceHelper(in IBinder helper)
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(CAR_SERVICE_INTERFACE);
- data.writeStrongBinder(mHelper.asBinder());
- try {
- mCarService.transact(IBinder.FIRST_CALL_TRANSACTION, // setCarServiceHelper
- data, null, Binder.FLAG_ONEWAY);
- } catch (RemoteException e) {
- Slog.w(TAG, "RemoteException from car service", e);
- handleCarServiceCrash();
+ if (DBG) {
+ Slog.d(TAG, "onServiceConnected:" + iBinder);
}
+ handleCarServiceConnection(iBinder);
}
@Override
@@ -68,8 +104,39 @@
};
public CarServiceHelperService(Context context) {
+ this(context, new CarUserManagerHelper(context));
+ }
+
+ @VisibleForTesting
+ CarServiceHelperService(Context context, CarUserManagerHelper carUserManagerHelper) {
super(context);
mContext = context;
+ mCarUserManagerHelper = carUserManagerHelper;
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (DBG) {
+ Slog.d(TAG, "onBootPhase:" + phase);
+ }
+ if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
+ checkForCarServiceConnection();
+ // TODO(b/126199560) Consider doing this earlier in onStart().
+ // Other than onStart, PHASE_THIRD_PARTY_APPS_CAN_START is the earliest timing.
+ setupAndStartUsers();
+ checkForCarServiceConnection();
+ } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
+ boolean shouldNotify = false;
+ synchronized (mLock) {
+ mSystemBootCompleted = true;
+ if (mCarService != null) {
+ shouldNotify = true;
+ }
+ }
+ if (shouldNotify) {
+ notifyAllUnlockedUsers();
+ }
+ }
}
@Override
@@ -84,17 +151,304 @@
System.loadLibrary("car-framework-service-jni");
}
+
+ @Override
+ public void onUnlockUser(int userHandle) {
+ handleUserLockStatusChange(userHandle, true);
+ if (DBG) {
+ Slog.d(TAG, "User" + userHandle + " unlocked");
+ }
+ }
+
+ @Override
+ public void onStopUser(int userHandle) {
+ handleUserLockStatusChange(userHandle, false);
+ }
+
+ @Override
+ public void onCleanupUser(int userHandle) {
+ handleUserLockStatusChange(userHandle, false);
+ }
+
+ @Override
+ public void onSwitchUser(int userHandle) {
+ synchronized (mLock) {
+ mLastSwitchedUser = userHandle;
+ if (mCarService == null) {
+ return; // The event will be delivered upon CarService connection.
+ }
+ }
+ sendSwitchUserBindercall(userHandle);
+ }
+
+ // Sometimes car service onConnected call is delayed a lot. car service binder can be
+ // found from ServiceManager directly. So do some polling during boot-up to connect to
+ // car service ASAP.
+ private void checkForCarServiceConnection() {
+ synchronized (mLock) {
+ if (mCarService != null) {
+ return;
+ }
+ }
+ IBinder iBinder = ServiceManager.checkService("car_service");
+ if (iBinder != null) {
+ if (DBG) {
+ Slog.d(TAG, "Car service found through ServiceManager:" + iBinder);
+ }
+ handleCarServiceConnection(iBinder);
+ }
+ }
+
+ private void handleCarServiceConnection(IBinder iBinder) {
+ int lastSwitchedUser;
+ boolean systemBootCompleted;
+ synchronized (mLock) {
+ if (mCarService == iBinder) {
+ return; // already connected.
+ }
+ if (mCarService != null) {
+ Slog.i(TAG, "car service binder changed, was:" + mCarService
+ + " new:" + iBinder);
+ }
+ mCarService = iBinder;
+ lastSwitchedUser = mLastSwitchedUser;
+ systemBootCompleted = mSystemBootCompleted;
+ }
+ Slog.i(TAG, "**CarService connected**");
+ sendSetCarServiceHelperBinderCall();
+ if (systemBootCompleted) {
+ notifyAllUnlockedUsers();
+ }
+ if (lastSwitchedUser != UserHandle.USER_NULL) {
+ sendSwitchUserBindercall(lastSwitchedUser);
+ }
+ }
+
+ private void handleUserLockStatusChange(int userHandle, boolean unlocked) {
+ boolean shouldNotify = false;
+ synchronized (mLock) {
+ Boolean oldStatus = mUserUnlockedStatus.get(userHandle);
+ if (oldStatus == null || oldStatus != unlocked) {
+ mUserUnlockedStatus.put(userHandle, unlocked);
+ if (mCarService != null && mSystemBootCompleted) {
+ shouldNotify = true;
+ }
+ }
+ }
+ if (shouldNotify) {
+ sendSetUserLockStatusBinderCall(userHandle, unlocked);
+ }
+ }
+
+ private void setupAndStartUsers() {
+ DevicePolicyManager devicePolicyManager =
+ mContext.getSystemService(DevicePolicyManager.class);
+ if (devicePolicyManager != null && devicePolicyManager.getUserProvisioningState()
+ != DevicePolicyManager.STATE_USER_UNMANAGED) {
+ Slog.i(TAG, "DevicePolicyManager active, skip user unlock/switch");
+ return;
+ }
+ // Offloading the whole unlock into separate thread did not help due to single locks
+ // used in AMS / PMS ended up stopping the world with lots of lock contention.
+ // To run these in background, there should be some improvements there.
+ int targetUserId = UserHandle.USER_SYSTEM;
+ if (mCarUserManagerHelper.getAllUsers().size() == 0) {
+ Slog.i(TAG, "Create new admin user and switch");
+ // On very first boot, create an admin user and switch to that user.
+ UserInfo admin = mCarUserManagerHelper.createNewAdminUser();
+ if (admin == null) {
+ Slog.e(TAG, "cannot create admin user");
+ return;
+ }
+ targetUserId = admin.id;
+ } else {
+ Slog.i(TAG, "Switch to default user");
+ targetUserId = mCarUserManagerHelper.getInitialUser();
+ }
+
+ // If system user is the only user to unlock, handle it when system completes the boot.
+ if (targetUserId == UserHandle.USER_SYSTEM) {
+ return;
+ }
+ IActivityManager am = ActivityManager.getService();
+ if (am == null) {
+ Slog.wtf(TAG, "cannot get ActivityManagerService");
+ return;
+ }
+ TimingsTraceLog traceLog = new TimingsTraceLog("SystemServerTiming",
+ Trace.TRACE_TAG_SYSTEM_SERVER);
+ traceLog.traceBegin("User0Unlock");
+ try {
+ // This is for force changing state into RUNNING_LOCKED. Otherwise unlock does not
+ // update the state and user 0 unlock happens twice.
+ if (!am.startUserInBackground(UserHandle.USER_SYSTEM)) {
+ // cannot start user
+ Slog.w(TAG, "cannot start system user");
+ } else if (!am.unlockUser(UserHandle.USER_SYSTEM, null, null, null)) {
+ // unlocking system user failed. But still continue for other setup.
+ Slog.w(TAG, "cannot unlock system user");
+ } else {
+ // user 0 started and unlocked
+ handleUserLockStatusChange(UserHandle.USER_SYSTEM, true);
+ }
+ } catch (RemoteException e) {
+ // should not happen for local call.
+ Slog.wtf("RemoteException from AMS", e);
+ }
+ traceLog.traceEnd();
+ // Do not unlock here to allow other stuffs done. Unlock will happen
+ // when system completes the boot.
+ // TODO(b/124460424) Unlock earlier?
+ traceLog.traceBegin("ForegroundUserStart");
+ try {
+ if (!am.startUserInForegroundWithListener(targetUserId, null)) {
+ Slog.e(TAG, "cannot start foreground user:" + targetUserId);
+ } else {
+ mCarUserManagerHelper.setLastActiveUser(targetUserId);
+ }
+ } catch (RemoteException e) {
+ // should not happen for local call.
+ Slog.wtf("RemoteException from AMS", e);
+ }
+ traceLog.traceEnd();
+ }
+
+
+ private void notifyAllUnlockedUsers() {
+ // only care about unlocked users
+ LinkedList<Integer> users = new LinkedList<>();
+ synchronized (mLock) {
+ for (Map.Entry<Integer, Boolean> entry : mUserUnlockedStatus.entrySet()) {
+ if (entry.getValue()) {
+ users.add(entry.getKey());
+ }
+ }
+ }
+ if (DBG) {
+ Slog.d(TAG, "notifyAllUnlockedUsers:" + users);
+ }
+ for (Integer i : users) {
+ sendSetUserLockStatusBinderCall(i, true);
+ }
+ }
+
+ private void sendSetCarServiceHelperBinderCall() {
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(CAR_SERVICE_INTERFACE);
+ data.writeStrongBinder(mHelper.asBinder());
+ // void setCarServiceHelper(in IBinder helper)
+ sendBinderCallToCarService(data, ICAR_CALL_SET_CAR_SERVICE_HELPER);
+ }
+
+ private void sendSetUserLockStatusBinderCall(int userHandle, boolean unlocked) {
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(CAR_SERVICE_INTERFACE);
+ data.writeInt(userHandle);
+ data.writeInt(unlocked ? 1 : 0);
+ // void setUserLockStatus(in int userHandle, in int unlocked)
+ sendBinderCallToCarService(data, ICAR_CALL_SET_USER_UNLOCK_STATUS);
+ }
+
+ private void sendSwitchUserBindercall(int userHandle) {
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(CAR_SERVICE_INTERFACE);
+ data.writeInt(userHandle);
+ // void onSwitchUser(in int userHandle)
+ sendBinderCallToCarService(data, ICAR_CALL_SET_SWITCH_USER);
+ }
+
+ private void sendBinderCallToCarService(Parcel data, int callNumber) {
+ // Cannot depend on ICar which is defined in CarService, so handle binder call directly
+ // instead.
+ IBinder carService;
+ synchronized (mLock) {
+ carService = mCarService;
+ }
+ try {
+ carService.transact(IBinder.FIRST_CALL_TRANSACTION + callNumber,
+ data, null, Binder.FLAG_ONEWAY);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException from car service", e);
+ handleCarServiceCrash();
+ } finally {
+ data.recycle();
+ }
+ }
+
+ // Adapted from frameworks/base/services/core/java/com/android/server/Watchdog.java
+ // TODO(b/131861630) use implementation common with Watchdog.java
+ //
+ private static ArrayList<Integer> getInterestingHalPids() {
+ try {
+ IServiceManager serviceManager = IServiceManager.getService();
+ ArrayList<IServiceManager.InstanceDebugInfo> dump =
+ serviceManager.debugDump();
+ HashSet<Integer> pids = new HashSet<>();
+ for (IServiceManager.InstanceDebugInfo info : dump) {
+ if (info.pid == IServiceManager.PidConstant.NO_PID) {
+ continue;
+ }
+
+ if (Watchdog.HAL_INTERFACES_OF_INTEREST.contains(info.interfaceName) ||
+ CAR_HAL_INTERFACES_OF_INTEREST.contains(info.interfaceName)) {
+ pids.add(info.pid);
+ }
+ }
+
+ return new ArrayList<Integer>(pids);
+ } catch (RemoteException e) {
+ return new ArrayList<Integer>();
+ }
+ }
+
+ // Adapted from frameworks/base/services/core/java/com/android/server/Watchdog.java
+ // TODO(b/131861630) use implementation common with Watchdog.java
+ //
+ private static ArrayList<Integer> getInterestingNativePids() {
+ ArrayList<Integer> pids = getInterestingHalPids();
+
+ int[] nativePids = Process.getPidsForCommands(Watchdog.NATIVE_STACKS_OF_INTEREST);
+ if (nativePids != null) {
+ pids.ensureCapacity(pids.size() + nativePids.length);
+ for (int i : nativePids) {
+ pids.add(i);
+ }
+ }
+
+ return pids;
+ }
+
+ // Borrowed from Watchdog.java. Create an ANR file from the call stacks.
+ //
+ private static void dumpServiceStacks() {
+ ArrayList<Integer> pids = new ArrayList<>();
+ pids.add(Process.myPid());
+
+ ActivityManagerService.dumpStackTraces(
+ pids, null, null, getInterestingNativePids());
+ }
+
private void handleCarServiceCrash() {
- //TODO define recovery bahavior
+ // Recovery behavior. Kill the system server and reset
+ // everything if enabled by the property.
+ boolean restartOnServiceCrash = SystemProperties.getBoolean(PROP_RESTART_RUNTIME, false);
+
+ dumpServiceStacks();
+ if (restartOnServiceCrash) {
+ Slog.w(TAG, "*** CARHELPER KILLING SYSTEM PROCESS: " + "CarService crash");
+ Slog.w(TAG, "*** GOODBYE!");
+ Process.killProcess(Process.myPid());
+ System.exit(10);
+ } else {
+ Slog.w(TAG, "*** CARHELPER ignoring: " + "CarService crash");
+ }
}
private static native int nativeForceSuspend(int timeoutMs);
private class ICarServiceHelperImpl extends ICarServiceHelper.Stub {
- /**
+ /**
* Force device to suspend
- *
- * @param timeoutMs
*/
@Override // Binder call
public int forceSuspend(int timeoutMs) {
diff --git a/tests/Android.mk b/tests/Android.mk
new file mode 100644
index 0000000..bb5e149
--- /dev/null
+++ b/tests/Android.mk
@@ -0,0 +1,36 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ $(call all-java-files-under, ../src/com/android/internal/car) \
+ $(call all-Iaidl-files-under, ../src/com/android/internal/car)
+
+LOCAL_PACKAGE_NAME := CarServicesTest
+LOCAL_PRIVATE_PLATFORM_APIS := true
+
+LOCAL_CERTIFICATE := platform
+
+LOCAL_MODULE_TAGS := tests
+
+# When built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_JAVA_LIBRARIES += \
+ android.test.runner \
+ android.test.base \
+ services
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ androidx.test.rules \
+ android.car.userlib \
+ junit \
+ mockito-target-minus-junit4 \
+ services \
+ truth-prebuilt
+
+include $(BUILD_PACKAGE)
\ No newline at end of file
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
new file mode 100644
index 0000000..008655a
--- /dev/null
+++ b/tests/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ package="com.android.internal.car"
+ android:sharedUserId="android.uid.system" >
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.internal.car"
+ android:label="Unit Tests for Car Framework Services"/>
+
+ <application android:label="CarServicesTest">
+ <uses-library android:name="android.test.runner" />
+ </application>
+</manifest>
diff --git a/tests/src/com/android/internal/car/CarHelperServiceTest.java b/tests/src/com/android/internal/car/CarHelperServiceTest.java
new file mode 100644
index 0000000..3f95366
--- /dev/null
+++ b/tests/src/com/android/internal/car/CarHelperServiceTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2018 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.internal.car;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Context;
+import android.content.pm.UserInfo;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.SystemService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class contains unit tests for the {@link CarServiceHelperService}.
+ *
+ * The following mocks are used:
+ * <ol>
+ * <li> {@link Context} provides system services and resources.
+ * <li> {@link CarUserManagerHelper} provides user info and actions.
+ * <ol/>
+ */
+@RunWith(AndroidJUnit4.class)
+public class CarHelperServiceTest {
+ private static final String DEFAULT_NAME = "Driver";
+ private CarServiceHelperService mCarServiceHelperService;
+ @Mock
+ private Context mMockContext;
+
+ @Mock
+ private Context mApplicationContext;
+
+ @Mock
+ private CarUserManagerHelper mCarUserManagerHelper;
+
+ /**
+ * Initialize objects and setup testing environment.
+ */
+ @Before
+ public void setUpMocks() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ doReturn(mApplicationContext).when(mMockContext).getApplicationContext();
+
+ mCarServiceHelperService = new CarServiceHelperService(mMockContext, mCarUserManagerHelper);
+ }
+
+ /**
+ * Test that the {@link CarServiceHelperService} starts up a secondary admin user
+ * upon first run.
+ */
+ @Test
+ public void testStartsSecondaryAdminUserOnFirstRun() {
+ UserInfo admin = mockAdminWithDefaultName(/* adminId= */ 10);
+
+ doReturn(new ArrayList<>()).when(mCarUserManagerHelper).getAllUsers();
+ mCarServiceHelperService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
+
+ verify(mCarUserManagerHelper).createNewAdminUser();
+ verify(mCarUserManagerHelper).switchToUserId(admin.id);
+ }
+
+ /**
+ * Test that the {@link CarServiceHelperService} updates last active user to the first
+ * admin user on first run.
+ */
+ @Test
+ public void testUpdateLastActiveUserOnFirstRun() {
+ UserInfo admin = mockAdminWithDefaultName(/* adminId= */ 10);
+
+ mCarServiceHelperService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
+
+ verify(mCarUserManagerHelper).setLastActiveUser(admin.id);
+ }
+
+ /**
+ * Test that the {@link CarServiceHelperService} starts up the last active user on reboot.
+ */
+ @Test
+ public void testStartsLastActiveUserOnReboot() {
+ List<UserInfo> users = new ArrayList<>();
+
+ int adminUserId = 10;
+ UserInfo admin =
+ new UserInfo(adminUserId, DEFAULT_NAME, UserInfo.FLAG_ADMIN);
+
+ int secUserId = 11;
+ UserInfo secUser =
+ new UserInfo(secUserId, DEFAULT_NAME, UserInfo.FLAG_ADMIN);
+
+ users.add(admin);
+ users.add(secUser);
+
+ doReturn(users).when(mCarUserManagerHelper).getAllUsers();
+ doReturn(secUserId).when(mCarUserManagerHelper).getInitialUser();
+
+ mCarServiceHelperService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
+
+ verify(mCarUserManagerHelper).switchToUserId(secUserId);
+ }
+
+ private UserInfo mockAdminWithDefaultName(int adminId) {
+ UserInfo admin = new UserInfo(adminId, DEFAULT_NAME, UserInfo.FLAG_ADMIN);
+ doReturn(admin).when(mCarUserManagerHelper).createNewAdminUser();
+ return admin;
+ }
+}