Merge Android 12

Bug: 202323961
Merged-In: I7452a5f39d9b55361153edf7919c59de59c8656c
Change-Id: I5d02df6aa32f956881f6b57ae094000827a2c532
diff --git a/Android.bp b/Android.bp
index cb13569..d26195e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -5,21 +5,19 @@
 java_library {
     name: "car-frameworks-service",
     installable: true,
-
     libs: [
         "services",
-        "android.car.internal.event-log-tags",
+        "android.hardware.automotive.vehicle-V2.0-java",
+        "com.android.car.internal.common",
     ],
     required: ["libcar-framework-service-jni"],
     srcs: [
         "src/**/*.java",
-        "src/com/android/internal/car/ICarServiceHelper.aidl",
     ],
     static_libs: [
-        "android.hardware.automotive.vehicle-V2.0-java",
-        "android.car.userlib",
         "android.car.watchdoglib",
-        "carwatchdog_aidl_interface-V2-java",
+        "com.android.car.internal.system",
+        "android.automotive.watchdog.internal-java",
     ],
 }
 
diff --git a/src/com/android/internal/car/CarDevicePolicySafetyChecker.java b/src/com/android/internal/car/CarDevicePolicySafetyChecker.java
new file mode 100644
index 0000000..0bc0c21
--- /dev/null
+++ b/src/com/android/internal/car/CarDevicePolicySafetyChecker.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2020 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 android.app.admin.DevicePolicyManager.OPERATION_CLEAR_APPLICATION_USER_DATA;
+import static android.app.admin.DevicePolicyManager.OPERATION_LOGOUT_USER;
+import static android.app.admin.DevicePolicyManager.OPERATION_REBOOT;
+import static android.app.admin.DevicePolicyManager.OPERATION_REQUEST_BUGREPORT;
+import static android.app.admin.DevicePolicyManager.OPERATION_SAFETY_REASON_DRIVING_DISTRACTION;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_APPLICATION_HIDDEN;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_APPLICATION_RESTRICTIONS;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_KEYGUARD_DISABLED;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_LOCK_TASK_FEATURES;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_LOCK_TASK_PACKAGES;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_PACKAGES_SUSPENDED;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_STATUS_BAR_DISABLED;
+import static android.app.admin.DevicePolicyManager.OPERATION_SET_SYSTEM_SETTING;
+import static android.app.admin.DevicePolicyManager.OPERATION_SWITCH_USER;
+import static android.app.admin.DevicePolicyManager.operationToString;
+
+import android.annotation.NonNull;
+import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
+import android.app.admin.DevicePolicyManagerLiteInternal;
+import android.app.admin.DevicePolicySafetyChecker;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
+
+/**
+ * Integrates {@link android.app.admin.DevicePolicyManager} operations with car UX restrictions.
+ */
+final class CarDevicePolicySafetyChecker {
+
+    private static final String TAG = CarDevicePolicySafetyChecker.class.getSimpleName();
+
+    private static final boolean DEBUG = false;
+
+    private static final int[] UNSAFE_OPERATIONS = new int[] {
+            OPERATION_CLEAR_APPLICATION_USER_DATA,
+            OPERATION_LOGOUT_USER,
+            OPERATION_REBOOT,
+            OPERATION_REQUEST_BUGREPORT,
+            OPERATION_SET_APPLICATION_HIDDEN,
+            OPERATION_SET_APPLICATION_RESTRICTIONS,
+            OPERATION_SET_KEYGUARD_DISABLED,
+            OPERATION_SET_LOCK_TASK_FEATURES,
+            OPERATION_SET_LOCK_TASK_PACKAGES,
+            OPERATION_SET_PACKAGES_SUSPENDED,
+            OPERATION_SET_STATUS_BAR_DISABLED,
+            OPERATION_SET_SYSTEM_SETTING,
+            OPERATION_SWITCH_USER
+    };
+
+    private final AtomicBoolean mSafe = new AtomicBoolean(true);
+
+    private final DevicePolicySafetyChecker mCheckerImplementation;
+    private final DevicePolicyManagerLiteInternal mDpmi;
+
+    CarDevicePolicySafetyChecker(DevicePolicySafetyChecker checkerImplementation) {
+        this(checkerImplementation,
+                LocalServices.getService(DevicePolicyManagerLiteInternal.class));
+    }
+
+    @VisibleForTesting
+    CarDevicePolicySafetyChecker(DevicePolicySafetyChecker checkerImplementation,
+            DevicePolicyManagerLiteInternal dpmi) {
+        mCheckerImplementation = Objects.requireNonNull(checkerImplementation,
+                "DevicePolicySafetyChecker cannot be null");
+        mDpmi = Objects.requireNonNull(dpmi, "DevicePolicyManagerLiteInternal cannot be null");
+    }
+
+    boolean isDevicePolicyOperationSafe(@DevicePolicyOperation int operation) {
+        boolean safe = true;
+        boolean globalSafe = mSafe.get();
+        if (!globalSafe) {
+            for (int unsafeOperation : UNSAFE_OPERATIONS) {
+                if (unsafeOperation == operation) {
+                    safe = false;
+                    break;
+                }
+            }
+        }
+
+        if (DEBUG) {
+            Slog.d(TAG, "isDevicePolicyOperationSafe(" + operationToString(operation)
+                    + "): " + safe + " (mSafe=" + globalSafe + ")");
+        }
+        return safe;
+    }
+
+    // TODO(b/172376923): override getUnsafeStateException to show error message explaining how to
+    // wrap it under CarDevicePolicyManager
+
+    void setSafe(boolean safe) {
+        Slog.i(TAG, "Setting safe to " + safe);
+        mSafe.set(safe);
+
+        mDpmi.notifyUnsafeOperationStateChanged(mCheckerImplementation,
+                OPERATION_SAFETY_REASON_DRIVING_DISTRACTION, /* isSafe= */ safe);
+    }
+
+    boolean isSafe() {
+        return mSafe.get();
+    }
+
+    void dump(@NonNull IndentingPrintWriter pw) {
+        pw.printf("Safe to run device policy operations: %b\n", mSafe.get());
+        pw.printf("Unsafe operations: %s\n", Arrays.stream(UNSAFE_OPERATIONS)
+                .mapToObj(o -> operationToString(o)).collect(Collectors.toList()));
+    }
+}
diff --git a/src/com/android/internal/car/CarServiceHelperService.java b/src/com/android/internal/car/CarServiceHelperService.java
index da96391..8e82316 100644
--- a/src/com/android/internal/car/CarServiceHelperService.java
+++ b/src/com/android/internal/car/CarServiceHelperService.java
@@ -16,30 +16,26 @@
 
 package com.android.internal.car;
 
-import static android.car.userlib.UserHelper.safeName;
-
-import static com.android.internal.car.ExternalConstants.CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_STARTING;
-import static com.android.internal.car.ExternalConstants.CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPED;
-import static com.android.internal.car.ExternalConstants.CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPING;
-import static com.android.internal.car.ExternalConstants.CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
-import static com.android.internal.car.ExternalConstants.CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED;
-import static com.android.internal.car.ExternalConstants.CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING;
+import static com.android.car.internal.SystemConstants.ICAR_SYSTEM_SERVER_CLIENT;
+import static com.android.car.internal.common.CommonConstants.CAR_SERVICE_INTERFACE;
+import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STARTING;
+import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPED;
+import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPING;
+import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
+import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED;
+import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING;
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.admin.DevicePolicyManager;
-import android.automotive.watchdog.ICarWatchdogMonitor;
-import android.automotive.watchdog.PowerCycle;
-import android.automotive.watchdog.StateType;
-import android.car.userlib.CarUserManagerHelper;
-import android.car.userlib.CommonConstants.CarUserServiceConstants;
-import android.car.userlib.HalCallback;
-import android.car.userlib.InitialUserSetter;
-import android.car.userlib.InitialUserSetter.InitialUserInfoType;
-import android.car.userlib.UserHalHelper;
-import android.car.userlib.UserHelper;
+import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
+import android.app.admin.DevicePolicyManager.OperationSafetyReason;
+import android.app.admin.DevicePolicySafetyChecker;
+import android.automotive.watchdog.internal.ICarWatchdogMonitor;
+import android.automotive.watchdog.internal.PowerCycle;
+import android.automotive.watchdog.internal.StateType;
 import android.car.watchdoglib.CarWatchdogDaemonHelper;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -48,37 +44,40 @@
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.UserInfo;
-import android.hardware.automotive.vehicle.V2_0.InitialUserInfoRequestType;
-import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
 import android.hidl.manager.V1_0.IServiceManager;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.Parcel;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.sysprop.CarProperties;
 import android.util.EventLog;
-import android.util.Slog;
-import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
+import android.util.IndentingPrintWriter;
 import android.util.TimeUtils;
 
+import com.android.car.internal.ICarServiceHelper;
+import com.android.car.internal.ICarSystemServerClient;
+import com.android.car.internal.common.CommonConstants.UserLifecycleEventType;
+import com.android.car.internal.common.EventLogTags;
+import com.android.car.internal.common.UserHelperLite;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.car.ExternalConstants.ICarConstants;
 import com.android.internal.os.IResultReceiver;
+import com.android.server.Dumpable;
+import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.Watchdog;
 import com.android.server.am.ActivityManagerService;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.UserManagerInternal.UserLifecycleListener;
+import com.android.server.utils.Slogf;
 import com.android.server.utils.TimingsTraceAndSlog;
 import com.android.server.wm.CarLaunchParamsModifier;
 
@@ -101,8 +100,9 @@
  * 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.
+public class CarServiceHelperService extends SystemService
+        implements Dumpable, DevicePolicySafetyChecker {
+
     private static final String TAG = "CarServiceHelper";
 
     // TODO(b/154033860): STOPSHIP if they're still true
@@ -117,74 +117,40 @@
             "android.hardware.automotive.audiocontrol@2.0::IAudioControl"
     );
 
-    // Message ID representing HAL timeout handling.
-    private static final int WHAT_HAL_TIMEOUT = 1;
     // Message ID representing post-processing of process dumping.
-    private static final int WHAT_POST_PROCESS_DUMPING = 2;
+    private static final int WHAT_POST_PROCESS_DUMPING = 1;
     // Message ID representing process killing.
-    private static final int WHAT_PROCESS_KILL = 3;
+    private static final int WHAT_PROCESS_KILL = 2;
+    // Message ID representing service unresponsiveness.
+    private static final int WHAT_SERVICE_UNRESPONSIVE = 3;
+
+    private static final long CAR_SERVICE_BINDER_CALL_TIMEOUT = 15_000;
 
     private static final long LIFECYCLE_TIMESTAMP_IGNORE = 0;
 
-    // Typically there are ~2-5 ops while system and non-system users are starting.
-    private final int NUMBER_PENDING_OPERATIONS = 5;
-
-    @UserIdInt
-    @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;
+    private IBinder mCarServiceBinder;
     @GuardedBy("mLock")
     private boolean mSystemBootCompleted;
 
-    // Key: user id, value: lifecycle
-    @GuardedBy("mLock")
-    private final SparseIntArray mLastUserLifecycle = new SparseIntArray();
-
-    private final CarUserManagerHelper mCarUserManagerHelper;
-    private final InitialUserSetter mInitialUserSetter;
-    private final UserManager mUserManager;
     private final CarLaunchParamsModifier mCarLaunchParamsModifier;
 
-    private final boolean mHalEnabled;
-    private final int mHalTimeoutMs;
-
-    // Handler is currently only used for handleHalTimedout(), which is removed once received.
-    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private final Handler mHandler;
+    private final HandlerThread mHandlerThread = new HandlerThread("CarServiceHelperService");
 
     private final ProcessTerminator mProcessTerminator = new ProcessTerminator();
-
-    @GuardedBy("mLock")
-    private boolean mInitialized;
+    private final CarServiceConnectedCallback mCarServiceConnectedCallback =
+            new CarServiceConnectedCallback();
+    private final CarServiceProxy mCarServiceProxy;
 
     /**
      * End-to-end time (from process start) for unlocking the first non-system user.
      */
     private long mFirstUnlockedUserDuration;
 
-    /**
-     * Used to calculate how long it took to get the {@code INITIAL_USER_INFO} response from HAL:
-     *
-     * <ul>
-     *   <li>{@code 0}: HAL not called yet
-     *   <li>{@code <0}: stores the time HAL was called (multiplied by -1)
-     *   <li>{@code >0}: contains the duration (in ms)
-     * </ul>
-     */
-    private int mHalResponseTime;
-
-    // TODO(b/150413515): rather than store Runnables, it would be more efficient to store some
-    // parcelables representing the operation, then pass them to setCarServiceHelper
-    @GuardedBy("mLock")
-    private ArrayList<Runnable> mPendingOperations;
-
-    @GuardedBy("mLock")
-    private boolean mCarServiceHasCrashed;
-
     private final CarWatchdogDaemonHelper mCarWatchdogDaemonHelper;
     private final ICarWatchdogMonitorImpl mCarWatchdogMonitor = new ICarWatchdogMonitorImpl(this);
     private final CarWatchdogDaemonHelper.OnConnectionChangeListener mConnectionListener =
@@ -198,7 +164,7 @@
         @Override
         public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
             if (DBG) {
-                Slog.d(TAG, "onServiceConnected:" + iBinder);
+                Slogf.d(TAG, "onServiceConnected: %s", iBinder);
             }
             handleCarServiceConnection(iBinder);
         }
@@ -220,89 +186,76 @@
                     || (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) == 0) {
                 return;
             }
-            int powerCycle = PowerCycle.POWER_CYCLE_SUSPEND;
+            int powerCycle = PowerCycle.POWER_CYCLE_SHUTDOWN_ENTER;
             try {
                 mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.POWER_CYCLE,
                         powerCycle, /* arg2= */ 0);
                 if (DBG) {
-                    Slog.d(TAG, "Notified car watchdog daemon a power cycle(" + powerCycle + ")");
+                    Slogf.d(TAG, "Notified car watchdog daemon of power cycle(%d)", powerCycle);
                 }
             } catch (RemoteException | RuntimeException e) {
-                Slog.w(TAG, "Notifying system state change failed: " + e);
+                Slogf.w(TAG, "Notifying power cycle state change failed: %s", e);
             }
         }
     };
 
+    private final CarDevicePolicySafetyChecker mCarDevicePolicySafetyChecker;
+
     public CarServiceHelperService(Context context) {
         this(context,
-                new CarUserManagerHelper(context),
-                /* initialUserSetter= */ null,
-                UserManager.get(context),
                 new CarLaunchParamsModifier(context),
                 new CarWatchdogDaemonHelper(TAG),
-                CarProperties.user_hal_enabled().orElse(false),
-                CarProperties.user_hal_timeout().orElse(5_000)
+                null
         );
     }
 
     @VisibleForTesting
     CarServiceHelperService(
             Context context,
-            CarUserManagerHelper userManagerHelper,
-            InitialUserSetter initialUserSetter,
-            UserManager userManager,
             CarLaunchParamsModifier carLaunchParamsModifier,
             CarWatchdogDaemonHelper carWatchdogDaemonHelper,
-            boolean halEnabled,
-            int halTimeoutMs) {
+            CarServiceProxy carServiceOperationManager) {
         super(context);
+
         mContext = context;
-        mCarUserManagerHelper = userManagerHelper;
-        mUserManager = userManager;
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
         mCarLaunchParamsModifier = carLaunchParamsModifier;
         mCarWatchdogDaemonHelper = carWatchdogDaemonHelper;
-        boolean halValidUserHalSettings = false;
-        if (halEnabled) {
-            if (halTimeoutMs > 0) {
-                Slog.i(TAG, "User HAL enabled with timeout of " + halTimeoutMs + "ms");
-                halValidUserHalSettings = true;
-            } else {
-                Slog.w(TAG, "Not using User HAL due to invalid value on userHalTimeoutMs config: "
-                        + halTimeoutMs);
-            }
-        }
-        if (halValidUserHalSettings) {
-            mHalEnabled = true;
-            mHalTimeoutMs = halTimeoutMs;
+        mCarServiceProxy =
+                carServiceOperationManager == null ? new CarServiceProxy(this)
+                        : carServiceOperationManager;
+        UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
+        if (umi != null) {
+            umi.addUserLifecycleListener(new UserLifecycleListener() {
+                @Override
+                public void onUserCreated(UserInfo user, Object token) {
+                    if (DBG) Slogf.d(TAG, "onUserCreated(): %s", user.toFullString());
+                }
+                @Override
+                public void onUserRemoved(UserInfo user) {
+                    if (DBG) Slogf.d(TAG, "onUserRemoved(): $s", user.toFullString());
+                    mCarServiceProxy.onUserRemoved(user);
+                }
+            });
         } else {
-            mHalEnabled = false;
-            mHalTimeoutMs = -1;
-            Slog.i(TAG, "Not using User HAL");
+            Slogf.e(TAG, "UserManagerInternal not available - should only happen on unit tests");
         }
-        if (initialUserSetter == null) {
-            // Called from main constructor, which cannot pass a lambda referencing itself
-            mInitialUserSetter = new InitialUserSetter(context, (u) -> setInitialUser(u));
-        } else {
-            mInitialUserSetter = initialUserSetter;
-        }
+        mCarDevicePolicySafetyChecker = new CarDevicePolicySafetyChecker(this);
     }
-
     @Override
     public void onBootPhase(int phase) {
         EventLog.writeEvent(EventLogTags.CAR_HELPER_BOOT_PHASE, phase);
-        if (DBG) Slog.d(TAG, "onBootPhase:" + phase);
+        if (DBG) Slogf.d(TAG, "onBootPhase: %d", phase);
 
         TimingsTraceAndSlog t = newTimingsTraceAndSlog();
         if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
             t.traceBegin("onBootPhase.3pApps");
             mCarLaunchParamsModifier.init();
-            checkForCarServiceConnection(t);
             setupAndStartUsers(t);
-            checkForCarServiceConnection(t);
             t.traceEnd();
         } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
             t.traceBegin("onBootPhase.completed");
-            managePreCreatedUsers();
             synchronized (mLock) {
                 mSystemBootCompleted = true;
             }
@@ -310,7 +263,7 @@
                 mCarWatchdogDaemonHelper.notifySystemStateChange(
                         StateType.BOOT_PHASE, phase, /* arg2= */ 0);
             } catch (RemoteException | RuntimeException e) {
-                Slog.w(TAG, "Failed to notify boot phase change: " + e);
+                Slogf.w(TAG, "Failed to notify boot phase change: %s", e);
             }
             t.traceEnd();
         }
@@ -318,7 +271,7 @@
 
     @Override
     public void onStart() {
-        EventLog.writeEvent(EventLogTags.CAR_HELPER_START, mHalEnabled ? 1 : 0);
+        EventLog.writeEvent(EventLogTags.CAR_HELPER_START);
 
         IntentFilter filter = new IntentFilter(Intent.ACTION_REBOOT);
         filter.addAction(Intent.ACTION_SHUTDOWN);
@@ -327,19 +280,62 @@
         mCarWatchdogDaemonHelper.connect();
         Intent intent = new Intent();
         intent.setPackage("com.android.car");
-        intent.setAction(ICarConstants.CAR_SERVICE_INTERFACE);
+        intent.setAction(CAR_SERVICE_INTERFACE);
         if (!mContext.bindServiceAsUser(intent, mCarServiceConnection, Context.BIND_AUTO_CREATE,
-                UserHandle.SYSTEM)) {
-            Slog.wtf(TAG, "cannot start car service");
+                mHandler, UserHandle.SYSTEM)) {
+            Slogf.wtf(TAG, "cannot start car service");
         }
         loadNativeLibrary();
     }
 
     @Override
+    public void dump(IndentingPrintWriter pw, String[] args) {
+        if (args == null || args.length == 0 || args[0].equals("-a")) {
+            pw.printf("System boot completed: %b\n", mSystemBootCompleted);
+            pw.print("First unlocked user duration: ");
+            TimeUtils.formatDuration(mFirstUnlockedUserDuration, pw); pw.println();
+            pw.printf("Queued tasks: %d\n", mProcessTerminator.mQueuedTask);
+            mCarServiceProxy.dump(pw);
+            mCarDevicePolicySafetyChecker.dump(pw);
+            return;
+        }
+
+        if ("--user-metrics-only".equals(args[0])) {
+            mCarServiceProxy.dumpUserMetrics(pw);
+            return;
+        }
+
+        if ("--is-operation-safe".equals(args[0]) & args.length > 1) {
+            String arg1 = args[1];
+            int operation = 0;
+            try {
+                operation = Integer.parseInt(arg1);
+            } catch (Exception e) {
+                pw.printf("Invalid operation type: %s\n", arg1);
+                return;
+
+            }
+            int reason = getUnsafeOperationReason(operation);
+            boolean safe = reason == DevicePolicyManager.OPERATION_SAFETY_REASON_NONE;
+            pw.printf("Operation %s is %s. Reason: %s\n",
+                    DevicePolicyManager.operationToString(operation),
+                    safe ? "SAFE" : "UNSAFE",
+                    DevicePolicyManager.operationSafetyReasonToString(reason));
+            return;
+        }
+        pw.printf("Invalid args: %s\n", Arrays.toString(args));
+    }
+
+    @Override
+    public String getDumpableName() {
+        return "CarServiceHelper";
+    }
+
+    @Override
     public void onUserUnlocking(@NonNull TargetUser user) {
         if (isPreCreated(user, USER_LIFECYCLE_EVENT_TYPE_UNLOCKING)) return;
         EventLog.writeEvent(EventLogTags.CAR_HELPER_USER_UNLOCKING, user.getUserIdentifier());
-        if (DBG) Slog.d(TAG, "onUserUnlocking(" + user + ")");
+        if (DBG) Slogf.d(TAG, "onUserUnlocking(%s)", user);
 
         sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKING, user);
     }
@@ -349,25 +345,13 @@
         if (isPreCreated(user, USER_LIFECYCLE_EVENT_TYPE_UNLOCKED)) return;
         int userId = user.getUserIdentifier();
         EventLog.writeEvent(EventLogTags.CAR_HELPER_USER_UNLOCKED, userId);
-        if (DBG) Slog.d(TAG, "onUserUnlocked(" + user + ")");
+        if (DBG) Slogf.d(TAG, "onUserUnlocked(%s)", user);
 
-        if (mFirstUnlockedUserDuration == 0 && !UserHelper.isHeadlessSystemUser(userId)) {
+        if (mFirstUnlockedUserDuration == 0 && !UserHelperLite.isHeadlessSystemUser(userId)) {
             mFirstUnlockedUserDuration = SystemClock.elapsedRealtime()
                     - Process.getStartElapsedRealtime();
-            Slog.i(TAG, "Time to unlock 1st user(" + user + "): "
-                    + TimeUtils.formatDuration(mFirstUnlockedUserDuration));
-            boolean operationQueued = false;
-            synchronized (mLock) {
-                mLastUserLifecycle.put(userId, USER_LIFECYCLE_EVENT_TYPE_UNLOCKED);
-                if (mCarService == null) {
-                    operationQueued = true;
-                    if (DBG) Slog.d(TAG, "Queuing first user unlock for user " + user);
-                    queueOperationLocked(() -> sendFirstUserUnlocked(user));
-                }
-            }
-            if (!operationQueued) {
-                sendFirstUserUnlocked(user);
-            }
+            Slogf.i(TAG, "Time to unlock 1st user(%s): %s", user,
+                    TimeUtils.formatDuration(mFirstUnlockedUserDuration));
         }
         sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED, user);
     }
@@ -376,7 +360,7 @@
     public void onUserStarting(@NonNull TargetUser user) {
         if (isPreCreated(user, USER_LIFECYCLE_EVENT_TYPE_STARTING)) return;
         EventLog.writeEvent(EventLogTags.CAR_HELPER_USER_STARTING, user.getUserIdentifier());
-        if (DBG) Slog.d(TAG, "onUserStarting(" + user + ")");
+        if (DBG) Slogf.d(TAG, "onUserStarting(%s)", user);
 
         sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STARTING, user);
     }
@@ -385,7 +369,7 @@
     public void onUserStopping(@NonNull TargetUser user) {
         if (isPreCreated(user, USER_LIFECYCLE_EVENT_TYPE_STOPPING)) return;
         EventLog.writeEvent(EventLogTags.CAR_HELPER_USER_STOPPING, user.getUserIdentifier());
-        if (DBG) Slog.d(TAG, "onUserStopping(" + user + ")");
+        if (DBG) Slogf.d(TAG, "onUserStopping(%s)", user);
 
         sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPING, user);
         int userId = user.getUserIdentifier();
@@ -396,7 +380,7 @@
     public void onUserStopped(@NonNull TargetUser user) {
         if (isPreCreated(user, USER_LIFECYCLE_EVENT_TYPE_STOPPED)) return;
         EventLog.writeEvent(EventLogTags.CAR_HELPER_USER_STOPPED, user.getUserIdentifier());
-        if (DBG) Slog.d(TAG, "onUserStopped(" + user + ")");
+        if (DBG) Slogf.d(TAG, "onUserStopped(%s)", user);
 
         sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPED, user);
     }
@@ -407,168 +391,65 @@
         EventLog.writeEvent(EventLogTags.CAR_HELPER_USER_SWITCHING,
                 from == null ? UserHandle.USER_NULL : from.getUserIdentifier(),
                 to.getUserIdentifier());
-        if (DBG) Slog.d(TAG, "onUserSwitching(" + from + ">>" + to + ")");
+        if (DBG) Slogf.d(TAG, "onUserSwitching(%s>>%s)", from, to);
 
-        sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING, from, to);
+        mCarServiceProxy.sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING,
+                from, to);
         int userId = to.getUserIdentifier();
         mCarLaunchParamsModifier.handleCurrentUserSwitching(userId);
     }
 
+    @Override // from DevicePolicySafetyChecker
+    @OperationSafetyReason
+    public int getUnsafeOperationReason(@DevicePolicyOperation int operation) {
+        return mCarDevicePolicySafetyChecker.isDevicePolicyOperationSafe(operation)
+                ? DevicePolicyManager.OPERATION_SAFETY_REASON_NONE
+                : DevicePolicyManager.OPERATION_SAFETY_REASON_DRIVING_DISTRACTION;
+    }
+
+    @Override // from DevicePolicySafetyChecker
+    public boolean isSafeOperation(@OperationSafetyReason int reason) {
+        return mCarDevicePolicySafetyChecker.isSafe();
+    }
+
+    @Override // from DevicePolicySafetyChecker
+    public void onFactoryReset(IResultReceiver callback) {
+        if (DBG) Slogf.d(TAG, "onFactoryReset: %s", callback);
+
+        mCarServiceProxy.onFactoryReset(callback);
+    }
+
     @VisibleForTesting
     void loadNativeLibrary() {
         System.loadLibrary("car-framework-service-jni");
     }
 
-    private boolean isPreCreated(@NonNull TargetUser user, int eventType) {
-        UserInfo userInfo = user.getUserInfo();
-        if (userInfo == null) {
-            Slog.wtf(TAG, "no UserInfo on " + user + " on eventType " + eventType);
-            return false;
-        }
-        if (!userInfo.preCreated) return false;
+    private boolean isPreCreated(@NonNull TargetUser user, @UserLifecycleEventType int eventType) {
+        if (!user.isPreCreated()) return false;
 
         if (DBG) {
-            Slog.d(TAG, "Ignoring event of type " + eventType + " for pre-created user "
-                    + userInfo.toFullString());
+            Slogf.d(TAG, "Ignoring event of type %d for pre-created user %s", eventType, user);
         }
         return true;
     }
 
-    /**
-     * Queues a binder operation so it's called when the service is connected.
-     */
-    private void queueOperationLocked(@NonNull Runnable operation) {
-        if (mPendingOperations == null) {
-            mPendingOperations = new ArrayList<>(NUMBER_PENDING_OPERATIONS);
-        }
-        mPendingOperations.add(operation);
-    }
-
-    // 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(@NonNull TimingsTraceAndSlog t) {
-        synchronized (mLock) {
-            if (mCarService != null) {
-                return;
-            }
-        }
-        t.traceBegin("checkForCarServiceConnection");
-        IBinder iBinder = ServiceManager.checkService("car_service");
-        if (iBinder != null) {
-            if (DBG) {
-                Slog.d(TAG, "Car service found through ServiceManager:" + iBinder);
-            }
-            handleCarServiceConnection(iBinder);
-        }
-        t.traceEnd();
-    }
-
-    @VisibleForTesting
-    int getHalResponseTime() {
-        return mHalResponseTime;
-    }
-
-    @VisibleForTesting
-    void setInitialHalResponseTime() {
-        mHalResponseTime = -((int) SystemClock.uptimeMillis());
-    }
-
-    @VisibleForTesting
-    void setFinalHalResponseTime() {
-        mHalResponseTime += (int) SystemClock.uptimeMillis();
-    }
-
     @VisibleForTesting
     void handleCarServiceConnection(IBinder iBinder) {
-        boolean carServiceHasCrashed;
-        int lastSwitchedUser;
-        ArrayList<Runnable> pendingOperations;
-        SparseIntArray lastUserLifecycle = null;
         synchronized (mLock) {
-            if (mCarService == iBinder) {
+            if (mCarServiceBinder == iBinder) {
                 return; // already connected.
             }
-            Slog.i(TAG, "car service binder changed, was:" + mCarService + " new:" + iBinder);
-            mCarService = iBinder;
-            carServiceHasCrashed = mCarServiceHasCrashed;
-            mCarServiceHasCrashed = false;
-            lastSwitchedUser = mLastSwitchedUser;
-            pendingOperations = mPendingOperations;
-            mPendingOperations = null;
-            if (carServiceHasCrashed) {
-                lastUserLifecycle = mLastUserLifecycle.clone();
-            }
+            Slogf.i(TAG, "car service binder changed, was %s new: %s", mCarServiceBinder, iBinder);
+            mCarServiceBinder = iBinder;
+            Slogf.i(TAG, "**CarService connected**");
         }
-        int numberOperations = pendingOperations == null ? 0 : pendingOperations.size();
-        EventLog.writeEvent(EventLogTags.CAR_HELPER_SVC_CONNECTED, numberOperations);
 
-        Slog.i(TAG, "**CarService connected**");
+        sendSetSystemServerConnectionsCall();
 
-        sendSetCarServiceHelperBinderCall();
-        if (carServiceHasCrashed) {
-            int numUsers = lastUserLifecycle.size();
-            TimingsTraceAndSlog t = newTimingsTraceAndSlog();
-            t.traceBegin("send-uses-after-reconnect-" + numUsers);
-            // Send user0 events first
-            int user0Lifecycle = lastUserLifecycle.get(UserHandle.USER_SYSTEM,
-                    USER_LIFECYCLE_EVENT_TYPE_STARTING);
-            lastUserLifecycle.delete(UserHandle.USER_SYSTEM);
-            boolean user0IsCurrent = lastSwitchedUser == UserHandle.USER_SYSTEM;
-            sendAllLifecyleToUser(UserHandle.USER_SYSTEM, user0Lifecycle, user0IsCurrent);
-            // Send current user events next
-            if (!user0IsCurrent) {
-                int currentUserLifecycle = lastUserLifecycle.get(lastSwitchedUser,
-                        USER_LIFECYCLE_EVENT_TYPE_STARTING);
-                lastUserLifecycle.delete(lastSwitchedUser);
-                sendAllLifecyleToUser(lastSwitchedUser, currentUserLifecycle,
-                        /* isCurrentUser= */ true);
-            }
-            // Send all other users' events
-            for (int i = 0; i < lastUserLifecycle.size(); i++) {
-                int userId = lastUserLifecycle.keyAt(i);
-                int lifecycle = lastUserLifecycle.valueAt(i);
-                sendAllLifecyleToUser(userId, lifecycle, /* isCurrentUser= */ false);
-            }
-            t.traceEnd();
-        } else if (pendingOperations != null) {
-            if (DBG) Slog.d(TAG, "Running " + numberOperations + " pending operations");
-            TimingsTraceAndSlog t = newTimingsTraceAndSlog();
-            t.traceBegin("send-pending-ops-" + numberOperations);
-            for (int i = 0; i < numberOperations; i++) {
-                Runnable operation = pendingOperations.get(i);
-                try {
-                    operation.run();
-                } catch (RuntimeException e) {
-                    Slog.w(TAG, "exception running operation #" + i + ": " + e);
-                }
-            }
-            t.traceEnd();
-        }
-    }
-
-    private void sendAllLifecyleToUser(@UserIdInt int userId, int lifecycle,
-            boolean isCurrentUser) {
-        if (DBG) {
-            Slog.d(TAG, "sendAllLifecyleToUser, user:" + userId + " lifecycle:" + lifecycle);
-        }
-        if (lifecycle >= USER_LIFECYCLE_EVENT_TYPE_STARTING) {
-            sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STARTING, LIFECYCLE_TIMESTAMP_IGNORE,
-                    UserHandle.USER_NULL, userId);
-        }
-        if (isCurrentUser && userId != UserHandle.USER_SYSTEM) {
-            // Do not care about actual previous user.
-            sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING, LIFECYCLE_TIMESTAMP_IGNORE,
-                    UserHandle.USER_SYSTEM, userId);
-        }
-        if (lifecycle >= USER_LIFECYCLE_EVENT_TYPE_UNLOCKING) {
-            sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKING, LIFECYCLE_TIMESTAMP_IGNORE,
-                    UserHandle.USER_NULL, userId);
-        }
-        if (lifecycle >= USER_LIFECYCLE_EVENT_TYPE_UNLOCKED) {
-            sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED, LIFECYCLE_TIMESTAMP_IGNORE,
-                    UserHandle.USER_NULL, userId);
-        }
+        mHandler.removeMessages(WHAT_SERVICE_UNRESPONSIVE);
+        mHandler.sendMessageDelayed(
+                obtainMessage(CarServiceHelperService::handleCarServiceUnresponsive, this)
+                        .setWhat(WHAT_SERVICE_UNRESPONSIVE), CAR_SERVICE_BINDER_CALL_TIMEOUT);
     }
 
     private TimingsTraceAndSlog newTimingsTraceAndSlog() {
@@ -576,528 +457,52 @@
     }
 
     private void setupAndStartUsers(@NonNull TimingsTraceAndSlog t) {
-        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;
-        }
+        // TODO(b/156263735): decide if it should return in case the device's on Retail Mode
         t.traceBegin("setupAndStartUsers");
-        if (mHalEnabled) {
-            Slog.i(TAG, "Delegating initial switching to HAL");
-            setupAndStartUsersUsingHal();
-        } else {
-            setupAndStartUsersDirectly(t, /* userLocales= */ null);
-        }
+        mCarServiceProxy.initBootUser();
         t.traceEnd();
     }
 
-    private void handleHalTimedout() {
-        synchronized (mLock) {
-            if (mInitialized) return;
-        }
-
-        Slog.w(TAG, "HAL didn't respond in " + mHalTimeoutMs + "ms; using default behavior");
-        setupAndStartUsersDirectly();
+    private void handleCarServiceUnresponsive() {
+        // This should not happen. Calling this method means ICarSystemServerClient binder is not
+        // returned after service connection. and CarService has not connected in the given time.
+        Slogf.w(TAG, "*** CARHELPER KILLING SYSTEM PROCESS: CarService unresponsive.");
+        Slogf.w(TAG, "*** GOODBYE!");
+        Process.killProcess(Process.myPid());
+        System.exit(10);
     }
 
-    private void setupAndStartUsersUsingHal() {
-        mHandler.sendMessageDelayed(obtainMessage(CarServiceHelperService::handleHalTimedout, this)
-                .setWhat(WHAT_HAL_TIMEOUT), mHalTimeoutMs);
-
-        // TODO(b/150413515): get rid of receiver once returned?
-        IResultReceiver receiver = new IResultReceiver.Stub() {
-            @Override
-            public void send(int resultCode, Bundle resultData) {
-                EventLog.writeEvent(EventLogTags.CAR_HELPER_HAL_RESPONSE, resultCode);
-
-                setFinalHalResponseTime();
-                if (DBG) {
-                    Slog.d(TAG, "Got result from HAL (" +
-                            UserHalHelper.halCallbackStatusToString(resultCode) + ") in "
-                            + TimeUtils.formatDuration(mHalResponseTime));
-                }
-
-                mHandler.removeMessages(WHAT_HAL_TIMEOUT);
-                // TODO(b/150222501): log how long it took to receive the response
-                // TODO(b/150413515): print resultData as well on 2 logging calls below
-                synchronized (mLock) {
-                    if (mInitialized) {
-                        Slog.w(TAG, "Result from HAL came too late, ignoring: "
-                                + UserHalHelper.halCallbackStatusToString(resultCode));
-                        return;
-                    }
-                }
-
-                if (resultCode != HalCallback.STATUS_OK) {
-                    Slog.w(TAG, "Service returned non-ok status ("
-                            + UserHalHelper.halCallbackStatusToString(resultCode)
-                            + "); using default behavior");
-                    fallbackToDefaultInitialUserBehavior();
-                    return;
-                }
-
-                if (resultData == null) {
-                    Slog.w(TAG, "Service returned null bundle");
-                    fallbackToDefaultInitialUserBehavior();
-                    return;
-                }
-
-                int action = resultData.getInt(CarUserServiceConstants.BUNDLE_INITIAL_INFO_ACTION,
-                        InitialUserInfoResponseAction.DEFAULT);
-
-                String userLocales = resultData
-                        .getString(CarUserServiceConstants.BUNDLE_USER_LOCALES);
-                if (userLocales != null) {
-                    Slog.i(TAG, "Changing user locales to " + userLocales);
-                }
-
-                switch (action) {
-                    case InitialUserInfoResponseAction.DEFAULT:
-                        EventLog.writeEvent(EventLogTags.CAR_HELPER_HAL_DEFAULT_BEHAVIOR,
-                                /* fallback= */ 0, userLocales);
-                        if (DBG) Slog.d(TAG, "User HAL returned DEFAULT behavior");
-                        setupAndStartUsersDirectly(newTimingsTraceAndSlog(), userLocales);
-                        return;
-                    case InitialUserInfoResponseAction.SWITCH:
-                        int userId = resultData.getInt(CarUserServiceConstants.BUNDLE_USER_ID);
-                        startUserByHalRequest(userId, userLocales);
-                        return;
-                    case InitialUserInfoResponseAction.CREATE:
-                        String name = resultData
-                                .getString(CarUserServiceConstants.BUNDLE_USER_NAME);
-                        int flags = resultData.getInt(CarUserServiceConstants.BUNDLE_USER_FLAGS);
-                        createUserByHalRequest(name, userLocales, flags);
-                        return;
-                    default:
-                        Slog.w(TAG, "Invalid InitialUserInfoResponseAction action: " + action);
-                }
-                fallbackToDefaultInitialUserBehavior();
-            }
-        };
-        int initialUserInfoRequestType = getInitialUserInfoRequestType();
-        EventLog.writeEvent(EventLogTags.CAR_HELPER_HAL_REQUEST, initialUserInfoRequestType);
-
-        setInitialHalResponseTime();
-        sendOrQueueGetInitialUserInfo(initialUserInfoRequestType, receiver);
-    }
-
-    @VisibleForTesting
-    int getInitialUserInfoRequestType() {
-        if (!mCarUserManagerHelper.hasInitialUser()) {
-            return InitialUserInfoRequestType.FIRST_BOOT;
-        }
-        if (mContext.getPackageManager().isDeviceUpgrading()) {
-            return InitialUserInfoRequestType.FIRST_BOOT_AFTER_OTA;
-        }
-        return InitialUserInfoRequestType.COLD_BOOT;
-    }
-
-    private void startUserByHalRequest(@UserIdInt int userId, @Nullable String userLocales) {
-        if (userId <= 0) {
-            Slog.w(TAG, "invalid (or missing) user id sent by HAL: " + userId);
-            fallbackToDefaultInitialUserBehavior();
-            return;
-        }
-
-        EventLog.writeEvent(EventLogTags.CAR_HELPER_HAL_START_USER, userId, userLocales);
-        if (DBG) Slog.d(TAG, "Starting user " + userId + " as requested by HAL");
-
-        // It doesn't need to replace guest, as the switch would fail anyways if the requested user
-        // was a guest because it wouldn't exist.
-        mInitialUserSetter.set(newInitialUserInfoBuilder(InitialUserSetter.TYPE_SWITCH)
-                .setUserLocales(userLocales)
-                .setSwitchUserId(userId).build());
-    }
-
-    private InitialUserSetter.Builder newInitialUserInfoBuilder(@InitialUserInfoType int type) {
-        return new InitialUserSetter.Builder(type)
-                .setSupportsOverrideUserIdProperty(!CarProperties.user_hal_enabled().orElse(false));
-    }
-
-    private void createUserByHalRequest(@Nullable String name, @Nullable String userLocales,
-            int halFlags) {
-        String friendlyName = "user with name '" + safeName(name) + "', locales " + userLocales
-                + ", and flags " + UserHalHelper.userFlagsToString(halFlags);
-        EventLog.writeEvent(EventLogTags.CAR_HELPER_HAL_CREATE_USER, halFlags, safeName(name),
-                userLocales);
-        if (DBG) Slog.d(TAG, "HAL request creation of " + friendlyName);
-
-        mInitialUserSetter.set(newInitialUserInfoBuilder(InitialUserSetter.TYPE_CREATE)
-                .setUserLocales(userLocales)
-                .setNewUserName(name)
-                .setNewUserFlags(halFlags).build());
-
-    }
-
-    private void fallbackToDefaultInitialUserBehavior() {
-        EventLog.writeEvent(EventLogTags.CAR_HELPER_HAL_DEFAULT_BEHAVIOR, /* fallback= */ 1);
-        if (DBG) Slog.d(TAG, "Falling back to DEFAULT initial user behavior");
-        setupAndStartUsersDirectly();
-    }
-
-    private void setupAndStartUsersDirectly() {
-        setupAndStartUsersDirectly(newTimingsTraceAndSlog(), /* userLocales= */ null);
-    }
-
-    private void setupAndStartUsersDirectly(@NonNull TimingsTraceAndSlog t,
-            @Nullable String userLocales) {
-        synchronized (mLock) {
-            if (mInitialized) {
-                Slog.wtf(TAG, "Already initialized", new Exception());
-                return;
-            }
-            mInitialized = true;
-        }
-
-        mInitialUserSetter.set(newInitialUserInfoBuilder(InitialUserSetter.TYPE_DEFAULT_BEHAVIOR)
-                .setUserLocales(userLocales)
-                .build());
-    }
-
-    @VisibleForTesting
-    void managePreCreatedUsers() {
-        // First gets how many pre-createad users are defined by the OEM
-        int numberRequestedGuests = CarProperties.number_pre_created_guests().orElse(0);
-        int numberRequestedUsers = CarProperties.number_pre_created_users().orElse(0);
-        EventLog.writeEvent(EventLogTags.CAR_HELPER_PRE_CREATION_REQUESTED, numberRequestedUsers,
-                numberRequestedGuests);
-        if (DBG) {
-            Slog.d(TAG, "managePreCreatedUsers(): OEM asked for " + numberRequestedGuests
-                    + " guests and " + numberRequestedUsers + " users");
-        }
-
-        if (numberRequestedGuests < 0 || numberRequestedUsers < 0) {
-            Slog.w(TAG, "preCreateUsers(): invalid values provided by OEM; "
-                    + "number_pre_created_guests=" + numberRequestedGuests
-                    + ", number_pre_created_users=" + numberRequestedUsers);
-            return;
-        }
-
-        // Then checks how many exist already
-        List<UserInfo> allUsers = mUserManager.getUsers(/* excludePartial= */ true,
-                /* excludeDying= */ true, /* excludePreCreated= */ false);
-
-        int allUsersSize = allUsers.size();
-        if (DBG) Slog.d(TAG, "preCreateUsers: total users size is " + allUsersSize);
-
-        int numberExistingGuests = 0;
-        int numberExistingUsers = 0;
-
-        // List of pre-created users that were not properly initialized. Typically happens when
-        // the system crashed / rebooted before they were fully started.
-        SparseBooleanArray invalidPreCreatedUsers = new SparseBooleanArray();
-
-        // List of all pre-created users - it will be used to remove unused ones (when needed)
-        SparseBooleanArray existingPrecreatedUsers = new SparseBooleanArray();
-
-        // List of extra pre-created users and guests - they will be removed
-        List<Integer> extraPreCreatedUsers = new ArrayList<>();
-
-        for (int i = 0; i < allUsersSize; i++) {
-            UserInfo user = allUsers.get(i);
-            if (!user.preCreated) continue;
-            if (!user.isInitialized()) {
-                Slog.w(TAG, "Found invalid pre-created user that needs to be removed: "
-                        + user.toFullString());
-                invalidPreCreatedUsers.append(user.id, /* notUsed=*/ true);
-                continue;
-            }
-            boolean isGuest = user.isGuest();
-            existingPrecreatedUsers.put(user.id, isGuest);
-            if (isGuest) {
-                numberExistingGuests++;
-                if (numberExistingGuests > numberRequestedGuests) {
-                    extraPreCreatedUsers.add(user.id);
-                }
-            } else {
-                numberExistingUsers++;
-                if (numberExistingUsers > numberRequestedUsers) {
-                    extraPreCreatedUsers.add(user.id);
-                }
-            }
-        }
-        if (DBG) {
-            Slog.d(TAG, "managePreCreatedUsers(): system already has " + numberExistingGuests
-                    + " pre-created guests," + numberExistingUsers + " pre-created users, and these"
-                    + " invalid users: " + invalidPreCreatedUsers
-                    + " extra pre-created users: " + extraPreCreatedUsers);
-        }
-
-        int numberGuestsToAdd = numberRequestedGuests - numberExistingGuests;
-        int numberUsersToAdd = numberRequestedUsers - numberExistingUsers;
-        int numberGuestsToRemove = numberExistingGuests - numberRequestedGuests;
-        int numberUsersToRemove = numberExistingUsers - numberRequestedUsers;
-        int numberInvalidUsersToRemove = invalidPreCreatedUsers.size();
-
-        EventLog.writeEvent(EventLogTags.CAR_HELPER_PRE_CREATION_STATUS,
-                numberExistingUsers, numberUsersToAdd, numberUsersToRemove,
-                numberExistingGuests, numberGuestsToAdd, numberGuestsToRemove,
-                numberInvalidUsersToRemove);
-
-        if (numberGuestsToAdd == 0 && numberUsersToAdd == 0 && numberInvalidUsersToRemove == 0) {
-            if (DBG) Slog.d(TAG, "managePreCreatedUsers(): everything in sync");
-            return;
-        }
-
-        // Finally, manage them....
-
-        // In theory, we could submit multiple user pre-creations in parallel, but we're
-        // submitting just 1 task, for 2 reasons:
-        //   1.To minimize it's effect on other system server initialization tasks.
-        //   2.The pre-created users will be unlocked in parallel anyways.
-        runAsync(() -> {
-            TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Async",
-                    Trace.TRACE_TAG_SYSTEM_SERVER);
-
-            t.traceBegin("preCreateUsers");
-            if (numberUsersToAdd > 0) {
-                preCreateUsers(t, numberUsersToAdd, /* isGuest= */ false);
-            }
-            if (numberGuestsToAdd > 0) {
-                preCreateUsers(t, numberGuestsToAdd, /* isGuest= */ true);
-            }
-
-            int totalNumberToRemove = extraPreCreatedUsers.size();
-            if (DBG) Slog.d(TAG, "Must delete " + totalNumberToRemove + " pre-created users");
-            if (totalNumberToRemove > 0) {
-                int[] usersToRemove = new int[totalNumberToRemove];
-                for (int i = 0; i < totalNumberToRemove; i++) {
-                    usersToRemove[i] = extraPreCreatedUsers.get(i);
-                }
-                removePreCreatedUsers(usersToRemove);
-            }
-
-            t.traceEnd();
-
-            if (numberInvalidUsersToRemove > 0) {
-                t.traceBegin("removeInvalidPreCreatedUsers");
-                for (int i = 0; i < numberInvalidUsersToRemove; i++) {
-                    int userId = invalidPreCreatedUsers.keyAt(i);
-                    Slog.i(TAG, "removing invalid pre-created user " + userId);
-                    mUserManager.removeUser(userId);
-                }
-                t.traceEnd();
-            }
-        });
-    }
-
-    private void preCreateUsers(@NonNull TimingsTraceAndSlog t, int size, boolean isGuest) {
-        String msg = isGuest ? "preCreateGuests-" + size : "preCreateUsers-" + size;
-        if (DBG) Slog.d(TAG, "preCreateUsers: " + msg);
-        t.traceBegin(msg);
-        for (int i = 1; i <= size; i++) {
-            UserInfo preCreated = preCreateUsers(t, isGuest);
-            if (preCreated == null) {
-                Slog.w(TAG, "Could not pre-create" + (isGuest ? " guest" : "")
-                        + " user #" + i);
-                continue;
-            }
-        }
-        t.traceEnd();
-    }
-
-    @VisibleForTesting
-    void runAsync(Runnable r) {
-        // We cannot use SystemServerInitThreadPool because user pre-creation can take too long,
-        // which would crash the SystemServer on SystemServerInitThreadPool.shutdown();
-        String threadName = TAG + ".AsyncTask";
-        Slog.i(TAG, "Starting thread " + threadName);
-        new Thread(() -> {
-            try {
-                r.run();
-                Slog.i(TAG, "Finishing thread " + threadName);
-            } catch (RuntimeException e) {
-                Slog.e(TAG, "runAsync() failed", e);
-                throw e;
-            }
-        }, threadName).start();
-    }
-
-    @Nullable
-    public UserInfo preCreateUsers(@NonNull TimingsTraceAndSlog t, boolean isGuest) {
-        String traceMsg = "pre-create" + (isGuest ? "-guest" : "-user");
-        t.traceBegin(traceMsg);
-        // NOTE: we want to get rid of UserManagerHelper, so let's call UserManager directly
-        String userType =
-                isGuest ? UserManager.USER_TYPE_FULL_GUEST : UserManager.USER_TYPE_FULL_SECONDARY;
-        UserInfo user = null;
-        try {
-            user = mUserManager.preCreateUser(userType);
-            if (user == null) {
-                logPrecreationFailure(traceMsg, /* cause= */ null);
-            }
-        } catch (Exception e) {
-            logPrecreationFailure(traceMsg, e);
-        } finally {
-            t.traceEnd();
-        }
-        return user;
-    }
-
-    private void removePreCreatedUsers(int[] usersToRemove) {
-        for (int userId : usersToRemove) {
-            Slog.i(TAG, "removing pre-created user with id " + userId);
-            mUserManager.removeUser(userId);
-        }
-    }
-
-    /**
-     * Logs proper message when user pre-creation fails (most likely because there are too many).
-     */
-    @VisibleForTesting
-    void logPrecreationFailure(@NonNull String operation, @Nullable Exception cause) {
-        int maxNumberUsers = UserManager.getMaxSupportedUsers();
-        int currentNumberUsers = mUserManager.getUserCount();
-        StringBuilder message = new StringBuilder(operation.length() + 100)
-                .append(operation).append(" failed. Number users: ").append(currentNumberUsers)
-                .append(" Max: ").append(maxNumberUsers);
-        if (cause == null) {
-            Slog.w(TAG, message.toString());
-        } else {
-            Slog.w(TAG, message.toString(), cause);
-        }
-    }
-
-    private void sendSetCarServiceHelperBinderCall() {
+    private void sendSetSystemServerConnectionsCall() {
         Parcel data = Parcel.obtain();
-        data.writeInterfaceToken(ICarConstants.CAR_SERVICE_INTERFACE);
+        data.writeInterfaceToken(CAR_SERVICE_INTERFACE);
         data.writeStrongBinder(mHelper.asBinder());
-        // void setCarServiceHelper(in IBinder helper)
-        sendBinderCallToCarService(data, ICarConstants.ICAR_CALL_SET_CAR_SERVICE_HELPER);
-    }
-
-    private void sendUserLifecycleEvent(int eventType, @NonNull TargetUser user) {
-        sendUserLifecycleEvent(eventType, /* from= */ null, user);
-    }
-
-    private void sendUserLifecycleEvent(int eventType, @Nullable TargetUser from,
-            @NonNull TargetUser to) {
-        long now = System.currentTimeMillis();
+        data.writeStrongBinder(mCarServiceConnectedCallback.asBinder());
+        IBinder binder;
         synchronized (mLock) {
-            if (eventType == USER_LIFECYCLE_EVENT_TYPE_SWITCHING) {
-                mLastSwitchedUser = to.getUserIdentifier();
-            } else if (eventType == USER_LIFECYCLE_EVENT_TYPE_STOPPING
-                    || eventType == USER_LIFECYCLE_EVENT_TYPE_STOPPED) {
-                mLastUserLifecycle.delete(to.getUserIdentifier());
-            } else {
-                mLastUserLifecycle.put(to.getUserIdentifier(), eventType);
-            }
-            if (mCarService == null) {
-                if (DBG) Slog.d(TAG, "Queuing lifecycle event " + eventType + " for user " + to);
-                queueOperationLocked(() -> sendUserLifecycleEvent(eventType, now, from, to));
-                return;
-            }
+            binder = mCarServiceBinder;
         }
-        TimingsTraceAndSlog t = newTimingsTraceAndSlog();
-        t.traceBegin("send-lifecycle-" + eventType + "-" + to.getUserIdentifier());
-        sendUserLifecycleEvent(eventType, now, from, to);
-        t.traceEnd();
-    }
-
-    private void sendUserLifecycleEvent(int eventType, long timestamp, @Nullable TargetUser from,
-            @NonNull TargetUser to) {
-        int fromId = from == null ? UserHandle.USER_NULL : from.getUserIdentifier();
-        int toId = to.getUserIdentifier();
-        sendUserLifecycleEvent(eventType, timestamp, fromId, toId);
-    }
-
-    private void sendUserLifecycleEvent(int eventType, long timestamp, @UserIdInt int fromId,
-            @UserIdInt int toId) {
-        Parcel data = Parcel.obtain();
-        data.writeInterfaceToken(ICarConstants.CAR_SERVICE_INTERFACE);
-        data.writeInt(eventType);
-        data.writeLong(timestamp);
-        data.writeInt(fromId);
-        data.writeInt(toId);
-        // void onUserLifecycleEvent(int eventType, long timestamp, int from, int to)
-        sendBinderCallToCarService(data, ICarConstants.ICAR_CALL_ON_USER_LIFECYCLE);
-    }
-
-    private void sendOrQueueGetInitialUserInfo(int requestType, @NonNull IResultReceiver receiver) {
-        synchronized (mLock) {
-            if (mCarService == null) {
-                if (DBG) Slog.d(TAG, "Queuing GetInitialUserInfo call for type " + requestType);
-                queueOperationLocked(() -> sendGetInitialUserInfo(requestType, receiver));
-                return;
-            }
-        }
-        sendGetInitialUserInfo(requestType, receiver);
-    }
-
-    private void sendGetInitialUserInfo(int requestType, @NonNull IResultReceiver receiver) {
-        Parcel data = Parcel.obtain();
-        data.writeInterfaceToken(ICarConstants.CAR_SERVICE_INTERFACE);
-        data.writeInt(requestType);
-        data.writeInt(mHalTimeoutMs);
-        data.writeStrongBinder(receiver.asBinder());
-        // void getInitialUserInfo(int requestType, int timeoutMs, in IResultReceiver receiver)
-        sendBinderCallToCarService(data, ICarConstants.ICAR_CALL_GET_INITIAL_USER_INFO);
-    }
-
-    @VisibleForTesting
-    void setInitialUser(@Nullable UserInfo user) {
-        synchronized (mLock) {
-            if (mCarService == null) {
-                if (DBG) Slog.d(TAG, "Queuing setInitialUser() call");
-                queueOperationLocked(() -> sendSetInitialUser(user));
-                return;
-            }
-        }
-        sendSetInitialUser(user);
-    }
-
-    private void sendSetInitialUser(@Nullable UserInfo user) {
-        if (DBG) Slog.d(TAG, "sendSetInitialUser(): " + user);
-        Parcel data = Parcel.obtain();
-        data.writeInterfaceToken(ICarConstants.CAR_SERVICE_INTERFACE);
-        data.writeInt(user != null ? user.id : UserHandle.USER_NULL);
-        // void setInitialUser(int userId)
-        sendBinderCallToCarService(data, ICarConstants.ICAR_CALL_SET_INITIAL_USER);
-    }
-
-    private void sendFirstUserUnlocked(@NonNull TargetUser user) {
-        long now = System.currentTimeMillis();
-        Parcel data = Parcel.obtain();
-        data.writeInterfaceToken(ICarConstants.CAR_SERVICE_INTERFACE);
-        data.writeInt(user.getUserIdentifier());
-        data.writeLong(now);
-        data.writeLong(mFirstUnlockedUserDuration);
-        data.writeInt(mHalResponseTime);
-        // void onFirstUserUnlocked(int userId, long timestamp, long duration, int halResponseTime)
-        sendBinderCallToCarService(data, ICarConstants.ICAR_CALL_FIRST_USER_UNLOCKED);
-    }
-
-    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;
-        }
-        if (carService == null) {
-            Slog.w(TAG, "Not calling txn " + callNumber + " because service is not bound yet",
-                    new Exception());
-            return;
-        }
-        int code = IBinder.FIRST_CALL_TRANSACTION + callNumber;
+        int code = IBinder.FIRST_CALL_TRANSACTION;
         try {
-            if (VERBOSE) Slog.v(TAG, "calling one-way binder transaction with code " + code);
-            carService.transact(code, data, null, Binder.FLAG_ONEWAY);
-            if (VERBOSE) Slog.v(TAG, "finished one-way binder transaction with code " + code);
+            if (VERBOSE) Slogf.v(TAG, "calling one-way binder transaction with code %d", code);
+            // oneway void setSystemServerConnections(in IBinder helper, in IBinder receiver) = 0;
+            binder.transact(code, data, null, Binder.FLAG_ONEWAY);
+            if (VERBOSE) Slogf.v(TAG, "finished one-way binder transaction with code %d", code);
         } catch (RemoteException e) {
-            Slog.w(TAG, "RemoteException from car service", e);
+            Slogf.w(TAG, "RemoteException from car service", e);
             handleCarServiceCrash();
         } catch (RuntimeException e) {
-            Slog.wtf(TAG, "Exception calling binder transaction " + callNumber + " (real code: "
-                    + code + ")", e);
+            Slogf.wtf(TAG, e, "Exception calling binder transaction (real code: %d)", code);
             throw e;
         } finally {
             data.recycle();
         }
     }
 
+    private void sendUserLifecycleEvent(@UserLifecycleEventType int eventType,
+            @NonNull TargetUser user) {
+        mCarServiceProxy.sendUserLifecycleEvent(eventType, /* from= */ null, user);
+    }
+
     // Adapted from frameworks/base/services/core/java/com/android/server/Watchdog.java
     // TODO(b/131861630) use implementation common with Watchdog.java
     //
@@ -1157,17 +562,16 @@
         // everything if enabled by the property.
         boolean restartOnServiceCrash = SystemProperties.getBoolean(PROP_RESTART_RUNTIME, false);
 
+        mHandler.removeMessages(WHAT_SERVICE_UNRESPONSIVE);
+
         dumpServiceStacks();
         if (restartOnServiceCrash) {
-            Slog.w(TAG, "*** CARHELPER KILLING SYSTEM PROCESS: " + "CarService crash");
-            Slog.w(TAG, "*** GOODBYE!");
+            Slogf.w(TAG, "*** CARHELPER KILLING SYSTEM PROCESS: CarService crash");
+            Slogf.w(TAG, "*** GOODBYE!");
             Process.killProcess(Process.myPid());
             System.exit(10);
         } else {
-            Slog.w(TAG, "*** CARHELPER ignoring: " + "CarService crash");
-        }
-        synchronized (mLock) {
-            mCarServiceHasCrashed = true;
+            Slogf.w(TAG, "*** CARHELPER ignoring: CarService crash");
         }
     }
 
@@ -1179,18 +583,18 @@
         try {
             mCarWatchdogDaemonHelper.registerMonitor(mCarWatchdogMonitor);
         } catch (RemoteException | RuntimeException e) {
-            Slog.w(TAG, "Cannot register to car watchdog daemon: " + e);
+            Slogf.w(TAG, "Cannot register to car watchdog daemon: %s", e);
         }
     }
 
     private void killProcessAndReportToMonitor(int pid) {
         String processName = getProcessName(pid);
         Process.killProcess(pid);
-        Slog.w(TAG, "carwatchdog killed " + processName + " (pid: " + pid + ")");
+        Slogf.w(TAG, "carwatchdog killed %s (pid: %d)", processName, pid);
         try {
             mCarWatchdogDaemonHelper.tellDumpFinished(mCarWatchdogMonitor, pid);
         } catch (RemoteException | RuntimeException e) {
-            Slog.w(TAG, "Cannot report monitor result to car watchdog daemon: " + e);
+            Slogf.w(TAG, "Cannot report monitor result to car watchdog daemon: %s", e);
         }
     }
 
@@ -1205,13 +609,15 @@
             }
             return Paths.get(line).getFileName().toString();
         } catch (IOException e) {
-            Slog.w(TAG, "Cannot read " + filename);
+            Slogf.w(TAG, "Cannot read %s", filename);
             return unknownProcessName;
         }
     }
 
     private static native int nativeForceSuspend(int timeoutMs);
 
+    // TODO(b/173664653): it's missing unit tests (for example, to make sure that
+    // when its setSafetyMode() is called, mCarDevicePolicySafetyChecker is updated).
     private class ICarServiceHelperImpl extends ICarServiceHelper.Stub {
         /**
          * Force device to suspend
@@ -1230,8 +636,8 @@
         }
 
         @Override
-        public void setDisplayWhitelistForUser(@UserIdInt int userId, int[] displayIds) {
-            mCarLaunchParamsModifier.setDisplayWhitelistForUser(userId, displayIds);
+        public void setDisplayAllowlistForUser(@UserIdInt int userId, int[] displayIds) {
+            mCarLaunchParamsModifier.setDisplayAllowListForUser(userId, displayIds);
         }
 
         @Override
@@ -1245,6 +651,32 @@
             mCarLaunchParamsModifier.setSourcePreferredComponents(
                     enableSourcePreferred, sourcePreferredComponents);
         }
+
+        @Override
+        public void setSafetyMode(boolean safe) {
+            mCarDevicePolicySafetyChecker.setSafe(safe);
+        }
+
+        @Override
+        public UserInfo createUserEvenWhenDisallowed(String name, String userType, int flags) {
+            if (DBG) {
+                Slogf.d(TAG, "createUserEvenWhenDisallowed(): name=%s, type=%s, flags=%s",
+                        UserHelperLite.safeName(name), userType, UserInfo.flagsToString(flags));
+            }
+            UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
+            try {
+                UserInfo user = umi.createUserEvenWhenDisallowed(name, userType, flags,
+                        /* disallowedPackages= */ null, /* token= */ null);
+                if (DBG) {
+                    Slogf.d(TAG, "User created: %s", (user == null ? "null" : user.toFullString()));
+                }
+                // TODO(b/172691310): decide if user should be affiliated when DeviceOwner is set
+                return user;
+            } catch (UserManager.CheckedUserOperationException e) {
+                Slogf.e(TAG, "Error creating user", e);
+                return null;
+            }
+        }
     }
 
     private class ICarWatchdogMonitorImpl extends ICarWatchdogMonitor.Stub {
@@ -1262,16 +694,6 @@
             }
             service.handleClientsNotResponding(pids);
         }
-
-        @Override
-        public int getInterfaceVersion() {
-            return this.VERSION;
-        }
-
-        @Override
-        public String getInterfaceHash() {
-            return this.HASH;
-        }
     }
 
     private final class ProcessTerminator {
@@ -1313,7 +735,7 @@
 
         private void dumpAndKillProcess(int pid) {
             if (DBG) {
-                Slog.d(TAG, "Dumping and killing process(pid: " + pid + ")");
+                Slogf.d(TAG, "Dumping and killing process(pid: %d)", pid);
             }
             ArrayList<Integer> javaPids = new ArrayList<>(1);
             ArrayList<Integer> nativePids = new ArrayList<>();
@@ -1324,7 +746,7 @@
                     nativePids.add(pid);
                 }
             } catch (IOException e) {
-                Slog.w(TAG, "Cannot get process information: " + e);
+                Slogf.w(TAG, "Cannot get process information: %s", e);
                 return;
             }
             nativePids.addAll(getInterestingNativePids());
@@ -1332,7 +754,7 @@
             ActivityManagerService.dumpStackTraces(javaPids, null, null, nativePids, null);
             long dumpTime = SystemClock.uptimeMillis() - startDumpTime;
             if (DBG) {
-                Slog.d(TAG, "Dumping process took " + dumpTime + "ms");
+                Slogf.d(TAG, "Dumping process took %dms", dumpTime);
             }
             // To give clients a chance of wrapping up before the termination.
             if (dumpTime < ONE_SECOND_MS) {
@@ -1353,4 +775,22 @@
             return target == "/system/bin/app_process32" || target == "/system/bin/app_process64";
         }
     }
+
+    private final class CarServiceConnectedCallback extends IResultReceiver.Stub {
+        @Override
+        public void send(int resultCode, Bundle resultData) {
+            mHandler.removeMessages(WHAT_SERVICE_UNRESPONSIVE);
+
+            IBinder binder;
+            if (resultData == null
+                    || (binder = resultData.getBinder(ICAR_SYSTEM_SERVER_CLIENT)) == null) {
+                Slogf.wtf(TAG, "setSystemServerConnections return NULL Binder.");
+                handleCarServiceUnresponsive();
+                return;
+            }
+
+            ICarSystemServerClient carService = ICarSystemServerClient.Stub.asInterface(binder);
+            mCarServiceProxy.handleCarServiceConnection(carService);
+        }
+    }
 }
diff --git a/src/com/android/internal/car/CarServiceProxy.java b/src/com/android/internal/car/CarServiceProxy.java
new file mode 100644
index 0000000..42c344b
--- /dev/null
+++ b/src/com/android/internal/car/CarServiceProxy.java
@@ -0,0 +1,512 @@
+/*
+ * Copyright (C) 2020 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 com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STARTING;
+import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPED;
+import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPING;
+import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
+import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED;
+import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.UserInfo;
+import android.os.RemoteException;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.util.DebugUtils;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+import com.android.car.internal.ICarSystemServerClient;
+import com.android.car.internal.common.CommonConstants.UserLifecycleEventType;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.IResultReceiver;
+import com.android.internal.util.Preconditions;
+import com.android.server.SystemService.TargetUser;
+import com.android.server.utils.TimingsTraceAndSlog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+
+/**
+ * Manages CarService operations requested by CarServiceHelperService.
+ *
+ * <p>
+ * It is used to send and re-send binder calls to CarService when it connects and dies & reconnects.
+ * It does not simply queue the operations, because it needs to "replay" some of them on every
+ * reconnection.
+ */
+final class CarServiceProxy {
+
+    /*
+     * The logic of re-queue:
+     *
+     * There are two sparse array - mLastUserLifecycle and mPendingOperations
+     *
+     * First sparse array - mLastUserLifecycle - is to keep track of the life-cycle events for each
+     * user. It would have the last life-cycle event of each running user (typically user 0 and the
+     * current user). All life-cycle events seen so far would be replayed on connection and
+     * reconnection.
+     *
+     * Second sparse array - mPendingOperations - would keep all the non-life-cycle events related
+     * operations, which are represented by PendintOperation and PendingOperationId.
+     * Most operations (like initBootUser and preCreateUsers) just need to be sent only, but some
+     * need to be queued (like onUserRemoved).
+     */
+
+    // Operation ID for each non life-cycle event calls
+    // NOTE: public because of DebugUtils
+    public static final int PO_INIT_BOOT_USER = 0;
+    public static final int PO_ON_USER_REMOVED = 1;
+    public static final int PO_ON_FACTORY_RESET = 2;
+
+    @IntDef(prefix = { "PO_" }, value = {
+            PO_INIT_BOOT_USER,
+            PO_ON_USER_REMOVED,
+            PO_ON_FACTORY_RESET
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PendingOperationId{}
+
+    private static final boolean DBG = false;
+    private static final String TAG = CarServiceProxy.class.getSimpleName();
+
+    private static final long LIFECYCLE_TIMESTAMP_IGNORE = 0;
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private boolean mCarServiceCrashed;
+    @UserIdInt
+    @GuardedBy("mLock")
+    private int mLastSwitchedUser = UserHandle.USER_NULL;
+    @UserIdInt
+    @GuardedBy("mLock")
+    private int mPreviousUserOfLastSwitchedUser = UserHandle.USER_NULL;
+    // Key: user id, value: life-cycle
+    @GuardedBy("mLock")
+    private final SparseIntArray mLastUserLifecycle = new SparseIntArray();
+    // Key: @PendingOperationId, value: PendingOperation
+    @GuardedBy("mLock")
+    private final SparseArray<PendingOperation> mPendingOperations = new SparseArray<>();
+
+    @GuardedBy("mLock")
+    private ICarSystemServerClient mCarService;
+
+    private final CarServiceHelperService mCarServiceHelperService;
+    private final UserMetrics mUserMetrics = new UserMetrics();
+
+    CarServiceProxy(CarServiceHelperService carServiceHelperService) {
+        mCarServiceHelperService = carServiceHelperService;
+    }
+
+    /**
+     * Handles new CarService Connection.
+     */
+    void handleCarServiceConnection(ICarSystemServerClient carService) {
+        Slog.i(TAG, "CarService connected.");
+        TimingsTraceAndSlog t = newTimingsTraceAndSlog();
+        t.traceBegin("handleCarServiceConnection");
+        synchronized (mLock) {
+            mCarService = carService;
+            mCarServiceCrashed = false;
+            runQueuedOperationLocked(PO_INIT_BOOT_USER);
+            runQueuedOperationLocked(PO_ON_USER_REMOVED);
+            runQueuedOperationLocked(PO_ON_FACTORY_RESET);
+        }
+        sendLifeCycleEvents();
+        t.traceEnd();
+    }
+
+    @GuardedBy("mLock")
+    private void runQueuedOperationLocked(@PendingOperationId int operationId) {
+        PendingOperation pendingOperation = mPendingOperations.get(operationId);
+        if (pendingOperation != null) {
+            runLocked(operationId, pendingOperation.value);
+            return;
+        }
+        if (DBG) {
+            Slog.d(TAG, "No queued operation of type " + pendingOperationToString(operationId));
+        }
+    }
+
+    private void sendLifeCycleEvents() {
+        int lastSwitchedUser;
+        SparseIntArray lastUserLifecycle;
+
+        synchronized (mLock) {
+            lastSwitchedUser = mLastSwitchedUser;
+            lastUserLifecycle = mLastUserLifecycle.clone();
+        }
+
+        // Send user0 events first
+        int user0Lifecycle = lastUserLifecycle.get(UserHandle.USER_SYSTEM);
+        boolean user0IsCurrent = lastSwitchedUser == UserHandle.USER_SYSTEM;
+        // If user0Lifecycle is 0, then no life-cycle event received yet.
+        if (user0Lifecycle != 0) {
+            sendAllLifecyleToUser(UserHandle.USER_SYSTEM, user0Lifecycle, user0IsCurrent);
+        }
+        lastUserLifecycle.delete(UserHandle.USER_SYSTEM);
+
+        // Send current user events next
+        if (!user0IsCurrent) {
+            int currentUserLifecycle = lastUserLifecycle.get(lastSwitchedUser);
+            // If currentUserLifecycle is 0, then no life-cycle event received yet.
+            if (currentUserLifecycle != 0) {
+                sendAllLifecyleToUser(lastSwitchedUser, currentUserLifecycle,
+                        /* isCurrentUser= */ true);
+            }
+        }
+
+        lastUserLifecycle.delete(lastSwitchedUser);
+
+        // Send all other users' events
+        for (int i = 0; i < lastUserLifecycle.size(); i++) {
+            int userId = lastUserLifecycle.keyAt(i);
+            int lifecycle = lastUserLifecycle.valueAt(i);
+            sendAllLifecyleToUser(userId, lifecycle, /* isCurrentUser= */ false);
+        }
+    }
+
+    private void sendAllLifecyleToUser(@UserIdInt int userId, int lifecycle,
+            boolean isCurrentUser) {
+        if (DBG) {
+            Slog.d(TAG, "sendAllLifecyleToUser, user:" + userId + " lifecycle:" + lifecycle);
+        }
+        if (lifecycle >= USER_LIFECYCLE_EVENT_TYPE_STARTING) {
+            sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_STARTING, UserHandle.USER_NULL,
+                    userId);
+        }
+
+        if (isCurrentUser && userId != UserHandle.USER_SYSTEM) {
+            synchronized (mLock) {
+                sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING,
+                        mPreviousUserOfLastSwitchedUser, userId);
+            }
+        }
+
+        if (lifecycle >= USER_LIFECYCLE_EVENT_TYPE_UNLOCKING) {
+            sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKING, UserHandle.USER_NULL,
+                    userId);
+        }
+
+        if (lifecycle >= USER_LIFECYCLE_EVENT_TYPE_UNLOCKED) {
+            sendUserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED, UserHandle.USER_NULL,
+                    userId);
+        }
+    }
+
+    /**
+     * Initializes boot user.
+     */
+    void initBootUser() {
+        if (DBG) Slog.d(TAG, "initBootUser()");
+
+        saveOrRun(PO_INIT_BOOT_USER);
+    }
+
+    // TODO(b/173664653): add unit test
+    /**
+     * Callback to indifcate the given user was removed.
+     */
+    void onUserRemoved(@NonNull UserInfo user) {
+        if (DBG) Slog.d(TAG, "onUserRemoved(): " + user.toFullString());
+
+        saveOrRun(PO_ON_USER_REMOVED, user);
+    }
+
+    // TODO(b/173664653): add unit test
+    /**
+     * Callback to ask user to confirm if it's ok to factory reset the device.
+     */
+    void onFactoryReset(@NonNull IResultReceiver callback) {
+        if (DBG) Slog.d(TAG, "onFactoryReset(): " + callback);
+
+        saveOrRun(PO_ON_FACTORY_RESET, callback);
+    }
+
+    private void saveOrRun(@PendingOperationId int operationId) {
+        saveOrRun(operationId, /* value= */ null);
+    }
+
+    private void saveOrRun(@PendingOperationId int operationId, @Nullable Object value) {
+        synchronized (mLock) {
+            if (mCarService == null) {
+                if (DBG) {
+                    Slog.d(TAG, "CarService null. Operation "
+                            + pendingOperationToString(operationId)
+                            + (value == null ? "" : "(" + value + ")") + " deferred.");
+                }
+                savePendingOperationLocked(operationId, value);
+                return;
+            }
+            if (operationId == PO_ON_FACTORY_RESET) {
+                // Must always persist it, so it's sent again if CarService is crashed before
+                // the next reboot or suspension-to-ram
+                savePendingOperationLocked(operationId, value);
+            }
+            runLocked(operationId, value);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void runLocked(@PendingOperationId int operationId, @Nullable Object value) {
+        if (DBG) Slog.d(TAG, "runLocked(): " + pendingOperationToString(operationId) + "/" + value);
+        try {
+            if (isServiceCrashedLoggedLocked(operationId)) {
+                return;
+            }
+            sendCarServiceActionLocked(operationId, value);
+            if (operationId == PO_ON_FACTORY_RESET) {
+                if (DBG) Slog.d(TAG, "NOT removing " + pendingOperationToString(operationId));
+                return;
+            }
+            if (DBG) Slog.d(TAG, "removing " + pendingOperationToString(operationId));
+            mPendingOperations.delete(operationId);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "RemoteException from car service", e);
+            handleCarServiceCrash();
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void savePendingOperationLocked(@PendingOperationId int operationId,
+            @Nullable Object value) {
+        PendingOperation pendingOperation = mPendingOperations.get(operationId);
+
+        if (pendingOperation == null) {
+            pendingOperation = new PendingOperation(operationId, value);
+            if (DBG) Slog.d(TAG, "Created " + pendingOperation);
+            mPendingOperations.put(operationId, pendingOperation);
+            return;
+        }
+        switch (operationId) {
+            case PO_ON_USER_REMOVED:
+                Preconditions.checkArgument((value instanceof UserInfo),
+                        "invalid value passed to ON_USER_REMOVED", value);
+                if (pendingOperation.value instanceof ArrayList) {
+                    if (DBG) Slog.d(TAG, "Adding " + value + " to existing " + pendingOperation);
+                    ((ArrayList) pendingOperation.value).add(value);
+                } else if (pendingOperation.value instanceof UserInfo) {
+                    ArrayList<Object> list = new ArrayList<>(2);
+                    list.add(pendingOperation.value);
+                    list.add(value);
+                    if (DBG) Slog.d(TAG, "Converting " + pendingOperation.value + " to " + list);
+                    pendingOperation.value = list;
+                } else {
+                    throw new IllegalStateException("Invalid value for ON_USER_REMOVED: " + value);
+                }
+                break;
+            case PO_ON_FACTORY_RESET:
+                PendingOperation newOperation = new PendingOperation(operationId, value);
+                if (DBG) Slog.d(TAG, "Replacing " + pendingOperation + " by " + newOperation);
+                mPendingOperations.put(operationId, newOperation);
+                break;
+            default:
+                if (DBG) {
+                    Slog.d(TAG, "Already saved operation of type "
+                            + pendingOperationToString(operationId));
+                }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void sendCarServiceActionLocked(@PendingOperationId int operationId,
+            @Nullable Object value) throws RemoteException {
+        if (DBG) {
+            Slog.d(TAG, "sendCarServiceActionLocked: Operation "
+                    + pendingOperationToString(operationId));
+        }
+        switch (operationId) {
+            case PO_INIT_BOOT_USER:
+                mCarService.initBootUser();
+                break;
+            case PO_ON_USER_REMOVED:
+                if (value instanceof ArrayList) {
+                    ArrayList<Object> list = (ArrayList<Object>) value;
+                    if (DBG) Slog.d(TAG, "Sending " + list.size() + " onUserRemoved() calls");
+                    for (Object user: list) {
+                        onUserRemovedLocked(user);
+                    }
+                } else {
+                    onUserRemovedLocked(value);
+                }
+                break;
+            case PO_ON_FACTORY_RESET:
+                mCarService.onFactoryReset((IResultReceiver) value);
+                break;
+            default:
+                Slog.wtf(TAG, "Invalid Operation. OperationId -" + operationId);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void onUserRemovedLocked(@NonNull Object value) throws RemoteException {
+        Preconditions.checkArgument((value instanceof UserInfo),
+                "Invalid value for ON_USER_REMOVED: %s", value);
+        UserInfo user = (UserInfo) value;
+        if (DBG) Slog.d(TAG, "Sending onUserRemoved(): " + user.toFullString());
+        mCarService.onUserRemoved(user);
+    }
+
+    /**
+     * Sends user life-cycle events to CarService.
+     */
+    void sendUserLifecycleEvent(@UserLifecycleEventType int eventType, @Nullable TargetUser from,
+            @NonNull TargetUser to) {
+        long now = System.currentTimeMillis();
+        int fromId = from == null ? UserHandle.USER_NULL : from.getUserIdentifier();
+        int toId = to.getUserIdentifier();
+        mUserMetrics.onEvent(eventType, now, fromId, toId);
+
+        synchronized (mLock) {
+            if (eventType == USER_LIFECYCLE_EVENT_TYPE_SWITCHING) {
+                mLastSwitchedUser = to.getUserIdentifier();
+                mPreviousUserOfLastSwitchedUser = from.getUserIdentifier();
+                mLastUserLifecycle.put(to.getUserIdentifier(), eventType);
+            } else if (eventType == USER_LIFECYCLE_EVENT_TYPE_STOPPING
+                    || eventType == USER_LIFECYCLE_EVENT_TYPE_STOPPED) {
+                mLastUserLifecycle.delete(to.getUserIdentifier());
+            } else {
+                mLastUserLifecycle.put(to.getUserIdentifier(), eventType);
+            }
+            if (mCarService == null) {
+                if (DBG) {
+                    Slog.d(TAG, "CarService null. sendUserLifecycleEvent() deferred for lifecycle"
+                            + " event " + eventType + " for user " + to);
+                }
+                return;
+            }
+        }
+        sendUserLifecycleEvent(eventType, fromId, toId);
+    }
+
+    private void sendUserLifecycleEvent(@UserLifecycleEventType int eventType,
+            @UserIdInt int fromId, @UserIdInt int toId) {
+        if (DBG) {
+            Slog.d(TAG, "sendUserLifecycleEvent():" + " eventType=" + eventType + ", fromId="
+                    + fromId + ", toId=" + toId);
+        }
+        try {
+            synchronized (mLock) {
+                if (isServiceCrashedLoggedLocked("sendUserLifecycleEvent")) return;
+                mCarService.onUserLifecycleEvent(eventType, fromId, toId);
+            }
+        } catch (RemoteException e) {
+            Slog.w(TAG, "RemoteException from car service", e);
+            handleCarServiceCrash();
+        }
+    }
+
+    private void handleCarServiceCrash() {
+        synchronized (mLock) {
+            mCarServiceCrashed = true;
+            mCarService = null;
+        }
+        Slog.w(TAG, "CarServiceCrashed. No more car service calls before reconnection.");
+        mCarServiceHelperService.handleCarServiceCrash();
+    }
+
+    private TimingsTraceAndSlog newTimingsTraceAndSlog() {
+        return new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_SYSTEM_SERVER);
+    }
+
+    @GuardedBy("mLock")
+    private boolean isServiceCrashedLoggedLocked(@PendingOperationId int operationId) {
+        return isServiceCrashedLoggedLocked(pendingOperationToString(operationId));
+    }
+
+    @GuardedBy("mLock")
+    private boolean isServiceCrashedLoggedLocked(@NonNull String operation) {
+        if (mCarServiceCrashed) {
+            Slog.w(TAG, "CarServiceCrashed. " + operation + " will be executed after reconnection");
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Dump
+     */
+    void dump(IndentingPrintWriter writer) {
+        writer.println("CarServiceProxy");
+        writer.increaseIndent();
+        writer.printf("mLastSwitchedUser=%s\n", mLastSwitchedUser);
+        writer.printf("mLastUserLifecycle:\n");
+        int user0Lifecycle = mLastUserLifecycle.get(UserHandle.USER_SYSTEM, 0);
+        if (user0Lifecycle != 0) {
+            writer.printf("SystemUser Lifecycle Event:%s\n", user0Lifecycle);
+        } else {
+            writer.println("SystemUser not initialized");
+        }
+
+        int lastUserLifecycle = mLastUserLifecycle.get(mLastSwitchedUser, 0);
+        if (mLastSwitchedUser != UserHandle.USER_SYSTEM && user0Lifecycle != 0) {
+            writer.printf("last user (%s) Lifecycle Event:%s\n",
+                    mLastSwitchedUser, lastUserLifecycle);
+        }
+
+        int size = mPendingOperations.size();
+        if (size == 0) {
+            writer.println("No pending operations");
+        } else {
+            writer.printf("%d pending operation%s:\n", size, size == 1 ? "" : "s");
+            writer.increaseIndent();
+            for (int i = 0; i < size; i++) {
+                writer.println(mPendingOperations.valueAt(i));
+            }
+            writer.decreaseIndent();
+        }
+        writer.decreaseIndent();
+        dumpUserMetrics(writer);
+    }
+
+    /**
+     * Dump User metrics
+     */
+    void dumpUserMetrics(IndentingPrintWriter writer) {
+        mUserMetrics.dump(writer);
+    }
+
+    private final class PendingOperation {
+        public final int id;
+        public @Nullable Object value;
+
+        PendingOperation(int id, @Nullable Object value) {
+            this.id = id;
+            this.value = value;
+        }
+
+        @Override
+        public String toString() {
+            return "PendingOperation[" + pendingOperationToString(id)
+                + (value == null ? "" : ": " + value) + "]";
+        }
+    }
+
+    @NonNull
+    private String pendingOperationToString(@PendingOperationId int operationType) {
+        return DebugUtils.constantToString(CarServiceProxy.class, "PO_" , operationType);
+    }
+}
diff --git a/src/com/android/internal/car/ExternalConstants.java b/src/com/android/internal/car/ExternalConstants.java
deleted file mode 100644
index d9d6345..0000000
--- a/src/com/android/internal/car/ExternalConstants.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2020 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;
-
-/**
- * Provides constants that are defined somewhere else and must be cloned here
- */
-final class ExternalConstants {
-
-    private ExternalConstants() {
-        throw new UnsupportedOperationException("contains only static constants");
-    }
-
-    // TODO(b/149797595): remove once ICar.aidl is split in 2
-    static final class ICarConstants {
-        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
-        static final int ICAR_CALL_SET_CAR_SERVICE_HELPER = 0;
-        static final int ICAR_CALL_ON_USER_LIFECYCLE = 1;
-        static final int ICAR_CALL_FIRST_USER_UNLOCKED = 2;
-        static final int ICAR_CALL_GET_INITIAL_USER_INFO = 3;
-        static final int ICAR_CALL_SET_INITIAL_USER = 4;
-
-        private ICarConstants() {
-            throw new UnsupportedOperationException("contains only static constants");
-        }
-    }
-
-   /**
-     * Constants used by {@link android.user.user.CarUserManager} - they cannot be defined on
-     * {@link android.car.userlib.CommonConstants} to avoid an extra dependency in the
-     * {@code android.car} project
-     */
-    static final class CarUserManagerConstants {
-
-        static final int USER_LIFECYCLE_EVENT_TYPE_STARTING = 1;
-        static final int USER_LIFECYCLE_EVENT_TYPE_SWITCHING = 2;
-        static final int USER_LIFECYCLE_EVENT_TYPE_UNLOCKING = 3;
-        static final int USER_LIFECYCLE_EVENT_TYPE_UNLOCKED = 4;
-        static final int USER_LIFECYCLE_EVENT_TYPE_STOPPING = 5;
-        static final int USER_LIFECYCLE_EVENT_TYPE_STOPPED = 6;
-
-        private CarUserManagerConstants() {
-            throw new UnsupportedOperationException("contains only static constants");
-        }
-    }
-}
diff --git a/src/com/android/internal/car/ICarServiceHelper.aidl b/src/com/android/internal/car/ICarServiceHelper.aidl
deleted file mode 100644
index 8145229..0000000
--- a/src/com/android/internal/car/ICarServiceHelper.aidl
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2017 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 android.content.ComponentName;
-
-import java.util.List;
-
-/**
- * Helper API for car service. Only for interaction between system server and car service.
- * @hide
- */
-interface ICarServiceHelper {
-    int forceSuspend(int timeoutMs);
-    /**
-    * Check
-    * {@link com.android.server.wm.CarLaunchParamsModifier#setDisplayWhitelistForUser(int, int[]).
-    */
-    void setDisplayWhitelistForUser(in int userId, in int[] displayIds);
-
-    /**
-     * Check
-     * {@link com.android.server.wm.CarLaunchParamsModifier#setPassengerDisplays(int[])}.
-     */
-    void setPassengerDisplays(in int[] displayIds);
-
-    /**
-     * Check
-     * {@link com.android.server.wm.CarLaunchParamsModifier#setSourcePreferredComponents(
-     *         boolean, List<ComponentName>)}.
-     */
-    void setSourcePreferredComponents(
-            in boolean enableSourcePreferred, in List<ComponentName> sourcePreferredComponents);
-}
diff --git a/src/com/android/internal/car/UserMetrics.java b/src/com/android/internal/car/UserMetrics.java
new file mode 100644
index 0000000..ff5fa75
--- /dev/null
+++ b/src/com/android/internal/car/UserMetrics.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2020 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 com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STARTING;
+import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPED;
+import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPING;
+import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
+import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED;
+import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.util.IndentingPrintWriter;
+import android.util.LocalLog;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+
+import com.android.car.internal.common.CommonConstants.UserLifecycleEventType;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Metrics for user switches.
+ *
+ * <p>It stores 2 types of metrics:
+ *
+ * <ol>
+ *   <li>Time to start a user (from start to unlock)
+ *   <li>Time to stop a user (from stop to shutdown)
+ * </ol>
+ *
+ * <p>It keeps track of the users being started and stopped, then logs the last
+ * {{@link #INITIAL_CAPACITY}} occurrences of each when the operation finished (so it can be dumped
+ * later).
+ */
+final class UserMetrics {
+
+    private static final String TAG = UserMetrics.class.getSimpleName();
+
+    /**
+     * Initial capacity for the current operations.
+     */
+    // Typically there are at most 2 users (system and 1st full), although it could be higher on
+    // garage mode
+    private static final int INITIAL_CAPACITY = 2;
+
+    // TODO(b/150413515): read from resources
+    private static final int LOG_SIZE = 10;
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private SparseArray<UserStartingMetric> mUserStartingMetrics;
+    @GuardedBy("mLock")
+    private SparseArray<UserStoppingMetric> mUserStoppingMetrics;
+
+    @GuardedBy("mLock")
+    private final LocalLog mUserStartedLogs = new LocalLog(LOG_SIZE);
+    @GuardedBy("mLock")
+    private final LocalLog mUserStoppedLogs = new LocalLog(LOG_SIZE);
+
+    /**
+     * Logs a user lifecycle event.
+     */
+    public void onEvent(@UserLifecycleEventType int eventType, long timestampMs,
+            @UserIdInt int fromUserId, @UserIdInt int toUserId) {
+        synchronized (mLock) {
+            switch(eventType) {
+                case USER_LIFECYCLE_EVENT_TYPE_STARTING:
+                    onUserStartingEventLocked(timestampMs, toUserId);
+                    return;
+                case USER_LIFECYCLE_EVENT_TYPE_SWITCHING:
+                    onUserSwitchingEventLocked(timestampMs, fromUserId, toUserId);
+                    return;
+                case USER_LIFECYCLE_EVENT_TYPE_UNLOCKING:
+                    onUserUnlockingEventLocked(timestampMs, toUserId);
+                    return;
+                case USER_LIFECYCLE_EVENT_TYPE_UNLOCKED:
+                    onUserUnlockedEventLocked(timestampMs, toUserId);
+                    return;
+                case USER_LIFECYCLE_EVENT_TYPE_STOPPING:
+                    onUserStoppingEventLocked(timestampMs, toUserId);
+                    return;
+                case USER_LIFECYCLE_EVENT_TYPE_STOPPED:
+                    onUserStoppedEventLocked(timestampMs, toUserId);
+                    return;
+                default:
+                    Slog.w(TAG, "Invalid event: " + eventType);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    SparseArray<UserStartingMetric> getUserStartMetrics() {
+        synchronized (mLock) {
+            return mUserStartingMetrics;
+        }
+    }
+
+    @VisibleForTesting
+    SparseArray<UserStoppingMetric> getUserStopMetrics() {
+        synchronized (mLock) {
+            return mUserStoppingMetrics;
+        }
+    }
+
+    private void onUserStartingEventLocked(long timestampMs, @UserIdInt int userId) {
+        if (mUserStartingMetrics == null) {
+            mUserStartingMetrics = new SparseArray<>(INITIAL_CAPACITY);
+        }
+
+        UserStartingMetric existingMetrics = mUserStartingMetrics.get(userId);
+        if (existingMetrics != null) {
+            Slog.w(TAG, "user re-started: " + existingMetrics);
+            finishUserStartingLocked(existingMetrics, /* removeMetric= */ false);
+        }
+
+        mUserStartingMetrics.put(userId, new UserStartingMetric(userId, timestampMs));
+    }
+
+    private void onUserSwitchingEventLocked(long timestampMs, @UserIdInt int fromUserId,
+            @UserIdInt int toUserId) {
+        UserStartingMetric metrics = getExistingMetricsLocked(mUserStartingMetrics, toUserId);
+        if (metrics == null) return;
+
+        metrics.switchFromUserId = fromUserId;
+        metrics.switchTime = timestampMs;
+    }
+
+    private void onUserUnlockingEventLocked(long timestampMs, @UserIdInt int userId) {
+        UserStartingMetric metrics = getExistingMetricsLocked(mUserStartingMetrics, userId);
+        if (metrics == null) return;
+
+        metrics.unlockingTime = timestampMs;
+    }
+
+    private void onUserUnlockedEventLocked(long timestampMs, @UserIdInt int userId) {
+        UserStartingMetric metrics = getExistingMetricsLocked(mUserStartingMetrics, userId);
+        if (metrics == null) return;
+
+        metrics.unlockedTime = timestampMs;
+
+        finishUserStartingLocked(metrics, /* removeMetric= */ true);
+    }
+
+    private void onUserStoppingEventLocked(long timestampMs, @UserIdInt int userId) {
+        if (mUserStoppingMetrics == null) {
+            mUserStoppingMetrics = new SparseArray<>(INITIAL_CAPACITY);
+        }
+        UserStoppingMetric existingMetrics = mUserStoppingMetrics.get(userId);
+        if (existingMetrics != null) {
+            Slog.w(TAG, "user re-stopped: " + existingMetrics);
+            finishUserStoppingLocked(existingMetrics, /* removeMetric= */ false);
+        }
+        mUserStoppingMetrics.put(userId, new UserStoppingMetric(userId, timestampMs));
+    }
+
+    private void onUserStoppedEventLocked(long timestampMs, @UserIdInt int userId) {
+        UserStoppingMetric metrics = getExistingMetricsLocked(mUserStoppingMetrics, userId);
+        if (metrics == null) return;
+
+        metrics.shutdownTime = timestampMs;
+        finishUserStoppingLocked(metrics, /* removeMetric= */ true);
+    }
+
+    @Nullable
+    private <T extends BaseUserMetric> T getExistingMetricsLocked(
+            @NonNull SparseArray<? extends BaseUserMetric> metrics, @UserIdInt int userId) {
+        if (metrics == null) {
+            Slog.w(TAG, "getExistingMetricsLocked() should not pass null metrics, except on tests");
+            return null;
+        }
+        @SuppressWarnings("unchecked")
+        T metric = (T) metrics.get(userId);
+        if (metric == null) {
+            String name = metrics == mUserStartingMetrics ? "starting" : "stopping";
+            Slog.w(TAG, "no " + name + " metrics for user " + userId);
+        }
+        return metric;
+    }
+
+    private void removeExistingMetricsLogged(@NonNull SparseArray<? extends BaseUserMetric> metrics,
+            @UserIdInt int userId) {
+        metrics.remove(userId);
+        if (metrics.size() != 0) return;
+
+        if (metrics == mUserStartingMetrics) {
+            mUserStartingMetrics = null;
+        } else {
+            mUserStoppingMetrics = null;
+        }
+    }
+
+    private void finishUserStartingLocked(@NonNull UserStartingMetric metrics,
+            boolean removeMetric) {
+        mUserStartedLogs.log(metrics.toString());
+        if (removeMetric) {
+            removeExistingMetricsLogged(mUserStartingMetrics, metrics.userId);
+        }
+    }
+
+    private void finishUserStoppingLocked(@NonNull UserStoppingMetric metrics,
+            boolean removeMetric) {
+        mUserStoppedLogs.log(metrics.toString());
+        if (removeMetric) {
+            removeExistingMetricsLogged(mUserStoppingMetrics, metrics.userId);
+        }
+    }
+
+    /**
+     * Dumps its contents.
+     */
+    public void dump(@NonNull IndentingPrintWriter pw) {
+        pw.println("* User Metrics *");
+        synchronized (mLock) {
+
+            dump(pw, "starting", mUserStartingMetrics);
+            dump(pw, "stopping", mUserStoppingMetrics);
+
+            pw.printf("Last %d started users\n", LOG_SIZE);
+            mUserStartedLogs.dump("  ", pw);
+
+            pw.printf("Last %d stopped users\n", LOG_SIZE);
+            mUserStoppedLogs.dump("  ", pw);
+
+            pw.println();
+        }
+    }
+
+    private void dump(@NonNull IndentingPrintWriter pw, @NonNull String message,
+            @NonNull SparseArray<? extends BaseUserMetric> metrics) {
+        pw.increaseIndent();
+        try {
+            if (metrics == null) {
+                pw.printf("no users %s\n", message);
+                return;
+            }
+            int size = metrics.size();
+            pw.printf("%d users %s\n", size, message);
+            for (int i = 0; i < size; i++) {
+                BaseUserMetric metric = metrics.valueAt(i);
+                pw.printf("%d: ", i);
+                metric.dump(pw);
+                pw.println();
+            }
+        } finally {
+            pw.decreaseIndent();
+        }
+    }
+
+    private abstract class BaseUserMetric {
+        public final @UserIdInt int userId;
+
+        protected BaseUserMetric(@UserIdInt int userId) {
+            this.userId = userId;
+        }
+
+        @Override
+        public String toString() {
+            StringWriter sw = new StringWriter();
+            PrintWriter pw = new PrintWriter(sw);
+            try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ")) {
+                dump(ipw);
+            }
+            pw.flush();
+            return sw.toString();
+        }
+
+        abstract void dump(@NonNull IndentingPrintWriter pw);
+    }
+
+    @VisibleForTesting
+    final class UserStartingMetric extends BaseUserMetric {
+        public final long startTime;
+        public long switchTime;
+        public long unlockingTime;
+        public long unlockedTime;
+        public @UserIdInt int switchFromUserId;
+
+        UserStartingMetric(@UserIdInt int userId, long startTime) {
+            super(userId);
+            this.startTime = startTime;
+        }
+
+        @Override
+        public void dump(@NonNull IndentingPrintWriter pw) {
+            pw.printf("user=%d start=", userId);
+            TimeUtils.dumpTime(pw, startTime);
+
+            if (switchTime > 0) {
+                long delta = switchTime - startTime;
+                pw.print(" switch");
+                if (switchFromUserId != 0) {
+                    pw.printf("(from %d)", switchFromUserId);
+                }
+                pw.print('=');
+                TimeUtils.formatDuration(delta, pw);
+            }
+
+            if (unlockingTime > 0) {
+                long delta = unlockingTime - startTime;
+                pw.print(" unlocking=");
+                TimeUtils.formatDuration(delta, pw);
+            }
+            if (unlockedTime > 0) {
+                long delta = unlockedTime - startTime;
+                pw.print(" unlocked=");
+                TimeUtils.formatDuration(delta, pw);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    final class UserStoppingMetric extends BaseUserMetric {
+        public final long stopTime;
+        public long shutdownTime;
+
+        UserStoppingMetric(@UserIdInt int userId, long stopTime) {
+            super(userId);
+            this.stopTime = stopTime;
+        }
+
+        @Override
+        public void dump(@NonNull IndentingPrintWriter pw) {
+            pw.printf("user=%d stop=", userId);
+            TimeUtils.dumpTime(pw, stopTime);
+
+            if (shutdownTime > 0) {
+                long delta = shutdownTime - stopTime;
+                pw.print(" shutdown=");
+                TimeUtils.formatDuration(delta, pw);
+            }
+        }
+    }
+}
diff --git a/src/com/android/server/wm/CarLaunchParamsModifier.java b/src/com/android/server/wm/CarLaunchParamsModifier.java
index 78bba9b..0997ff2 100644
--- a/src/com/android/server/wm/CarLaunchParamsModifier.java
+++ b/src/com/android/server/wm/CarLaunchParamsModifier.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wm;
 
+import static com.android.server.wm.ActivityStarter.Request;
+
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityOptions;
 import android.app.ActivityTaskManager;
@@ -132,7 +135,7 @@
      */
     public void init() {
         mAtm = (ActivityTaskManagerService) ActivityTaskManager.getService();
-        LaunchParamsController controller = mAtm.mStackSupervisor.getLaunchParamsController();
+        LaunchParamsController controller = mAtm.mTaskSupervisor.getLaunchParamsController();
         controller.registerModifier(this);
         mDisplayManager = mContext.getSystemService(DisplayManager.class);
         mDisplayManager.registerDisplayListener(mDisplayListener,
@@ -168,7 +171,7 @@
         }
     }
 
-    private void removeUserFromWhitelistsLocked(int userId) {
+    private void removeUserFromAllowlistsLocked(int userId) {
         for (int i = mDisplayToProfileUserMapping.size() - 1; i >= 0; i--) {
             if (mDisplayToProfileUserMapping.valueAt(i) == userId) {
                 mDisplayToProfileUserMapping.removeAt(i);
@@ -182,27 +185,27 @@
         // Note that the current user is never stopped. It always takes switching into
         // non-current user before stopping the user.
         synchronized (mLock) {
-            removeUserFromWhitelistsLocked(stoppedUser);
+            removeUserFromAllowlistsLocked(stoppedUser);
         }
     }
 
     /**
-     * Sets display whiltelist for the userId. For passenger user, activity will be always launched
-     * to a display in the whitelist. If requested display is not in the whitelist, the 1st display
-     * in the whitelist will be selected as target display.
+     * Sets display allowlist for the userId. For passenger user, activity will be always launched
+     * to a display in the allowlist. If requested display is not in the allowlist, the 1st display
+     * in the allowlist will be selected as target display.
      *
-     * <p>The whitelist is kept only for profile user. Assigning the current user unassigns users
+     * <p>The allowlist is kept only for profile user. Assigning the current user unassigns users
      * for the given displays.
      */
-    public void setDisplayWhitelistForUser(int userId, int[] displayIds) {
+    public void setDisplayAllowListForUser(int userId, int[] displayIds) {
         if (DBG) {
-            Slog.d(TAG, "setDisplayWhitelistForUser userId:" + userId
+            Slog.d(TAG, "setDisplayAllowlistForUser userId:" + userId
                     + " displays:" + displayIds);
         }
         synchronized (mLock) {
             for (int displayId : displayIds) {
                 if (!mPassengerDisplays.contains(displayId)) {
-                    Slog.w(TAG, "setDisplayWhitelistForUser called with display:" + displayId
+                    Slog.w(TAG, "setDisplayAllowlistForUser called with display:" + displayId
                             + " not in passenger display list:" + mPassengerDisplays);
                     continue;
                 }
@@ -220,7 +223,7 @@
             if (displayIds.length > 0) {
                 mDefaultDisplayForProfileUser.put(userId, displayIds[0]);
             } else {
-                removeUserFromWhitelistsLocked(userId);
+                removeUserFromAllowlistsLocked(userId);
             }
         }
     }
@@ -265,8 +268,10 @@
      * allowed, change to the 1st allowed display.</p>
      */
     @Override
-    public int onCalculate(Task task, ActivityInfo.WindowLayout layout, ActivityRecord activity,
-            ActivityRecord source, ActivityOptions options, int phase,
+    @Result
+    public int onCalculate(@Nullable Task task, @Nullable ActivityInfo.WindowLayout layout,
+            @Nullable ActivityRecord activity, @Nullable ActivityRecord source,
+            ActivityOptions options, @Nullable Request request, int phase,
             LaunchParamsController.LaunchParams currentParams,
             LaunchParamsController.LaunchParams outParams) {
         int userId;
@@ -357,7 +362,7 @@
                 break decision;
             }
             targetDisplayArea = getAlternativeDisplayAreaForPassengerLocked(
-                    userId, targetDisplayArea);
+                    userId, activity, request);
         }
         if (targetDisplayArea != null && originalDisplayArea != targetDisplayArea) {
             Slog.i(TAG, "Changed launching display, user:" + userId
@@ -372,21 +377,10 @@
 
     @Nullable
     private TaskDisplayArea getAlternativeDisplayAreaForPassengerLocked(int userId,
-            TaskDisplayArea originalDisplayArea) {
-        int displayId = mDefaultDisplayForProfileUser.get(userId, Display.INVALID_DISPLAY);
-        if (displayId != Display.INVALID_DISPLAY) {
-            return getDefaultTaskDisplayAreaOnDisplay(displayId);
-        }
-        // return the 1st passenger display area if it exists
-        if (!mPassengerDisplays.isEmpty()) {
-            Slog.w(TAG, "No default display area for user:" + userId
-                    + " reassign to 1st passenger display area");
-            return getDefaultTaskDisplayAreaOnDisplay(mPassengerDisplays.get(0));
-        }
-        Slog.w(TAG, "No default display for user:" + userId
-                + " and no passenger display, keep the requested display area:"
-                + originalDisplayArea);
-        return originalDisplayArea;
+            @NonNull ActivityRecord activityRecord, @Nullable Request request) {
+        TaskDisplayArea sourceDisplayArea = sourceDisplayArea(userId, activityRecord, request);
+
+        return sourceDisplayArea != null ? sourceDisplayArea : fallbackDisplayArea(userId);
     }
 
     @VisibleForTesting
@@ -398,4 +392,99 @@
         }
         return dc.getDefaultTaskDisplayArea();
     }
+
+    /**
+     * Calculates the {@link TaskDisplayArea} for the source of the request. The source is
+     * calculated implicitly from the request or the activity record.
+     *
+     * @param userId ID of the current active user
+     * @param activityRecord {@link ActivityRecord} that is to be shown
+     * @param request {@link Request} data for showing the {@link ActivityRecord}
+     * @return {@link TaskDisplayArea} First non {@code null} candidate display area that is allowed
+     * for the user.  It is allowed if the display has been added to the profile mapping.
+     */
+    @Nullable
+    private TaskDisplayArea sourceDisplayArea(int userId, @NonNull ActivityRecord activityRecord,
+            @Nullable Request request) {
+        List<WindowProcessController> candidateControllers = candidateControllers(activityRecord,
+                request);
+
+        for (int i = 0; i < candidateControllers.size(); i++) {
+            WindowProcessController controller = candidateControllers.get(i);
+            TaskDisplayArea candidate = controller.getTopActivityDisplayArea();
+            int displayId = candidate != null ? candidate.getDisplayId() : Display.INVALID_DISPLAY;
+            int userForDisplay = mDisplayToProfileUserMapping.get(displayId, UserHandle.USER_NULL);
+            if (userForDisplay == userId) {
+                return candidate;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Calculates a list of {@link WindowProcessController} that can calculate the
+     * {@link TaskDisplayArea} to house the {@link ActivityRecord}. Controllers are calculated since
+     * calculating the display can be expensive. The list is ordered in the
+     * following way
+     * <ol>
+     *     <li>Controller for the activity record from the process name and app uid</li>
+     *     <li>Controller for the activity that is launching the given record</li>
+     *     <li>Controller for the actual process that is launching the record</li>
+     * </ol>
+     *
+     * @param activityRecord {@link ActivityRecord} that is to be shown
+     * @param request {@link Request} data for showing the {@link ActivityRecord}
+     * @return {@link List} of {@link WindowProcessController} ordered by preference to be shown
+     */
+    private List<WindowProcessController> candidateControllers(
+            @NonNull ActivityRecord activityRecord, @Nullable Request request) {
+        WindowProcessController firstController = mAtm.getProcessController(
+                activityRecord.getProcessName(), activityRecord.getUid());
+
+        WindowProcessController secondController = mAtm.getProcessController(
+                activityRecord.getLaunchedFromPid(), activityRecord.getLaunchedFromUid());
+
+        WindowProcessController thirdController = request == null ? null :
+                mAtm.getProcessController(request.realCallingPid, request.realCallingUid);
+
+        List<WindowProcessController> candidates = new ArrayList<>(3);
+
+        if (firstController != null) {
+            candidates.add(firstController);
+        }
+        if (secondController != null) {
+            candidates.add(secondController);
+        }
+        if (thirdController != null) {
+            candidates.add(thirdController);
+        }
+
+        return candidates;
+    }
+
+    /**
+     * Return a {@link TaskDisplayArea} that can be used if a source display area is not found.
+     * First check the default display for the user. If it is absent select the first passenger
+     * display if present.  If both are absent return {@code null}
+     *
+     * @param userId ID of the active user
+     * @return {@link TaskDisplayArea} that is recommended when a display area is not specified
+     */
+    @Nullable
+    private TaskDisplayArea fallbackDisplayArea(int userId) {
+        int displayIdForUserProfile = mDefaultDisplayForProfileUser.get(userId,
+                Display.INVALID_DISPLAY);
+        if (displayIdForUserProfile != Display.INVALID_DISPLAY) {
+            int displayId = mDefaultDisplayForProfileUser.get(userId);
+            return getDefaultTaskDisplayAreaOnDisplay(displayId);
+        }
+
+        if (!mPassengerDisplays.isEmpty()) {
+            int displayId = mPassengerDisplays.get(0);
+            return getDefaultTaskDisplayAreaOnDisplay(displayId);
+        }
+
+        return null;
+    }
+
 }
diff --git a/tests/Android.mk b/tests/Android.mk
index 79cddee..1cf4dcc 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -25,17 +25,18 @@
 LOCAL_JAVA_LIBRARIES += \
     android.test.runner \
     android.test.base \
-    android.hardware.automotive.vehicle-V2.0-java
+    android.hardware.automotive.vehicle-V2.0-java \
+    com.android.car.internal.common
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-    android.car.internal.event-log-tags \
     android.car.test.utils \
-    android.car.userlib \
     android.car.watchdoglib \
     androidx.test.ext.junit \
     androidx.test.rules \
+    com.android.car.internal.system \
     mockito-target-extended-minus-junit4 \
     services.core \
+    testng \
     truth-prebuilt
 
 # mockito-target-extended dependencies
diff --git a/tests/src/com/android/internal/car/CarDevicePolicySafetyCheckerTest.java b/tests/src/com/android/internal/car/CarDevicePolicySafetyCheckerTest.java
new file mode 100644
index 0000000..ef50f92
--- /dev/null
+++ b/tests/src/com/android/internal/car/CarDevicePolicySafetyCheckerTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2020 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 android.app.admin.DevicePolicyManager.operationToString;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.Mockito.verify;
+
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
+import android.app.admin.DevicePolicyManagerLiteInternal;
+import android.app.admin.DevicePolicySafetyChecker;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+@RunWith(Parameterized.class)
+public final class CarDevicePolicySafetyCheckerTest {
+
+    @Rule
+    public final MockitoRule rule = MockitoJUnit.rule();
+
+    @Mock
+    private DevicePolicySafetyChecker mCheckerImplementation;
+
+    @Mock
+    private DevicePolicyManagerLiteInternal mDpmi;
+
+    private CarDevicePolicySafetyChecker mChecker;
+
+    private final @DevicePolicyOperation int mOperation;
+    private final boolean mSafe;
+
+    @Before
+    public void setFixtures() {
+        mChecker = new CarDevicePolicySafetyChecker(mCheckerImplementation, mDpmi);
+    }
+
+    @Parameterized.Parameters
+    public static Collection<?> packageManagers() {
+        return Arrays.asList(new Object[][] {
+                // unsafe operations
+                { DevicePolicyManager.OPERATION_CLEAR_APPLICATION_USER_DATA, false },
+                { DevicePolicyManager.OPERATION_LOGOUT_USER, false },
+                { DevicePolicyManager.OPERATION_REBOOT, false },
+                { DevicePolicyManager.OPERATION_REQUEST_BUGREPORT, false },
+                { DevicePolicyManager.OPERATION_SET_APPLICATION_HIDDEN, false },
+                { DevicePolicyManager.OPERATION_SET_APPLICATION_RESTRICTIONS, false },
+                { DevicePolicyManager.OPERATION_SET_KEYGUARD_DISABLED, false },
+                { DevicePolicyManager.OPERATION_SET_PACKAGES_SUSPENDED, false },
+                { DevicePolicyManager.OPERATION_SET_STATUS_BAR_DISABLED, false },
+                { DevicePolicyManager.OPERATION_SET_SYSTEM_SETTING, false },
+                { DevicePolicyManager.OPERATION_SWITCH_USER, false },
+                { DevicePolicyManager.OPERATION_SET_LOCK_TASK_FEATURES, false },
+                { DevicePolicyManager.OPERATION_SET_LOCK_TASK_PACKAGES, false },
+
+                // safe operations
+                { DevicePolicyManager.OPERATION_WIPE_DATA, true }, // Safe because it'll be delayed
+                { DevicePolicyManager.OPERATION_CREATE_AND_MANAGE_USER, true },
+                { DevicePolicyManager.OPERATION_INSTALL_CA_CERT, true },
+                { DevicePolicyManager.OPERATION_INSTALL_KEY_PAIR, true },
+                { DevicePolicyManager.OPERATION_INSTALL_SYSTEM_UPDATE, true },
+                { DevicePolicyManager.OPERATION_LOCK_NOW, true },
+                { DevicePolicyManager.OPERATION_REMOVE_ACTIVE_ADMIN, true },
+                { DevicePolicyManager.OPERATION_REMOVE_KEY_PAIR, true },
+                { DevicePolicyManager.OPERATION_REMOVE_USER, true },
+                { DevicePolicyManager.OPERATION_SET_ALWAYS_ON_VPN_PACKAGE, true },
+                { DevicePolicyManager.OPERATION_SET_CAMERA_DISABLED, true },
+                { DevicePolicyManager.OPERATION_SET_FACTORY_RESET_PROTECTION_POLICY, true },
+                { DevicePolicyManager.OPERATION_SET_GLOBAL_PRIVATE_DNS, true },
+                { DevicePolicyManager.OPERATION_SET_KEEP_UNINSTALLED_PACKAGES, true },
+                { DevicePolicyManager.OPERATION_SET_LOGOUT_ENABLED, true },
+                { DevicePolicyManager.OPERATION_SET_MASTER_VOLUME_MUTED, true },
+                { DevicePolicyManager.OPERATION_SET_OVERRIDE_APNS_ENABLED, true },
+                { DevicePolicyManager.OPERATION_SET_PERMISSION_GRANT_STATE, true },
+                { DevicePolicyManager.OPERATION_SET_PERMISSION_POLICY, true },
+                { DevicePolicyManager.OPERATION_SET_RESTRICTIONS_PROVIDER, true },
+                { DevicePolicyManager.OPERATION_SET_SYSTEM_UPDATE_POLICY, true },
+                { DevicePolicyManager.OPERATION_SET_TRUST_AGENT_CONFIGURATION, true },
+                { DevicePolicyManager.OPERATION_SET_USER_CONTROL_DISABLED_PACKAGES, true },
+                { DevicePolicyManager.OPERATION_SET_USER_RESTRICTION, true },
+                { DevicePolicyManager.OPERATION_START_USER_IN_BACKGROUND, true },
+                { DevicePolicyManager.OPERATION_STOP_USER, true },
+                { DevicePolicyManager.OPERATION_UNINSTALL_CA_CERT, true }
+        });
+    }
+
+    public CarDevicePolicySafetyCheckerTest(@DevicePolicyOperation int operation, boolean safe) {
+        mOperation = operation;
+        mSafe = safe;
+    }
+
+    @Test
+    public void testSafe() throws Exception {
+        boolean isSafe = true;
+        mChecker.setSafe(isSafe);
+
+        assertWithMessage("safety of %s when car is safe", operationToString(mOperation))
+                .that(mChecker.isDevicePolicyOperationSafe(mOperation)).isTrue();
+
+        verifySafetyNofiticationSend(isSafe);
+    }
+
+    @Test
+    public void testUnsafe() throws Exception {
+        boolean isSafe = false;
+        mChecker.setSafe(isSafe);
+
+        if (mSafe) {
+            assertWithMessage("safety of %s EVEN when car isn't safe",
+                    operationToString(mOperation))
+                            .that(mChecker.isDevicePolicyOperationSafe(mOperation)).isTrue();
+        } else {
+            assertWithMessage("safety of %s when car isn't safe",
+                    operationToString(mOperation))
+                            .that(mChecker.isDevicePolicyOperationSafe(mOperation)).isFalse();
+        }
+
+        verifySafetyNofiticationSend(isSafe);
+    }
+
+    private void verifySafetyNofiticationSend(boolean isSafe) {
+        verify(mDpmi).notifyUnsafeOperationStateChanged(mCheckerImplementation,
+                DevicePolicyManager.OPERATION_SAFETY_REASON_DRIVING_DISTRACTION, isSafe);
+    }
+}
diff --git a/tests/src/com/android/internal/car/CarHelperServiceTest.java b/tests/src/com/android/internal/car/CarHelperServiceTest.java
deleted file mode 100644
index e20009d..0000000
--- a/tests/src/com/android/internal/car/CarHelperServiceTest.java
+++ /dev/null
@@ -1,1232 +0,0 @@
-/*
- * 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 android.car.test.util.UserTestingHelper.getDefaultUserType;
-import static android.car.test.util.UserTestingHelper.newGuestUser;
-import static android.car.test.util.UserTestingHelper.newSecondaryUser;
-import static android.car.test.util.UserTestingHelper.UserInfoBuilder;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.junit.Assert.fail;
-import static org.mockito.AdditionalAnswers.answerVoid;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.ArgumentMatchers.notNull;
-import static org.mockito.ArgumentMatchers.nullable;
-
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-
-import android.annotation.NonNull;
-import android.annotation.UserIdInt;
-import android.app.ActivityManager;
-import android.car.test.mocks.AbstractExtendedMockitoTestCase;
-import android.car.test.mocks.SyncAnswer;
-import android.car.userlib.CarUserManagerHelper;
-import android.car.userlib.HalCallback;
-import android.car.userlib.CommonConstants.CarUserServiceConstants;
-import android.car.userlib.InitialUserSetter;
-import android.car.userlib.UserHalHelper;
-import android.car.watchdoglib.CarWatchdogDaemonHelper;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.hardware.automotive.vehicle.V2_0.UserFlags;
-import android.hardware.automotive.vehicle.V2_0.InitialUserInfoRequestType;
-import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Parcel;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Log;
-import android.sysprop.CarProperties;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.car.ExternalConstants.CarUserManagerConstants;
-import com.android.internal.car.ExternalConstants.ICarConstants;
-import com.android.internal.os.IResultReceiver;
-import com.android.server.SystemService;
-import com.android.server.SystemService.TargetUser;
-import com.android.server.wm.CarLaunchParamsModifier;
-import com.android.server.utils.TimingsTraceAndSlog;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatcher;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.quality.Strictness;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Optional;
-
-/**
- * This class contains unit tests for the {@link CarServiceHelperService}.
- */
-@RunWith(AndroidJUnit4.class)
-public class CarHelperServiceTest extends AbstractExtendedMockitoTestCase {
-
-    private static final String TAG = CarHelperServiceTest.class.getSimpleName();
-
-    private static final int PRE_CREATED_USER_ID = 24;
-    private static final int PRE_CREATED_GUEST_ID = 25;
-    private static final int USER_MANAGER_TIMEOUT_MS = 100;
-
-    private static final String HAL_USER_NAME = "HAL 9000";
-    private static final int HAL_USER_ID = 42;
-    private static final int HAL_USER_FLAGS = 108;
-
-    private static final String USER_LOCALES = "LOL";
-
-    private static final int HAL_TIMEOUT_MS = 500;
-
-    private static final int ADDITIONAL_TIME_MS = 200;
-
-    private static final int HAL_NOT_REPLYING_TIMEOUT_MS = HAL_TIMEOUT_MS + ADDITIONAL_TIME_MS;
-
-    private static final long POST_HAL_NOT_REPLYING_TIMEOUT_MS = HAL_NOT_REPLYING_TIMEOUT_MS
-            + ADDITIONAL_TIME_MS;
-
-
-    // Spy used in tests that need to verify folloing method:
-    // managePreCreatedUsers, postAsyncPreCreatedUser, preCreateUsers
-    private CarServiceHelperService mHelper;
-
-    @Mock
-    private Context mMockContext;
-    @Mock
-    private PackageManager mPackageManager;
-    @Mock
-    private Context mApplicationContext;
-    @Mock
-    private CarUserManagerHelper mUserManagerHelper;
-    @Mock
-    private UserManager mUserManager;
-    @Mock
-    private CarLaunchParamsModifier mCarLaunchParamsModifier;
-    @Mock
-    private CarWatchdogDaemonHelper mCarWatchdogDaemonHelper;
-    @Mock
-    private IBinder mICarBinder;
-    @Mock
-    private InitialUserSetter mInitialUserSetter;
-
-    @Captor
-    private ArgumentCaptor<Parcel> mBinderCallData;
-
-    private Exception mBinderCallException;
-
-    /**
-     * Initialize objects and setup testing environment.
-     */
-    @Override
-    protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
-        session
-                .spyStatic(CarProperties.class)
-                .spyStatic(UserManager.class);
-    }
-
-    @Before
-    public void setUpMocks() {
-        mHelper = spy(new CarServiceHelperService(
-                mMockContext,
-                mUserManagerHelper,
-                mInitialUserSetter,
-                mUserManager,
-                mCarLaunchParamsModifier,
-                mCarWatchdogDaemonHelper,
-                /* halEnabled= */ true,
-                HAL_TIMEOUT_MS));
-
-        when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
-    }
-
-    @Test
-    public void testCarServiceLaunched() throws Exception {
-        mockRegisterReceiver();
-        mockBindService();
-        mockLoadLibrary();
-
-        mHelper.onStart();
-
-        verifyBindService();
-    }
-
-    @Test
-    public void testHandleCarServiceCrash() throws Exception {
-        mockHandleCarServiceCrash();
-        mockCarServiceException();
-
-        mHelper.handleCarServiceConnection(mICarBinder);
-
-        verify(mHelper).handleCarServiceCrash();
-    }
-
-    /**
-     * Test that the {@link CarServiceHelperService} starts up a secondary admin user upon first
-     * run.
-     */
-    @Test
-    public void testInitialInfo_noHal() throws Exception {
-        CarServiceHelperService halLessHelper = new CarServiceHelperService(
-                mMockContext,
-                mUserManagerHelper,
-                mInitialUserSetter,
-                mUserManager,
-                mCarLaunchParamsModifier,
-                mCarWatchdogDaemonHelper,
-                /* halEnabled= */ false,
-                HAL_TIMEOUT_MS);
-
-        halLessHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
-
-        verifyDefaultBootBehavior();
-    }
-
-    @Test
-    public void testInitialInfo_halReturnedDefault() throws Exception {
-        bindMockICar();
-
-        expectICarGetInitialUserInfo(InitialUserInfoAction.DEFAULT);
-
-        mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
-
-        assertNoICarCallExceptions();
-        verifyICarGetInitialUserInfoCalled();
-        assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
-
-        verifyDefaultBootBehavior();
-    }
-
-    @Test
-    public void testInitialInfo_halReturnedDefault_withLocale() throws Exception {
-        bindMockICar();
-
-        expectICarGetInitialUserInfo(InitialUserInfoAction.DEFAULT_WITH_LOCALE);
-
-        mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
-
-        assertNoICarCallExceptions();
-        verifyICarGetInitialUserInfoCalled();
-        assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
-
-        verifyDefaultBootBehaviorWithLocale();
-    }
-
-    @Test
-    public void testInitialInfo_halServiceNeverReturned() throws Exception {
-        bindMockICar();
-
-        expectICarGetInitialUserInfo(InitialUserInfoAction.DO_NOT_REPLY);
-
-        mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
-        sleep("before asserting DEFAULT behavior", POST_HAL_NOT_REPLYING_TIMEOUT_MS);
-
-        assertNoICarCallExceptions();
-        verifyICarGetInitialUserInfoCalled();
-        assertThat(mHelper.getHalResponseTime()).isLessThan(0);
-
-        verifyDefaultBootBehavior();
-    }
-
-    @Test
-    public void testInitialInfo_halServiceReturnedTooLate() throws Exception {
-        bindMockICar();
-
-        expectICarGetInitialUserInfo(InitialUserInfoAction.DELAYED_REPLY);
-
-        mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
-        sleep("before asserting DEFAULT behavior", POST_HAL_NOT_REPLYING_TIMEOUT_MS);
-
-        assertNoICarCallExceptions();
-        verifyICarGetInitialUserInfoCalled();
-        assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
-
-        sleep("to make sure not called again", POST_HAL_NOT_REPLYING_TIMEOUT_MS);
-
-        verifyDefaultBootBehavior();
-    }
-
-    @Test
-    public void testInitialInfo_halReturnedNonOkResultCode() throws Exception {
-        bindMockICar();
-
-        expectICarGetInitialUserInfo(InitialUserInfoAction.NON_OK_RESULT_CODE);
-
-        mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
-
-        assertNoICarCallExceptions();
-        verifyICarGetInitialUserInfoCalled();
-        assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
-
-        verifyDefaultBootBehavior();
-    }
-
-    @Test
-    public void testInitialInfo_halReturnedOkWithNoBundle() throws Exception {
-        bindMockICar();
-
-        expectICarGetInitialUserInfo(InitialUserInfoAction.NULL_BUNDLE);
-
-        mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
-
-        assertNoICarCallExceptions();
-        verifyICarGetInitialUserInfoCalled();
-        assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
-
-        verifyDefaultBootBehavior();
-    }
-
-    @Test
-    public void testInitialInfo_halReturnedSwitch_ok() throws Exception {
-        bindMockICar();
-
-        expectICarGetInitialUserInfo(InitialUserInfoAction.SWITCH_OK);
-        mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
-
-        assertNoICarCallExceptions();
-        verifyICarGetInitialUserInfoCalled();
-        assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
-
-        verifyUserSwitchedByHal();
-    }
-
-    @Test
-    public void testInitialInfo_halReturnedSwitch_ok_withLocale() throws Exception {
-        bindMockICar();
-
-        expectICarGetInitialUserInfo(InitialUserInfoAction.SWITCH_OK_WITH_LOCALE);
-        mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
-
-        assertNoICarCallExceptions();
-        verifyICarGetInitialUserInfoCalled();
-        assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
-
-        verifyUserSwitchedByHalWithLocale();
-    }
-
-    @Test
-    public void testInitialInfo_halReturnedSwitch_switchMissingUserId() throws Exception {
-        bindMockICar();
-
-        expectICarGetInitialUserInfo(InitialUserInfoAction.SWITCH_MISSING_USER_ID);
-
-        mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
-
-        assertNoICarCallExceptions();
-        verifyICarGetInitialUserInfoCalled();
-        assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
-
-        verifyUserNotSwitchedByHal();
-        verifyDefaultBootBehavior();
-    }
-
-    @Test
-    public void testInitialInfo_halReturnedCreateOk() throws Exception {
-        bindMockICar();
-
-        expectICarGetInitialUserInfo((r) -> sendCreateDefaultHalUserAction(r));
-
-        mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
-
-        assertNoICarCallExceptions();
-        verifyICarGetInitialUserInfoCalled();
-        assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
-
-        verifyUserCreatedByHal();
-    }
-
-    @Test
-    public void testInitialInfo_halReturnedCreateOk_withLocale() throws Exception {
-        bindMockICar();
-
-        expectICarGetInitialUserInfo(
-                (r) -> sendCreateAction(r, HAL_USER_NAME, HAL_USER_FLAGS, USER_LOCALES));
-
-        mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
-
-        assertNoICarCallExceptions();
-        verifyICarGetInitialUserInfoCalled();
-        assertThat(mHelper.getHalResponseTime()).isGreaterThan(0);
-
-        verifyUserCreatedByHalWithLocale();
-    }
-
-    @Test
-    public void testOnUserStarting_notifiesICar() throws Exception {
-        bindMockICar();
-
-        int userId = 10;
-        expectICarOnUserLifecycleEvent(CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_STARTING,
-                userId);
-
-        mHelper.onUserStarting(newTargetUser(userId));
-
-        assertNoICarCallExceptions();
-        verifyICarOnUserLifecycleEventCalled();
-    }
-
-    @Test
-    public void testOnUserStarting_preCreatedDoesntNotifyICar() throws Exception {
-        bindMockICar();
-
-        mHelper.onUserStarting(newTargetUser(10, /* preCreated= */ true));
-
-        verifyICarOnUserLifecycleEventNeverCalled();
-    }
-
-    @Test
-    public void testOnUserSwitching_notifiesICar() throws Exception {
-        bindMockICar();
-
-        int currentUserId = 10;
-        int targetUserId = 11;
-        expectICarOnUserLifecycleEvent(CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_SWITCHING,
-                currentUserId, targetUserId);
-
-        mHelper.onUserSwitching(newTargetUser(currentUserId),
-                newTargetUser(targetUserId));
-
-        assertNoICarCallExceptions();
-    }
-
-    @Test
-    public void testOnUserSwitching_preCreatedDoesntNotifyICar() throws Exception {
-        bindMockICar();
-
-        mHelper.onUserSwitching(newTargetUser(10), newTargetUser(11, /* preCreated= */ true));
-
-        verifyICarOnUserLifecycleEventNeverCalled();
-    }
-
-    @Test
-    public void testOnUserUnlocking_notifiesICar() throws Exception {
-        bindMockICar();
-
-        int userId = 10;
-        expectICarOnUserLifecycleEvent(CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING,
-                userId);
-
-        mHelper.onUserUnlocking(newTargetUser(userId));
-
-        assertNoICarCallExceptions();
-    }
-
-    @Test
-    public void testOnUserUnlocking_preCreatedDoesntNotifyICar() throws Exception {
-        bindMockICar();
-
-        mHelper.onUserUnlocking(newTargetUser(10, /* preCreated= */ true));
-
-        verifyICarOnUserLifecycleEventNeverCalled();
-    }
-
-    @Test
-    public void testOnUserUnlocked_notifiesICar_systemUserFirst() throws Exception {
-        bindMockICar();
-
-        int systemUserId = UserHandle.USER_SYSTEM;
-        expectICarOnUserLifecycleEvent(CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED,
-                systemUserId);
-
-        int firstUserId = 10;
-        expectICarFirstUserUnlocked(firstUserId);
-
-        setHalResponseTime();
-        mHelper.onUserUnlocked(newTargetUser(systemUserId));
-        mHelper.onUserUnlocked(newTargetUser(firstUserId));
-
-        assertNoICarCallExceptions();
-
-        verifyICarOnUserLifecycleEventCalled(); // system user
-        verifyICarFirstUserUnlockedCalled();    // first user
-    }
-
-    @Test
-    public void testOnUserUnlocked_notifiesICar_firstUserReportedJustOnce() throws Exception {
-        bindMockICar();
-
-        int firstUserId = 10;
-        expectICarFirstUserUnlocked(firstUserId);
-
-        int secondUserId = 11;
-        expectICarOnUserLifecycleEvent(CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED,
-                secondUserId);
-
-        setHalResponseTime();
-        mHelper.onUserUnlocked(newTargetUser(firstUserId));
-        mHelper.onUserUnlocked(newTargetUser(secondUserId));
-
-        assertNoICarCallExceptions();
-
-        verifyICarFirstUserUnlockedCalled();    // first user
-        verifyICarOnUserLifecycleEventCalled(); // second user
-    }
-
-    @Test
-    public void testOnUserStopping_notifiesICar() throws Exception {
-        bindMockICar();
-
-        int userId = 10;
-        expectICarOnUserLifecycleEvent(CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPING,
-                userId);
-
-        mHelper.onUserStopping(newTargetUser(userId));
-
-        assertNoICarCallExceptions();
-    }
-
-    @Test
-    public void testOnUserStopping_preCreatedDoesntNotifyICar() throws Exception {
-        bindMockICar();
-
-        mHelper.onUserStopping(newTargetUser(10, /* preCreated= */ true));
-
-        verifyICarOnUserLifecycleEventNeverCalled();
-    }
-
-    @Test
-    public void testOnUserStopped_notifiesICar() throws Exception {
-        bindMockICar();
-
-        int userId = 10;
-        expectICarOnUserLifecycleEvent(CarUserManagerConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPED,
-                userId);
-
-        mHelper.onUserStopped(newTargetUser(userId));
-
-        assertNoICarCallExceptions();
-    }
-
-    @Test
-    public void testOnUserStopped_preCreatedDoesntNotifyICar() throws Exception {
-        bindMockICar();
-
-        mHelper.onUserStopped(newTargetUser(10, /* preCreated= */ true));
-
-        verifyICarOnUserLifecycleEventNeverCalled();
-    }
-
-    @Test
-    public void testSendSetInitialUserInfoNotifiesICar() throws Exception {
-        bindMockICar();
-
-        UserInfo user = new UserInfo(42, "Dude", UserInfo.FLAG_ADMIN);
-        expectICarSetInitialUserInfo(user);
-
-        mHelper.setInitialUser(user);
-
-        verifyICarSetInitialUserCalled();
-        assertNoICarCallExceptions();
-    }
-
-    @Test
-    public void testInitialUserInfoRequestType_FirstBoot() throws Exception {
-        when(mUserManagerHelper.hasInitialUser()).thenReturn(false);
-        when(mPackageManager.isDeviceUpgrading()).thenReturn(true);
-        assertThat(mHelper.getInitialUserInfoRequestType())
-                .isEqualTo(InitialUserInfoRequestType.FIRST_BOOT);
-    }
-
-    @Test
-    public void testInitialUserInfoRequestType_FirstBootAfterOTA() throws Exception {
-        when(mUserManagerHelper.hasInitialUser()).thenReturn(true);
-        when(mPackageManager.isDeviceUpgrading()).thenReturn(true);
-        assertThat(mHelper.getInitialUserInfoRequestType())
-                .isEqualTo(InitialUserInfoRequestType.FIRST_BOOT_AFTER_OTA);
-    }
-
-    @Test
-    public void testInitialUserInfoRequestType_ColdBoot() throws Exception {
-        when(mUserManagerHelper.hasInitialUser()).thenReturn(true);
-        when(mPackageManager.isDeviceUpgrading()).thenReturn(false);
-        assertThat(mHelper.getInitialUserInfoRequestType())
-                .isEqualTo(InitialUserInfoRequestType.COLD_BOOT);
-    }
-
-    @Test
-    public void testPreCreatedUsersLessThanRequested() throws Exception {
-        // Set existing user
-        expectNoPreCreatedUser();
-        // Set number of requested user
-        setNumberRequestedUsersProperty(1);
-        setNumberRequestedGuestsProperty(0);
-        mockRunAsync();
-        SyncAnswer syncUserInfo = mockPreCreateUser(/* isGuest= */ false);
-
-        mHelper.managePreCreatedUsers();
-        syncUserInfo.await(USER_MANAGER_TIMEOUT_MS);
-
-        verifyUserCreated(/* isGuest= */ false);
-    }
-
-    @Test
-    public void testPreCreatedGuestsLessThanRequested() throws Exception {
-        // Set existing user
-        expectNoPreCreatedUser();
-        // Set number of requested user
-        setNumberRequestedUsersProperty(0);
-        setNumberRequestedGuestsProperty(1);
-        mockRunAsync();
-        SyncAnswer syncUserInfo = mockPreCreateUser(/* isGuest= */ true);
-
-        mHelper.managePreCreatedUsers();
-        syncUserInfo.await(USER_MANAGER_TIMEOUT_MS);
-
-        verifyUserCreated(/* isGuest= */ true);
-    }
-
-    @Test
-    public void testRemovePreCreatedUser() throws Exception {
-        UserInfo user = expectPreCreatedUser(/* isGuest= */ false,
-                /* isInitialized= */ true);
-        setNumberRequestedUsersProperty(0);
-        setNumberRequestedGuestsProperty(0);
-        mockRunAsync();
-
-        SyncAnswer syncRemoveStatus = mockRemoveUser(PRE_CREATED_USER_ID);
-
-        mHelper.managePreCreatedUsers();
-        syncRemoveStatus.await(USER_MANAGER_TIMEOUT_MS);
-
-        verifyUserRemoved(user);
-    }
-
-    @Test
-    public void testRemovePreCreatedGuest() throws Exception {
-        UserInfo user = expectPreCreatedUser(/* isGuest= */ true,
-                /* isInitialized= */ true);
-        setNumberRequestedUsersProperty(0);
-        setNumberRequestedGuestsProperty(0);
-        mockRunAsync();
-        SyncAnswer syncRemoveStatus = mockRemoveUser(PRE_CREATED_GUEST_ID);
-
-        mHelper.managePreCreatedUsers();
-        syncRemoveStatus.await(USER_MANAGER_TIMEOUT_MS);
-
-        verifyUserRemoved(user);
-    }
-
-    @Test
-    public void testRemoveInvalidPreCreatedUser() throws Exception {
-        UserInfo user = expectPreCreatedUser(/* isGuest= */ false,
-                /* isInitialized= */ false);
-        setNumberRequestedUsersProperty(0);
-        setNumberRequestedGuestsProperty(0);
-        mockRunAsync();
-        SyncAnswer syncRemoveStatus = mockRemoveUser(PRE_CREATED_USER_ID);
-
-        mHelper.managePreCreatedUsers();
-        syncRemoveStatus.await(ADDITIONAL_TIME_MS);
-
-        verifyUserRemoved(user);
-    }
-
-    @Test
-    public void testManagePreCreatedUsersDoNothing() throws Exception {
-        UserInfo user = expectPreCreatedUser(/* isGuest= */ false,
-                /* isInitialized= */ true);
-        setNumberRequestedUsersProperty(1);
-        setNumberRequestedGuestsProperty(0);
-        mockPreCreateUser(/* isGuest= */ false);
-        mockRemoveUser(PRE_CREATED_USER_ID);
-
-        mHelper.managePreCreatedUsers();
-
-        verifyPostPreCreatedUserSkipped();
-    }
-
-    @Test
-    public void testManagePreCreatedUsersOnBootCompleted() throws Exception {
-        mockRunAsync();
-
-        mHelper.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
-
-        verifyManagePreCreatedUsers();
-    }
-
-    @Test
-    public void testPreCreateUserExceptionLogged() throws Exception {
-        SyncAnswer syncException = mockPreCreateUserException();
-        TimingsTraceAndSlog trace = new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_SYSTEM_SERVER);
-        mHelper.preCreateUsers(trace, false);
-
-        verifyPostPreCreatedUserException();
-        assertThat(trace.getUnfinishedTracesForDebug()).isEmpty();
-    }
-
-    private void setHalResponseTime() {
-        mHelper.setInitialHalResponseTime();
-        SystemClock.sleep(1); // must sleep at least 1ms so it's not 0
-        mHelper.setFinalHalResponseTime();
-    }
-
-    /**
-     * Used in cases where the result of calling HAL for the initial info should be the same as
-     * not using HAL.
-     */
-    private void verifyDefaultBootBehavior() throws Exception {
-        verify(mInitialUserSetter).set(argThat((info) -> {
-            return info.type == InitialUserSetter.TYPE_DEFAULT_BEHAVIOR && info.userLocales == null;
-        }));
-    }
-
-    private void verifyDefaultBootBehaviorWithLocale() {
-        verify(mInitialUserSetter).set(argThat((info) -> {
-            return info.type == InitialUserSetter.TYPE_DEFAULT_BEHAVIOR
-                    && USER_LOCALES.equals(info.userLocales);
-        }));
-    }
-
-    private TargetUser newTargetUser(int userId) {
-        return newTargetUser(userId, /* preCreated= */ false);
-    }
-
-    private TargetUser newTargetUser(int userId, boolean preCreated) {
-        TargetUser targetUser = mock(TargetUser.class);
-        when(targetUser.getUserIdentifier()).thenReturn(userId);
-        UserInfo userInfo = new UserInfo();
-        userInfo.id = userId;
-        userInfo.preCreated = preCreated;
-        when(targetUser.getUserInfo()).thenReturn(userInfo);
-        return targetUser;
-    }
-
-    private void bindMockICar() throws Exception {
-        // Must set the binder expectation, otherwise checks for other transactions would fail
-        expectICarSetCarServiceHelper();
-        mHelper.handleCarServiceConnection(mICarBinder);
-    }
-
-    private void verifyUserCreatedByHal() throws Exception {
-        verify(mInitialUserSetter).set(argThat((info) -> {
-            return info.type == InitialUserSetter.TYPE_CREATE
-                    && info.newUserName == HAL_USER_NAME
-                    && info.newUserFlags == HAL_USER_FLAGS
-                    && info.userLocales == null;
-        }));
-    }
-
-    private void verifyUserCreatedByHalWithLocale() throws Exception {
-        verify(mInitialUserSetter).set(argThat((info) -> {
-            return info.type == InitialUserSetter.TYPE_CREATE
-                    && info.newUserName == HAL_USER_NAME
-                    && info.newUserFlags == HAL_USER_FLAGS
-                    && info.userLocales == USER_LOCALES;
-        }));
-    }
-
-    private void verifyUserSwitchedByHal() {
-        verify(mInitialUserSetter).set(argThat((info) -> {
-            return info.type == InitialUserSetter.TYPE_SWITCH
-                    && info.switchUserId == HAL_USER_ID
-                    && info.userLocales == null;
-        }));
-    }
-
-    private void verifyUserSwitchedByHalWithLocale() {
-        verify(mInitialUserSetter).set(argThat((info) -> {
-            return info.type == InitialUserSetter.TYPE_SWITCH
-                    && info.switchUserId == HAL_USER_ID
-                    && info.userLocales == USER_LOCALES;
-        }));
-    }
-
-    private void verifyUserNotSwitchedByHal() {
-        verify(mInitialUserSetter, never()).set(argThat((info) -> {
-            return info.type == InitialUserSetter.TYPE_SWITCH;
-        }));
-    }
-
-    private void verifyBindService () throws Exception {
-        verify(mMockContext).bindServiceAsUser(
-                argThat(intent -> intent.getAction().equals(ICarConstants.CAR_SERVICE_INTERFACE)),
-                any(), eq(Context.BIND_AUTO_CREATE), eq(UserHandle.SYSTEM));
-    }
-
-    private void verifyHandleCarServiceCrash() throws Exception {
-        verify(mHelper).handleCarServiceCrash();
-    }
-
-    private void mockRegisterReceiver() {
-        when(mMockContext.registerReceiverForAllUsers(any(), any(), any(), any()))
-                .thenReturn(new Intent());
-    }
-
-    private void mockBindService() {
-        when(mMockContext.bindServiceAsUser(any(), any(),
-                eq(Context.BIND_AUTO_CREATE), eq(UserHandle.SYSTEM)))
-                .thenReturn(true);
-    }
-
-    private void mockLoadLibrary() {
-        doNothing().when(mHelper).loadNativeLibrary();
-    }
-
-    private void mockCarServiceException() throws Exception {
-        when(mICarBinder.transact(anyInt(), notNull(), isNull(), eq(Binder.FLAG_ONEWAY)))
-                .thenThrow(new RemoteException("mock car service Crash"));
-    }
-
-    private void mockHandleCarServiceCrash() throws Exception {
-        doNothing().when(mHelper).handleCarServiceCrash();
-    }
-
-    // TODO: create a custom matcher / verifier for binder calls
-    private void expectICarOnUserLifecycleEvent(int eventType, int expectedUserId)
-            throws Exception {
-        expectICarOnUserLifecycleEvent(eventType, UserHandle.USER_NULL, expectedUserId);
-    }
-
-    private void expectICarSetCarServiceHelper() throws Exception {
-        int txn = IBinder.FIRST_CALL_TRANSACTION
-                + ICarConstants.ICAR_CALL_SET_CAR_SERVICE_HELPER;
-        when(mICarBinder.transact(eq(txn), notNull(), isNull(), eq(Binder.FLAG_ONEWAY)))
-                .thenReturn(true);
-    }
-
-    private void expectICarOnUserLifecycleEvent(int expectedEventType, int expectedFromUserId,
-            int expectedToUserId) throws Exception {
-        int txn = IBinder.FIRST_CALL_TRANSACTION + ICarConstants.ICAR_CALL_ON_USER_LIFECYCLE;
-        long before = System.currentTimeMillis();
-
-        when(mICarBinder.transact(eq(txn), notNull(), isNull(),
-                eq(Binder.FLAG_ONEWAY))).thenAnswer((invocation) -> {
-                    try {
-                        long after = System.currentTimeMillis();
-                        Log.d(TAG, "Answering txn " + txn);
-                        Parcel data = (Parcel) invocation.getArguments()[1];
-                        data.setDataPosition(0);
-                        data.enforceInterface(ICarConstants.CAR_SERVICE_INTERFACE);
-                        int actualEventType = data.readInt();
-                        long actualTimestamp = data.readLong();
-                        int actualFromUserId = data.readInt();
-                        int actualToUserId = data.readInt();
-                        Log.d(TAG, "Unmarshalled data: eventType=" + actualEventType
-                                + ", timestamp= " + actualTimestamp
-                                + ", fromUserId= " + actualFromUserId
-                                + ", toUserId= " + actualToUserId);
-                        List<String> errors = new ArrayList<>();
-                        assertParcelValueInRange(errors, "timestamp", before, actualTimestamp, after);
-                        assertParcelValue(errors, "eventType", expectedEventType, actualEventType);
-                        assertParcelValue(errors, "fromUserId", expectedFromUserId,
-                                actualFromUserId);
-                        assertParcelValue(errors, "toUserId", expectedToUserId, actualToUserId);
-                        assertNoParcelErrors(errors);
-                        return true;
-                    } catch (Exception e) {
-                        Log.e(TAG, "Exception answering binder call", e);
-                        mBinderCallException = e;
-                        return false;
-                    }
-                });
-    }
-
-    private void expectICarFirstUserUnlocked(int expectedUserId) throws Exception {
-        int txn = IBinder.FIRST_CALL_TRANSACTION + ICarConstants.ICAR_CALL_FIRST_USER_UNLOCKED;
-        long before = System.currentTimeMillis();
-        long minDuration = SystemClock.elapsedRealtime() - Process.getStartElapsedRealtime();
-
-        when(mICarBinder.transact(eq(txn), notNull(), isNull(),
-                eq(Binder.FLAG_ONEWAY))).thenAnswer((invocation) -> {
-                    try {
-                        long after = System.currentTimeMillis();
-                        Log.d(TAG, "Answering txn " + txn);
-                        Parcel data = (Parcel) invocation.getArguments()[1];
-                        data.setDataPosition(0);
-                        data.enforceInterface(ICarConstants.CAR_SERVICE_INTERFACE);
-                        int actualUserId = data.readInt();
-                        long actualTimestamp = data.readLong();
-                        long actualDuration = data.readLong();
-                        int actualHalResponseTime = data.readInt();
-                        Log.d(TAG, "Unmarshalled data: userId= " + actualUserId
-                                + ", timestamp= " + actualTimestamp
-                                + ", duration=" + actualDuration
-                                + ", halResponseTime=" + actualHalResponseTime);
-                        List<String> errors = new ArrayList<>();
-                        assertParcelValue(errors, "userId", expectedUserId, actualUserId);
-                        assertParcelValueInRange(errors, "timestamp", before, actualTimestamp,
-                                after);
-                        assertMinimumParcelValue(errors, "duration", minDuration, actualDuration);
-                        assertMinimumParcelValue(errors, "halResponseTime", 1, actualHalResponseTime);
-                        assertNoParcelErrors(errors);
-                        return true;
-                    } catch (Exception e) {
-                        Log.e(TAG, "Exception answering binder call", e);
-                        mBinderCallException = e;
-                        return false;
-                    }
-                });
-
-    }
-
-    enum InitialUserInfoAction {
-        DEFAULT,
-        DEFAULT_WITH_LOCALE,
-        DO_NOT_REPLY,
-        DELAYED_REPLY,
-        NON_OK_RESULT_CODE,
-        NULL_BUNDLE,
-        SWITCH_OK,
-        SWITCH_OK_WITH_LOCALE,
-        SWITCH_MISSING_USER_ID
-    }
-
-    private void expectICarGetInitialUserInfo(InitialUserInfoAction action) throws Exception {
-        expectICarGetInitialUserInfo((receiver) ->{
-            switch (action) {
-                case DEFAULT:
-                    sendDefaultAction(receiver);
-                    break;
-                case DEFAULT_WITH_LOCALE:
-                    sendDefaultAction(receiver, USER_LOCALES);
-                    break;
-                case DO_NOT_REPLY:
-                    Log.d(TAG, "NOT replying to bind call");
-                    break;
-                case DELAYED_REPLY:
-                    sleep("before sending result", HAL_NOT_REPLYING_TIMEOUT_MS);
-                    sendDefaultAction(receiver);
-                    break;
-                case NON_OK_RESULT_CODE:
-                    Log.d(TAG, "sending bad result code");
-                    receiver.send(-1, null);
-                    break;
-                case NULL_BUNDLE:
-                    Log.d(TAG, "sending OK without bundle");
-                    receiver.send(HalCallback.STATUS_OK, null);
-                    break;
-                case SWITCH_OK:
-                    sendValidSwitchAction(receiver, /* userLocales= */ null);
-                    break;
-                case SWITCH_OK_WITH_LOCALE:
-                    sendValidSwitchAction(receiver, USER_LOCALES);
-                    break;
-                case SWITCH_MISSING_USER_ID:
-                    Log.d(TAG, "sending Switch without user Id");
-                    sendSwitchAction(receiver, /* id= */ null, /* userLocales= */ null);
-                    break;
-               default:
-                    throw new IllegalArgumentException("invalid action: " + action);
-            }
-        });
-    }
-
-    private void expectICarGetInitialUserInfo(GetInitialUserInfoAction action) throws Exception {
-        int txn = IBinder.FIRST_CALL_TRANSACTION + ICarConstants.ICAR_CALL_GET_INITIAL_USER_INFO;
-        when(mICarBinder.transact(eq(txn), notNull(), isNull(),
-                eq(Binder.FLAG_ONEWAY))).thenAnswer((invocation) -> {
-                    try {
-                        Log.d(TAG, "Answering txn " + txn);
-                        Parcel data = (Parcel) invocation.getArguments()[1];
-                        data.setDataPosition(0);
-                        data.enforceInterface(ICarConstants.CAR_SERVICE_INTERFACE);
-                        int actualRequestType = data.readInt();
-                        int actualTimeoutMs = data.readInt();
-                        IResultReceiver receiver = IResultReceiver.Stub
-                                .asInterface(data.readStrongBinder());
-
-                        Log.d(TAG, "Unmarshalled data: requestType= " + actualRequestType
-                                + ", timeout=" + actualTimeoutMs
-                                + ", receiver =" + receiver);
-                        action.onReceiver(receiver);
-                        return true;
-                    } catch (Exception e) {
-                        Log.e(TAG, "Exception answering binder call", e);
-                        mBinderCallException = e;
-                        return false;
-                    }
-                });
-    }
-
-    private void expectICarSetInitialUserInfo(UserInfo user) throws RemoteException {
-        int txn = IBinder.FIRST_CALL_TRANSACTION + ICarConstants.ICAR_CALL_SET_INITIAL_USER;
-        when(mICarBinder.transact(eq(txn), notNull(), isNull(),
-                eq(Binder.FLAG_ONEWAY))).thenAnswer((invocation) -> {
-                    try {
-                        Log.d(TAG, "Answering txn " + txn);
-                        Parcel data = (Parcel) invocation.getArguments()[1];
-                        data.setDataPosition(0);
-                        data.enforceInterface(ICarConstants.CAR_SERVICE_INTERFACE);
-                        int actualUserId = data.readInt();
-                        Log.d(TAG, "Unmarshalled data: user= " + actualUserId);
-                        assertThat(actualUserId).isEqualTo(user.id);
-                        return true;
-                    } catch (Exception e) {
-                        Log.e(TAG, "Exception answering binder call", e);
-                        mBinderCallException = e;
-                        return false;
-                    }
-                });
-    }
-
-    private void expectNoPreCreatedUser() throws Exception {
-        when(mUserManager.getUsers(/* excludePartial= */ true,
-                /* excludeDying= */ true, /* excludePreCreated= */ false))
-                .thenReturn(new ArrayList<UserInfo> ());
-    }
-
-    private UserInfo expectPreCreatedUser(boolean isGuest, boolean isInitialized)
-            throws Exception {
-        int userId = isGuest ? PRE_CREATED_GUEST_ID : PRE_CREATED_USER_ID;
-        UserInfo user = new UserInfoBuilder(userId)
-                .setGuest(isGuest)
-                .setPreCreated(true)
-                .setInitialized(isInitialized)
-                .build();
-
-        when(mUserManager.getUsers(/* excludePartial= */ true,
-                /* excludeDying= */ true, /* excludePreCreated= */ false))
-                .thenReturn(Arrays.asList(user));
-        return user;
-    }
-
-    private interface GetInitialUserInfoAction {
-        void onReceiver(IResultReceiver receiver) throws Exception;
-    }
-
-    private void sendDefaultAction(IResultReceiver receiver) throws Exception {
-        sendDefaultAction(receiver, /* userLocales= */ null);
-    }
-
-    private void sendDefaultAction(IResultReceiver receiver, String userLocales) throws Exception {
-        Log.d(TAG, "Sending DEFAULT action to receiver " + receiver);
-        Bundle data = new Bundle();
-        data.putInt(CarUserServiceConstants.BUNDLE_INITIAL_INFO_ACTION,
-                InitialUserInfoResponseAction.DEFAULT);
-        if (userLocales != null) {
-            data.putString(CarUserServiceConstants.BUNDLE_USER_LOCALES, userLocales);
-        }
-        receiver.send(HalCallback.STATUS_OK, data);
-    }
-
-    private void sendValidSwitchAction(IResultReceiver receiver, String userLocales)
-            throws Exception {
-        Log.d(TAG, "Sending SWITCH (" + HAL_USER_ID + ") action to receiver " + receiver);
-        sendSwitchAction(receiver, HAL_USER_ID, userLocales);
-    }
-
-    private void sendSwitchAction(IResultReceiver receiver, Integer id, String userLocales)
-            throws Exception {
-        Bundle data = new Bundle();
-        data.putInt(CarUserServiceConstants.BUNDLE_INITIAL_INFO_ACTION,
-                InitialUserInfoResponseAction.SWITCH);
-        if (id != null) {
-            data.putInt(CarUserServiceConstants.BUNDLE_USER_ID, id);
-        }
-        if (userLocales != null) {
-            data.putString(CarUserServiceConstants.BUNDLE_USER_LOCALES, userLocales);
-        }
-        receiver.send(HalCallback.STATUS_OK, data);
-    }
-
-    private void sendCreateDefaultHalUserAction(IResultReceiver receiver) throws Exception {
-        sendCreateAction(receiver, HAL_USER_NAME, HAL_USER_FLAGS, /* userLocales= */ null);
-    }
-
-    private void sendCreateAction(IResultReceiver receiver, String name, Integer flags,
-            String userLocales) throws Exception {
-        Bundle data = new Bundle();
-        data.putInt(CarUserServiceConstants.BUNDLE_INITIAL_INFO_ACTION,
-                InitialUserInfoResponseAction.CREATE);
-        if (name != null) {
-            data.putString(CarUserServiceConstants.BUNDLE_USER_NAME, name);
-        }
-        if (flags != null) {
-            data.putInt(CarUserServiceConstants.BUNDLE_USER_FLAGS, flags);
-        }
-        if (userLocales != null) {
-            data.putString(CarUserServiceConstants.BUNDLE_USER_LOCALES, userLocales);
-        }
-        receiver.send(HalCallback.STATUS_OK, data);
-    }
-
-    private void sleep(String reason, long napTimeMs) {
-        Log.d(TAG, "Sleeping for " + napTimeMs + "ms: " + reason);
-        SystemClock.sleep(napTimeMs);
-        Log.d(TAG, "Woke up (from '"  + reason + "')");
-    }
-
-    private void verifyICarOnUserLifecycleEventCalled() throws Exception {
-        verifyICarTxnCalled(ICarConstants.ICAR_CALL_ON_USER_LIFECYCLE);
-    }
-
-    private void verifyICarOnUserLifecycleEventNeverCalled() throws Exception {
-        verifyICarTxnNeverCalled(ICarConstants.ICAR_CALL_ON_USER_LIFECYCLE);
-    }
-
-    private void verifyICarFirstUserUnlockedCalled() throws Exception {
-        verifyICarTxnCalled(ICarConstants.ICAR_CALL_FIRST_USER_UNLOCKED);
-    }
-
-    private void verifyICarGetInitialUserInfoCalled() throws Exception {
-        verifyICarTxnCalled(ICarConstants.ICAR_CALL_GET_INITIAL_USER_INFO);
-    }
-
-    private void verifyICarSetInitialUserCalled() throws Exception {
-        verifyICarTxnCalled(ICarConstants.ICAR_CALL_SET_INITIAL_USER);
-    }
-
-    private void verifyICarTxnCalled(int txnId) throws Exception {
-        int txn = IBinder.FIRST_CALL_TRANSACTION + txnId;
-        verify(mICarBinder).transact(eq(txn), notNull(), isNull(), eq(Binder.FLAG_ONEWAY));
-    }
-
-    private void verifyICarTxnNeverCalled(int txnId) throws Exception {
-        int txn = IBinder.FIRST_CALL_TRANSACTION + txnId;
-        verify(mICarBinder, never()).transact(eq(txn), notNull(), isNull(), eq(Binder.FLAG_ONEWAY));
-    }
-
-    private void setNumberRequestedUsersProperty(int numberUser) {
-        doReturn(Optional.of(numberUser)).when(() -> CarProperties.number_pre_created_users());
-    }
-
-    private void setNumberRequestedGuestsProperty(int numberGuest) {
-        doReturn(Optional.of(numberGuest)).when(() -> CarProperties.number_pre_created_guests());
-    }
-
-    private void mockRunAsync() {
-        doAnswer(answerVoid(Runnable::run)).when(mHelper).runAsync(any(Runnable.class));
-    }
-
-    private SyncAnswer mockPreCreateUser(boolean isGuest) {
-        UserInfo newUser = isGuest ? newGuestUser(PRE_CREATED_GUEST_ID) :
-                newSecondaryUser(PRE_CREATED_USER_ID);
-        SyncAnswer<UserInfo> syncUserInfo = SyncAnswer.forReturn(newUser);
-        when(mUserManager.preCreateUser(getDefaultUserType(isGuest)))
-                .thenAnswer(syncUserInfo);
-
-        return syncUserInfo;
-    }
-
-    private SyncAnswer mockRemoveUser(@UserIdInt int userId) {
-        SyncAnswer<Boolean> syncRemoveStatus = SyncAnswer.forReturn(true);
-        when(mUserManager.removeUser(userId)).thenAnswer(syncRemoveStatus);
-
-        return syncRemoveStatus;
-    }
-
-    private SyncAnswer mockPreCreateUserException() {
-        SyncAnswer<UserInfo> syncException = SyncAnswer.forException(new Exception());
-        when(mUserManager.preCreateUser(anyString()))
-                .thenAnswer(syncException);
-        return syncException;
-    }
-
-    private void verifyUserCreated(boolean isGuest) throws Exception {
-        String userType =
-                isGuest ? UserManager.USER_TYPE_FULL_GUEST : UserManager.USER_TYPE_FULL_SECONDARY;
-        verify(mUserManager).preCreateUser(eq(userType));
-    }
-
-    private void verifyUserRemoved(UserInfo user) throws Exception {
-        verify(mUserManager).removeUser(user.id);
-    }
-
-    private void verifyPostPreCreatedUserSkipped() throws Exception {
-        verify(mHelper, never()).runAsync(any());
-    }
-
-    private void verifyPostPreCreatedUserException() throws Exception {
-        verify(mHelper).logPrecreationFailure(anyString(), any());
-    }
-
-    private void verifyManagePreCreatedUsers() throws Exception {
-        verify(mHelper).managePreCreatedUsers();
-    }
-
-    private void assertParcelValue(List<String> errors, String field, int expected,
-            int actual) {
-        if (expected != actual) {
-            errors.add(String.format("%s mismatch: expected=%d, actual=%d",
-                    field, expected, actual));
-        }
-    }
-
-    private void assertParcelValueInRange(List<String> errors, String field, long before,
-            long actual, long after) {
-        if (actual < before || actual> after) {
-            errors.add(field + " (" + actual+ ") not in range [" + before + ", " + after + "]");
-        }
-    }
-
-    private void assertMinimumParcelValue(List<String> errors, String field, long min,
-            long actual) {
-        if (actual < min) {
-            errors.add("Minimum " + field + " should be " + min + " (was " + actual + ")");
-        }
-    }
-
-    private void assertNoParcelErrors(List<String> errors) {
-        int size = errors.size();
-        if (size == 0) return;
-
-        StringBuilder msg = new StringBuilder().append(size).append(" errors on parcel: ");
-        for (String error : errors) {
-            msg.append("\n\t").append(error);
-        }
-        msg.append('\n');
-        throw new IllegalArgumentException(msg.toString());
-    }
-
-    /**
-     * Asserts that no exception was thrown when answering to a mocked {@code ICar} binder call.
-     * <p>
-     * This method should be called before asserting the expected results of a test, so it makes
-     * clear why the test failed when the call was not made as expected.
-     */
-    private void assertNoICarCallExceptions() throws Exception {
-        if (mBinderCallException != null)
-            throw mBinderCallException;
-
-    }
-}
diff --git a/tests/src/com/android/internal/car/CarServiceHelperServiceTest.java b/tests/src/com/android/internal/car/CarServiceHelperServiceTest.java
new file mode 100644
index 0000000..a2da2a5
--- /dev/null
+++ b/tests/src/com/android/internal/car/CarServiceHelperServiceTest.java
@@ -0,0 +1,292 @@
+/*
+ * 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 com.android.car.internal.common.CommonConstants.CAR_SERVICE_INTERFACE;
+import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STARTING;
+import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPED;
+import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPING;
+import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
+import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.ArgumentMatchers.notNull;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+
+import android.annotation.UserIdInt;
+import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.car.watchdoglib.CarWatchdogDaemonHelper;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.server.SystemService;
+import com.android.server.SystemService.TargetUser;
+import com.android.server.wm.CarLaunchParamsModifier;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+/**
+ * This class contains unit tests for the {@link CarServiceHelperService}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CarServiceHelperServiceTest extends AbstractExtendedMockitoTestCase {
+
+    private static final String TAG = CarServiceHelperServiceTest.class.getSimpleName();
+
+    private CarServiceHelperService mHelperSpy;
+    private CarServiceHelperService mHelper;
+
+    @Mock
+    private Context mMockContext;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private CarLaunchParamsModifier mCarLaunchParamsModifier;
+    @Mock
+    private CarWatchdogDaemonHelper mCarWatchdogDaemonHelper;
+    @Mock
+    private IBinder mICarBinder;
+    @Mock
+    private CarServiceProxy mCarServiceProxy;
+
+    /**
+     * Initialize objects and setup testing environment.
+     */
+    @Override
+    protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
+        session.spyStatic(ServiceManager.class);
+    }
+
+    @Before
+    public void setTestFixtures() {
+        mHelper = new CarServiceHelperService(
+                mMockContext,
+                mCarLaunchParamsModifier,
+                mCarWatchdogDaemonHelper,
+                mCarServiceProxy);
+        mHelperSpy = spy(mHelper);
+        when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
+    }
+
+    @Test
+    public void testCarServiceLaunched() throws Exception {
+        mockRegisterReceiver();
+        mockBindService();
+        mockLoadLibrary();
+
+        mHelperSpy.onStart();
+
+        verifyBindService();
+    }
+
+    @Test
+    public void testHandleCarServiceCrash() throws Exception {
+        mockHandleCarServiceCrash();
+        mockCarServiceException();
+
+        mHelperSpy.handleCarServiceConnection(mICarBinder);
+
+        verify(mHelperSpy).handleCarServiceCrash();
+    }
+
+    @Test
+    public void testOnUserStarting_notifiesICar() throws Exception {
+        int userId = 10;
+
+        mHelper.onUserStarting(newTargetUser(userId));
+
+        verifyICarOnUserLifecycleEventCalled(USER_LIFECYCLE_EVENT_TYPE_STARTING,
+                UserHandle.USER_NULL, userId);
+    }
+
+    @Test
+    public void testOnUserStarting_preCreatedDoesntNotifyICar() throws Exception {
+        mHelper.onUserStarting(newTargetUser(10, /* preCreated= */ true));
+
+        verifyICarOnUserLifecycleEventNeverCalled();
+    }
+
+    @Test
+    public void testOnUserSwitching_notifiesICar() throws Exception {
+        int currentUserId = 10;
+        int targetUserId = 11;
+
+        mHelper.onUserSwitching(newTargetUser(currentUserId),
+                newTargetUser(targetUserId));
+
+        verifyICarOnUserLifecycleEventCalled(USER_LIFECYCLE_EVENT_TYPE_SWITCHING,
+                currentUserId, targetUserId);
+    }
+
+    @Test
+    public void testOnUserSwitching_preCreatedDoesntNotifyICar() throws Exception {
+        mHelper.onUserSwitching(newTargetUser(10), newTargetUser(11, /* preCreated= */ true));
+
+        verifyICarOnUserLifecycleEventNeverCalled();
+    }
+
+    @Test
+    public void testOnUserUnlocking_notifiesICar() throws Exception {
+        int userId = 10;
+
+        mHelper.onUserUnlocking(newTargetUser(userId));
+
+        verifyICarOnUserLifecycleEventCalled(USER_LIFECYCLE_EVENT_TYPE_UNLOCKING,
+                UserHandle.USER_NULL, userId);
+    }
+
+    @Test
+    public void testOnUserUnlocking_preCreatedDoesntNotifyICar() throws Exception {
+        mHelper.onUserUnlocking(newTargetUser(10, /* preCreated= */ true));
+
+        verifyICarOnUserLifecycleEventNeverCalled();
+    }
+
+    @Test
+    public void testOnUserStopping_notifiesICar() throws Exception {
+        int userId = 10;
+
+        mHelper.onUserStopping(newTargetUser(userId));
+
+        verifyICarOnUserLifecycleEventCalled(USER_LIFECYCLE_EVENT_TYPE_STOPPING,
+                UserHandle.USER_NULL, userId);
+    }
+
+    @Test
+    public void testOnUserStopping_preCreatedDoesntNotifyICar() throws Exception {
+        mHelper.onUserStopping(newTargetUser(10, /* preCreated= */ true));
+
+        verifyICarOnUserLifecycleEventNeverCalled();
+    }
+
+    @Test
+    public void testOnUserStopped_notifiesICar() throws Exception {
+        int userId = 10;
+
+        mHelper.onUserStopped(newTargetUser(userId));
+
+        verifyICarOnUserLifecycleEventCalled(USER_LIFECYCLE_EVENT_TYPE_STOPPED,
+                UserHandle.USER_NULL, userId);
+    }
+
+    @Test
+    public void testOnUserStopped_preCreatedDoesntNotifyICar() throws Exception {
+        mHelper.onUserStopped(newTargetUser(10, /* preCreated= */ true));
+
+        verifyICarOnUserLifecycleEventNeverCalled();
+    }
+
+    @Test
+    public void testOnBootPhase_thirdPartyCanStart_initBootUser() throws Exception {
+        mHelper.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
+
+        verifyInitBootUser();
+    }
+
+    private TargetUser newTargetUser(int userId) {
+        return newTargetUser(userId, /* preCreated= */ false);
+    }
+
+    private TargetUser newTargetUser(int userId, boolean preCreated) {
+        TargetUser targetUser = mock(TargetUser.class);
+        when(targetUser.getUserIdentifier()).thenReturn(userId);
+        when(targetUser.isPreCreated()).thenReturn(preCreated);
+        return targetUser;
+    }
+
+    private void verifyBindService() throws Exception {
+        verify(mMockContext).bindServiceAsUser(
+                argThat(intent -> intent.getAction().equals(CAR_SERVICE_INTERFACE)),
+                any(), eq(Context.BIND_AUTO_CREATE), any(), eq(UserHandle.SYSTEM));
+    }
+
+    private void mockRegisterReceiver() {
+        when(mMockContext.registerReceiverForAllUsers(any(), any(), any(), any()))
+                .thenReturn(new Intent());
+    }
+
+    private void mockBindService() {
+        when(mMockContext.bindServiceAsUser(any(), any(),
+                eq(Context.BIND_AUTO_CREATE), any(), eq(UserHandle.SYSTEM)))
+                .thenReturn(true);
+    }
+
+    private void mockLoadLibrary() {
+        doNothing().when(mHelperSpy).loadNativeLibrary();
+    }
+
+    private void mockCarServiceException() throws Exception {
+        when(mICarBinder.transact(anyInt(), notNull(), isNull(), eq(Binder.FLAG_ONEWAY)))
+                .thenThrow(new RemoteException("mock car service Crash"));
+    }
+
+    private void mockHandleCarServiceCrash() throws Exception {
+        doNothing().when(mHelperSpy).handleCarServiceCrash();
+    }
+
+    enum InitialUserInfoAction {
+        DEFAULT,
+        DEFAULT_WITH_LOCALE,
+        DO_NOT_REPLY,
+        DELAYED_REPLY,
+        NON_OK_RESULT_CODE,
+        NULL_BUNDLE,
+        SWITCH_OK,
+        SWITCH_OK_WITH_LOCALE,
+        SWITCH_MISSING_USER_ID
+    }
+
+    private void verifyICarOnUserLifecycleEventCalled(int eventType,
+            @UserIdInt int fromId, @UserIdInt int toId) throws Exception {
+        verify(mCarServiceProxy).sendUserLifecycleEvent(eq(eventType),
+                isTargetUser(fromId), isTargetUser(toId));
+    }
+
+    private static TargetUser isTargetUser(@UserIdInt int userId) {
+        return argThat((user) -> {
+            return user == null || user.getUserIdentifier() == userId;
+        });
+    }
+
+    private void verifyICarOnUserLifecycleEventNeverCalled() throws Exception {
+        verify(mCarServiceProxy, never()).sendUserLifecycleEvent(anyInt(), any(), any());
+    }
+
+    private void verifyInitBootUser() throws Exception {
+        verify(mCarServiceProxy).initBootUser();
+    }
+}
diff --git a/tests/src/com/android/internal/car/CarServiceProxyTest.java b/tests/src/com/android/internal/car/CarServiceProxyTest.java
new file mode 100644
index 0000000..74bc8b0
--- /dev/null
+++ b/tests/src/com/android/internal/car/CarServiceProxyTest.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2020 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 com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.car.test.util.UserTestingHelper.UserInfoBuilder;
+import android.content.pm.UserInfo;
+import android.os.RemoteException;
+
+import com.android.car.internal.ICarSystemServerClient;
+import com.android.internal.os.IResultReceiver;
+import com.android.server.SystemService.TargetUser;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+public class CarServiceProxyTest extends AbstractExtendedMockitoTestCase {
+
+    @Mock
+    private CarServiceHelperService mCarServiceHelperService;
+    @Mock
+    private ICarSystemServerClient mCarService;
+
+    @Mock
+    private IResultReceiver mFactoryResetCallback1;
+
+    @Mock
+    private IResultReceiver mFactoryResetCallback2;
+
+    private final TargetUser mFromUser = new TargetUser(new UserInfo(101, "fromUser", 0));
+    private final TargetUser mToUser = new TargetUser(new UserInfo(102, "toUser", 0));
+
+    private final UserInfo mRemovedUser1 = new UserInfoBuilder(100).build();
+    private final UserInfo mRemovedUser2 = new UserInfoBuilder(200).build();
+    private final UserInfo mRemovedUser3 = new UserInfoBuilder(300).build();
+
+    private CarServiceProxy mCarServiceProxy;
+
+    @Before
+    public void setUpMocks() {
+        mCarServiceProxy = new CarServiceProxy(mCarServiceHelperService);
+    }
+
+    @Test
+    public void testInitBootUser_CarServiceNotNull() throws RemoteException {
+        connectToCarService();
+
+        callInitBootUser();
+
+        verifyInitBootUserCalled();
+    }
+
+    @Test
+    public void testInitBootUser_CarServiceNull() throws RemoteException {
+        callInitBootUser();
+
+        verifyInitBootUserNeverCalled();
+    }
+
+    @Test
+    public void testSendUserLifecycleEvent_CarServiceNotNull() throws RemoteException {
+        connectToCarService();
+
+        callSendLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING);
+
+        verifySendLifecycleEventCalled(USER_LIFECYCLE_EVENT_TYPE_SWITCHING);
+    }
+
+    @Test
+    public void testSendUserLifecycleEvent_CarServiceNull() throws RemoteException {
+        callSendLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING);
+
+        verifySendLifecycleEventNeverCalled();
+    }
+
+    @Test
+    public void testHandleCarServiceConnection() throws RemoteException {
+        callInitBootUser();
+        callSendLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING);
+        callOnUserRemoved();
+
+        // Call again to make sure only one call is made after the service is connected
+        callInitBootUser();
+
+        verifyInitBootUserNeverCalled();
+        verifySendLifecycleEventNeverCalled();
+        verifyOnUserRemovedNeverCalled();
+
+        connectToCarService();
+
+        verifyInitBootUserCalled();
+        verifySendLifecycleEventCalled(USER_LIFECYCLE_EVENT_TYPE_SWITCHING);
+        verifyOnUserRemovedCalled();
+    }
+
+    @Test
+    public void testOnUserRemoved_CarServiceNotNull() throws RemoteException {
+        connectToCarService();
+
+        callOnUserRemoved();
+
+        verifyOnUserRemovedCalled();
+    }
+
+    @Test
+    public void testOnUserRemoved_CarServiceNull() throws RemoteException {
+        callOnUserRemoved();
+
+        verifyOnUserRemovedNeverCalled();
+    }
+
+    @Test
+    public void testOnFactoryReset_CarServiceNotNull() throws RemoteException {
+        connectToCarService();
+
+        callOnFactoryReset(mFactoryResetCallback1);
+        callOnFactoryReset(mFactoryResetCallback2);
+
+        verifyOnFactoryResetCalled(mFactoryResetCallback1);
+        verifyOnFactoryResetCalled(mFactoryResetCallback2);
+    }
+
+    @Test
+    public void testOnFactoryReset_CarServiceNull() throws RemoteException {
+        callOnFactoryReset(mFactoryResetCallback1);
+        callOnFactoryReset(mFactoryResetCallback2);
+
+        verifyOnFactoryResetNeverCalled();
+    }
+
+    @Test
+    public void testOnFactoryReset_CarServiceNullThenConnected() throws RemoteException {
+        callOnFactoryReset(mFactoryResetCallback1);
+        callOnFactoryReset(mFactoryResetCallback2);
+        connectToCarService();
+
+        verifyOnFactoryResetNotCalled(mFactoryResetCallback1);
+        verifyOnFactoryResetCalled(mFactoryResetCallback2);
+    }
+
+    private void connectToCarService() {
+        mCarServiceProxy.handleCarServiceConnection(mCarService);
+    }
+
+    private void callInitBootUser() {
+        mCarServiceProxy.initBootUser();
+    }
+
+    private void callSendLifecycleEvent(int eventType) {
+        mCarServiceProxy.sendUserLifecycleEvent(eventType, mFromUser, mToUser);
+    }
+
+    private void callOnUserRemoved() {
+        mCarServiceProxy.onUserRemoved(mRemovedUser1);
+        mCarServiceProxy.onUserRemoved(mRemovedUser2);
+        mCarServiceProxy.onUserRemoved(mRemovedUser3);
+    }
+
+    private void callOnFactoryReset(IResultReceiver callback) {
+        mCarServiceProxy.onFactoryReset(callback);
+    }
+
+    private void verifyInitBootUserCalled() throws RemoteException {
+        verify(mCarService).initBootUser();
+    }
+
+    private void verifyInitBootUserNeverCalled() throws RemoteException {
+        verify(mCarService, never()).initBootUser();
+    }
+
+    private void verifySendLifecycleEventCalled(int eventType) throws RemoteException {
+        verify(mCarService).onUserLifecycleEvent(eventType,
+                mFromUser.getUserIdentifier(), mToUser.getUserIdentifier());
+    }
+
+    private void verifySendLifecycleEventNeverCalled() throws RemoteException {
+        verify(mCarService, never()).onUserLifecycleEvent(anyInt(), anyInt(), anyInt());
+    }
+
+    private void verifyOnUserRemovedCalled() throws RemoteException {
+        verify(mCarService).onUserRemoved(mRemovedUser1);
+        verify(mCarService).onUserRemoved(mRemovedUser2);
+        verify(mCarService).onUserRemoved(mRemovedUser3);
+    }
+
+    private void verifyOnUserRemovedNeverCalled() throws RemoteException {
+        verify(mCarService, never()).onUserRemoved(any());
+    }
+
+    private void verifyOnFactoryResetCalled(IResultReceiver callback) throws RemoteException {
+        verify(mCarService).onFactoryReset(callback);
+    }
+
+    private void verifyOnFactoryResetNotCalled(IResultReceiver callback) throws RemoteException {
+        verify(mCarService, never()).onFactoryReset(callback);
+    }
+
+    private void verifyOnFactoryResetNeverCalled() throws RemoteException {
+        verify(mCarService, never()).onFactoryReset(any());
+    }
+}
diff --git a/tests/src/com/android/internal/car/UserMetricsTest.java b/tests/src/com/android/internal/car/UserMetricsTest.java
new file mode 100644
index 0000000..67ed9f9
--- /dev/null
+++ b/tests/src/com/android/internal/car/UserMetricsTest.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2020 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 com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STARTING;
+import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPED;
+import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_STOPPING;
+import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
+import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED;
+import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.annotation.UserIdInt;
+import android.os.SystemClock;
+import android.util.SparseArray;
+
+import com.android.internal.car.UserMetrics.UserStartingMetric;
+import com.android.internal.car.UserMetrics.UserStoppingMetric;
+
+import org.junit.Test;
+
+public final class UserMetricsTest {
+
+    private final UserMetrics mUserMetrics = new UserMetrics();
+    @UserIdInt
+    private final int mFromUserId = 10;
+    @UserIdInt
+    private final int mUserId = 11;
+
+    @Test
+    public void testStartingEvent_success() {
+        long timestamp = sendStartingEvent(mUserId);
+
+        assertStartTime(timestamp, mUserId);
+    }
+
+    @Test
+    public void testStartingEvent_multipleCallsDifferentUser() {
+        long timestamp1 = sendStartingEvent(mUserId);
+        int userId = 12;
+        long timestamp2 = sendStartingEvent(userId);
+
+        assertStartTime(timestamp1, mUserId);
+        assertStartTime(timestamp2, userId);
+    }
+
+    @Test
+    public void testStartingEvent_multipleCallsSameUser() {
+        long timestamp1 = sendStartingEvent(mUserId);
+        assertStartTime(timestamp1, mUserId);
+        long timestamp2 = sendStartingEvent(mUserId);
+
+        assertStartTime(timestamp2, mUserId);
+    }
+
+    @Test
+    public void testSwitchingEvent_failure() {
+        sendSwitchingEvent(mFromUserId, mUserId);
+
+        assertNoStartingMetric(mUserId);
+    }
+
+    @Test
+    public void testSwitchingEvent_success() {
+        sendStartingEvent(mUserId);
+        long timestamp = sendSwitchingEvent(mFromUserId, mUserId);
+
+        assertSwitchTime(timestamp, mFromUserId, mUserId);
+    }
+
+    @Test
+    public void testUnlockingEvent_failure() {
+        sendUnlockingEvent(mUserId);
+
+        assertNoStartingMetric(mUserId);
+    }
+
+    @Test
+    public void testUnlockingEvent_success() {
+        sendStartingEvent(mUserId);
+        long timestamp = sendUnlockingEvent(mUserId);
+
+        assertUnlockingTime(timestamp, mUserId);
+    }
+
+    @Test
+    public void testUnlockedEvent_failure() {
+        sendUnlockedEvent(mUserId);
+
+        assertNoStartingMetric(mUserId);
+    }
+
+    @Test
+    public void testUnlockedEvent_success() {
+        long timestamp = sendStartingEvent(mUserId);
+        assertStartTime(timestamp, mUserId);
+        sendUnlockedEvent(mUserId);
+
+        // a successful unlocked event would have removed the metric
+        assertNoStartingMetric(mUserId);
+    }
+
+    @Test
+    public void testStopingEvent_success() {
+        long timestamp = sendStopingEvent(mUserId);
+
+        assertStopingTime(timestamp, mUserId);
+    }
+
+    @Test
+    public void testStopingEvent_multipleCallsDifferentUser() {
+        long timestamp1 = sendStopingEvent(mUserId);
+        int userId = 12;
+        long timestamp2 = sendStopingEvent(userId);
+
+        assertStopingTime(timestamp1, mUserId);
+        assertStopingTime(timestamp2, userId);
+    }
+
+    @Test
+    public void testStopingEvent_multipleCallsSameUser() {
+        long timestamp1 = sendStopingEvent(mUserId);
+        assertStopingTime(timestamp1, mUserId);
+        long timestamp2 = sendStopingEvent(mUserId);
+
+        assertStopingTime(timestamp2, mUserId);
+    }
+
+    @Test
+    public void testStoppedEvent_failure() {
+        sendStoppedEvent(mUserId);
+
+        assertNoStoppingMetric(mUserId);
+    }
+
+    @Test
+    public void testStoppedEvent_success() {
+        long timestamp = sendStopingEvent(mUserId);
+        assertStopingTime(timestamp, mUserId);
+        sendStoppedEvent(mUserId);
+
+        // a successful stopped event would have removed the metric
+        assertNoStoppingMetric(mUserId);
+    }
+
+    private long sendStartingEvent(@UserIdInt int userId) {
+        long timestampMs = SystemClock.elapsedRealtimeNanos();
+        mUserMetrics.onEvent(USER_LIFECYCLE_EVENT_TYPE_STARTING, timestampMs, /* fromUserId */ -1,
+                userId);
+        return timestampMs;
+    }
+
+    private long sendSwitchingEvent(@UserIdInt int fromUserId, @UserIdInt int userId) {
+        long timestampMs = SystemClock.elapsedRealtimeNanos();
+        mUserMetrics.onEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING, timestampMs, fromUserId, userId);
+        return timestampMs;
+    }
+
+    private long sendUnlockingEvent(@UserIdInt int userId) {
+        long timestampMs = SystemClock.elapsedRealtimeNanos();
+        mUserMetrics.onEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKING, timestampMs, /* fromUserId */ -1,
+                userId);
+        return timestampMs;
+    }
+
+    private long sendUnlockedEvent(@UserIdInt int userId) {
+        long timestampMs = SystemClock.elapsedRealtimeNanos();
+        mUserMetrics.onEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED, timestampMs, /* fromUserId */ -1,
+                userId);
+        return timestampMs;
+    }
+
+    private long sendStopingEvent(@UserIdInt int userId) {
+        long timestampMs = SystemClock.elapsedRealtimeNanos();
+        mUserMetrics.onEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPING, timestampMs, /* fromUserId */ -1,
+                userId);
+        return timestampMs;
+    }
+
+    private long sendStoppedEvent(@UserIdInt int userId) {
+        long timestampMs = SystemClock.elapsedRealtimeNanos();
+        mUserMetrics.onEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPED, timestampMs, /* fromUserId */ -1,
+                userId);
+        return timestampMs;
+    }
+
+    private void assertStartTime(long timestamp, @UserIdInt int userId) {
+        SparseArray<UserStartingMetric> startArray = mUserMetrics.getUserStartMetrics();
+        UserStartingMetric metric = startArray.get(userId);
+        assertThat(metric.startTime).isEqualTo(timestamp);
+    }
+
+    private void assertSwitchTime(long timestamp, @UserIdInt int fromUserId,
+            @UserIdInt int userId) {
+        SparseArray<UserStartingMetric> startArray = mUserMetrics.getUserStartMetrics();
+        UserStartingMetric metric = startArray.get(userId);
+        assertThat(metric.switchFromUserId).isEqualTo(fromUserId);
+        assertThat(metric.switchTime).isEqualTo(timestamp);
+    }
+
+    private void assertUnlockingTime(long timestamp, int userId) {
+        SparseArray<UserStartingMetric> startArray = mUserMetrics.getUserStartMetrics();
+        UserStartingMetric metric = startArray.get(userId);
+        assertThat(metric.unlockingTime).isEqualTo(timestamp);
+    }
+
+    private void assertNoStartingMetric(@UserIdInt int userId) {
+        SparseArray<UserStartingMetric> startArray = mUserMetrics.getUserStartMetrics();
+        assertThrows(NullPointerException.class, () -> startArray.get(userId));
+    }
+
+    private void assertStopingTime(long timestamp, @UserIdInt int userId) {
+        SparseArray<UserStoppingMetric> stopArray = mUserMetrics.getUserStopMetrics();
+        UserStoppingMetric metric = stopArray.get(userId);
+        assertThat(metric.stopTime).isEqualTo(timestamp);
+    }
+
+    private void assertNoStoppingMetric(@UserIdInt int userId) {
+        SparseArray<UserStoppingMetric> stopArray = mUserMetrics.getUserStopMetrics();
+        assertThrows(NullPointerException.class, () -> stopArray.get(userId));
+    }
+}
diff --git a/tests/src/com/android/server/wm/CarLaunchParamsModifierTest.java b/tests/src/com/android/server/wm/CarLaunchParamsModifierTest.java
index ae6fa06..d641cff 100644
--- a/tests/src/com/android/server/wm/CarLaunchParamsModifierTest.java
+++ b/tests/src/com/android/server/wm/CarLaunchParamsModifierTest.java
@@ -29,6 +29,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
 
 import android.annotation.UserIdInt;
@@ -40,7 +42,6 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
-import android.content.res.Configuration;
 import android.hardware.display.DisplayManager;
 import android.os.UserHandle;
 import android.view.Display;
@@ -48,7 +49,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
-import com.android.server.AttributeCache;
+import com.android.internal.policy.AttributeCache;
 import com.android.server.LocalServices;
 import com.android.server.display.color.ColorDisplayService;
 
@@ -62,6 +63,11 @@
 
 import java.util.Arrays;
 
+/**
+ * Tests for {@link CarLaunchParamsModifier}
+ * Build/Install/Run:
+ *  atest CarServicesTest:CarLaunchParamsModifierTest
+ */
 @RunWith(AndroidJUnit4.class)
 public class CarLaunchParamsModifierTest {
     private static final int PASSENGER_DISPLAY_ID_10 = 10;
@@ -79,7 +85,7 @@
     @Mock
     private ActivityTaskManagerService mActivityTaskManagerService;
     @Mock
-    private ActivityStackSupervisor mActivityStackSupervisor;
+    private ActivityTaskSupervisor mActivityTaskSupervisor;
     @Mock
     private RecentTasks mRecentTasks;
     @Mock
@@ -154,8 +160,8 @@
                 .startMocking();
         when(mContext.getSystemService(DisplayManager.class)).thenReturn(mDisplayManager);
         doReturn(mActivityTaskManagerService).when(() -> ActivityTaskManager.getService());
-        mActivityTaskManagerService.mStackSupervisor = mActivityStackSupervisor;
-        when(mActivityStackSupervisor.getLaunchParamsController()).thenReturn(
+        mActivityTaskManagerService.mTaskSupervisor = mActivityTaskSupervisor;
+        when(mActivityTaskSupervisor.getLaunchParamsController()).thenReturn(
                 mLaunchParamsController);
         mActivityTaskManagerService.mRootWindowContainer = mRootWindowContainer;
         mActivityTaskManagerService.mWindowManager = mWindowManagerService;
@@ -193,7 +199,8 @@
         mCurrentParams.mPreferredTaskDisplayArea = mModifier
                 .getDefaultTaskDisplayAreaOnDisplay(display.getDisplayId());
         assertThat(mModifier.onCalculate(mTask, mWindowLayout, mActivityRecordActivity,
-                mActivityRecordSource, mActivityOptions, 0, mCurrentParams, mOutParams))
+                mActivityRecordSource, mActivityOptions, null /* request */, 0, mCurrentParams,
+                mOutParams))
                 .isEqualTo(LaunchParamsController.LaunchParamsModifier.RESULT_SKIP);
     }
 
@@ -207,7 +214,8 @@
                 .getDefaultTaskDisplayAreaOnDisplay(displayAssigned.getDisplayId());
         mCurrentParams.mPreferredTaskDisplayArea = requestedTaskDisplayArea;
         assertThat(mModifier.onCalculate(mTask, mWindowLayout, mActivityRecordActivity,
-                mActivityRecordSource, mActivityOptions, 0, mCurrentParams, mOutParams))
+                mActivityRecordSource, mActivityOptions, null /* request */, 0, mCurrentParams,
+                mOutParams))
                 .isEqualTo(LaunchParamsController.LaunchParamsModifier.RESULT_DONE);
         assertThat(mOutParams.mPreferredTaskDisplayArea).isEqualTo(assignedTaskDisplayArea);
     }
@@ -219,7 +227,8 @@
         }
         mCurrentParams.mPreferredTaskDisplayArea = null;
         assertThat(mModifier.onCalculate(mTask, mWindowLayout, mActivityRecordActivity,
-                mActivityRecordSource, mActivityOptions, 0, mCurrentParams, mOutParams))
+                mActivityRecordSource, mActivityOptions, null /* request */, 0, mCurrentParams,
+                mOutParams))
                 .isEqualTo(LaunchParamsController.LaunchParamsModifier.RESULT_DONE);
         assertThat(mOutParams.mPreferredTaskDisplayArea).isEqualTo(expectedDisplayArea);
     }
@@ -228,7 +237,8 @@
         mTask.mUserId = userId;
         mCurrentParams.mPreferredTaskDisplayArea = null;
         assertThat(mModifier.onCalculate(mTask, mWindowLayout, mActivityRecordActivity,
-                mActivityRecordSource, mActivityOptions, 0, mCurrentParams, mOutParams))
+                mActivityRecordSource, mActivityOptions, null /* request */, 0, mCurrentParams,
+                mOutParams))
                 .isEqualTo(LaunchParamsController.LaunchParamsModifier.RESULT_SKIP);
         assertThat(mOutParams.mPreferredTaskDisplayArea).isNull();
     }
@@ -241,12 +251,11 @@
         info.applicationInfo.packageName = packageName;
         Intent intent = new Intent();
         intent.setClassName(packageName, className);
-        return new ActivityRecord(mActivityTaskManagerService, /* caller */null,
-                /* launchedFromPid */ 0, /* launchedFromUid */ 0, /* launchedFromPackage */ null,
-                /* launchedFromFeature */ null, intent, /* resolvedType */ null, info,
-                new Configuration(), /* resultTo */ null, /* resultWho */ null, /* reqCode */ 0,
-                /*componentSpecified*/ false, /* rootVoiceInteraction */ false,
-                mActivityStackSupervisor, /* options */ null, /* sourceRecord */ null);
+
+        return new ActivityRecord.Builder(mActivityTaskManagerService)
+                .setIntent(intent)
+                .setActivityInfo(info)
+                .build();
     }
 
     @Test
@@ -300,7 +309,7 @@
                 mDisplay11ForPassenger.getDisplayId()});
 
         final int passengerUserId = 100;
-        mModifier.setDisplayWhitelistForUser(passengerUserId,
+        mModifier.setDisplayAllowListForUser(passengerUserId,
                 new int[]{mDisplay10ForPassenger.getDisplayId()});
 
         assertDisplayIsAllowed(passengerUserId, mDisplay10ForPassenger);
@@ -312,13 +321,13 @@
                 mDisplay11ForPassenger.getDisplayId()});
 
         int passengerUserId1 = 100;
-        mModifier.setDisplayWhitelistForUser(passengerUserId1,
+        mModifier.setDisplayAllowListForUser(passengerUserId1,
                 new int[]{mDisplay11ForPassenger.getDisplayId()});
 
         assertDisplayIsAllowed(passengerUserId1, mDisplay11ForPassenger);
 
         int passengerUserId2 = 101;
-        mModifier.setDisplayWhitelistForUser(passengerUserId2,
+        mModifier.setDisplayAllowListForUser(passengerUserId2,
                 new int[]{mDisplay11ForPassenger.getDisplayId()});
 
         assertDisplayIsAllowed(passengerUserId2, mDisplay11ForPassenger);
@@ -332,7 +341,7 @@
                 mDisplay11ForPassenger.getDisplayId()});
 
         final int passengerUserId = 100;
-        mModifier.setDisplayWhitelistForUser(
+        mModifier.setDisplayAllowListForUser(
                 passengerUserId, new int[]{mDisplay10ForPassenger.getDisplayId()});
 
         assertDisplayIsReassigned(passengerUserId, mDisplay0ForDriver, mDisplay10ForPassenger);
@@ -345,7 +354,7 @@
                 mDisplay11ForPassenger.getDisplayId()});
 
         int passengerUserId = 100;
-        mModifier.setDisplayWhitelistForUser(
+        mModifier.setDisplayAllowListForUser(
                 passengerUserId, new int[]{mDisplay11ForPassenger.getDisplayId()});
         assertDisplayIsAllowed(passengerUserId, mDisplay11ForPassenger);
 
@@ -361,11 +370,11 @@
                 mDisplay11ForPassenger.getDisplayId()});
 
         int passengerUserId = 100;
-        mModifier.setDisplayWhitelistForUser(
+        mModifier.setDisplayAllowListForUser(
                 passengerUserId, new int[]{mDisplay11ForPassenger.getDisplayId()});
         assertDisplayIsAllowed(passengerUserId, mDisplay11ForPassenger);
 
-        mModifier.setDisplayWhitelistForUser(
+        mModifier.setDisplayAllowListForUser(
                 UserHandle.USER_SYSTEM, new int[]{mDisplay11ForPassenger.getDisplayId()});
 
         assertDisplayIsReassigned(passengerUserId, mDisplay0ForDriver, mDisplay10ForPassenger);
@@ -378,7 +387,7 @@
                 mDisplay11ForPassenger.getDisplayId()});
 
         final int passengerUserId = 100;
-        mModifier.setDisplayWhitelistForUser(passengerUserId,
+        mModifier.setDisplayAllowListForUser(passengerUserId,
                 new int[]{mDisplay10ForPassenger.getDisplayId(),
                         mDisplay11ForPassenger.getDisplayId()});
 
@@ -397,7 +406,7 @@
                 mDisplay11ForPassenger.getDisplayId()});
 
         final int passengerUserId = 100;
-        mModifier.setDisplayWhitelistForUser(passengerUserId,
+        mModifier.setDisplayAllowListForUser(passengerUserId,
                 new int[]{mDisplay10ForPassenger.getDisplayId(),
                         mDisplay11ForPassenger.getDisplayId()});
 
@@ -416,7 +425,7 @@
                 mDisplay11ForPassenger.getDisplayId()});
 
         final int passengerUserId = 100;
-        mModifier.setDisplayWhitelistForUser(passengerUserId,
+        mModifier.setDisplayAllowListForUser(passengerUserId,
                 new int[]{mDisplay10ForPassenger.getDisplayId(),
                         mDisplay10ForPassenger.getDisplayId()});
 
@@ -431,7 +440,7 @@
         final int wasDriver = 10;
         final int wasPassenger = 11;
         mModifier.handleCurrentUserSwitching(wasDriver);
-        mModifier.setDisplayWhitelistForUser(wasPassenger,
+        mModifier.setDisplayAllowListForUser(wasPassenger,
                 new int[]{mDisplay10ForPassenger.getDisplayId(),
                         mDisplay11ForPassenger.getDisplayId()});
 
@@ -445,7 +454,7 @@
         final int driver = wasPassenger;
         final int passenger = wasDriver;
         mModifier.handleCurrentUserSwitching(driver);
-        mModifier.setDisplayWhitelistForUser(passenger,
+        mModifier.setDisplayAllowListForUser(passenger,
                 new int[]{mDisplay10ForPassenger.getDisplayId(),
                         mDisplay11ForPassenger.getDisplayId()});
 
@@ -472,7 +481,7 @@
     public void testPreferSourceForPassenger() {
         mModifier.setPassengerDisplays(new int[]{PASSENGER_DISPLAY_ID_10, PASSENGER_DISPLAY_ID_11});
         int passengerUserId = 100;
-        mModifier.setDisplayWhitelistForUser(passengerUserId,
+        mModifier.setDisplayAllowListForUser(passengerUserId,
                 new int[]{PASSENGER_DISPLAY_ID_10, PASSENGER_DISPLAY_ID_11});
         when(mActivityRecordSource.getDisplayArea()).thenReturn(mDisplayArea11ForPassenger);
 
@@ -505,7 +514,7 @@
     @Test
     public void testPreferSourceDoNotAssignDisplayForNonSpecifiedActivity() {
         when(mActivityRecordSource.getDisplayArea()).thenReturn(mDisplayArea0ForDriver);
-        mActivityRecordActivity = buildActivityRecord("dummyPackage", "dummyActivity");
+        mActivityRecordActivity = buildActivityRecord("placeholderPackage", "placeholderActivity");
         mModifier.setSourcePreferredComponents(true,
                 Arrays.asList(new ComponentName("testPackage", "testActivity")));
 
@@ -546,4 +555,113 @@
 
         assertDisplayIsAssigned(UserHandle.USER_SYSTEM, mDisplayArea0ForDriver);
     }
+
+    @Test
+    public void testSourceDisplayFromProcessDisplayIfAvailable() {
+        int userId = 10;
+        String processName = "processName";
+        int processUid = 11;
+        when(mActivityRecordActivity.getProcessName())
+                .thenReturn(processName);
+        when(mActivityRecordActivity.getUid())
+                .thenReturn(processUid);
+        mModifier.setPassengerDisplays(new int[]{mDisplay11ForPassenger.getDisplayId(),
+                mDisplay10ForPassenger.getDisplayId()});
+        mModifier.setDisplayAllowListForUser(userId,
+                new int[]{mDisplay10ForPassenger.getDisplayId()});
+        WindowProcessController controller = mock(WindowProcessController.class);
+        when(mActivityTaskManagerService.getProcessController(processName, processUid))
+                .thenReturn(controller);
+        when(controller.getTopActivityDisplayArea())
+                .thenReturn(mDisplayArea10ForPassenger);
+        mCurrentParams.mPreferredTaskDisplayArea = null;
+        mTask.mUserId = userId;
+
+        assertThat(mModifier.onCalculate(mTask, mWindowLayout, mActivityRecordActivity,
+                mActivityRecordSource, null, null /* request */, 0, mCurrentParams, mOutParams))
+                .isEqualTo(TaskLaunchParamsModifier.RESULT_DONE);
+        assertThat(mOutParams.mPreferredTaskDisplayArea)
+                .isEqualTo(mDisplayArea10ForPassenger);
+    }
+
+    @Test
+    public void testSourceDisplayFromLaunchingDisplayIfAvailable() {
+        int userId = 10;
+        int launchedFromPid = 1324;
+        int launchedFromUid = 325;
+        when(mActivityRecordActivity.getLaunchedFromPid())
+                .thenReturn(launchedFromPid);
+        when(mActivityRecordActivity.getLaunchedFromUid())
+                .thenReturn(launchedFromUid);
+        mModifier.setPassengerDisplays(new int[]{mDisplay11ForPassenger.getDisplayId(),
+                mDisplay10ForPassenger.getDisplayId()});
+        mModifier.setDisplayAllowListForUser(userId,
+                new int[]{mDisplay10ForPassenger.getDisplayId()});
+        WindowProcessController controller = mock(WindowProcessController.class);
+        when(mActivityTaskManagerService.getProcessController(launchedFromPid, launchedFromUid))
+                .thenReturn(controller);
+        when(controller.getTopActivityDisplayArea())
+                .thenReturn(mDisplayArea10ForPassenger);
+        mCurrentParams.mPreferredTaskDisplayArea = null;
+        mTask.mUserId = 10;
+
+        assertThat(mModifier.onCalculate(mTask, mWindowLayout, mActivityRecordActivity,
+                mActivityRecordSource, null, null /* request */, 0, mCurrentParams, mOutParams))
+                .isEqualTo(TaskLaunchParamsModifier.RESULT_DONE);
+        assertThat(mOutParams.mPreferredTaskDisplayArea)
+                .isEqualTo(mDisplayArea10ForPassenger);
+    }
+
+    @Test
+    public void testSourceDisplayFromCallingDisplayIfAvailable() {
+        int userId = 10;
+        ActivityStarter.Request request = fakeRequest();
+        mModifier.setPassengerDisplays(new int[]{mDisplay11ForPassenger.getDisplayId(),
+                mDisplay10ForPassenger.getDisplayId()});
+        mModifier.setDisplayAllowListForUser(userId,
+                new int[]{mDisplay10ForPassenger.getDisplayId()});
+        WindowProcessController controller = mock(WindowProcessController.class);
+        when(mActivityTaskManagerService.getProcessController(request.realCallingPid,
+                request.realCallingUid))
+                .thenReturn(controller);
+        when(controller.getTopActivityDisplayArea())
+                .thenReturn(mDisplayArea10ForPassenger);
+        mCurrentParams.mPreferredTaskDisplayArea = null;
+        mTask.mUserId = userId;
+
+        assertThat(mModifier.onCalculate(mTask, mWindowLayout, mActivityRecordActivity,
+                mActivityRecordSource, null, request, 0, mCurrentParams, mOutParams))
+                .isEqualTo(TaskLaunchParamsModifier.RESULT_DONE);
+        assertThat(mOutParams.mPreferredTaskDisplayArea)
+                .isEqualTo(mDisplayArea10ForPassenger);
+    }
+
+    @Test
+    public void testSourceDisplayIgnoredIfNotInAllowList() {
+        ActivityStarter.Request request = fakeRequest();
+        mModifier.setPassengerDisplays(new int[]{mDisplay11ForPassenger.getDisplayId(),
+                mDisplay10ForPassenger.getDisplayId()});
+        WindowProcessController controller = mock(WindowProcessController.class);
+        when(mActivityTaskManagerService.getProcessController(anyString(), anyInt()))
+                .thenReturn(controller);
+        when(mActivityTaskManagerService.getProcessController(anyInt(), anyInt()))
+                .thenReturn(controller);
+        when(controller.getTopActivityDisplayArea())
+                .thenReturn(mDisplayArea10ForPassenger);
+        mCurrentParams.mPreferredTaskDisplayArea = null;
+        mTask.mUserId = 10;
+
+        assertThat(mModifier.onCalculate(mTask, mWindowLayout, mActivityRecordActivity,
+                mActivityRecordSource, null, request, 0, mCurrentParams, mOutParams))
+                .isEqualTo(TaskLaunchParamsModifier.RESULT_DONE);
+        assertThat(mOutParams.mPreferredTaskDisplayArea)
+                .isEqualTo(mDisplayArea11ForPassenger);
+    }
+
+    private ActivityStarter.Request fakeRequest() {
+        ActivityStarter.Request request = new ActivityStarter.Request();
+        request.realCallingPid = 1324;
+        request.realCallingUid = 235;
+        return request;
+    }
 }