Merge "Hiding [HiddenSupperclass] warnings since they are expected. See https://b.corp.google.com/issues/147708787#comment8 for more info." into rvc-dev
diff --git a/FrameworkPackageStubs/AndroidManifest.xml b/FrameworkPackageStubs/AndroidManifest.xml
index 912944c..6c25bcd 100644
--- a/FrameworkPackageStubs/AndroidManifest.xml
+++ b/FrameworkPackageStubs/AndroidManifest.xml
@@ -95,6 +95,10 @@
                 <action android:name="android.settings.USER_DICTIONARY_SETTINGS" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
+            <intent-filter android:priority="-1">
+                <action android:name="android.settings.PICTURE_IN_PICTURE_SETTINGS" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
         </activity>
 
         <!-- CDD Core Application Intents Stubs -->
diff --git a/car-internal-lib/src/com/android/internal/car/EventLogTags.logtags b/car-internal-lib/src/com/android/internal/car/EventLogTags.logtags
index 5fec33f..f404172 100644
--- a/car-internal-lib/src/com/android/internal/car/EventLogTags.logtags
+++ b/car-internal-lib/src/com/android/internal/car/EventLogTags.logtags
@@ -78,6 +78,12 @@
 150112 car_user_svc_switch_user_from_hal_req (request_id|1),(uid|1)
 150113 car_user_svc_set_user_auth_req (uid|1),(user_id|1),(number_associations|1)
 150114 car_user_svc_set_user_auth_resp (number_values|1),(error_message|3)
+150115 car_user_svc_create_user_req (safe_name|3),(user_type|3),(flags|1),(timeout|1)
+150116 car_user_svc_create_user_resp (status|1),(result|1),(error_message|3)
+150117 car_user_svc_create_user_user_created (user_id|1),(safe_name|3),(user_type|3),(flags|1)
+150118 car_user_svc_create_user_user_removed (user_id|1),(reason|3)
+150119 car_user_svc_remove_user_req (user_id|1)
+150120 car_user_svc_remove_user_resp (user_id|1),(result|1)
 
 150140 car_user_hal_initial_user_info_req (request_id|1),(request_type|1),(timeout|1)
 150141 car_user_hal_initial_user_info_resp (request_id|1),(status|1),(action|1),(user_id|1),(flags|1),(safe_name|3),(user_locales|3)
@@ -92,13 +98,18 @@
 150150 car_user_hal_oem_switch_user_req (request_id|1),(target_user_id|1)
 150151 car_user_hal_create_user_req (request_id|1),(safe_name|3),(flags|1),(timeout|1)
 150152 car_user_hal_create_user_resp (request_id|1),(status|1),(result|1),(error_message|3)
+150153 car_user_hal_remove_user_req (target_user_id|1),(current_user_id|1)
 
 150171 car_user_mgr_add_listener (uid|1)
 150172 car_user_mgr_remove_listener (uid|1)
 150173 car_user_mgr_disconnected (uid|1)
-150174 car_user_mgr_switch_user_request (uid|1),(user_id|1)
-150175 car_user_mgr_switch_user_response (uid|1),(status|1),(error_message|3)
+150174 car_user_mgr_switch_user_req (uid|1),(user_id|1)
+150175 car_user_mgr_switch_user_resp (uid|1),(status|1),(error_message|3)
 150176 car_user_mgr_get_user_auth_req (types|4)
 150177 car_user_mgr_get_user_auth_resp (values|4)
 150178 car_user_mgr_set_user_auth_req (types_and_values_pairs|4)
 150179 car_user_mgr_set_user_auth_resp (values|4)
+150180 car_user_mgr_create_user_req (uid|1),(safe_name|3),(user_type|3),(flags|1)
+150181 car_user_mgr_create_user_resp (uid|1),(status|1),(error_message|3)
+150182 car_user_mgr_remove_user_req (uid|1),(user_id|1)
+150183 car_user_mgr_remove_user_resp (uid|1),(status|1)
\ No newline at end of file
diff --git a/car-lib/api/system-current.txt b/car-lib/api/system-current.txt
index 4a966d4..c404acd 100644
--- a/car-lib/api/system-current.txt
+++ b/car-lib/api/system-current.txt
@@ -955,6 +955,17 @@
 
 }
 
+package android.car.settings {
+
+  public class CarSettings {
+  }
+
+  public static final class CarSettings.Secure {
+    field public static final String KEY_AUDIO_FOCUS_NAVIGATION_REJECTED_DURING_CALL = "android.car.KEY_AUDIO_FOCUS_NAVIGATION_REJECTED_DURING_CALL";
+  }
+
+}
+
 package android.car.storagemonitoring {
 
   public final class CarStorageMonitoringManager {
diff --git a/car-lib/src/android/car/CarAppFocusManager.java b/car-lib/src/android/car/CarAppFocusManager.java
index 3d4d90d..cc0d10b 100644
--- a/car-lib/src/android/car/CarAppFocusManager.java
+++ b/car-lib/src/android/car/CarAppFocusManager.java
@@ -45,8 +45,11 @@
         /**
          * Application focus has changed. Note that {@link CarAppFocusManager} instance
          * causing the change will not get this notification.
-         * @param appType
-         * @param active
+         *
+         * <p>Note that this call can happen for app focus grant, release, and ownership change.
+         *
+         * @param appType appType where the focus change has happened.
+         * @param active {@code true} if there is an active owner for the focus.
          */
         void onAppFocusChanged(@AppFocusType int appType, boolean active);
     }
diff --git a/car-lib/src/android/car/ICarUserService.aidl b/car-lib/src/android/car/ICarUserService.aidl
index 921c146..c5a4c98 100644
--- a/car-lib/src/android/car/ICarUserService.aidl
+++ b/car-lib/src/android/car/ICarUserService.aidl
@@ -17,6 +17,8 @@
 package android.car;
 
 import android.content.pm.UserInfo;
+import android.car.user.UserCreationResult;
+import android.car.user.UserRemovalResult;
 import android.car.user.UserIdentificationAssociationResponse;
 import android.car.user.UserSwitchResult;
 import com.android.internal.infra.AndroidFuture;
@@ -24,10 +26,14 @@
 
 /** @hide */
 interface ICarUserService {
-    UserInfo createDriver(in String name, boolean admin);
-    UserInfo createPassenger(in String name, int driverId);
+    UserInfo createDriver(@nullable String name, boolean admin);
+    UserInfo createPassenger(@nullable String name, int driverId);
     void switchDriver(int driverId, in AndroidFuture<UserSwitchResult> receiver);
     void switchUser(int tagerUserId, int timeoutMs, in AndroidFuture<UserSwitchResult> receiver);
+    void setUserSwitchUiCallback(in IResultReceiver callback);
+    void createUser(@nullable String name, String userType, int flags, int timeoutMs,
+      in AndroidFuture<UserCreationResult> receiver);
+    UserRemovalResult removeUser(int userId);
     List<UserInfo> getAllDrivers();
     List<UserInfo> getPassengers(int driverId);
     boolean startPassenger(int passengerId, int zoneId);
@@ -38,5 +44,4 @@
     UserIdentificationAssociationResponse getUserIdentificationAssociation(in int[] types);
     void setUserIdentificationAssociation(int timeoutMs, in int[] types, in int[] values,
       in AndroidFuture<UserIdentificationAssociationResponse> result);
-    void setUserSwitchUiCallback(in IResultReceiver callback);
 }
diff --git a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
index 5272548..cdf0cca 100644
--- a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
+++ b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java
@@ -412,13 +412,13 @@
             startActivityAsUser(intent, mActivityOptions.toBundle(), UserHandle.CURRENT);
             Log.i(TAG, String.format("Activity launched: %s (options: %s, displayId: %d)",
                     mActivityOptions, intent, mActivityOptions.getLaunchDisplayId()));
-        } catch (ActivityNotFoundException ex) {
+        } catch (ActivityNotFoundException e) {
             Log.w(TAG, "Unable to find activity for intent: " + intent);
             return false;
-        } catch (Exception ex) {
+        } catch (RuntimeException e) {
             // Catch all other possible exception to prevent service disruption by misbehaving
             // applications.
-            Log.e(TAG, "Error trying to launch intent: " + intent + ". Ignored", ex);
+            Log.e(TAG, "Error trying to launch intent: " + intent + ". Ignored", e);
             return false;
         }
         return true;
diff --git a/car-lib/src/android/car/content/pm/AppBlockingPackageInfo.java b/car-lib/src/android/car/content/pm/AppBlockingPackageInfo.java
index dbabc50..001c1d2 100644
--- a/car-lib/src/android/car/content/pm/AppBlockingPackageInfo.java
+++ b/car-lib/src/android/car/content/pm/AppBlockingPackageInfo.java
@@ -123,10 +123,13 @@
 
     public static final Parcelable.Creator<AppBlockingPackageInfo> CREATOR =
             new Parcelable.Creator<AppBlockingPackageInfo>() {
+
+                @Override
                 public AppBlockingPackageInfo createFromParcel(Parcel in) {
                     return new AppBlockingPackageInfo(in);
                 }
 
+                @Override
                 public AppBlockingPackageInfo[] newArray(int size) {
                     return new AppBlockingPackageInfo[size];
                 }
diff --git a/car-lib/src/android/car/content/pm/CarPackageManager.java b/car-lib/src/android/car/content/pm/CarPackageManager.java
index 98c5822..6a8c7fb 100644
--- a/car-lib/src/android/car/content/pm/CarPackageManager.java
+++ b/car-lib/src/android/car/content/pm/CarPackageManager.java
@@ -123,6 +123,8 @@
     /**
      * Restarts the requested task. If task with {@code taskId} does not exist, do nothing.
      *
+     * <p>This requires {@code android.permission.REAL_GET_TASKS} permission.
+     *
      * @hide
      */
     public void restartTask(int taskId) {
diff --git a/car-lib/src/android/car/hardware/hvac/CarHvacManager.java b/car-lib/src/android/car/hardware/hvac/CarHvacManager.java
index f6a2d53..971f1fc 100644
--- a/car-lib/src/android/car/hardware/hvac/CarHvacManager.java
+++ b/car-lib/src/android/car/hardware/hvac/CarHvacManager.java
@@ -29,6 +29,8 @@
 import android.util.ArraySet;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
@@ -45,12 +47,15 @@
 @Deprecated
 @SystemApi
 public final class CarHvacManager extends CarManagerBase {
-    private final static boolean DBG = false;
-    private final static String TAG = "CarHvacManager";
+    private static final boolean DBG = false;
+    private static final String TAG = "CarHvacManager";
     private final CarPropertyManager mCarPropertyMgr;
-    private final ArraySet<CarHvacEventCallback> mCallbacks = new ArraySet<>();
     private CarPropertyEventListenerToBase mListenerToBase = null;
 
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private final ArraySet<CarHvacEventCallback> mCallbacks = new ArraySet<>();
 
     /**
      * HVAC property IDs for get/set methods
@@ -252,7 +257,7 @@
     private static class CarPropertyEventListenerToBase implements CarPropertyEventCallback {
         private final WeakReference<CarHvacManager> mManager;
 
-        public CarPropertyEventListenerToBase(CarHvacManager manager) {
+        CarPropertyEventListenerToBase(CarHvacManager manager) {
             mManager = new WeakReference<>(manager);
         }
 
@@ -275,7 +280,7 @@
 
     private void handleOnChangeEvent(CarPropertyValue value) {
         Collection<CarHvacEventCallback> callbacks;
-        synchronized (this) {
+        synchronized (mLock) {
             callbacks = new ArraySet<>(mCallbacks);
         }
         if (!callbacks.isEmpty()) {
@@ -287,7 +292,7 @@
 
     private void handleOnErrorEvent(int propertyId, int zone) {
         Collection<CarHvacEventCallback> callbacks;
-        synchronized (this) {
+        synchronized (mLock) {
             callbacks = new ArraySet<>(mCallbacks);
         }
         if (!callbacks.isEmpty()) {
@@ -315,16 +320,18 @@
      * Implement wrappers for contained CarPropertyManager object
      * @param callback
      */
-    public synchronized void registerCallback(CarHvacEventCallback callback) {
-        if (mCallbacks.isEmpty()) {
-            mListenerToBase = new CarPropertyEventListenerToBase(this);
-        }
-        List<CarPropertyConfig> configs = getPropertyList();
-        for (CarPropertyConfig c : configs) {
+    public void registerCallback(CarHvacEventCallback callback) {
+        synchronized (mLock) {
+            if (mCallbacks.isEmpty()) {
+                mListenerToBase = new CarPropertyEventListenerToBase(this);
+            }
+            List<CarPropertyConfig> configs = getPropertyList();
+            for (CarPropertyConfig c : configs) {
                 // Register each individual propertyId
-            mCarPropertyMgr.registerCallback(mListenerToBase, c.getPropertyId(), 0);
+                mCarPropertyMgr.registerCallback(mListenerToBase, c.getPropertyId(), 0);
+            }
+            mCallbacks.add(callback);
         }
-        mCallbacks.add(callback);
     }
 
     /**
@@ -332,21 +339,23 @@
      * this listener, all listening will be stopped.
      * @param callback
      */
-    public synchronized void unregisterCallback(CarHvacEventCallback callback) {
-        mCallbacks.remove(callback);
-        try {
-            List<CarPropertyConfig> configs = getPropertyList();
-            for (CarPropertyConfig c : configs) {
-                // Register each individual propertyId
-                mCarPropertyMgr.unregisterCallback(mListenerToBase, c.getPropertyId());
+    public void unregisterCallback(CarHvacEventCallback callback) {
+        synchronized (mLock) {
+            mCallbacks.remove(callback);
+            try {
+                List<CarPropertyConfig> configs = getPropertyList();
+                for (CarPropertyConfig c : configs) {
+                    // Register each individual propertyId
+                    mCarPropertyMgr.unregisterCallback(mListenerToBase, c.getPropertyId());
 
+                }
+            } catch (RuntimeException e) {
+                Log.e(TAG, "getPropertyList exception ", e);
             }
-        } catch (Exception e) {
-            Log.e(TAG, "getPropertyList exception ", e);
-        }
-        if (mCallbacks.isEmpty()) {
-            mCarPropertyMgr.unregisterCallback(mListenerToBase);
-            mListenerToBase = null;
+            if (mCallbacks.isEmpty()) {
+                mCarPropertyMgr.unregisterCallback(mListenerToBase);
+                mListenerToBase = null;
+            }
         }
     }
 
@@ -435,7 +444,7 @@
     /** @hide */
     public void onCarDisconnected() {
         // TODO(b/142730482) Fix synchronization to use separate mLock
-        synchronized (this) {
+        synchronized (mLock) {
             mCallbacks.clear();
         }
         mCarPropertyMgr.onCarDisconnected();
diff --git a/car-lib/src/android/car/media/CarAudioManager.java b/car-lib/src/android/car/media/CarAudioManager.java
index 31eb173..9f57617 100644
--- a/car-lib/src/android/car/media/CarAudioManager.java
+++ b/car-lib/src/android/car/media/CarAudioManager.java
@@ -32,8 +32,6 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
-import android.view.Display;
-import android.view.DisplayAddress;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -538,43 +536,6 @@
     }
 
     /**
-     * Get the zone id for the display
-     *
-     * @param  display display to query
-     * @return zone id for display or
-     * CarAudioManager.PRIMARY_AUDIO_ZONE if no match is found.
-     * @hide
-     */
-    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
-    public int getZoneIdForDisplay(Display display) {
-        DisplayAddress address = display.getAddress();
-        if (address instanceof DisplayAddress.Physical) {
-            DisplayAddress.Physical physicalAddress = (DisplayAddress.Physical) address;
-            if (physicalAddress != null) {
-                return getZoneIdForDisplayPortId(physicalAddress.getPort());
-            }
-        }
-        return PRIMARY_AUDIO_ZONE;
-    }
-
-    /**
-     * Get the zone id for the display port id passed in
-     *
-     * @param  displayPortId display port id to query
-     * @return zone id for display port id or
-     * CarAudioManager.PRIMARY_AUDIO_ZONE if no match is found.
-     * @hide
-     */
-    @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
-    public int getZoneIdForDisplayPortId(byte displayPortId) {
-        try {
-            return mService.getZoneIdForDisplayPortId(displayPortId);
-        } catch (RemoteException e) {
-            return handleRemoteExceptionFromCarService(e, 0);
-        }
-    }
-
-    /**
      * Gets the output device for a given {@link AudioAttributes} usage in zoneId.
      *
      * <p><b>Note:</b> To be used for routing to a specific device. Most applications should
diff --git a/car-lib/src/android/car/media/ICarAudio.aidl b/car-lib/src/android/car/media/ICarAudio.aidl
index 7491a19..a8997ce 100644
--- a/car-lib/src/android/car/media/ICarAudio.aidl
+++ b/car-lib/src/android/car/media/ICarAudio.aidl
@@ -48,8 +48,6 @@
     boolean setZoneIdForUid(int zoneId, int uid);
     boolean clearZoneIdForUid(int uid);
 
-    int getZoneIdForDisplayPortId(byte displayPortId);
-
     String getOutputDeviceAddressForUsage(int zoneId, int usage);
 
     List<AudioDeviceAttributes> getInputDevicesForZoneId(int zoneId);
diff --git a/car-lib/src/android/car/settings/CarSettings.java b/car-lib/src/android/car/settings/CarSettings.java
index 932eec0..3f5a6c3 100644
--- a/car-lib/src/android/car/settings/CarSettings.java
+++ b/car-lib/src/android/car/settings/CarSettings.java
@@ -23,6 +23,7 @@
  *
  * @hide
  */
+@SystemApi
 public class CarSettings {
 
     private CarSettings() {
@@ -73,6 +74,22 @@
                 "android.car.ENABLE_USER_SWITCH_DEVELOPER_MESSAGE";
 
         /**
+         * User id of the last foreground user
+         *
+         * @hide
+         */
+        public static final String LAST_ACTIVE_USER_ID =
+                        "android.car.LAST_ACTIVE_USER_ID";
+
+        /**
+         * User id of the last persistent (i.e, not counting ephemeral guests) foreground user
+         *
+         * @hide
+         */
+        public static final String LAST_ACTIVE_PERSISTENT_USER_ID =
+                        "android.car.LAST_ACTIVE_PERSISTENT_USER_ID";
+
+        /**
          * Defines global runtime overrides to system bar policy.
          *
          * See {@link com.android.systemui.wm.BarControlPolicy} for value format.
diff --git a/car-lib/src/android/car/storagemonitoring/CarStorageMonitoringManager.java b/car-lib/src/android/car/storagemonitoring/CarStorageMonitoringManager.java
index ac59bac..6f993e3 100644
--- a/car-lib/src/android/car/storagemonitoring/CarStorageMonitoringManager.java
+++ b/car-lib/src/android/car/storagemonitoring/CarStorageMonitoringManager.java
@@ -47,9 +47,17 @@
     private final SingleMessageHandler<IoStats> mMessageHandler;
     private final Set<IoStatsListener> mListeners = new HashSet<>();
 
+    /**
+     * Implementers will be notified on every new I/O activity calculated stats.
+     */
     public interface IoStatsListener {
+
+        /**
+         * Invoked when a new periodic snapshot delta of I/O activities is calculated.
+         */
         void onSnapshot(IoStats snapshot);
     }
+
     private static final class ListenerToService extends IIoStatsListener.Stub {
         private final WeakReference<CarStorageMonitoringManager> mManager;
 
@@ -109,7 +117,7 @@
      * It will return either PRE_EOL_INFO_UNKNOWN if the value can't be determined,
      * or one of PRE_EOL_INFO_{NORMAL|WARNING|URGENT} depending on the device state.
      */
-    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+    @RequiresPermission(value = Car.PERMISSION_STORAGE_MONITORING)
     public int getPreEolIndicatorStatus() {
         try {
             return mService.getPreEolIndicatorStatus();
@@ -127,7 +135,7 @@
      *
      * If either or both indicators are not available, they will be reported as UNKNOWN.
      */
-    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+    @RequiresPermission(value = Car.PERMISSION_STORAGE_MONITORING)
     public WearEstimate getWearEstimate() {
         try {
             return mService.getWearEstimate();
@@ -147,7 +155,7 @@
      *
      * If no indicators are available, an empty list will be returned.
      */
-    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+    @RequiresPermission(value = Car.PERMISSION_STORAGE_MONITORING)
     public List<WearEstimateChange> getWearEstimateHistory() {
         try {
             return mService.getWearEstimateHistory();
@@ -166,7 +174,7 @@
      *
      * If the information is not available, an empty list will be returned.
      */
-    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+    @RequiresPermission(value = Car.PERMISSION_STORAGE_MONITORING)
     public List<IoStatsEntry> getBootIoStats() {
         try {
             return mService.getBootIoStats();
@@ -196,7 +204,7 @@
      *
      * <p>If the information is not available, SHUTDOWN_COST_INFO_MISSING will be returned.</p>s
      */
-    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+    @RequiresPermission(value = Car.PERMISSION_STORAGE_MONITORING)
     public long getShutdownDiskWriteAmount() {
         try {
             return mService.getShutdownDiskWriteAmount();
@@ -213,7 +221,7 @@
      *
      * If the information is not available, an empty list will be returned.
      */
-    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+    @RequiresPermission(value = Car.PERMISSION_STORAGE_MONITORING)
     public List<IoStatsEntry> getAggregateIoStats() {
         try {
             return mService.getAggregateIoStats();
@@ -233,7 +241,7 @@
      *
      * If the information is not available, an empty list will be returned.
      */
-    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+    @RequiresPermission(value = Car.PERMISSION_STORAGE_MONITORING)
     public List<IoStats> getIoStatsDeltas() {
         try {
             return mService.getIoStatsDeltas();
@@ -250,7 +258,7 @@
      *
      * The timing of availability of the deltas is configurable by the OEM.
      */
-    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+    @RequiresPermission(value = Car.PERMISSION_STORAGE_MONITORING)
     public void registerListener(IoStatsListener listener) {
         try {
             if (mListeners.isEmpty()) {
@@ -268,7 +276,7 @@
     /**
      * This method removes a registered listener of I/O stats deltas.
      */
-    @RequiresPermission(value=Car.PERMISSION_STORAGE_MONITORING)
+    @RequiresPermission(value = Car.PERMISSION_STORAGE_MONITORING)
     public void unregisterListener(IoStatsListener listener) {
         try {
             if (!mListeners.remove(listener)) {
diff --git a/car-lib/src/android/car/storagemonitoring/WearEstimate.java b/car-lib/src/android/car/storagemonitoring/WearEstimate.java
index ee6aa31..07ef4c7 100644
--- a/car-lib/src/android/car/storagemonitoring/WearEstimate.java
+++ b/car-lib/src/android/car/storagemonitoring/WearEstimate.java
@@ -47,11 +47,10 @@
     public static final WearEstimate UNKNOWN_ESTIMATE = new WearEstimate(UNKNOWN, UNKNOWN);
 
     public static final Parcelable.Creator<WearEstimate> CREATOR =
-        new Parcelable.Creator<WearEstimate>() {
-            public WearEstimate createFromParcel(Parcel in) {
+            new Parcelable.Creator<WearEstimate>() {
+        public WearEstimate createFromParcel(Parcel in) {
                 return new WearEstimate(in);
             }
-
             public WearEstimate[] newArray(int size) {
                 return new WearEstimate[size];
             }
@@ -60,16 +59,16 @@
     /**
      * Wear estimate data for "type A" storage.
      */
-    @IntRange(from=-1, to=100)
+    @IntRange(from = -1, to = 100)
     public final int typeA;
 
     /**
      * Wear estimate data for "type B" storage.
      */
-    @IntRange(from=-1, to=100)
+    @IntRange(from = -1, to = 100)
     public final int typeB;
 
-    private static final int validateWearValue(int value) {
+    private static int validateWearValue(int value) {
         if (value == UNKNOWN) return value;
         if ((value >= 0) && (value <= 100)) return value;
         throw new IllegalArgumentException(value + " is not a valid wear estimate");
@@ -137,7 +136,7 @@
     @Override
     public boolean equals(Object other) {
         if (other instanceof WearEstimate) {
-            WearEstimate wo = (WearEstimate)other;
+            WearEstimate wo = (WearEstimate) other;
             return wo.typeA == typeA && wo.typeB == typeB;
         }
         return false;
@@ -148,7 +147,7 @@
         return Objects.hash(typeA, typeB);
     }
 
-    private static final String wearValueToString(int value) {
+    private static String wearValueToString(int value) {
         if (value == UNKNOWN) return "unknown";
         return value + "%";
     }
diff --git a/car-lib/src/android/car/storagemonitoring/WearEstimateChange.java b/car-lib/src/android/car/storagemonitoring/WearEstimateChange.java
index 2d454b4..a2839d5 100644
--- a/car-lib/src/android/car/storagemonitoring/WearEstimateChange.java
+++ b/car-lib/src/android/car/storagemonitoring/WearEstimateChange.java
@@ -15,15 +15,16 @@
  */
 package android.car.storagemonitoring;
 
+import static java.util.Objects.requireNonNull;
+
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
+
 import java.time.Instant;
 import java.util.Objects;
 
-import static java.util.Objects.requireNonNull;
-
 /**
  * Change in wear-out information.
  *
@@ -35,15 +36,15 @@
 @SystemApi
 public final class WearEstimateChange implements Parcelable {
     public static final Parcelable.Creator<WearEstimateChange> CREATOR =
-        new Parcelable.Creator<WearEstimateChange>() {
-            public WearEstimateChange createFromParcel(Parcel in) {
-                return new WearEstimateChange(in);
-            }
+            new Parcelable.Creator<WearEstimateChange>() {
+        public WearEstimateChange createFromParcel(Parcel in) {
+            return new WearEstimateChange(in);
+        }
 
-            public WearEstimateChange[] newArray(int size) {
+        public WearEstimateChange[] newArray(int size) {
                 return new WearEstimateChange[size];
             }
-        };
+    };
 
     /**
      * The previous wear estimate.
@@ -110,12 +111,12 @@
     @Override
     public boolean equals(Object other) {
         if (other instanceof WearEstimateChange) {
-            WearEstimateChange wo = (WearEstimateChange)other;
-            return wo.isAcceptableDegradation == isAcceptableDegradation &&
-                wo.uptimeAtChange == uptimeAtChange &&
-                wo.dateAtChange.equals(dateAtChange) &&
-                wo.oldEstimate.equals(oldEstimate) &&
-                wo.newEstimate.equals(newEstimate);
+            WearEstimateChange wo = (WearEstimateChange) other;
+            return wo.isAcceptableDegradation == isAcceptableDegradation
+                    && wo.uptimeAtChange == uptimeAtChange
+                    && wo.dateAtChange.equals(dateAtChange)
+                    && wo.oldEstimate.equals(oldEstimate)
+                    && wo.newEstimate.equals(newEstimate);
         }
         return false;
     }
diff --git a/car-lib/src/android/car/user/CarUserManager.java b/car-lib/src/android/car/user/CarUserManager.java
index 048ae0b..fcdc89a 100644
--- a/car-lib/src/android/car/user/CarUserManager.java
+++ b/car-lib/src/android/car/user/CarUserManager.java
@@ -32,6 +32,7 @@
 import android.car.CarManagerBase;
 import android.car.ICarUserService;
 import android.content.pm.UserInfo;
+import android.content.pm.UserInfo.UserInfoFlag;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -208,7 +209,7 @@
                 @Override
                 protected void onCompleted(UserSwitchResult result, Throwable err) {
                     if (result != null) {
-                        EventLog.writeEvent(EventLogTags.CAR_USER_MGR_SWITCH_USER_RESPONSE, uid,
+                        EventLog.writeEvent(EventLogTags.CAR_USER_MGR_SWITCH_USER_RESP, uid,
                                 result.getStatus(), result.getErrorMessage());
                     } else {
                         Log.w(TAG, "switchUser(" + targetUserId + ") failed: " + err);
@@ -216,7 +217,7 @@
                     super.onCompleted(result, err);
                 };
             };
-            EventLog.writeEvent(EventLogTags.CAR_USER_MGR_SWITCH_USER_REQUEST, uid, targetUserId);
+            EventLog.writeEvent(EventLogTags.CAR_USER_MGR_SWITCH_USER_REQ, uid, targetUserId);
             mService.switchUser(targetUserId, HAL_TIMEOUT_MS, future);
             return future;
         } catch (RemoteException e) {
@@ -228,6 +229,64 @@
     }
 
     /**
+     * Creates a new Android user.
+     *
+     * @hide
+     */
+    @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.CREATE_USERS})
+    public AndroidFuture<UserCreationResult> createUser(@Nullable String name,
+            @NonNull String userType, @UserInfoFlag int flags) {
+        int uid = myUid();
+        try {
+            AndroidFuture<UserCreationResult> future = new AndroidFuture<UserCreationResult>() {
+                @Override
+                protected void onCompleted(UserCreationResult result, Throwable err) {
+                    if (result != null) {
+                        EventLog.writeEvent(EventLogTags.CAR_USER_MGR_CREATE_USER_RESP, uid,
+                                result.getStatus(), result.getErrorMessage());
+                    } else {
+                        Log.w(TAG, "createUser(" + userType + "," + UserInfo.flagsToString(flags)
+                                + ") failed: " + err);
+                    }
+                    super.onCompleted(result, err);
+                };
+            };
+            EventLog.writeEvent(EventLogTags.CAR_USER_MGR_CREATE_USER_REQ, uid,
+                    safeName(name), userType, flags);
+            mService.createUser(name, userType, flags, HAL_TIMEOUT_MS, future);
+            return future;
+        } catch (RemoteException e) {
+            AndroidFuture<UserCreationResult> future = new AndroidFuture<>();
+            future.complete(new UserCreationResult(UserCreationResult.STATUS_HAL_INTERNAL_FAILURE,
+                    null, null));
+            return handleRemoteExceptionFromCarService(e, future);
+        }
+    }
+
+     /**
+     * Removes a user.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public UserRemovalResult removeUser(@UserIdInt int userId) {
+        int uid = myUid();
+        EventLog.writeEvent(EventLogTags.CAR_USER_MGR_REMOVE_USER_REQ, uid, userId);
+        int status = UserRemovalResult.STATUS_HAL_INTERNAL_FAILURE;
+        try {
+            UserRemovalResult result = mService.removeUser(userId);
+            status = result.getStatus();
+            return result;
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e,
+                    new UserRemovalResult(UserRemovalResult.STATUS_HAL_INTERNAL_FAILURE));
+        } finally {
+            EventLog.writeEvent(EventLogTags.CAR_USER_MGR_REMOVE_USER_RESP, uid, status);
+        }
+    }
+
+    /**
      * Adds a listener for {@link UserLifecycleEvent user lifecycle events}.
      *
      * @throws IllegalStateException if the listener was already added.
@@ -504,7 +563,7 @@
      * @hide
      */
     public boolean isValidUser(@UserIdInt int userId) {
-        List<UserInfo> allUsers = mUserManager.getUsers();
+        List<UserInfo> allUsers = mUserManager.getUsers(/* excludeDying= */ true);
         for (int i = 0; i < allUsers.size(); i++) {
             UserInfo user = allUsers.get(i);
             if (user.id == userId && (userId != UserHandle.USER_SYSTEM
@@ -515,6 +574,13 @@
         return false;
     }
 
+    // TODO(b/150413515): use from UserHelper instead (would require a new make target, otherwise it
+    // would include the whole car-user-lib)
+    @Nullable
+    private static String safeName(@Nullable String name) {
+        return name == null ? name : name.length() + "_chars";
+    }
+
     /**
      * Defines a lifecycle event for an Android user.
      *
diff --git a/car-lib/src/android/car/user/CommonResults.java b/car-lib/src/android/car/user/CommonResults.java
new file mode 100644
index 0000000..9238a0b
--- /dev/null
+++ b/car-lib/src/android/car/user/CommonResults.java
@@ -0,0 +1,57 @@
+/*
+ * 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 android.car.user;
+
+/**
+ * Defines status for common result objects.
+ */
+final class CommonResults {
+
+    /**
+     * Operation was is successful for both HAL and Android.
+     */
+    public static final int STATUS_SUCCESSFUL = 1;
+
+    /**
+     * Operation failed on Android.
+     */
+    public static final int STATUS_ANDROID_FAILURE = 2;
+
+    /**
+     * Operation failed on HAL.
+     */
+    public static final int STATUS_HAL_FAILURE = 3;
+
+    /**
+     * Operation failed due to an error communication with HAL (like timeout).
+     */
+    public static final int STATUS_HAL_INTERNAL_FAILURE = 4;
+
+    /**
+     * Operation failed due to invalid request.
+     */
+    public static final int STATUS_INVALID_REQUEST = 5;
+
+    /**
+     * Reference for common status - anything higher than this can be used for custom status
+     */
+    static final int LAST_COMMON_STATUS = 100;
+
+    private CommonResults() {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/car-lib/src/android/car/user/UserCreationResult.aidl b/car-lib/src/android/car/user/UserCreationResult.aidl
new file mode 100644
index 0000000..a735202
--- /dev/null
+++ b/car-lib/src/android/car/user/UserCreationResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.car.user;
+
+parcelable UserCreationResult;
diff --git a/car-lib/src/android/car/user/UserCreationResult.java b/car-lib/src/android/car/user/UserCreationResult.java
new file mode 100644
index 0000000..fa29cf4
--- /dev/null
+++ b/car-lib/src/android/car/user/UserCreationResult.java
@@ -0,0 +1,281 @@
+/*
+ * 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 android.car.user;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.content.pm.UserInfo;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * User creation results.
+ *
+ * @hide
+ */
+@DataClass(
+        genToString = true,
+        genHiddenConstructor = true,
+        genHiddenConstDefs = true)
+public final class UserCreationResult implements Parcelable {
+
+    /**
+     * {@link Status} called when user creation is successful for both HAL and Android.
+     *
+     * @hide
+     */
+    public static final int STATUS_SUCCESSFUL = CommonResults.STATUS_SUCCESSFUL;
+
+    /**
+     * {@link Status} called when user creation failed on Android - HAL is not even called in this
+     * case.
+     *
+     * @hide
+     */
+    public static final int STATUS_ANDROID_FAILURE = CommonResults.STATUS_ANDROID_FAILURE;
+
+    /**
+     * {@link Status} called when user was created on Android but HAL returned a failure - the
+     * Android user is automatically removed.
+     *
+     * @hide
+     */
+    public static final int STATUS_HAL_FAILURE = CommonResults.STATUS_HAL_FAILURE;
+
+    /**
+     * {@link Status} called when user creation is failed for HAL for some internal error - the
+     * Android user is not automatically removed.
+     *
+     * @hide
+     */
+    public static final int STATUS_HAL_INTERNAL_FAILURE = CommonResults.STATUS_HAL_INTERNAL_FAILURE;
+
+    /**
+     * Gets the user switch result status.
+     *
+     * @return either {@link UserCreationResult#STATUS_SUCCESSFUL},
+     *         {@link UserCreationResult#STATUS_ANDROID_FAILURE},
+     *         {@link UserCreationResult#STATUS_HAL_FAILURE},
+     *         or
+     *         {@link UserCreationResult#STATUS_HAL_INTERNAL_FAILURE}
+     */
+    private final @Status int mStatus;
+
+    /**
+     * Gets the created user.
+     */
+    @Nullable
+    private final UserInfo mUser;
+
+    /**
+     * Gets the error message sent by HAL, if any.
+     */
+    @Nullable
+    private final String mErrorMessage;
+
+    /**
+     * Checks if this result is successful.
+     */
+    public boolean isSuccess() {
+        return mStatus == STATUS_SUCCESSFUL;
+    }
+
+    // TODO(b/158195639): if you change any status constant, you need to manually assign its values
+    // (rather than using CommonResults) before running codegen to regenerate the class
+
+
+    // Code below generated by codegen v1.0.15.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/packages/services/Car/car-lib/src/android/car/user/UserCreationResult.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /** @hide */
+    @IntDef(prefix = "STATUS_", value = {
+        STATUS_SUCCESSFUL,
+        STATUS_ANDROID_FAILURE,
+        STATUS_HAL_FAILURE,
+        STATUS_HAL_INTERNAL_FAILURE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface Status {}
+
+    /** @hide */
+    @DataClass.Generated.Member
+    public static String statusToString(@Status int value) {
+        switch (value) {
+            case STATUS_SUCCESSFUL:
+                    return "STATUS_SUCCESSFUL";
+            case STATUS_ANDROID_FAILURE:
+                    return "STATUS_ANDROID_FAILURE";
+            case STATUS_HAL_FAILURE:
+                    return "STATUS_HAL_FAILURE";
+            case STATUS_HAL_INTERNAL_FAILURE:
+                    return "STATUS_HAL_INTERNAL_FAILURE";
+            default: return Integer.toHexString(value);
+        }
+    }
+
+    /**
+     * Creates a new UserCreationResult.
+     *
+     * @param status
+     *   Gets the user switch result status.
+     *
+     *   @return either {@link UserCreationResult#STATUS_SUCCESSFUL},
+     *           {@link UserCreationResult#STATUS_ANDROID_FAILURE},
+     *           {@link UserCreationResult#STATUS_HAL_FAILURE},
+     *           or
+     *           {@link UserCreationResult#STATUS_HAL_INTERNAL_FAILURE}
+     * @param user
+     *   Gets the created user.
+     * @param errorMessage
+     *   Gets the error message sent by HAL, if any.
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public UserCreationResult(
+            int status,
+            @Nullable UserInfo user,
+            @Nullable String errorMessage) {
+        this.mStatus = status;
+        this.mUser = user;
+        this.mErrorMessage = errorMessage;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * Gets the user switch result status.
+     *
+     * @return either {@link UserCreationResult#STATUS_SUCCESSFUL},
+     *         {@link UserCreationResult#STATUS_ANDROID_FAILURE},
+     *         {@link UserCreationResult#STATUS_HAL_FAILURE},
+     *         or
+     *         {@link UserCreationResult#STATUS_HAL_INTERNAL_FAILURE}
+     */
+    @DataClass.Generated.Member
+    public int getStatus() {
+        return mStatus;
+    }
+
+    /**
+     * Gets the created user.
+     */
+    @DataClass.Generated.Member
+    public @Nullable UserInfo getUser() {
+        return mUser;
+    }
+
+    /**
+     * Gets the error message sent by HAL, if any.
+     */
+    @DataClass.Generated.Member
+    public @Nullable String getErrorMessage() {
+        return mErrorMessage;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "UserCreationResult { " +
+                "status = " + mStatus + ", " +
+                "user = " + mUser + ", " +
+                "errorMessage = " + mErrorMessage +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mUser != null) flg |= 0x2;
+        if (mErrorMessage != null) flg |= 0x4;
+        dest.writeByte(flg);
+        dest.writeInt(mStatus);
+        if (mUser != null) dest.writeTypedObject(mUser, flags);
+        if (mErrorMessage != null) dest.writeString(mErrorMessage);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ UserCreationResult(@android.annotation.NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        int status = in.readInt();
+        UserInfo user = (flg & 0x2) == 0 ? null : (UserInfo) in.readTypedObject(UserInfo.CREATOR);
+        String errorMessage = (flg & 0x4) == 0 ? null : in.readString();
+
+        this.mStatus = status;
+        this.mUser = user;
+        this.mErrorMessage = errorMessage;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @android.annotation.NonNull Parcelable.Creator<UserCreationResult> CREATOR
+            = new Parcelable.Creator<UserCreationResult>() {
+        @Override
+        public UserCreationResult[] newArray(int size) {
+            return new UserCreationResult[size];
+        }
+
+        @Override
+        public UserCreationResult createFromParcel(@android.annotation.NonNull android.os.Parcel in) {
+            return new UserCreationResult(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1591121994170L,
+            codegenVersion = "1.0.15",
+            sourceFile = "packages/services/Car/car-lib/src/android/car/user/UserCreationResult.java",
+            inputSignatures = "public static final  int STATUS_SUCCESSFUL\npublic static final  int STATUS_ANDROID_FAILURE\npublic static final  int STATUS_HAL_FAILURE\npublic static final  int STATUS_HAL_INTERNAL_FAILURE\nprivate final  int mStatus\nprivate final @android.annotation.Nullable android.content.pm.UserInfo mUser\nprivate final @android.annotation.Nullable java.lang.String mErrorMessage\npublic  boolean isSuccess()\nclass UserCreationResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/car-lib/src/android/car/user/UserRemovalResult.aidl b/car-lib/src/android/car/user/UserRemovalResult.aidl
new file mode 100644
index 0000000..00a0eba
--- /dev/null
+++ b/car-lib/src/android/car/user/UserRemovalResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.car.user;
+
+parcelable UserRemovalResult;
diff --git a/car-lib/src/android/car/user/UserRemovalResult.java b/car-lib/src/android/car/user/UserRemovalResult.java
new file mode 100644
index 0000000..dbfd8a6
--- /dev/null
+++ b/car-lib/src/android/car/user/UserRemovalResult.java
@@ -0,0 +1,249 @@
+/*
+ * 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 android.car.user;
+
+import android.annotation.IntDef;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * User remove result.
+ *
+ * @hide
+ */
+@DataClass(
+        genToString = true,
+        genHiddenConstructor = true,
+        genHiddenConstDefs = true)
+public final class UserRemovalResult implements Parcelable {
+
+    /**
+     * When user remove is successful.
+     *
+     * @hide
+     */
+    public static final int STATUS_SUCCESSFUL = CommonResults.STATUS_SUCCESSFUL;
+
+    /**
+     * When user remove fails for android. Hal user is not removed.
+     *
+     * @hide
+     */
+    public static final int STATUS_ANDROID_FAILURE = CommonResults.STATUS_ANDROID_FAILURE;
+
+    /**
+     * When remove user fails for unknown error.
+     *
+     * @hide
+     */
+    public static final int STATUS_HAL_INTERNAL_FAILURE = CommonResults.STATUS_HAL_INTERNAL_FAILURE;
+
+     /**
+     * When user to remove is same as current user.
+     *
+     * @hide
+     */
+    public static final int STATUS_TARGET_USER_IS_CURRENT_USER =
+            CommonResults.LAST_COMMON_STATUS + 1;
+
+    /**
+     * When user to remove doesn't exits.
+     *
+     * @hide
+     */
+    public static final int STATUS_USER_DOES_NOT_EXIST = CommonResults.LAST_COMMON_STATUS + 2;
+
+    /**
+     * When user to remove is last admin user.
+     *
+     * @hide
+     */
+    public static final int STATUS_TARGET_USER_IS_LAST_ADMIN_USER =
+            CommonResults.LAST_COMMON_STATUS + 3;
+
+    /**
+     * Gets the user switch result status.
+     *
+     * @return either {@link UserRemovalResult#STATUS_SUCCESSFUL},
+     *         {@link UserRemovalResult#STATUS_ANDROID_FAILURE},
+     *         {@link UserRemovalResult#STATUS_HAL_INTERNAL_FAILURE},
+     *         {@link UserRemovalResult#STATUS_TARGET_USER_IS_CURRENT_USER},
+     *         {@link UserRemovalResult#STATUS_USER_DOES_NOT_EXIST}, or
+     *         {@link UserRemovalResult#STATUS_TARGET_USER_IS_LAST_ADMIN_USER}.
+     */
+    private final @Status int mStatus;
+
+    public boolean isSuccess() {
+        return mStatus == STATUS_SUCCESSFUL;
+    }
+
+    // TODO(b/158195639): if you change any status constant, you need to manually assign its values
+    // (rather than using CommonResults) before running codegen to regenerate the class
+
+
+    // Code below generated by codegen v1.0.15.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/packages/services/Car/car-lib/src/android/car/user/UserRemovalResult.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /** @hide */
+    @IntDef(prefix = "STATUS_", value = {
+        STATUS_SUCCESSFUL,
+        STATUS_ANDROID_FAILURE,
+        STATUS_HAL_INTERNAL_FAILURE,
+        STATUS_TARGET_USER_IS_CURRENT_USER,
+        STATUS_USER_DOES_NOT_EXIST,
+        STATUS_TARGET_USER_IS_LAST_ADMIN_USER
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface Status {}
+
+    /** @hide */
+    @DataClass.Generated.Member
+    public static String statusToString(@Status int value) {
+        switch (value) {
+            case STATUS_SUCCESSFUL:
+                    return "STATUS_SUCCESSFUL";
+            case STATUS_ANDROID_FAILURE:
+                    return "STATUS_ANDROID_FAILURE";
+            case STATUS_HAL_INTERNAL_FAILURE:
+                    return "STATUS_HAL_INTERNAL_FAILURE";
+            case STATUS_TARGET_USER_IS_CURRENT_USER:
+                    return "STATUS_TARGET_USER_IS_CURRENT_USER";
+            case STATUS_USER_DOES_NOT_EXIST:
+                    return "STATUS_USER_DOES_NOT_EXIST";
+            case STATUS_TARGET_USER_IS_LAST_ADMIN_USER:
+                    return "STATUS_TARGET_USER_IS_LAST_ADMIN_USER";
+            default: return Integer.toHexString(value);
+        }
+    }
+
+    /**
+     * Creates a new UserRemovalResult.
+     *
+     * @param status
+     *   Gets the user switch result status.
+     *
+     *   @return either {@link UserRemovalResult#STATUS_SUCCESSFUL},
+     *           {@link UserRemovalResult#STATUS_ANDROID_FAILURE},
+     *           {@link UserRemovalResult#STATUS_HAL_INTERNAL_FAILURE},
+     *           {@link UserRemovalResult#STATUS_TARGET_USER_IS_CURRENT_USER},
+     *           {@link UserRemovalResult#STATUS_USER_DOES_NOT_EXIST}, or
+     *           {@link UserRemovalResult#STATUS_TARGET_USER_IS_LAST_ADMIN_USER}.
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public UserRemovalResult(
+            int status) {
+        this.mStatus = status;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * Gets the user switch result status.
+     *
+     * @return either {@link UserRemovalResult#STATUS_SUCCESSFUL},
+     *         {@link UserRemovalResult#STATUS_ANDROID_FAILURE},
+     *         {@link UserRemovalResult#STATUS_HAL_INTERNAL_FAILURE},
+     *         {@link UserRemovalResult#STATUS_TARGET_USER_IS_CURRENT_USER},
+     *         {@link UserRemovalResult#STATUS_USER_DOES_NOT_EXIST}, or
+     *         {@link UserRemovalResult#STATUS_TARGET_USER_IS_LAST_ADMIN_USER}.
+     */
+    @DataClass.Generated.Member
+    public int getStatus() {
+        return mStatus;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "UserRemovalResult { " +
+                "status = " + mStatus +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeInt(mStatus);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ UserRemovalResult(@android.annotation.NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        int status = in.readInt();
+
+        this.mStatus = status;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @android.annotation.NonNull Parcelable.Creator<UserRemovalResult> CREATOR
+            = new Parcelable.Creator<UserRemovalResult>() {
+        @Override
+        public UserRemovalResult[] newArray(int size) {
+            return new UserRemovalResult[size];
+        }
+
+        @Override
+        public UserRemovalResult createFromParcel(@android.annotation.NonNull android.os.Parcel in) {
+            return new UserRemovalResult(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1591259644931L,
+            codegenVersion = "1.0.15",
+            sourceFile = "packages/services/Car/car-lib/src/android/car/user/UserRemovalResult.java",
+            inputSignatures = "public static final  int STATUS_SUCCESSFUL\npublic static final  int STATUS_ANDROID_FAILURE\npublic static final  int STATUS_HAL_INTERNAL_FAILURE\npublic static final  int STATUS_TARGET_USER_IS_CURRENT_USER\npublic static final  int STATUS_USER_DOES_NOT_EXIST\npublic static final  int STATUS_TARGET_USER_IS_LAST_ADMIN_USER\nprivate final  int mStatus\npublic  boolean isSuccess()\nclass UserRemovalResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/car-lib/src/android/car/user/UserSwitchResult.java b/car-lib/src/android/car/user/UserSwitchResult.java
index 5792bf6..55e2dbc 100644
--- a/car-lib/src/android/car/user/UserSwitchResult.java
+++ b/car-lib/src/android/car/user/UserSwitchResult.java
@@ -37,66 +37,51 @@
 public final class UserSwitchResult implements Parcelable {
 
     /**
-     * {@link UserSwitchStatus} called when user switch is successful for both HAL and Android.
-     *
-     * @hide
+     * When user switch is successful for both HAL and Android.
      */
-    public static final int STATUS_SUCCESSFUL = 1;
+    public static final int STATUS_SUCCESSFUL = CommonResults.STATUS_SUCCESSFUL;
 
     /**
-     * {@link UserSwitchStatus} called when user switch is only successful for Hal but not for
-     * Android. Hal user switch rollover message have been sent.
-     *
-     * @hide
+     * When user switch is only successful for Hal but not for Android. Hal user switch rollover
+     * message have been sent.
      */
-    public static final int STATUS_ANDROID_FAILURE = 2;
+    public static final int STATUS_ANDROID_FAILURE = CommonResults.STATUS_ANDROID_FAILURE;
 
     /**
-     * {@link UserSwitchStatus} called when user switch is failed for HAL. User switch for Android
-     * is not called.
-     *
-     * @hide
+     * When user switch fails for HAL. User switch for Android is not called.
      */
-    public static final int STATUS_HAL_FAILURE = 3;
+    public static final int STATUS_HAL_FAILURE = CommonResults.STATUS_HAL_FAILURE;
 
     /**
-     * {@link UserSwitchStatus} called when user switch is failed for HAL for some internal error.
-     * User switch for Android is not called.
-     *
-     * @hide
+     * When user switch fails for HAL for some internal error. User switch for Android is not
+     * called.
      */
-    public static final int STATUS_HAL_INTERNAL_FAILURE = 4;
+    public static final int STATUS_HAL_INTERNAL_FAILURE = CommonResults.STATUS_HAL_INTERNAL_FAILURE;
 
     /**
-     * {@link UserSwitchStatus} called when target user is same as current user.
-     *
-     * @hide
+     * When given parameters or environment states are invalid for switching user. HAL or Android
+     * user switch is not requested.
      */
-    public static final int STATUS_ALREADY_REQUESTED_USER = 5;
+    public static final int STATUS_INVALID_REQUEST = CommonResults.STATUS_INVALID_REQUEST;
 
     /**
-     * {@link UserSwitchStatus} called when another user switch request for the same target user is
-     * in process.
-     *
-     * @hide
+     * When target user is same as current user.
      */
-    public static final int STATUS_TARGET_USER_ALREADY_BEING_SWITCHED_TO = 6;
+    public static final int STATUS_ALREADY_REQUESTED_USER =
+            CommonResults.LAST_COMMON_STATUS + 1;
 
     /**
-     * {@link UserSwitchStatus} called when another user switch request for a new different target
-     * user is received. Previous request is abandoned.
-     *
-     * @hide
+     * When another user switch request for the same target user is in process.
      */
-    public static final int STATUS_TARGET_USER_ABANDONED_DUE_TO_A_NEW_REQUEST = 7;
+    public static final int STATUS_TARGET_USER_ALREADY_BEING_SWITCHED_TO =
+            CommonResults.LAST_COMMON_STATUS + 2;
 
     /**
-     * {@link UserSwitchStatus} called when given parameters or environment states are invalid for
-     * switching user. HAL or Android user switch is not requested.
-     *
-     * @hide
+     * When another user switch request for a new different target user is received. Previous
+     * request is abandoned.
      */
-    public static final int STATUS_INVALID_REQUEST = 8;
+    public static final int STATUS_TARGET_USER_ABANDONED_DUE_TO_A_NEW_REQUEST =
+            CommonResults.LAST_COMMON_STATUS + 3;
 
     /**
      * Gets the user switch result status.
@@ -106,11 +91,11 @@
      *         {@link UserSwitchResult#STATUS_HAL_FAILURE},
      *         {@link UserSwitchResult#STATUS_HAL_INTERNAL_FAILURE},
      *         {@link UserSwitchResult#STATUS_ALREADY_REQUESTED_USER},
-     *         {@link UserSwitchResult#STATUS_TARGET_USER_ALREADY_BEING_SWITCHED_TO}
+     *         {@link UserSwitchResult#STATUS_TARGET_USER_ALREADY_BEING_SWITCHED_TO},
      *         {@link UserSwitchResult#STATUS_TARGET_USER_ABANDONED_DUE_TO_A_NEW_REQUEST}, or
      *         {@link UserSwitchResult#STATUS_INVALID_REQUEST}.
      */
-    private final int mStatus;
+    private final @Status int mStatus;
 
     /**
      * Gets the error message, if any.
@@ -118,6 +103,15 @@
     @Nullable
     private final String mErrorMessage;
 
+    /**
+     * Check if {@link UserSwitchResult} is successful.
+     */
+    public boolean isSuccess() {
+        return mStatus == STATUS_SUCCESSFUL || mStatus == STATUS_ALREADY_REQUESTED_USER;
+    }
+
+    // TODO(b/158195639): if you change any status constant, you need to manually assign its values
+    // (rather than using CommonResults) before running codegen to regenerate the class
 
 
     // Code below generated by codegen v1.0.15.
@@ -183,7 +177,7 @@
      *           {@link UserSwitchResult#STATUS_HAL_FAILURE},
      *           {@link UserSwitchResult#STATUS_HAL_INTERNAL_FAILURE},
      *           {@link UserSwitchResult#STATUS_ALREADY_REQUESTED_USER},
-     *           {@link UserSwitchResult#STATUS_TARGET_USER_ALREADY_BEING_SWITCHED_TO}
+     *           {@link UserSwitchResult#STATUS_TARGET_USER_ALREADY_BEING_SWITCHED_TO},
      *           {@link UserSwitchResult#STATUS_TARGET_USER_ABANDONED_DUE_TO_A_NEW_REQUEST}, or
      *           {@link UserSwitchResult#STATUS_INVALID_REQUEST}.
      * @param errorMessage
@@ -208,7 +202,7 @@
      *         {@link UserSwitchResult#STATUS_HAL_FAILURE},
      *         {@link UserSwitchResult#STATUS_HAL_INTERNAL_FAILURE},
      *         {@link UserSwitchResult#STATUS_ALREADY_REQUESTED_USER},
-     *         {@link UserSwitchResult#STATUS_TARGET_USER_ALREADY_BEING_SWITCHED_TO}
+     *         {@link UserSwitchResult#STATUS_TARGET_USER_ALREADY_BEING_SWITCHED_TO},
      *         {@link UserSwitchResult#STATUS_TARGET_USER_ABANDONED_DUE_TO_A_NEW_REQUEST}, or
      *         {@link UserSwitchResult#STATUS_INVALID_REQUEST}.
      */
@@ -286,10 +280,10 @@
     };
 
     @DataClass.Generated(
-            time = 1589580732431L,
+            time = 1590737883648L,
             codegenVersion = "1.0.15",
             sourceFile = "packages/services/Car/car-lib/src/android/car/user/UserSwitchResult.java",
-            inputSignatures = "public static final  int STATUS_SUCCESSFUL\npublic static final  int STATUS_ANDROID_FAILURE\npublic static final  int STATUS_HAL_FAILURE\npublic static final  int STATUS_HAL_INTERNAL_FAILURE\npublic static final  int STATUS_ALREADY_REQUESTED_USER\npublic static final  int STATUS_TARGET_USER_ALREADY_BEING_SWITCHED_TO\npublic static final  int STATUS_TARGET_USER_ABANDONED_DUE_TO_A_NEW_REQUEST\npublic static final  int STATUS_INVALID_REQUEST\nprivate final  int mStatus\nprivate final @android.annotation.Nullable java.lang.String mErrorMessage\nclass UserSwitchResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+            inputSignatures = "public static final  int STATUS_SUCCESSFUL\npublic static final  int STATUS_ANDROID_FAILURE\npublic static final  int STATUS_HAL_FAILURE\npublic static final  int STATUS_HAL_INTERNAL_FAILURE\npublic static final  int STATUS_ALREADY_REQUESTED_USER\npublic static final  int STATUS_TARGET_USER_ALREADY_BEING_SWITCHED_TO\npublic static final  int STATUS_TARGET_USER_ABANDONED_DUE_TO_A_NEW_REQUEST\npublic static final  int STATUS_INVALID_REQUEST\nprivate final  int mStatus\nprivate final @android.annotation.Nullable java.lang.String mErrorMessage\npublic  boolean isSuccess()\nclass UserSwitchResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/car-test-lib/Android.bp b/car-test-lib/Android.bp
index 965ea7b..4522982 100644
--- a/car-test-lib/Android.bp
+++ b/car-test-lib/Android.bp
@@ -43,5 +43,6 @@
     libs: [
         "android.hardware.automotive.vehicle-V2.0-java",
         "mockito-target-extended",
+        "compatibility-device-util-axt",
     ],
 }
diff --git a/car-test-lib/src/android/car/test/mocks/AbstractExtendedMockitoTestCase.java b/car-test-lib/src/android/car/test/mocks/AbstractExtendedMockitoTestCase.java
index 35f0274..c89a337 100644
--- a/car-test-lib/src/android/car/test/mocks/AbstractExtendedMockitoTestCase.java
+++ b/car-test-lib/src/android/car/test/mocks/AbstractExtendedMockitoTestCase.java
@@ -31,6 +31,8 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Trace;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -59,6 +61,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Base class for tests that must use {@link com.android.dx.mockito.inline.extended.ExtendedMockito}
@@ -83,11 +86,11 @@
  */
 public abstract class AbstractExtendedMockitoTestCase {
 
-    private static final boolean TRACE = false;
-
-    private static final boolean VERBOSE = false;
     private static final String TAG = AbstractExtendedMockitoTestCase.class.getSimpleName();
 
+    private static final boolean TRACE = false;
+    private static final boolean VERBOSE = false;
+
     private final List<Class<?>> mStaticSpiedClasses = new ArrayList<>();
 
     // Tracks (S)Log.wtf() calls made during code execution, then used on verifyWtfNeverLogged()
@@ -128,6 +131,7 @@
     @After
     public final void finishSession() {
         beginTrace("finishSession()");
+        completeAllHandlerThreadTasks();
         if (mSession != null) {
             beginTrace("finishMocking()");
             mSession.finishMocking();
@@ -139,6 +143,38 @@
     }
 
     /**
+     * Waits for completion of all pending Handler tasks for all HandlerThread in the process.
+     *
+     * <p>This can prevent pending Handler tasks of one test from affecting another. This does not
+     * work if the message is posted with delay.
+     */
+    protected void completeAllHandlerThreadTasks() {
+        beginTrace("completeAllHandlerThreadTasks");
+        Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
+        ArrayList<HandlerThread> handlerThreads = new ArrayList<>(threadSet.size());
+        Thread currentThread = Thread.currentThread();
+        for (Thread t : threadSet) {
+            if (t != currentThread && t instanceof HandlerThread) {
+                handlerThreads.add((HandlerThread) t);
+            }
+        }
+        ArrayList<SyncRunnable> syncs = new ArrayList<>(handlerThreads.size());
+        Log.i(TAG, "will wait for " + handlerThreads.size() + " HandlerThreads");
+        for (int i = 0; i < handlerThreads.size(); i++) {
+            Handler handler = new Handler(handlerThreads.get(i).getLooper());
+            SyncRunnable sr = new SyncRunnable(() -> { });
+            handler.post(sr);
+            syncs.add(sr);
+        }
+        beginTrace("waitForComplete");
+        for (int i = 0; i < syncs.size(); i++) {
+            syncs.get(i).waitForComplete();
+        }
+        endTrace(); // waitForComplete
+        endTrace(); // completeAllHandlerThreadTasks
+    }
+
+    /**
      * Adds key-value(int) pair in mocked Settings.Global and Settings.Secure
      */
     protected void putSettingsInt(@NonNull String key, int value) {
@@ -167,6 +203,13 @@
     }
 
     /**
+     * Asserts that the giving settings was not set.
+     */
+    protected void assertSettingsNotSet(String key) {
+        mSettings.assertDoesNotContainsKey(key);
+    }
+
+    /**
      * Subclasses can use this method to initialize the Mockito session that's started before every
      * test on {@link #startSession()}.
      *
@@ -486,6 +529,13 @@
         public int getInt(String key) {
             return get(key, null, Integer.class);
         }
+
+        public void assertDoesNotContainsKey(String key) {
+            if (mSettingsMapping.containsKey(key)) {
+                throw new AssertionError("Should not have key " + key + ", but has: "
+                        + mSettingsMapping.get(key));
+            }
+        }
     }
 
     /**
@@ -496,4 +546,33 @@
     @Target({METHOD})
     public static @interface ExpectWtf {
     }
+
+    private static final class SyncRunnable implements Runnable {
+        private final Runnable mTarget;
+        private volatile boolean mComplete = false;
+
+        private SyncRunnable(Runnable target) {
+            mTarget = target;
+        }
+
+        @Override
+        public void run() {
+            mTarget.run();
+            synchronized (this) {
+                mComplete = true;
+                notifyAll();
+            }
+        }
+
+        private void waitForComplete() {
+            synchronized (this) {
+                while (!mComplete) {
+                    try {
+                        wait();
+                    } catch (InterruptedException e) {
+                    }
+                }
+            }
+        }
+    }
 }
diff --git a/car-test-lib/src/android/car/test/mocks/AndroidMockitoHelper.java b/car-test-lib/src/android/car/test/mocks/AndroidMockitoHelper.java
index d05b013..20925e8 100644
--- a/car-test-lib/src/android/car/test/mocks/AndroidMockitoHelper.java
+++ b/car-test-lib/src/android/car/test/mocks/AndroidMockitoHelper.java
@@ -21,9 +21,11 @@
 import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.car.test.util.UserTestingHelper;
+import android.car.test.util.UserTestingHelper.UserInfoBuilder;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
@@ -78,7 +80,7 @@
     }
 
     /**
-     * Mocks {@code UserManager.getUserInfo(userId)} to return a {@link UserInfo} with the given
+     * Mocks {@code UserManager#getUserInfo(userId)} to return a {@link UserInfo} with the given
      * {@code flags}.
      */
     @NonNull
@@ -99,7 +101,7 @@
     }
 
     /**
-     * Mocks {@code UserManager.getUserInfo(userId)} when the {@code userId} is the system user's.
+     * Mocks {@code UserManager#getUserInfo(userId)} when the {@code userId} is the system user's.
      */
     @NonNull
     public static void mockUmGetSystemUser(@NonNull UserManager um) {
@@ -109,7 +111,7 @@
     }
 
     /**
-     * Mocks {@code UserManager.getUsers(excludeDying)} to return the given users.
+     * Mocks {@code UserManager#getUsers(excludeDying)} to return the given users.
      */
     public static void mockUmGetUsers(@NonNull UserManager um, @NonNull UserInfo... users) {
         Objects.requireNonNull(um);
@@ -118,7 +120,7 @@
     }
 
     /**
-     * Mocks {@code UserManager.getUsers(excludeDying)} to return simple users with the given ids.
+     * Mocks {@code UserManager#getUsers(excludeDying)} to return simple users with the given ids.
      */
     public static void mockUmGetUsers(@NonNull UserManager um, @NonNull @UserIdInt int... userIds) {
         List<UserInfo> users = UserTestingHelper.newUsers(userIds);
@@ -126,14 +128,22 @@
     }
 
     /**
-     * Mocks a call to {@code UserManager.getUsers()}.
+     * Mocks a call to {@code UserManager#getUsers()}, which includes dying users.
      */
-    public static void mockUmGetUsers(@NonNull UserManager um, @NonNull List<UserInfo> userInfos) {
+    public static void mockUmGetAllUsers(@NonNull UserManager um,
+            @NonNull List<UserInfo> userInfos) {
         when(um.getUsers()).thenReturn(userInfos);
     }
 
     /**
-     * Mocks a call to {@code UserManager.isUserRunning(userId)}.
+     * Mocks a call to {@code UserManager#getUsers(true)}, which excludes dying users.
+     */
+    public static void mockUmGetUsers(@NonNull UserManager um, @NonNull List<UserInfo> userInfos) {
+        when(um.getUsers(/* excludeDying= */ true)).thenReturn(userInfos);
+    }
+
+    /**
+     * Mocks a call to {@code UserManager#isUserRunning(userId)}.
      */
     public static void mockUmIsUserRunning(@NonNull UserManager um, @UserIdInt int userId,
             boolean isRunning) {
@@ -141,6 +151,32 @@
     }
 
     /**
+     * Mocks a successful call to {@code UserManager#createUser(String, String, int)}, returning
+     * a user with the passed arguments.
+     */
+    @NonNull
+    public static UserInfo mockUmCreateUser(@NonNull UserManager um, @Nullable String name,
+            @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int userId) {
+        UserInfo userInfo = new UserInfoBuilder(userId)
+                        .setName(name)
+                        .setType(userType)
+                        .setFlags(flags)
+                        .build();
+        when(um.createUser(name, userType, flags)).thenReturn(userInfo);
+        return userInfo;
+    }
+
+    /**
+     * Mocks a call to {@code UserManager#createUser(String, String, int)} that throws the given
+     * runtime exception.
+     */
+    @NonNull
+    public static void mockUmCreateUser(@NonNull UserManager um, @Nullable String name,
+            @NonNull String userType, @UserInfoFlag int flags, @NonNull RuntimeException e) {
+        when(um.createUser(name, userType, flags)).thenThrow(e);
+    }
+
+    /**
      * Mocks a call to {@link ServiceManager#getService(name)}.
      *
      * <p><b>Note: </b>it must be made inside a
diff --git a/car-test-lib/src/android/car/test/util/UserTestingHelper.java b/car-test-lib/src/android/car/test/util/UserTestingHelper.java
index 92a612c..d1c2f13 100644
--- a/car-test-lib/src/android/car/test/util/UserTestingHelper.java
+++ b/car-test-lib/src/android/car/test/util/UserTestingHelper.java
@@ -15,6 +15,8 @@
  */
 package android.car.test.util;
 
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -84,6 +86,27 @@
     }
 
     /**
+     * Sets the property that defines the maximum number of uses allowed in the device.
+     */
+    public static void setMaxSupportedUsers(int max) {
+        runShellCommand("setprop fw.max_users %d", max);
+    }
+
+    /**
+     * Configures the user to use PIN credentials.
+     */
+    public static void setUserLockCredentials(@UserIdInt int userId, int pin) {
+        runShellCommand("locksettings set-pin %s --user %d ", pin, userId);
+    }
+
+    /**
+     * Clears the user credentials using current PIN.
+     */
+    public static void clearUserLockCredentials(@UserIdInt int userId, int pin) {
+        runShellCommand("locksettings clear --old %d --user %d ", pin, userId);
+    }
+
+    /**
      * Builder for {@link UserInfo} objects.
      */
     public static final class UserInfoBuilder {
diff --git a/car-usb-handler/src/android/car/usb/handler/AoapInterface.java b/car-usb-handler/src/android/car/usb/handler/AoapInterface.java
index 21aa64e..3b7fbfe 100644
--- a/car-usb-handler/src/android/car/usb/handler/AoapInterface.java
+++ b/car-usb-handler/src/android/car/usb/handler/AoapInterface.java
@@ -22,6 +22,10 @@
 import android.util.Pair;
 
 import java.io.IOException;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -97,7 +101,7 @@
     /**
      * Accessory write timeout.
      */
-    public static final int AOAP_TIMEOUT_MS = 2000;
+    public static final int AOAP_TIMEOUT_MS = 50;
 
     /**
      * Set of VID:PID pairs blacklisted through config_AoapIncompatibleDeviceIds. Only
@@ -107,14 +111,30 @@
 
     private static final String TAG = AoapInterface.class.getSimpleName();
 
+    @Retention(RetentionPolicy.SOURCE)
+    @Target({ ElementType.FIELD, ElementType.PARAMETER })
+    public @interface Direction {}
+
+    @Direction
+    public static final int WRITE = 1;
+    @Direction
+    public static final int READ = 2;
+
+
     public static int getProtocol(UsbDeviceConnection conn) {
         byte[] buffer = new byte[2];
-        int len = conn.controlTransfer(
-                UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_VENDOR,
-                ACCESSORY_GET_PROTOCOL, 0, 0, buffer, 2, AOAP_TIMEOUT_MS);
-        if (len != 2) {
+
+        int len = transfer(conn, READ, ACCESSORY_GET_PROTOCOL, 0, buffer, buffer.length);
+        if (len == 0) {
             return -1;
         }
+        if (len < 0) {
+            Log.w(TAG, "getProtocol() failed. Retrying...");
+            len = transfer(conn, READ, ACCESSORY_GET_PROTOCOL, 0, buffer, buffer.length);
+            if (len != buffer.length) {
+                return -1;
+            }
+        }
         return (buffer[1] << 8) | buffer[0];
     }
 
@@ -125,21 +145,22 @@
     public static void sendString(UsbDeviceConnection conn, int index, String string)
             throws IOException {
         byte[] buffer = (string + "\0").getBytes();
-        int len = conn.controlTransfer(
-                UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR,
-                ACCESSORY_SEND_STRING, 0, index,
-                buffer, buffer.length, AOAP_TIMEOUT_MS);
+        int len = transfer(conn, WRITE, ACCESSORY_SEND_STRING, index, buffer,
+                buffer.length);
         if (len != buffer.length) {
-            throw new IOException("Failed to send string " + index + ": \"" + string + "\"");
+            Log.w(TAG, "sendString for " + index + ":" + string + " failed. Retrying...");
+            len = transfer(conn, WRITE, ACCESSORY_SEND_STRING, index, buffer,
+                buffer.length);
+            if (len != buffer.length) {
+                throw new IOException("Failed to send string " + index + ": \"" + string + "\"");
+            }
         } else {
             Log.i(TAG, "Sent string " + index + ": \"" + string + "\"");
         }
     }
 
     public static void sendAoapStart(UsbDeviceConnection conn) throws IOException {
-        int len = conn.controlTransfer(
-                UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR,
-                ACCESSORY_START, 0, 0, null, 0, AOAP_TIMEOUT_MS);
+        int len = transfer(conn, WRITE, ACCESSORY_START, 0, null, 0);
         if (len < 0) {
             throw new IOException("Control transfer for accessory start failed: " + len);
         }
@@ -181,4 +202,22 @@
         return vid == USB_ACCESSORY_VENDOR_ID
                 && USB_ACCESSORY_MODE_PRODUCT_ID.contains(pid);
     }
+
+    private static int transfer(UsbDeviceConnection conn, @Direction int direction, int string,
+            int index, byte[] buffer, int length) {
+        int directionConstant;
+        switch (direction) {
+            case READ:
+                directionConstant = UsbConstants.USB_DIR_IN;
+                break;
+            case WRITE:
+                directionConstant = UsbConstants.USB_DIR_OUT;
+                break;
+            default:
+                Log.w(TAG, "Unknown direction for transfer: " + direction);
+                return -1;
+        }
+        return conn.controlTransfer(directionConstant | UsbConstants.USB_TYPE_VENDOR, string, 0,
+            index, buffer, length, AOAP_TIMEOUT_MS);
+    }
 }
diff --git a/car-usb-handler/src/android/car/usb/handler/UsbDeviceHandlerResolver.java b/car-usb-handler/src/android/car/usb/handler/UsbDeviceHandlerResolver.java
index 8b2f425..a1be7dd 100644
--- a/car-usb-handler/src/android/car/usb/handler/UsbDeviceHandlerResolver.java
+++ b/car-usb-handler/src/android/car/usb/handler/UsbDeviceHandlerResolver.java
@@ -71,18 +71,16 @@
     private final PackageManager mPackageManager;
     private final UsbDeviceHandlerResolverCallback mDeviceCallback;
     private final Context mContext;
-    private final HandlerThread mHandlerThread;
-    private final UsbDeviceResolverHandler mHandler;
     private final AoapServiceManager mAoapServiceManager;
+    private HandlerThread mHandlerThread;
+    private UsbDeviceResolverHandler mHandler;
 
     public UsbDeviceHandlerResolver(UsbManager manager, Context context,
             UsbDeviceHandlerResolverCallback deviceListener) {
         mUsbManager = manager;
         mContext = context;
         mDeviceCallback = deviceListener;
-        mHandlerThread = new HandlerThread(TAG);
-        mHandlerThread.start();
-        mHandler = new UsbDeviceResolverHandler(mHandlerThread.getLooper());
+        createHandlerThread();
         mPackageManager = context.getPackageManager();
         mAoapServiceManager = new AoapServiceManager(mContext.getApplicationContext());
     }
@@ -104,9 +102,22 @@
     }
 
     /**
+     * Listener for failed {@code startAosp} command.
+     *
+     * <p>If {@code startAosp} fails, the device could be left in a inconsistent state, that's why
+     * we go back to USB enumeration, instead of just repeating the command.
+     */
+    public interface StartAoapFailureListener {
+
+        /** Called if startAoap fails. */
+        void onFailure();
+    }
+
+    /**
      * Dispatches device to component.
      */
-    public boolean dispatch(UsbDevice device, ComponentName component, boolean inAoap) {
+    public boolean dispatch(UsbDevice device, ComponentName component, boolean inAoap,
+            StartAoapFailureListener failureListener) {
         if (LOCAL_LOGD) {
             Log.d(TAG, "dispatch: " + device + " component: " + component + " inAoap: " + inAoap);
         }
@@ -128,10 +139,20 @@
                         packageMatches(activityInfo, intent.getAction(), device, true);
 
                 if (filter != null) {
+                    if (!mHandlerThread.isAlive()) {
+                        // Start a new thread. Used only when startAoap fails, and we need to
+                        // re-enumerate device in order to try again.
+                        createHandlerThread();
+                    }
                     mHandlerThread.getThreadHandler().post(() -> {
                         if (mAoapServiceManager.canSwitchDeviceToAoap(device,
                                 ComponentName.unflattenFromString(filter.mAoapService))) {
-                            requestAoapSwitch(device, filter);
+                            try {
+                                requestAoapSwitch(device, filter);
+                            } catch (IOException e) {
+                                Log.w(TAG, "Start AOAP command failed:" + e);
+                                failureListener.onFailure();
+                            }
                         } else {
                             Log.i(TAG, "Ignore AOAP switch for device " + device
                                     + " handled by " + filter.mAoapService);
@@ -151,6 +172,12 @@
         return true;
     }
 
+    private void createHandlerThread() {
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+        mHandler = new UsbDeviceResolverHandler(mHandlerThread.getLooper());
+    }
+
     private static Intent createDeviceAttachedIntent(UsbDevice device) {
         Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
         intent.putExtra(UsbManager.EXTRA_DEVICE, device);
@@ -192,7 +219,7 @@
         return settings;
     }
 
-    private void requestAoapSwitch(UsbDevice device, UsbDeviceFilter filter) {
+    private void requestAoapSwitch(UsbDevice device, UsbDeviceFilter filter) throws IOException {
         UsbDeviceConnection connection = UsbUtil.openConnection(mUsbManager, device);
         if (connection == null) {
             Log.e(TAG, "Failed to connect to usb device.");
@@ -209,11 +236,9 @@
                     filter.mAoapVersion,
                     filter.mAoapUri,
                     hashedSerial);
-        } catch (IOException e) {
-            Log.w(TAG, "Failed to switch device into AOAP mode", e);
+        } finally {
+            connection.close();
         }
-
-        connection.close();
     }
 
     private String getHashed(String serial) {
diff --git a/car-usb-handler/src/android/car/usb/handler/UsbHostController.java b/car-usb-handler/src/android/car/usb/handler/UsbHostController.java
index 3fcb67b..0da71ac 100644
--- a/car-usb-handler/src/android/car/usb/handler/UsbHostController.java
+++ b/car-usb-handler/src/android/car/usb/handler/UsbHostController.java
@@ -30,6 +30,7 @@
 import com.android.internal.annotations.GuardedBy;
 
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 
 /**
@@ -300,6 +301,12 @@
 
         private static final int DEVICE_REMOVE_TIMEOUT_MS = 500;
 
+        // Used to get the device that we are trying to connect to, if mActiveDevice is removed and
+        // startAoap fails afterwards. Used during USB enumeration when retrying to startAoap when
+        // there are multiple devices attached.
+        private int mLastDeviceId = 0;
+        private int mStartAoapRetries = 1;
+
         private UsbHostControllerHandler(Looper looper) {
             super(looper);
         }
@@ -308,6 +315,24 @@
             sendEmptyMessageDelayed(MSG_DEVICE_REMOVED, DEVICE_REMOVE_TIMEOUT_MS);
         }
 
+        private void onFailure() {
+            if (mStartAoapRetries == 0) {
+                Log.w(TAG, "Reached maximum retry count for startAoap. Giving up Aoa handshake.");
+                return;
+            }
+            mStartAoapRetries--;
+
+            Log.d(TAG, "Restarting USB enumeration.");
+            Iterator<UsbDevice> deviceIterator = mUsbManager.getDeviceList().values().iterator();
+            while (deviceIterator.hasNext()) {
+                UsbDevice device = deviceIterator.next();
+                if (mLastDeviceId == device.getDeviceId()) {
+                    processDevice(device);
+                    return;
+                }
+            }
+        }
+
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
@@ -318,8 +343,10 @@
                     UsbHostControllerHandlerDispatchData data =
                             (UsbHostControllerHandlerDispatchData) msg.obj;
                     UsbDevice device = data.getUsbDevice();
+                    mLastDeviceId = device.getDeviceId();
                     UsbDeviceSettings settings = data.getUsbDeviceSettings();
-                    if (!mUsbResolver.dispatch(device, settings.getHandler(), settings.getAoap())) {
+                    if (!mUsbResolver.dispatch(device, settings.getHandler(), settings.getAoap(),
+                            this::onFailure)) {
                         if (data.mRetries > 0) {
                             --data.mRetries;
                             Message nextMessage = Message.obtain(msg);
diff --git a/car_product/build/preinstalled-packages-product-car-base.xml b/car_product/build/preinstalled-packages-product-car-base.xml
index e888eb5..9587c83 100644
--- a/car_product/build/preinstalled-packages-product-car-base.xml
+++ b/car_product/build/preinstalled-packages-product-car-base.xml
@@ -166,6 +166,12 @@
         <install-in user-type="SYSTEM" />
     </install-in-user-type>
 
+    <!-- Failed to pass CTS if not installed for system user -->
+    <install-in-user-type package="com.android.car.ui.sharedlibrary">
+        <install-in user-type="FULL" />
+        <install-in user-type="SYSTEM" />
+    </install-in-user-type>
+
 <!--
   Apps that do need to run on SYSTEM and evaluated by package owner.
   Here the apps will have FULL only.
@@ -260,9 +266,6 @@
     <install-in-user-type package="com.android.car.calendar">
         <install-in user-type="FULL" />
     </install-in-user-type>
-    <install-in-user-type package="com.android.car.ui.sharedlibrary">
-        <install-in user-type="FULL" />
-    </install-in-user-type>
     <install-in-user-type package="com.android.wifi.inprocess.overlay.car">
         <install-in user-type="FULL" />
     </install-in-user-type>
diff --git a/car_product/sepolicy/private/carservice_app.te b/car_product/sepolicy/private/carservice_app.te
index d62cb27..4841891 100644
--- a/car_product/sepolicy/private/carservice_app.te
+++ b/car_product/sepolicy/private/carservice_app.te
@@ -76,3 +76,9 @@
 get_prop(carservice_app, vehicle_hal_prop)
 
 carwatchdog_client_domain(carservice_app)
+
+# For ActivityBlockingActiviy
+allow carservice_app gpu_device:chr_file rw_file_perms;
+allow carservice_app gpu_device:dir r_dir_perms;
+allow carservice_app gpu_service:service_manager find;
+binder_call(carservice_app, gpuservice)
diff --git a/evs/apps/default/ConfigManager.h b/evs/apps/default/ConfigManager.h
index 9c6d1a2..7c2186d 100644
--- a/evs/apps/default/ConfigManager.h
+++ b/evs/apps/default/ConfigManager.h
@@ -16,8 +16,9 @@
 #ifndef CONFIG_MANAGER_H
 #define CONFIG_MANAGER_H
 
-#include <vector>
+#include <cerrno>
 #include <string>
+#include <vector>
 
 #include <system/graphics-base.h>
 
@@ -78,16 +79,27 @@
 
     const std::vector<CameraInfo>& getCameras() const   { return mCameras; };
 
-    bool  setActiveDisplayId(int displayId) {
-        if (displayId < 0 or displayId > mDisplays.size()) {
-            printf("Display %d is invalid.  Current active display is display %d.",
-                   displayId, mActiveDisplayId);
-            return false;
+    int  setActiveDisplayId(int displayId) {
+        if (displayId == -1) {
+            // -1 is reserved for the default display, which is the first
+            // display in config.json's display list
+            printf("Uses a display with id %d", mDisplays[0].port);
+            mActiveDisplayId = mDisplays[0].port;
+            return mActiveDisplayId;
+        } else if (displayId < 0) {
+            printf("Display %d is invalid.", displayId);
+            return -ENOENT;
+        } else {
+            for (auto display : mDisplays) {
+                if (display.port == displayId) {
+                    mActiveDisplayId = displayId;
+                    return mActiveDisplayId;
+                }
+            }
+
+            printf("Display %d does not exist.", displayId);
+            return -ENOENT;
         }
-
-        mActiveDisplayId = displayId;
-
-        return true;
     }
     const std::vector<DisplayInfo>& getDisplays() const { return mDisplays; };
     const DisplayInfo& getActiveDisplay() const { return mDisplays[mActiveDisplayId]; };
diff --git a/evs/apps/default/config.json.readme b/evs/apps/default/config.json.readme
index 7d1ec9d..561adcc 100644
--- a/evs/apps/default/config.json.readme
+++ b/evs/apps/default/config.json.readme
@@ -19,7 +19,7 @@
     "rearExtent" : 40           // The extent of the car body behind the read axel
   },
   "displays" : [                // This configures the dimensions of the surround view display
-    {
+    {                           // The first display will be used as the default display
       "displayPort" : 1         // Display port number, the target display is connected to.
       "frontRange" : 100,       // How far to render the view in front of the front bumper
       "rearRange" : 100         // How far the view extends behind the rear bumper
diff --git a/evs/apps/default/evs_app.cpp b/evs/apps/default/evs_app.cpp
index 9f2f2c8..b30bcc7 100644
--- a/evs/apps/default/evs_app.cpp
+++ b/evs/apps/default/evs_app.cpp
@@ -94,7 +94,7 @@
     bool useVehicleHal = true;
     bool printHelp = false;
     const char* evsServiceName = "default";
-    int displayId = 1;
+    int displayId = -1;
     bool useExternalMemory = false;
     android_pixel_format_t extMemoryFormat = HAL_PIXEL_FORMAT_RGBA_8888;
     for (int i=1; i< argc; i++) {
@@ -133,7 +133,8 @@
         printf("  --test\n\tDo not talk to Vehicle Hal, but simulate 'reverse' instead\n");
         printf("  --hw\n\tBypass EvsManager by connecting directly to EvsEnumeratorHw\n");
         printf("  --mock\n\tConnect directly to EvsEnumeratorHw-Mock\n");
-        printf("  --display\n\tSpecify the display to use\n");
+        printf("  --display\n\tSpecify the display to use.  If this is not set, the first"
+                              "display in config.json's list will be used.\n");
         printf("  --extmem  <format>\n\t"
                "Application allocates buffers to capture camera frames.  "
                "Available format strings are (case insensitive):\n");
@@ -153,7 +154,7 @@
     ConfigManager config;
     if (!config.initialize("/system/etc/automotive/evs/config.json")) {
         LOG(ERROR) << "Missing or improper configuration for the EVS application.  Exiting.";
-        return 1;
+        return EXIT_FAILURE;
     }
 
     // Set thread pool size to one to avoid concurrent events from the HAL.
@@ -171,19 +172,25 @@
     if (pEvs.get() == nullptr) {
         LOG(ERROR) << "getService(" << evsServiceName
                    << ") returned NULL.  Exiting.";
-        return 1;
+        return EXIT_FAILURE;
     }
 
     // Request exclusive access to the EVS display
     LOG(INFO) << "Acquiring EVS Display";
 
     // We'll use an available display device.
+    displayId = config.setActiveDisplayId(displayId);
+    if (displayId < 0) {
+        PLOG(ERROR) << "EVS Display is unknown.  Exiting.";
+        return EXIT_FAILURE;
+    }
+
     android::sp<IEvsDisplay> pDisplay = pEvs->openDisplay_1_1(displayId);
     if (pDisplay.get() == nullptr) {
         LOG(ERROR) << "EVS Display unavailable.  Exiting.";
-        return 1;
+        return EXIT_FAILURE;
     }
-    config.setActiveDisplayId(displayId);
+
     config.useExternalMemory(useExternalMemory);
     config.setExternalMemoryFormat(extMemoryFormat);
 
@@ -194,13 +201,13 @@
         pVnet = IVehicle::getService();
         if (pVnet.get() == nullptr) {
             LOG(ERROR) << "Vehicle HAL getService returned NULL.  Exiting.";
-            return 1;
+            return EXIT_FAILURE;
         } else {
             // Register for vehicle state change callbacks we care about
             // Changes in these values are what will trigger a reconfiguration of the EVS pipeline
             if (!subscribeToVHal(pVnet, pEvsListener, VehicleProperty::GEAR_SELECTION)) {
                 LOG(ERROR) << "Without gear notification, we can't support EVS.  Exiting.";
-                return 1;
+                return EXIT_FAILURE;
             }
             if (!subscribeToVHal(pVnet, pEvsListener, VehicleProperty::TURN_SIGNAL_STATE)) {
                 LOG(WARNING) << "Didn't get turn signal notifications, so we'll ignore those.";
@@ -215,7 +222,7 @@
     EvsStateControl *pStateController = new EvsStateControl(pVnet, pEvs, pDisplay, config);
     if (!pStateController->startUpdateLoop()) {
         LOG(ERROR) << "Initial configuration failed.  Exiting.";
-        return 1;
+        return EXIT_FAILURE;
     }
 
     // Run forever, reacting to events as necessary
@@ -226,5 +233,5 @@
     // One known example is if another process preempts our registration for our service name.
     LOG(ERROR) << "EVS Listener stopped.  Exiting.";
 
-    return 0;
+    return EXIT_SUCCESS;
 }
diff --git a/evs/manager/1.1/HalCamera.cpp b/evs/manager/1.1/HalCamera.cpp
index a90e83b..6e6f77d 100644
--- a/evs/manager/1.1/HalCamera.cpp
+++ b/evs/manager/1.1/HalCamera.cpp
@@ -92,6 +92,10 @@
 
     // Add this virtualCamera to our ownership list via weak pointer
     mClients.emplace_back(virtualCamera);
+
+    // Update statistics
+    mUsageStats->updateNumClients(mClients.size());
+
     return true;
 }
 
@@ -114,6 +118,9 @@
     if (!changeFramesInFlight(0)) {
         LOG(ERROR) << "Error when trying to reduce the in flight buffer count";
     }
+
+    // Update statistics
+    mUsageStats->updateNumClients(mClients.size());
 }
 
 
@@ -352,7 +359,7 @@
             mHwCamera->doneWithFrame_1_1(returnedBuffers);
 
             // Counts a returned buffer
-            mUsageStats->framesReturned();
+            mUsageStats->framesReturned(returnedBuffers);
         }
     }
 
@@ -412,7 +419,7 @@
     }
 
     // Reports the number of received buffers
-    mUsageStats->framesReceived(buffer.size());
+    mUsageStats->framesReceived(buffer);
 
     // Frames are being forwarded to active v1.0 clients and v1.1 clients if we
     // failed to create a timeline.
@@ -437,7 +444,7 @@
         mHwCamera->doneWithFrame_1_1(buffer);
 
         // Reports a returned buffer
-        mUsageStats->framesReturned();
+        mUsageStats->framesReturned(buffer);
     } else {
         // Add an entry for this frame in our tracking list.
         unsigned i;
diff --git a/evs/manager/1.1/stats/CameraUsageStats.cpp b/evs/manager/1.1/stats/CameraUsageStats.cpp
index 11f8229..593577e 100644
--- a/evs/manager/1.1/stats/CameraUsageStats.cpp
+++ b/evs/manager/1.1/stats/CameraUsageStats.cpp
@@ -18,6 +18,15 @@
 
 #include <statslog.h>
 
+#include <android-base/logging.h>
+
+namespace {
+
+    // Length of frame roundtrip history
+    const int kMaxHistoryLength = 100;
+
+}
+
 namespace android {
 namespace automotive {
 namespace evs {
@@ -26,6 +35,50 @@
 
 using ::android::base::Result;
 using ::android::base::StringAppendF;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::automotive::evs::V1_1::BufferDesc;
+
+void CameraUsageStats::updateFrameStatsOnArrival(
+        const hidl_vec<BufferDesc>& bufs) {
+    const auto now = android::uptimeMillis();
+    for (const auto& b : bufs) {
+        auto it = mBufferHistory.find(b.bufferId);
+        if (it == mBufferHistory.end()) {
+            mBufferHistory.emplace(b.bufferId, now);
+        } else {
+            it->second.timestamp = now;
+        }
+    }
+}
+
+
+void CameraUsageStats::updateFrameStatsOnReturn(
+        const hidl_vec<BufferDesc>& bufs) {
+    const auto now = android::uptimeMillis();
+    for (auto& b : bufs) {
+        auto it = mBufferHistory.find(b.bufferId);
+        if (it == mBufferHistory.end()) {
+            LOG(WARNING) << "Buffer " << b.bufferId << " from "
+                         << b.deviceId << " is unknown.";
+        } else {
+            const auto roundtrip = now - it->second.timestamp;
+            it->second.history.emplace(roundtrip);
+            it->second.sum += roundtrip;
+            if (it->second.history.size() > kMaxHistoryLength) {
+                it->second.sum -= it->second.history.front();
+                it->second.history.pop();
+            }
+
+            if (roundtrip > it->second.peak) {
+                it->second.peak = roundtrip;
+            }
+
+            if (mStats.framesFirstRoundtripLatency == 0) {
+                mStats.framesFirstRoundtripLatency = roundtrip;
+            }
+        }
+    }
+}
 
 
 void CameraUsageStats::framesReceived(int n) {
@@ -34,12 +87,30 @@
 }
 
 
+void CameraUsageStats::framesReceived(
+        const hidl_vec<BufferDesc>& bufs) {
+    AutoMutex lock(mMutex);
+    mStats.framesReceived += bufs.size();
+
+    updateFrameStatsOnArrival(bufs);
+}
+
+
 void CameraUsageStats::framesReturned(int n) {
     AutoMutex lock(mMutex);
     mStats.framesReturned += n;
 }
 
 
+void CameraUsageStats::framesReturned(
+        const hidl_vec<BufferDesc>& bufs) {
+    AutoMutex lock(mMutex);
+    mStats.framesReturned += bufs.size();
+
+    updateFrameStatsOnReturn(bufs);
+}
+
+
 void CameraUsageStats::framesIgnored(int n) {
     AutoMutex lock(mMutex);
     mStats.framesIgnored += n;
@@ -58,6 +129,14 @@
 }
 
 
+void CameraUsageStats::updateNumClients(size_t n) {
+    AutoMutex lock(mMutex);
+    if (n > mStats.peakClientsCount) {
+        mStats.peakClientsCount = n;
+    }
+}
+
+
 int64_t CameraUsageStats::getTimeCreated() const {
     AutoMutex lock(mMutex);
     return mTimeCreatedMs;
@@ -76,16 +155,33 @@
 }
 
 
-CameraUsageStatsRecord CameraUsageStats::snapshot() const {
+CameraUsageStatsRecord CameraUsageStats::snapshot() {
     AutoMutex lock(mMutex);
+
+    int32_t sum = 0;
+    int32_t peak = 0;
+    int32_t len = 0;
+    for (auto& [id, rec] : mBufferHistory) {
+        sum += rec.sum;
+        len += rec.history.size();
+        if (peak < rec.peak) {
+            peak = rec.peak;
+        }
+    }
+
+    mStats.framesPeakRoundtripLatency = peak;
+    mStats.framesAvgRoundtripLatency = (double)sum / len;
     return mStats;
 }
 
 
 Result<void> CameraUsageStats::writeStats() const {
     AutoMutex lock(mMutex);
+
+    // Reports the usage statistics before the destruction
+    // EvsUsageStatsReported atom is defined in
+    // frameworks/base/cmds/statsd/src/atoms.proto
     const auto duration = android::uptimeMillis() - mTimeCreatedMs;
-    // TODO(b/156131016): calculates and reports frame roundtrip latencies
     android::util::stats_write(android::util::EVS_USAGE_STATS_REPORTED,
                                mId,
                                mStats.peakClientsCount,
diff --git a/evs/manager/1.1/stats/CameraUsageStats.h b/evs/manager/1.1/stats/CameraUsageStats.h
index 4bc4ea8..7a7224d 100644
--- a/evs/manager/1.1/stats/CameraUsageStats.h
+++ b/evs/manager/1.1/stats/CameraUsageStats.h
@@ -17,8 +17,12 @@
 #ifndef ANDROID_AUTOMOTIVE_EVS_V1_1_CAMERAUSAGESTATS_H
 #define ANDROID_AUTOMOTIVE_EVS_V1_1_CAMERAUSAGESTATS_H
 
+#include <queue>
+#include <unordered_map>
+
 #include <inttypes.h>
 
+#include <android/hardware/automotive/evs/1.1/types.h>
 #include <android-base/result.h>
 #include <android-base/stringprintf.h>
 #include <utils/Mutex.h>
@@ -89,18 +93,46 @@
                 "%sFrames Received: %" PRId64 "\n"
                 "%sFrames Returned: %" PRId64 "\n"
                 "%sFrames Ignored : %" PRId64 "\n"
-                "%sFrames Skipped To Sync: %" PRId64 "\n\n",
+                "%sFrames Skipped To Sync: %" PRId64 "\n"
+                "%sFrames First Roundtrip: %" PRId64 "\n"
+                "%sFrames Peak Roundtrip: %" PRId64 "\n"
+                "%sFrames Average Roundtrip: %f\n"
+                "%sPeak Number of Clients: %" PRId32 "\n\n",
                 indent, ns2ms(timestamp),
                 indent, framesReceived,
                 indent, framesReturned,
                 indent, framesIgnored,
-                indent, framesSkippedToSync);
+                indent, framesSkippedToSync,
+                indent, framesFirstRoundtripLatency,
+                indent, framesPeakRoundtripLatency,
+                indent, framesAvgRoundtripLatency,
+                indent, peakClientsCount);
 
         return buffer;
     }
 };
 
 
+struct BufferRecord {
+    BufferRecord(int64_t timestamp) :
+        timestamp(timestamp),
+        sum(0),
+        peak(0) {}
+
+    // Recent processing time
+    std::queue<int32_t> history;
+
+    // Timestamp on the buffer arrival
+    int64_t timestamp;
+
+    // Sum of processing times
+    int64_t sum;
+
+    // Peak processing time
+    int64_t peak;
+};
+
+
 class CameraUsageStats : public RefBase {
 public:
     CameraUsageStats(int32_t id)
@@ -122,18 +154,34 @@
     // Usage statistics to collect
     CameraUsageStatsRecord mStats GUARDED_BY(mMutex);
 
+    // Frame buffer histories
+    std::unordered_map<int, BufferRecord> mBufferHistory GUARDED_BY(mMutex);
+
 public:
     void framesReceived(int n = 1) EXCLUDES(mMutex);
     void framesReturned(int n = 1) EXCLUDES(mMutex);
+    void framesReceived(
+            const hardware::hidl_vec<::android::hardware::automotive::evs::V1_1::BufferDesc>& bufs
+        ) EXCLUDES(mMutex);
+    void framesReturned(
+            const hardware::hidl_vec<::android::hardware::automotive::evs::V1_1::BufferDesc>& bufs
+        ) EXCLUDES(mMutex);
     void framesIgnored(int n = 1) EXCLUDES(mMutex);
     void framesSkippedToSync(int n = 1) EXCLUDES(mMutex);
     void eventsReceived() EXCLUDES(mMutex);
     int64_t getTimeCreated() const EXCLUDES(mMutex);
     int64_t getFramesReceived() const EXCLUDES(mMutex);
     int64_t getFramesReturned() const EXCLUDES(mMutex);
+    void updateNumClients(size_t n) EXCLUDES(mMutex);
+    void updateFrameStatsOnArrival(
+            const hardware::hidl_vec<::android::hardware::automotive::evs::V1_1::BufferDesc>& bufs
+        ) REQUIRES(mMutex);
+    void updateFrameStatsOnReturn(
+            const hardware::hidl_vec<::android::hardware::automotive::evs::V1_1::BufferDesc>& bufs
+        ) REQUIRES(mMutex);
 
     // Returns the statistics collected so far
-    CameraUsageStatsRecord snapshot() const EXCLUDES(mMutex);
+    CameraUsageStatsRecord snapshot() EXCLUDES(mMutex);
 
     // Reports the usage statistics
     android::base::Result<void> writeStats() const EXCLUDES(mMutex);
diff --git a/evs/manager/1.1/stats/StatsCollector.cpp b/evs/manager/1.1/stats/StatsCollector.cpp
index de85e5a..b57f928 100644
--- a/evs/manager/1.1/stats/StatsCollector.cpp
+++ b/evs/manager/1.1/stats/StatsCollector.cpp
@@ -205,11 +205,6 @@
             PLOG(WARNING) << "Failed to set background scheduling prioirty";
         }
 
-        auto ret = pthread_setname_np(pthread_self(), "EvsCameraUsageCollect");
-        if (ret != 0) {
-            PLOG(WARNING) << "Failed to name a collection thread";
-        }
-
         // Sets a looper for the communication
         mLooper->setLooper(Looper::prepare(/*opts=*/0));
 
@@ -227,6 +222,11 @@
         }
     });
 
+    auto ret = pthread_setname_np(mCollectionThread.native_handle(), "EvsUsageCollect");
+    if (ret != 0) {
+        PLOG(WARNING) << "Failed to name a collection thread";
+    }
+
     return {};
 }
 
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index 4c310a8..9194bc1 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -44,6 +44,11 @@
     <!--  Service responsible for displaying information on the car instrument cluster. -->
     <string name="instrumentClusterRendererService" translatable="false">android.car.cluster/.ClusterRenderingService</string>
 
+    <!--  Service responsible for handling the rotary controller input. This service will start
+          on boot or on user switch. Set this string to empty if you don't want to start this
+          service. -->
+    <string name="rotaryService" translatable="false">com.android.car.rotary/com.android.car.rotary.RotaryService</string>
+
     <!--  Whether to enable Activity blocking for safety. When Activity blocking is enabled,
           only whitelisted safe Activities will be allowed while car is not parked. -->
     <bool name="enableActivityBlockingForSafety">true</bool>
diff --git a/service/src/com/android/car/AppFocusService.java b/service/src/com/android/car/AppFocusService.java
index d97bec8..cc1a530 100644
--- a/service/src/com/android/car/AppFocusService.java
+++ b/service/src/com/android/car/AppFocusService.java
@@ -161,7 +161,7 @@
                             ownerInfo.binderInterface, appType);
                     if (DBG) {
                         Log.i(CarLog.TAG_APP_FOCUS, "losing app type "
-                                + appType + "," + ownerInfo.toString());
+                                + appType + "," + ownerInfo);
                     }
                 }
                 updateFocusOwner(appType, info);
@@ -169,20 +169,20 @@
             info.addOwnedAppType(appType);
             mDispatchHandler.requestAppFocusOwnershipGrantDispatch(
                     info.binderInterface, appType);
-            if (mActiveAppTypes.add(appType)) {
-                if (DBG) {
-                    Log.i(CarLog.TAG_APP_FOCUS, "adding active app type " + appType + ","
-                            + info.toString());
-                }
-                for (BinderInterfaceContainer.BinderInterface<IAppFocusListener> client :
-                        mAllChangeClients.getInterfaces()) {
-                    ClientInfo clientInfo = (ClientInfo) client;
-                    // dispatch events only when there is change after filter and the listener
-                    // is not coming from the current caller.
-                    if (clientInfo.getAppTypes().contains(appType)) {
-                        mDispatchHandler.requestAppFocusChangeDispatch(clientInfo.binderInterface,
-                                appType, true);
-                    }
+            mActiveAppTypes.add(appType);
+            if (DBG) {
+                Log.i(CarLog.TAG_APP_FOCUS, "updating active app type " + appType + ","
+                        + info);
+            }
+            // Always dispatch.
+            for (BinderInterfaceContainer.BinderInterface<IAppFocusListener> client :
+                    mAllChangeClients.getInterfaces()) {
+                ClientInfo clientInfo = (ClientInfo) client;
+                // dispatch events only when there is change after filter and the listener
+                // is not coming from the current caller.
+                if (clientInfo.getAppTypes().contains(appType)) {
+                    mDispatchHandler.requestAppFocusChangeDispatch(clientInfo.binderInterface,
+                            appType, true);
                 }
             }
         }
@@ -212,8 +212,7 @@
                 mActiveAppTypes.remove(appType);
                 info.removeOwnedAppType(appType);
                 if (DBG) {
-                    Log.i(CarLog.TAG_APP_FOCUS, "abandoning focus " + appType
-                            + "," + info.toString());
+                    Log.i(CarLog.TAG_APP_FOCUS, "abandoning focus " + appType + "," + info);
                 }
                 for (FocusOwnershipCallback ownershipCallback : mFocusOwnershipCallbacks) {
                     ownershipCallback.onFocusAbandoned(appType, info.mUid, info.mPid);
@@ -270,7 +269,7 @@
             for (BinderInterfaceContainer.BinderInterface<IAppFocusOwnershipCallback> client :
                     mAllOwnershipClients.getInterfaces()) {
                 OwnershipClientInfo clientInfo = (OwnershipClientInfo) client;
-                writer.println(clientInfo.toString());
+                writer.println(clientInfo);
             }
         }
     }
diff --git a/service/src/com/android/car/CarInputService.java b/service/src/com/android/car/CarInputService.java
index 65c616f..222e760 100644
--- a/service/src/com/android/car/CarInputService.java
+++ b/service/src/com/android/car/CarInputService.java
@@ -19,6 +19,7 @@
 import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_PUSH_TO_TALK;
 
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
@@ -32,6 +33,8 @@
 import android.car.input.ICarInputCallback;
 import android.car.input.ICarInputListener;
 import android.car.input.RotaryEvent;
+import android.car.user.CarUserManager;
+import android.car.userlib.UserHelper;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -58,6 +61,7 @@
 import android.view.ViewConfiguration;
 
 import com.android.car.hal.InputHalService;
+import com.android.car.user.CarUserService;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.AssistUtils;
@@ -159,6 +163,7 @@
 
     private final Context mContext;
     private final InputHalService mInputHalService;
+    private final CarUserService mUserService;
     private final TelecomManager mTelecomManager;
     private final AssistUtils mAssistUtils;
     // The ComponentName of the CarInputListener service. Can be changed via resource overlay,
@@ -175,6 +180,8 @@
     // from Settings.Secure for the current user, falling back to the system-wide default
     // long-press delay defined in ViewConfiguration. May be overridden for testing.
     private final IntSupplier mLongPressDelaySupplier;
+    // ComponentName of the RotaryService.
+    private final String mRotaryServiceComponentName;
 
     private final Object mLock = new Object();
 
@@ -270,6 +277,13 @@
         }
     };
 
+    private final CarUserManager.UserLifecycleListener mUserLifecycleListener = event -> {
+        Log.d(CarLog.TAG_INPUT, "CarInputService.onEvent(" + event + ")");
+        if (CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING == event.getEventType()) {
+            updateRotaryServiceSettings(event.getUserId());
+        }
+    };
+
     @Nullable
     private static ComponentName getDefaultInputComponent(Context context) {
         String carInputService = context.getString(R.string.inputService);
@@ -285,8 +299,9 @@
                 UserHandle.USER_CURRENT);
     }
 
-    public CarInputService(Context context, InputHalService inputHalService) {
-        this(context, inputHalService, new Handler(Looper.getMainLooper()),
+    public CarInputService(Context context, InputHalService inputHalService,
+            CarUserService userService) {
+        this(context, inputHalService, userService, new Handler(Looper.getMainLooper()),
                 context.getSystemService(TelecomManager.class), new AssistUtils(context),
                 event ->
                         context.getSystemService(InputManager.class)
@@ -297,14 +312,15 @@
     }
 
     @VisibleForTesting
-    CarInputService(Context context, InputHalService inputHalService, Handler handler,
-            TelecomManager telecomManager, AssistUtils assistUtils,
+    CarInputService(Context context, InputHalService inputHalService, CarUserService userService,
+            Handler handler, TelecomManager telecomManager, AssistUtils assistUtils,
             KeyEventListener mainDisplayHandler, Supplier<String> lastCalledNumberSupplier,
             @Nullable ComponentName customInputServiceComponent,
             IntSupplier longPressDelaySupplier) {
         mContext = context;
         mCaptureController = new InputCaptureClientController(context);
         mInputHalService = inputHalService;
+        mUserService = userService;
         mTelecomManager = telecomManager;
         mAssistUtils = assistUtils;
         mMainDisplayHandler = mainDisplayHandler;
@@ -317,6 +333,8 @@
                         handler, longPressDelaySupplier, this::handleVoiceAssistLongPress);
         mCallKeyTimer =
                 new KeyPressTimer(handler, longPressDelaySupplier, this::handleCallLongPress);
+
+        mRotaryServiceComponentName = mContext.getString(R.string.rotaryService);
     }
 
     @VisibleForTesting
@@ -367,6 +385,9 @@
             mBluetoothAdapter.getProfileProxy(
                     mContext, mBluetoothProfileServiceListener, BluetoothProfile.HEADSET_CLIENT);
         }
+        if (!TextUtils.isEmpty(mRotaryServiceComponentName)) {
+            mUserService.addUserLifecycleListener(mUserLifecycleListener);
+        }
     }
 
     @Override
@@ -385,6 +406,9 @@
                 mBluetoothHeadsetClient = null;
             }
         }
+        if (!TextUtils.isEmpty(mRotaryServiceComponentName)) {
+            mUserService.removeUserLifecycleListener(mUserLifecycleListener);
+        }
     }
 
     @Override
@@ -705,4 +729,19 @@
         intent.setComponent(mCustomInputServiceComponent);
         return mContext.bindService(intent, mInputServiceConnection, Context.BIND_AUTO_CREATE);
     }
+
+    private void updateRotaryServiceSettings(@UserIdInt int userId) {
+        if (UserHelper.isHeadlessSystemUser(userId)) {
+            return;
+        }
+        ContentResolver contentResolver = mContext.getContentResolver();
+        Settings.Secure.putStringForUser(contentResolver,
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                mRotaryServiceComponentName,
+                userId);
+        Settings.Secure.putStringForUser(contentResolver,
+                Settings.Secure.ACCESSIBILITY_ENABLED,
+                "1",
+                userId);
+    }
 }
diff --git a/service/src/com/android/car/CarPowerManagementService.java b/service/src/com/android/car/CarPowerManagementService.java
index 5a2aee2..aef68cd 100644
--- a/service/src/com/android/car/CarPowerManagementService.java
+++ b/service/src/com/android/car/CarPowerManagementService.java
@@ -44,6 +44,7 @@
 import android.os.PowerManager;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -59,6 +60,7 @@
 import com.android.car.user.CarUserService;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IVoiceInteractionManagerService;
 
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
@@ -137,6 +139,8 @@
     private final CarUserService mUserService;
     private final InitialUserSetter mInitialUserSetter;
 
+    private final IVoiceInteractionManagerService mVoiceInteractionManagerService;
+
     // TODO:  Make this OEM configurable.
     private static final int SHUTDOWN_POLLING_INTERVAL_MS = 2000;
     private static final int SHUTDOWN_EXTEND_MAX_MS = 5000;
@@ -168,13 +172,16 @@
         this(context, context.getResources(), powerHal, systemInterface, UserManager.get(context),
                 carUserService, new InitialUserSetter(context,
                         (u) -> carUserService.setInitialUser(u),
-                        context.getString(R.string.default_guest_name)));
+                        context.getString(R.string.default_guest_name)),
+                IVoiceInteractionManagerService.Stub.asInterface(
+                        ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE)));
     }
 
     @VisibleForTesting
     public CarPowerManagementService(Context context, Resources resources, PowerHalService powerHal,
             SystemInterface systemInterface, UserManager userManager, CarUserService carUserService,
-            InitialUserSetter initialUserSetter) {
+            InitialUserSetter initialUserSetter,
+            IVoiceInteractionManagerService voiceInteractionService) {
         mContext = context;
         mHal = powerHal;
         mSystemInterface = systemInterface;
@@ -194,6 +201,7 @@
         }
         mUserService = carUserService;
         mInitialUserSetter = initialUserSetter;
+        mVoiceInteractionManagerService = voiceInteractionService;
     }
 
     @VisibleForTesting
@@ -408,6 +416,7 @@
 
         mSystemInterface.setDisplayState(true);
         sendPowerManagerEvent(CarPowerStateListener.ON);
+
         mHal.sendOn();
 
         try {
@@ -415,6 +424,8 @@
         } catch (Exception e) {
             Log.e(CarLog.TAG_POWER, "Could not switch user on resume", e);
         }
+
+        setVoiceInteractionDisabled(false);
     }
 
     @VisibleForTesting // Ideally it should not be exposed, but it speeds up the unit tests
@@ -552,6 +563,7 @@
     }
 
     private void handleShutdownPrepare(CpmsState newState) {
+        setVoiceInteractionDisabled(true);
         mSystemInterface.setDisplayState(false);
         // Shutdown on finish if the system doesn't support deep sleep or doesn't allow it.
         synchronized (mLock) {
@@ -619,6 +631,8 @@
                 throw new AssertionError("Should not return from PowerManager.reboot()");
             }
         }
+        setVoiceInteractionDisabled(true);
+
         if (mustShutDown) {
             // shutdown HU
             mSystemInterface.shutdown();
@@ -628,6 +642,14 @@
         mShutdownOnNextSuspend = false;
     }
 
+    private void setVoiceInteractionDisabled(boolean disabled) {
+        try {
+            mVoiceInteractionManagerService.setDisabled(disabled);
+        } catch (RemoteException e) {
+            Log.w(TAG, "setVoiceIntefactionDisabled(" + disabled + ") failed", e);
+        }
+    }
+
     @GuardedBy("mLock")
     private void releaseTimerLocked() {
         if (mTimer != null) {
@@ -737,18 +759,18 @@
             simulateSleepByWaiting();
             nextListenerState = CarPowerStateListener.SHUTDOWN_CANCELLED;
         } else {
-            boolean sleepSucceeded = mSystemInterface.enterDeepSleep();
+            boolean sleepSucceeded = suspendWithRetries();
             if (!sleepSucceeded) {
-                // Suspend failed! VHAL should transition CPMS to shutdown.
-                Log.e(CarLog.TAG_POWER, "Sleep did not succeed. Now attempting to shut down.");
-                mSystemInterface.shutdown();
+                // Suspend failed and we shut down instead.
+                // We either won't get here at all or we will power off very soon.
                 return;
             }
+            // We suspended and have now resumed
             nextListenerState = CarPowerStateListener.SUSPEND_EXIT;
         }
-        // On resume, reset nextWakeup time. If not set again, system will suspend/shutdown forever.
         synchronized (mLock) {
             mIsResuming = true;
+            // Any wakeup time from before is no longer valid.
             mNextWakeupSec = 0;
         }
         Log.i(CarLog.TAG_POWER, "Resuming after suspending");
@@ -1074,6 +1096,37 @@
         }
     }
 
+    // Send the command to enter Suspend to RAM.
+    // If the command is not successful, try again.
+    // If it fails repeatedly, send the command to shut down.
+    // Returns true if we successfully suspended.
+    private boolean suspendWithRetries() {
+        final int maxTries = 3;
+        final long retryIntervalMs = 10;
+        int tryCount = 0;
+
+        while (true) {
+            Log.i(CarLog.TAG_POWER, "Entering Suspend to RAM");
+            boolean suspendSucceeded = mSystemInterface.enterDeepSleep();
+            if (suspendSucceeded) {
+                return true;
+            }
+            tryCount++;
+            if (tryCount >= maxTries) {
+                break;
+            }
+            // We failed to suspend. Block the thread briefly and try again.
+            Log.w(CarLog.TAG_POWER, "Failed to Suspend; will retry later.");
+            try {
+                Thread.sleep(retryIntervalMs);
+            } catch (InterruptedException ignored) { }
+        }
+        // Too many failures trying to suspend. Shut down.
+        Log.w(CarLog.TAG_POWER, "Could not Suspend to RAM. Shutting down.");
+        mSystemInterface.shutdown();
+        return false;
+    }
+
     private static class CpmsState {
         // NOTE: When modifying states below, make sure to update CarPowerStateChanged.State in
         //   frameworks/base/cmds/statsd/src/atoms.proto also.
diff --git a/service/src/com/android/car/CarShellCommand.java b/service/src/com/android/car/CarShellCommand.java
index 7f7818d..0a8c420 100644
--- a/service/src/com/android/car/CarShellCommand.java
+++ b/service/src/com/android/car/CarShellCommand.java
@@ -34,7 +34,9 @@
 import android.car.input.CarInputManager;
 import android.car.input.RotaryEvent;
 import android.car.user.CarUserManager;
+import android.car.user.UserCreationResult;
 import android.car.user.UserIdentificationAssociationResponse;
+import android.car.user.UserRemovalResult;
 import android.car.user.UserSwitchResult;
 import android.car.userlib.HalCallback;
 import android.car.userlib.UserHalHelper;
@@ -45,7 +47,9 @@
 import android.hardware.automotive.vehicle.V2_0.CreateUserStatus;
 import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
 import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
+import android.hardware.automotive.vehicle.V2_0.RemoveUserRequest;
 import android.hardware.automotive.vehicle.V2_0.SwitchUserMessageType;
+import android.hardware.automotive.vehicle.V2_0.SwitchUserRequest;
 import android.hardware.automotive.vehicle.V2_0.SwitchUserStatus;
 import android.hardware.automotive.vehicle.V2_0.UserFlags;
 import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociation;
@@ -90,6 +94,7 @@
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 final class CarShellCommand extends ShellCommand {
 
@@ -122,6 +127,7 @@
     private static final String COMMAND_INJECT_ROTARY = "inject-rotary";
     private static final String COMMAND_GET_INITIAL_USER_INFO = "get-initial-user-info";
     private static final String COMMAND_SWITCH_USER = "switch-user";
+    private static final String COMMAND_REMOVE_USER = "remove-user";
     private static final String COMMAND_CREATE_USER = "create-user";
     private static final String COMMAND_GET_INITIAL_USER = "get-initial-user";
     private static final String COMMAND_SET_USER_ID_TO_OCCUPANT_ZONE =
@@ -152,6 +158,8 @@
                 android.Manifest.permission.MANAGE_USERS);
         USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_SWITCH_USER,
                 android.Manifest.permission.MANAGE_USERS);
+        USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_REMOVE_USER,
+                android.Manifest.permission.MANAGE_USERS);
         USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_CREATE_USER,
                 android.Manifest.permission.MANAGE_USERS);
         USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_GET_USER_AUTH_ASSOCIATION,
@@ -371,6 +379,10 @@
         pw.println("\t  The --hal-only option only calls HAL, without switching the user,");
         pw.println("\t  while the --timeout defines how long to wait for the HAL response.");
 
+        pw.printf("\t%s <USER_ID> [--hal-only]\n", COMMAND_REMOVE_USER);
+        pw.println("\t  Removes user with USER_ID using the HAL integration.");
+        pw.println("\t  The --hal-only option only calls HAL, without removing the user,");
+
         pw.printf("\t%s [--hal-only] [--timeout TIMEOUT_MS] [--type TYPE] [--flags FLAGS] [NAME]\n",
                 COMMAND_CREATE_USER);
         pw.println("\t  Creates a new user using the HAL integration.");
@@ -606,6 +618,9 @@
             case COMMAND_SWITCH_USER:
                 switchUser(args, writer);
                 break;
+            case COMMAND_REMOVE_USER:
+                removeUser(args, writer);
+                break;
             case COMMAND_CREATE_USER:
                 createUser(args, writer);
                 break;
@@ -951,12 +966,15 @@
         if (halOnly) {
             CountDownLatch latch = new CountDownLatch(1);
             UserHalService userHal = mHal.getUserHal();
-            UsersInfo usersInfo = generateUsersInfo();
             UserInfo targetUserInfo = new UserInfo();
             targetUserInfo.userId = targetUserId;
             targetUserInfo.flags = getUserHalFlags(targetUserId);
 
-            userHal.switchUser(targetUserInfo, timeout, usersInfo, (status, resp) -> {
+            SwitchUserRequest request = new SwitchUserRequest();
+            request.targetUser = targetUserInfo;
+            request.usersInfo = generateUsersInfo();
+
+            userHal.switchUser(request, timeout, (status, resp) -> {
                 try {
                     Log.d(TAG, "SwitchUserResponse: status=" + status + ", resp=" + resp);
                     writer.printf("Call Status: %s\n",
@@ -976,7 +994,7 @@
                     // Android error. This is to "rollback" the HAL switch.
                     if (status == HalCallback.STATUS_OK
                             && resp.status == SwitchUserStatus.SUCCESS) {
-                        userHal.postSwitchResponse(resp.requestId, targetUserInfo, usersInfo);
+                        userHal.postSwitchResponse(request);
                     }
                 } finally {
                     latch.countDown();
@@ -989,12 +1007,13 @@
         AndroidFuture<UserSwitchResult> future = carUserManager.switchUser(targetUserId);
         UserSwitchResult result = waitForFuture(writer, future, timeout);
         if (result == null) return;
-        writer.printf("UserSwitchResult: status = %s\n",
+        writer.printf("UserSwitchResult: status=%s",
                 UserSwitchResult.statusToString(result.getStatus()));
         String msg = result.getErrorMessage();
-        if (msg != null && !msg.isEmpty()) {
-            writer.printf("UserSwitchResult: Message = %s\n", msg);
+        if (!TextUtils.isEmpty(msg)) {
+            writer.printf(", errorMessage=%s", msg);
         }
+        writer.println();
     }
 
     private void createUser(String[] args, PrintWriter writer) {
@@ -1037,8 +1056,23 @@
                 + ", halOnly=" + halOnly + ", timeout=" + timeout);
 
         if (!halOnly) {
-            // TODO(b/150408921): implement it
-            throw new UnsupportedOperationException("must pass --hal-only for now");
+            CarUserManager carUserManager = getCarUserManager(mContext);
+            AndroidFuture<UserCreationResult> future = carUserManager
+                    .createUser(name, userType, flags);
+
+            UserCreationResult result = waitForFuture(writer, future, timeout);
+            if (result == null) return;
+
+            android.content.pm.UserInfo user = result.getUser();
+            writer.printf("UserCreationResult: status=%s, user=%s",
+                    UserCreationResult.statusToString(result.getStatus()),
+                    user == null ? "N/A" : user.toFullString());
+            String msg = result.getErrorMessage();
+            if (!TextUtils.isEmpty(msg)) {
+                writer.printf(", errorMessage=%s", msg);
+            }
+            writer.println();
+            return;
         }
 
         CountDownLatch latch = new CountDownLatch(1);
@@ -1047,8 +1081,11 @@
         CreateUserRequest request = new CreateUserRequest();
 
         UserManager um = UserManager.get(mContext);
-        android.content.pm.UserInfo newUser =
-                um.createUser(name, userType, flags);
+        android.content.pm.UserInfo newUser = um.createUser(name, userType, flags);
+        if (newUser == null) {
+            writer.printf("Failed to create user");
+            return;
+        }
         writer.printf("New user: %s\n", newUser.toFullString());
         Log.i(TAG, "Created new user: " + newUser.toFullString());
 
@@ -1057,31 +1094,79 @@
 
         request.usersInfo = generateUsersInfo();
 
-        userHal.createUser(request, timeout, (status, resp) -> {
-            Log.d(TAG, "CreateUserResponse: status=" + status + ", resp=" + resp);
-            try {
+        AtomicBoolean halOk = new AtomicBoolean(false);
+        try {
+            userHal.createUser(request, timeout, (status, resp) -> {
+                Log.d(TAG, "CreateUserResponse: status=" + status + ", resp=" + resp);
                 writer.printf("Call Status: %s\n",
                         UserHalHelper.halCallbackStatusToString(status));
-                if (status != HalCallback.STATUS_OK) {
-                    return;
+                if (status == HalCallback.STATUS_OK) {
+                    halOk.set(resp.status == CreateUserStatus.SUCCESS);
+                    writer.printf("Request id: %d\n", resp.requestId);
+                    writer.printf("Create Status: %s\n", CreateUserStatus.toString(resp.status));
+                    String errorMessage = resp.errorMessage;
+                    if (!TextUtils.isEmpty(errorMessage)) {
+                        writer.printf("Error message: %s", errorMessage);
+                    }
                 }
-                writer.printf("Request id: %d\n", resp.requestId);
-                writer.printf("Create Status: %s\n", CreateUserStatus.toString(resp.status));
-                String errorMessage = resp.errorMessage;
-                if (!TextUtils.isEmpty(errorMessage)) {
-                    writer.printf("Error message: %s", errorMessage);
-                }
-
-                if (resp.status == CreateUserStatus.FAILURE) {
-                    Log.i(TAG, "Removing new user due to HAL failure");
-                    boolean removed = um.removeUser(newUser.id);
-                    writer.printf("User removed: %b\n", removed);
-                }
-            } finally {
                 latch.countDown();
+            });
+            waitForHal(writer, latch, timeout);
+        } catch (Exception e) {
+            writer.printf("HAL failed: %s\n", e);
+        } finally {
+            if (!halOk.get()) {
+                writer.printf("Removing user %d due to HAL failure\n", newUser.id);
+                boolean removed = um.removeUser(newUser.id);
+                writer.printf("User removed: %b\n", removed);
             }
-        });
-        waitForHal(writer, latch, timeout);
+        }
+    }
+
+    private void removeUser(String[] args, PrintWriter writer) {
+        if (args.length < 2) {
+            writer.println("Insufficient number of args");
+            return;
+        }
+
+        int userId = Integer.parseInt(args[1]);
+        boolean halOnly = false;
+
+        for (int i = 2; i < args.length; i++) {
+            String arg = args[i];
+            switch (arg) {
+                case "--hal-only":
+                    halOnly = true;
+                    break;
+                default:
+                    writer.println("Invalid option at index " + i + ": " + arg);
+                    return;
+            }
+        }
+
+        Log.d(TAG, "handleRemoveUser(): User to remove=" + userId + ", halOnly=" + halOnly);
+
+        if (halOnly) {
+            UserHalService userHal = mHal.getUserHal();
+            UsersInfo usersInfo = generateUsersInfo();
+            UserInfo userInfo = new UserInfo();
+            userInfo.userId = userId;
+            userInfo.flags = getUserHalFlags(userId);
+
+            RemoveUserRequest request = new RemoveUserRequest();
+            request.removedUserInfo = userInfo;
+            request.usersInfo = usersInfo;
+
+            userHal.removeUser(request);
+            writer.printf("User removal sent for HAL only.\n");
+            return;
+        }
+
+        CarUserManager carUserManager = getCarUserManager(mContext);
+        UserRemovalResult result = carUserManager.removeUser(userId);
+        if (result == null) return;
+        writer.printf("UserRemovalResult: status = %s\n",
+                UserRemovalResult.statusToString(result.getStatus()));
     }
 
     private static <T> T waitForFuture(@NonNull PrintWriter writer,
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index 53e76fa..4d78dad 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -208,7 +208,7 @@
                 mCarUserService);
         mPerUserCarServiceHelper = new PerUserCarServiceHelper(serviceContext, mCarUserService);
         mCarBluetoothService = new CarBluetoothService(serviceContext, mPerUserCarServiceHelper);
-        mCarInputService = new CarInputService(serviceContext, mHal.getInputHal());
+        mCarInputService = new CarInputService(serviceContext, mHal.getInputHal(), mCarUserService);
         mCarProjectionService = new CarProjectionService(
                 serviceContext, null /* handler */, mCarInputService, mCarBluetoothService);
         mGarageModeService = new GarageModeService(mContext);
diff --git a/service/src/com/android/car/audio/CarAudioService.java b/service/src/com/android/car/audio/CarAudioService.java
index b0dd41d..3efbd18 100644
--- a/service/src/com/android/car/audio/CarAudioService.java
+++ b/service/src/com/android/car/audio/CarAudioService.java
@@ -56,7 +56,6 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseIntArray;
-import android.view.DisplayAddress;
 import android.view.KeyEvent;
 
 import com.android.car.CarLocalServices;
@@ -1046,31 +1045,6 @@
         return true;
     }
 
-    /**
-     * Gets the zone id for the display port id.
-     * @param displayPortId display port id to match
-     * @return zone id for the display port id or
-     * CarAudioManager.PRIMARY_AUDIO_ZONE if none are found
-     */
-    @Override
-    public int getZoneIdForDisplayPortId(byte displayPortId) {
-        enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
-        requireDynamicRouting();
-        synchronized (mImplLock) {
-            for (int index = 0; index < mCarAudioZones.length; index++) {
-                CarAudioZone zone = mCarAudioZones[index];
-                List<DisplayAddress.Physical> displayAddresses = zone.getPhysicalDisplayAddresses();
-                if (displayAddresses.stream().anyMatch(displayAddress->
-                        displayAddress.getPort() == displayPortId)) {
-                    return index;
-                }
-            }
-
-            // Everything else defaults to primary audio zone
-            return CarAudioManager.PRIMARY_AUDIO_ZONE;
-        }
-    }
-
     @Override
     public void registerVolumeCallback(@NonNull IBinder binder) {
         synchronized (mImplLock) {
diff --git a/service/src/com/android/car/audio/CarAudioZone.java b/service/src/com/android/car/audio/CarAudioZone.java
index a87b4b1..f4a4a33 100644
--- a/service/src/com/android/car/audio/CarAudioZone.java
+++ b/service/src/com/android/car/audio/CarAudioZone.java
@@ -19,7 +19,6 @@
 import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
 import android.util.Log;
-import android.view.DisplayAddress;
 
 import com.android.car.CarLog;
 import com.android.internal.util.Preconditions;
@@ -45,14 +44,12 @@
     private final int mId;
     private final String mName;
     private final List<CarVolumeGroup> mVolumeGroups;
-    private final List<DisplayAddress.Physical> mPhysicalDisplayAddresses;
     private List<AudioDeviceAttributes> mInputAudioDevice;
 
     CarAudioZone(int id, String name) {
         mId = id;
         mName = name;
         mVolumeGroups = new ArrayList<>();
-        mPhysicalDisplayAddresses = new ArrayList<>();
         mInputAudioDevice = new ArrayList<>();
     }
 
@@ -96,23 +93,6 @@
     }
 
     /**
-     * Associates a new display physical port with this audio zone. This can be used to
-     * identify what zone an activity should produce sound in when launching on a particular display
-     * @param physicalDisplayAddress port to associate with this zone
-     */
-    void addPhysicalDisplayAddress(DisplayAddress.Physical physicalDisplayAddress) {
-        mPhysicalDisplayAddresses.add(physicalDisplayAddress);
-    }
-
-    /**
-     * Gets list of ports for displays associated with this audio zone
-     * @return list of Physical ports for displays associated with this audio zone
-     */
-    List<DisplayAddress.Physical> getPhysicalDisplayAddresses() {
-        return mPhysicalDisplayAddresses;
-    }
-
-    /**
      * @return Snapshot of available {@link CarVolumeGroup}s in array.
      */
     CarVolumeGroup[] getVolumeGroups() {
@@ -173,11 +153,6 @@
     void dump(String indent, PrintWriter writer) {
         String internalIndent = indent + "\t";
         writer.printf("%sCarAudioZone(%s:%d) isPrimary? %b\n", indent, mName, mId, isPrimaryZone());
-        for (DisplayAddress.Physical physical: mPhysicalDisplayAddresses) {
-            long port = (long) physical.getPort();
-            writer.printf("%sDisplayAddress.Physical(%d)\n", internalIndent, port);
-        }
-        writer.println();
 
         for (CarVolumeGroup group : mVolumeGroups) {
             group.dump(internalIndent, writer);
diff --git a/service/src/com/android/car/audio/CarAudioZonesHelper.java b/service/src/com/android/car/audio/CarAudioZonesHelper.java
index 3ff25e9..1a4e0ad 100644
--- a/service/src/com/android/car/audio/CarAudioZonesHelper.java
+++ b/service/src/com/android/car/audio/CarAudioZonesHelper.java
@@ -22,7 +22,6 @@
 import android.text.TextUtils;
 import android.util.SparseIntArray;
 import android.util.Xml;
-import android.view.DisplayAddress;
 
 import com.android.car.audio.CarAudioContext.AudioContext;
 import com.android.internal.util.Preconditions;
@@ -55,14 +54,11 @@
     private static final String TAG_VOLUME_GROUP = "group";
     private static final String TAG_AUDIO_DEVICE = "device";
     private static final String TAG_CONTEXT = "context";
-    private static final String TAG_DISPLAYS = "displays";
-    private static final String TAG_DISPLAY = "display";
     private static final String ATTR_VERSION = "version";
     private static final String ATTR_IS_PRIMARY = "isPrimary";
     private static final String ATTR_ZONE_NAME = "name";
     private static final String ATTR_DEVICE_ADDRESS = "address";
     private static final String ATTR_CONTEXT_NAME = "context";
-    private static final String ATTR_PHYSICAL_PORT = "port";
     private static final String ATTR_ZONE_ID = "audioZoneId";
     private static final String ATTR_OCCUPANT_ZONE_ID = "occupantZoneId";
     private static final String TAG_INPUT_DEVICES = "inputDevices";
@@ -132,7 +128,6 @@
     private final Map<String, CarAudioDeviceInfo> mAddressToCarAudioDeviceInfo;
     private final Map<String, AudioDeviceInfo> mAddressToInputAudioDeviceInfo;
     private final InputStream mInputStream;
-    private final Set<Long> mPortIds;
     private final SparseIntArray mZoneIdToOccupantZoneIdMapping;
     private final Set<Integer> mAudioZoneIds;
     private final Set<String> mInputAudioDevices;
@@ -158,7 +153,6 @@
         mAddressToInputAudioDeviceInfo =
                 CarAudioZonesHelper.generateAddressToInputAudioDeviceInfoMap(inputDeviceInfo);
         mNextSecondaryZoneId = CarAudioManager.PRIMARY_AUDIO_ZONE + 1;
-        mPortIds = new HashSet<>();
         mZoneIdToOccupantZoneIdMapping = new SparseIntArray();
         mAudioZoneIds = new HashSet<>();
         mInputAudioDevices = new HashSet<>();
@@ -258,8 +252,6 @@
             // Expect one <volumeGroups> in one audio zone
             if (TAG_VOLUME_GROUPS.equals(parser.getName())) {
                 parseVolumeGroups(parser, zone);
-            } else if (TAG_DISPLAYS.equals(parser.getName())) {
-                parseDisplays(parser, zone);
             } else if (TAG_INPUT_DEVICES.equals(parser.getName())) {
                 parseInputAudioDevices(parser, zone);
             } else {
@@ -364,37 +356,6 @@
         mInputAudioDevices.add(audioDeviceAddress);
     }
 
-    private void parseDisplays(XmlPullParser parser, CarAudioZone zone)
-            throws IOException, XmlPullParserException {
-        while (parser.next() != XmlPullParser.END_TAG) {
-            if (parser.getEventType() != XmlPullParser.START_TAG) continue;
-            if (TAG_DISPLAY.equals(parser.getName())) {
-                zone.addPhysicalDisplayAddress(parsePhysicalDisplayAddress(parser));
-            }
-            skip(parser);
-        }
-    }
-
-    private DisplayAddress.Physical parsePhysicalDisplayAddress(XmlPullParser parser) {
-        String port = parser.getAttributeValue(NAMESPACE, ATTR_PHYSICAL_PORT);
-        long portId;
-        try {
-            portId = Long.parseLong(port);
-        } catch (NumberFormatException e) {
-            throw new IllegalArgumentException(String.format("Port %s is not a number", port), e);
-        }
-        validatePortIsUnique(portId);
-        return DisplayAddress.fromPhysicalDisplayId(portId);
-    }
-
-    private void validatePortIsUnique(Long portId) {
-        if (mPortIds.contains(portId)) {
-            throw new IllegalArgumentException(
-                    String.format("Port Id %d is already associated with a zone", portId));
-        }
-        mPortIds.add(portId);
-    }
-
     private void validateOccupantZoneIdIsUnique(int occupantZoneId) {
         if (mZoneIdToOccupantZoneIdMapping.indexOfValue(occupantZoneId) > -1) {
             throw new IllegalArgumentException(ATTR_OCCUPANT_ZONE_ID + " " + occupantZoneId
diff --git a/service/src/com/android/car/hal/UserHalService.java b/service/src/com/android/car/hal/UserHalService.java
index 33cf970..645f563 100644
--- a/service/src/com/android/car/hal/UserHalService.java
+++ b/service/src/com/android/car/hal/UserHalService.java
@@ -17,6 +17,7 @@
 
 import static android.car.VehiclePropertyIds.CREATE_USER;
 import static android.car.VehiclePropertyIds.INITIAL_USER_INFO;
+import static android.car.VehiclePropertyIds.REMOVE_USER;
 import static android.car.VehiclePropertyIds.SWITCH_USER;
 import static android.car.VehiclePropertyIds.USER_IDENTIFICATION_ASSOCIATION;
 
@@ -34,7 +35,9 @@
 import android.hardware.automotive.vehicle.V2_0.CreateUserResponse;
 import android.hardware.automotive.vehicle.V2_0.CreateUserStatus;
 import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
+import android.hardware.automotive.vehicle.V2_0.RemoveUserRequest;
 import android.hardware.automotive.vehicle.V2_0.SwitchUserMessageType;
+import android.hardware.automotive.vehicle.V2_0.SwitchUserRequest;
 import android.hardware.automotive.vehicle.V2_0.SwitchUserResponse;
 import android.hardware.automotive.vehicle.V2_0.SwitchUserStatus;
 import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationType;
@@ -165,6 +168,9 @@
                     mHandler.sendMessage(obtainMessage(
                             UserHalService::handleOnCreateUserResponse, this, value));
                     break;
+                case REMOVE_USER:
+                    Log.w(TAG, "Received REMOVE_USER HAL event: " + value);
+                    break;
                 case USER_IDENTIFICATION_ASSOCIATION:
                     mHandler.sendMessage(obtainMessage(
                             UserHalService::handleOnUserIdentificationAssociation, this, value));
@@ -270,33 +276,30 @@
     /**
      * Calls HAL to asynchronously switch user.
      *
-     * @param targetInfo target user for user switching
+     * @param request metadata
      * @param timeoutMs how long to wait (in ms) for the property change event.
-     * @param usersInfo current state of Android users.
      * @param callback to handle the response.
      *
      * @throws IllegalStateException if the HAL does not support user management (callers should
      * call {@link #isSupported()} first to avoid this exception).
      */
-    public void switchUser(@NonNull UserInfo targetInfo, int timeoutMs,
-            @NonNull UsersInfo usersInfo, @NonNull HalCallback<SwitchUserResponse> callback) {
-        if (DBG) Log.d(TAG, "switchUser(" + targetInfo + ")");
-
-        Objects.requireNonNull(targetInfo);
+    public void switchUser(@NonNull SwitchUserRequest request, int timeoutMs,
+            @NonNull HalCallback<SwitchUserResponse> callback) {
         Preconditions.checkArgumentPositive(timeoutMs, "timeout must be positive");
-        Objects.requireNonNull(callback);
-        UserHalHelper.checkValid(usersInfo);
+        Objects.requireNonNull(callback, "callback cannot be null");
 
+        if (DBG) Log.d(TAG, "switchUser(" + request + ")");
         VehiclePropValue propRequest;
         int requestId;
         synchronized (mLock) {
             checkSupportedLocked();
             if (hasPendingRequestLocked(SwitchUserResponse.class, callback)) return;
             requestId = getNextRequestId();
+            request.requestId = requestId;
+            request.messageType = SwitchUserMessageType.ANDROID_SWITCH;
+            propRequest = UserHalHelper.toVehiclePropValue(request);
             EventLog.writeEvent(EventLogTags.CAR_USER_HAL_SWITCH_USER_REQ, requestId,
-                    targetInfo.userId, timeoutMs);
-            propRequest = getPropRequestForSwitchUserLocked(requestId,
-                    SwitchUserMessageType.ANDROID_SWITCH, targetInfo, usersInfo);
+                    request.targetUser.userId, timeoutMs);
             addPendingRequestLocked(requestId, SwitchUserResponse.class, callback);
         }
 
@@ -304,6 +307,34 @@
     }
 
     /**
+     * Calls HAL to remove user.
+     *
+     * @throws IllegalStateException if the HAL does not support user management (callers should
+     * call {@link #isSupported()} first to avoid this exception).
+     */
+    public void removeUser(@NonNull RemoveUserRequest request) {
+        Objects.requireNonNull(request, "request cannot be null");
+
+        if (DBG) Log.d(TAG, "removeUser(" + request.removedUserInfo.userId + ")");
+        EventLog.writeEvent(EventLogTags.CAR_USER_HAL_REMOVE_USER_REQ,
+                request.removedUserInfo.userId, request.usersInfo.currentUser.userId);
+
+        VehiclePropValue propRequest;
+        synchronized (mLock) {
+            checkSupportedLocked();
+            request.requestId = getNextRequestId();
+            propRequest = UserHalHelper.toVehiclePropValue(request);
+
+        }
+        try {
+            if (DBG) Log.d(TAG, "Calling hal.set(): " + propRequest);
+            mHal.set(propRequest);
+        } catch (ServiceSpecificException e) {
+            Log.w(TAG, "Failed to set REMOVE USER", e);
+        }
+    }
+
+    /**
      * Calls HAL to indicate an Android user was created.
      *
      * @param request info agout the created user.
@@ -337,23 +368,17 @@
 
     /**
      * Calls HAL after android user switch.
-     *
-     * @param requestId for which switch response is sent.
-     * @param targetInfo target user info.
-     * @param usersInfo current state of Android users.
      */
-    public void postSwitchResponse(int requestId, @NonNull UserInfo targetInfo,
-            @NonNull UsersInfo usersInfo) {
-        EventLog.writeEvent(EventLogTags.CAR_USER_HAL_POST_SWITCH_USER_REQ, requestId,
-                targetInfo.userId, usersInfo.currentUser.userId);
-        if (DBG) Log.d(TAG, "postSwitchResponse(" + targetInfo + ")");
-        UserHalHelper.checkValid(usersInfo);
+    public void postSwitchResponse(@NonNull SwitchUserRequest request) {
+        EventLog.writeEvent(EventLogTags.CAR_USER_HAL_POST_SWITCH_USER_REQ, request.requestId,
+                request.targetUser.userId, request.usersInfo.currentUser.userId);
+        if (DBG) Log.d(TAG, "postSwitchResponse(" + request.targetUser.userId + ")");
 
         VehiclePropValue propRequest;
         synchronized (mLock) {
             checkSupportedLocked();
-            propRequest = getPropRequestForSwitchUserLocked(requestId,
-                    SwitchUserMessageType.ANDROID_POST_SWITCH, targetInfo, usersInfo);
+            request.messageType = SwitchUserMessageType.ANDROID_POST_SWITCH;
+            propRequest = UserHalHelper.toVehiclePropValue(request);
         }
 
         try {
@@ -368,22 +393,18 @@
      * Calls HAL to switch user after legacy Android user switch. Legacy Android user switch means
      * user switch is not requested by {@link CarUserManager} or OEM, and user switch is directly
      * requested by {@link ActivityManager}
-     *
-     * @param targetInfo target user info.
-     * @param usersInfo current state of Android users.
      */
-    public void legacyUserSwitch(@NonNull UserInfo targetInfo, @NonNull UsersInfo usersInfo) {
-        if (DBG) Log.d(TAG, "userSwitchLegacy(" + targetInfo + ")");
-        UserHalHelper.checkValid(usersInfo);
+    public void legacyUserSwitch(@NonNull SwitchUserRequest request) {
+        if (DBG) Log.d(TAG, "userSwitchLegacy(" + request + ")");
 
         VehiclePropValue propRequest;
         synchronized (mLock) {
             checkSupportedLocked();
             int requestId = getNextRequestId();
             EventLog.writeEvent(EventLogTags.CAR_USER_HAL_LEGACY_SWITCH_USER_REQ, requestId,
-                    targetInfo.userId, usersInfo.currentUser.userId);
-            propRequest = getPropRequestForSwitchUserLocked(requestId,
-                    SwitchUserMessageType.LEGACY_ANDROID_SWITCH, targetInfo, usersInfo);
+                    request.targetUser.userId, request.usersInfo.currentUser.userId);
+            request.messageType = SwitchUserMessageType.LEGACY_ANDROID_SWITCH;
+            propRequest = UserHalHelper.toVehiclePropValue(request);
         }
 
         try {
diff --git a/service/src/com/android/car/pm/CarPackageManagerService.java b/service/src/com/android/car/pm/CarPackageManagerService.java
index 1d1bbb6..9f66e49 100644
--- a/service/src/com/android/car/pm/CarPackageManagerService.java
+++ b/service/src/com/android/car/pm/CarPackageManagerService.java
@@ -224,6 +224,11 @@
      */
     @Override
     public void restartTask(int taskId) {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.REAL_GET_TASKS)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException(
+                    "requires permission " + android.Manifest.permission.REAL_GET_TASKS);
+        }
         mSystemActivityMonitoringService.restartTask(taskId);
     }
 
diff --git a/service/src/com/android/car/stats/CarStatsService.java b/service/src/com/android/car/stats/CarStatsService.java
index 20b451c..bf79ee8 100644
--- a/service/src/com/android/car/stats/CarStatsService.java
+++ b/service/src/com/android/car/stats/CarStatsService.java
@@ -27,7 +27,7 @@
 import com.android.car.CarStatsLog;
 import com.android.car.stats.VmsClientLogger.ConnectionState;
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.ConcurrentUtils;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -102,7 +102,7 @@
         mStatsManager.setPullAtomCallback(
                 CarStatsLog.VMS_CLIENT_STATS,
                 metadata,
-                BackgroundThread.getExecutor(),
+                ConcurrentUtils.DIRECT_EXECUTOR,
                 (atomTag, data) -> pullVmsClientStats(atomTag, data)
         );
     }
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index d7de501..bd45abb 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -33,7 +33,9 @@
 import android.car.user.CarUserManager.UserLifecycleEvent;
 import android.car.user.CarUserManager.UserLifecycleEventType;
 import android.car.user.CarUserManager.UserLifecycleListener;
+import android.car.user.UserCreationResult;
 import android.car.user.UserIdentificationAssociationResponse;
+import android.car.user.UserRemovalResult;
 import android.car.user.UserSwitchResult;
 import android.car.userlib.CarUserManagerHelper;
 import android.car.userlib.CommonConstants.CarUserServiceConstants;
@@ -45,10 +47,15 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.UserInfo;
+import android.content.pm.UserInfo.UserInfoFlag;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.hardware.automotive.vehicle.V2_0.CreateUserRequest;
+import android.hardware.automotive.vehicle.V2_0.CreateUserStatus;
 import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
 import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
+import android.hardware.automotive.vehicle.V2_0.RemoveUserRequest;
+import android.hardware.automotive.vehicle.V2_0.SwitchUserRequest;
 import android.hardware.automotive.vehicle.V2_0.SwitchUserStatus;
 import android.hardware.automotive.vehicle.V2_0.UserIdentificationGetRequest;
 import android.hardware.automotive.vehicle.V2_0.UserIdentificationResponse;
@@ -297,6 +304,7 @@
         writer.printf("EnablePassengerSupport: %s\n", mEnablePassengerSupport);
         writer.printf("User HAL timeout: %dms\n",  mHalTimeoutMs);
         writer.printf("Initial user: %s\n", mInitialUser);
+
         writer.println("Relevant overlayable properties");
         Resources res = mContext.getResources();
         writer.printf("%sowner_name=%s\n", indent,
@@ -308,9 +316,18 @@
                     mRequestIdForUserSwitchInProcess);
         writer.printf("System UI package name=%s\n", getSystemUiPackageName());
 
+        writer.println("Relevant Global settings");
+        dumpGlobalProperty(writer, indent, CarSettings.Global.LAST_ACTIVE_USER_ID);
+        dumpGlobalProperty(writer, indent, CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID);
+
         dumpUserMetrics(writer);
     }
 
+    private void dumpGlobalProperty(PrintWriter writer, String indent, String property) {
+        String value = Settings.Global.getString(mContext.getContentResolver(), property);
+        writer.printf("%s%s=%s\n", indent, property, value);
+    }
+
     /**
      * Dumps user metrics.
      */
@@ -432,13 +449,13 @@
         if (UserHelper.isHeadlessSystemUser(driverId)) {
             // System user doesn't associate with real person, can not be switched to.
             Log.w(TAG_USER, "switching to system user in headless system user mode is not allowed");
-            sendResult(receiver, UserSwitchResult.STATUS_INVALID_REQUEST);
+            sendUserSwitchResult(receiver, UserSwitchResult.STATUS_INVALID_REQUEST);
             return;
         }
         int userSwitchable = mUserManager.getUserSwitchability();
         if (userSwitchable != UserManager.SWITCHABILITY_STATUS_OK) {
             Log.w(TAG_USER, "current process is not allowed to switch user");
-            sendResult(receiver, UserSwitchResult.STATUS_INVALID_REQUEST);
+            sendUserSwitchResult(receiver, UserSwitchResult.STATUS_INVALID_REQUEST);
             return;
         }
         switchUser(driverId, mHalTimeoutMs, receiver);
@@ -597,7 +614,7 @@
                 timeoutMs);
         Objects.requireNonNull(receiver, "receiver cannot be null");
         checkManageUsersPermission("getInitialInfo");
-        UsersInfo usersInfo = getUsersInfo();
+        UsersInfo usersInfo = UserHalHelper.newUsersInfo(mUserManager);
         mHal.getInitialUserInfo(requestType, timeoutMs, usersInfo, (status, resp) -> {
             Bundle resultData = null;
             if (resp != null) {
@@ -699,7 +716,7 @@
                 mHalTimeoutMs);
         Objects.requireNonNull(callback, "callback cannot be null");
         checkManageUsersPermission("getInitialUserInfo");
-        UsersInfo usersInfo = getUsersInfo();
+        UsersInfo usersInfo = UserHalHelper.newUsersInfo(mUserManager);
         mHal.getInitialUserInfo(requestType, mHalTimeoutMs, usersInfo, callback);
     }
 
@@ -756,7 +773,7 @@
                 Log.d(TAG_USER, "Current user is same as requested target user: " + targetUserId);
             }
             int resultStatus = UserSwitchResult.STATUS_ALREADY_REQUESTED_USER;
-            sendResult(receiver, resultStatus);
+            sendUserSwitchResult(receiver, resultStatus);
             return;
         }
 
@@ -779,7 +796,7 @@
                 }
 
                 int resultStatus = UserSwitchResult.STATUS_TARGET_USER_ALREADY_BEING_SWITCHED_TO;
-                sendResult(receiver, resultStatus);
+                sendUserSwitchResult(receiver, resultStatus);
                 return;
             }
             else {
@@ -788,12 +805,10 @@
             }
         }
 
-        UsersInfo usersInfo = getUsersInfo();
-        android.hardware.automotive.vehicle.V2_0.UserInfo halTargetUser =
-                new android.hardware.automotive.vehicle.V2_0.UserInfo();
-        halTargetUser.userId = targetUser.id;
-        halTargetUser.flags = UserHalHelper.convertFlags(targetUser);
-        mHal.switchUser(halTargetUser, timeoutMs, usersInfo, (status, resp) -> {
+        UsersInfo usersInfo = UserHalHelper.newUsersInfo(mUserManager);
+        SwitchUserRequest request = createUserSwitchRequest(targetUserId, usersInfo);
+
+        mHal.switchUser(request, timeoutMs, (status, resp) -> {
             if (Log.isLoggable(TAG_USER, Log.DEBUG)) {
                 Log.d(TAG, "switch response: status="
                         + UserHalHelper.halCallbackStatusToString(status) + ", resp=" + resp);
@@ -807,7 +822,7 @@
                     Log.w(TAG, "invalid callback status ("
                             + UserHalHelper.halCallbackStatusToString(status) + ") for response "
                             + resp);
-                    sendResult(receiver, resultStatus);
+                    sendUserSwitchResult(receiver, resultStatus);
                     mUserIdForUserSwitchInProcess = UserHandle.USER_NULL;
                     return;
                 }
@@ -825,7 +840,7 @@
                     }
                     resultStatus =
                             UserSwitchResult.STATUS_TARGET_USER_ABANDONED_DUE_TO_A_NEW_REQUEST;
-                    sendResult(receiver, resultStatus);
+                    sendUserSwitchResult(receiver, resultStatus);
                     mUserIdForUserSwitchInProcess = UserHandle.USER_NULL;
                     return;
                 }
@@ -853,24 +868,83 @@
                         // HAL failed to switch user
                         resultStatus = UserSwitchResult.STATUS_HAL_FAILURE;
                         break;
+                    default:
+                        // Shouldn't happen because UserHalService validates the status
+                        Log.wtf(TAG, "Received invalid user switch status from HAL: " + resp);
                 }
 
                 if (mRequestIdForUserSwitchInProcess == 0) {
                     mUserIdForUserSwitchInProcess = UserHandle.USER_NULL;
                 }
             }
-            sendResult(receiver, resultStatus, resp.errorMessage);
+            sendUserSwitchResult(receiver, resultStatus, resp.errorMessage);
         });
     }
 
+    @Override
+    public UserRemovalResult removeUser(@UserIdInt int userId) {
+        checkManageUsersPermission("removeUser");
+        EventLog.writeEvent(EventLogTags.CAR_USER_SVC_REMOVE_USER_REQ, userId);
+        // If the requested user is the current user, return error.
+        if (ActivityManager.getCurrentUser() == userId) {
+            return logAndGetResults(userId,
+                    UserRemovalResult.STATUS_TARGET_USER_IS_CURRENT_USER);
+        }
+
+        // If requested user is the only admin user, return error.
+        UserInfo userInfo = mUserManager.getUserInfo(userId);
+        if (userInfo == null) {
+            return logAndGetResults(userId, UserRemovalResult.STATUS_USER_DOES_NOT_EXIST);
+        }
+
+        android.hardware.automotive.vehicle.V2_0.UserInfo halUser =
+                new android.hardware.automotive.vehicle.V2_0.UserInfo();
+        halUser.userId = userInfo.id;
+        halUser.flags = UserHalHelper.convertFlags(userInfo);
+        UsersInfo usersInfo = UserHalHelper.newUsersInfo(mUserManager);
+
+        // Do not delete last admin user.
+        if (UserHalHelper.isAdmin(halUser.flags)) {
+            int size = usersInfo.existingUsers.size();
+            int totalAdminUsers = 0;
+            for (int i = 0; i < size; i++) {
+                if (UserHalHelper.isAdmin(usersInfo.existingUsers.get(i).flags)) {
+                    totalAdminUsers++;
+                }
+            }
+            if (totalAdminUsers == 1) {
+                return logAndGetResults(userId,
+                        UserRemovalResult.STATUS_TARGET_USER_IS_LAST_ADMIN_USER);
+            }
+        }
+
+        // First remove user from android and then remove from HAL because HAL remove user is one
+        // way call.
+        if (!mUserManager.removeUser(userId)) {
+            return logAndGetResults(userId, UserRemovalResult.STATUS_ANDROID_FAILURE);
+        }
+
+        RemoveUserRequest request = new RemoveUserRequest();
+        request.removedUserInfo = halUser;
+        request.usersInfo = usersInfo;
+        mHal.removeUser(request);
+        return logAndGetResults(userId, UserRemovalResult.STATUS_SUCCESSFUL);
+    }
+
+    private UserRemovalResult logAndGetResults(@UserIdInt int userId,
+            @UserRemovalResult.Status int result) {
+        EventLog.writeEvent(EventLogTags.CAR_USER_SVC_REMOVE_USER_RESP, userId, result);
+        return new UserRemovalResult(result);
+    }
+
     private void sendUserSwitchUiCallback(@UserIdInt int targetUserId) {
         if (mUserSwitchUiReceiver == null) {
             Log.w(TAG_USER, "No User switch UI receiver.");
             return;
         }
 
+        EventLog.writeEvent(EventLogTags.CAR_USER_SVC_SWITCH_USER_UI_REQ, targetUserId);
         try {
-            EventLog.writeEvent(EventLogTags.CAR_USER_SVC_SWITCH_USER_UI_REQ, targetUserId);
             mUserSwitchUiReceiver.send(targetUserId, null);
         } catch (RemoteException e) {
             Log.e(TAG_USER, "Error calling user switch UI receiver.", e);
@@ -878,6 +952,106 @@
     }
 
     @Override
+    public void createUser(@Nullable String name, @NonNull String userType, @UserInfoFlag int flags,
+            int timeoutMs, @NonNull AndroidFuture<UserCreationResult> receiver) {
+        Objects.requireNonNull(userType, "user type cannot be null");
+        Objects.requireNonNull(receiver, "receiver cannot be null");
+        checkManageOrCreateUsersPermission("createUser");
+        EventLog.writeEvent(EventLogTags.CAR_USER_SVC_CREATE_USER_REQ, UserHelper.safeName(name),
+                userType, flags, timeoutMs);
+
+        UserInfo newUser;
+        try {
+            newUser = mUserManager.createUser(name, userType, flags);
+            if (newUser == null) {
+                Log.w(TAG, "um.createUser() returned null for user of type " + userType
+                        + " and flags " + UserInfo.flagsToString(flags));
+                sendUserCreationResultFailure(receiver, UserCreationResult.STATUS_ANDROID_FAILURE);
+                return;
+            }
+            if (Log.isLoggable(TAG_USER, Log.DEBUG)) {
+                Log.d(TAG, "Created user: " + newUser.toFullString());
+            }
+            EventLog.writeEvent(EventLogTags.CAR_USER_SVC_CREATE_USER_USER_CREATED, newUser.id,
+                    UserHelper.safeName(newUser.name), newUser.userType, newUser.flags);
+        } catch (RuntimeException e) {
+            Log.e(TAG_USER, "Error creating user of type " + userType + " and flags"
+                    + UserInfo.flagsToString(flags), e);
+            sendUserCreationResultFailure(receiver, UserCreationResult.STATUS_ANDROID_FAILURE);
+            return;
+        }
+
+        CreateUserRequest request = new CreateUserRequest();
+        request.usersInfo = UserHalHelper.newUsersInfo(mUserManager);
+        if (!TextUtils.isEmpty(name)) {
+            request.newUserName = name;
+        }
+        request.newUserInfo.userId = newUser.id;
+        request.newUserInfo.flags = UserHalHelper.convertFlags(newUser);
+        if (Log.isLoggable(TAG_USER, Log.DEBUG)) {
+            Log.d(TAG, "Create user request: " + request);
+        }
+
+        try {
+            mHal.createUser(request, timeoutMs, (status, resp) -> {
+                int resultStatus = UserCreationResult.STATUS_HAL_INTERNAL_FAILURE;
+                if (Log.isLoggable(TAG_USER, Log.DEBUG)) {
+                    Log.d(TAG, "createUserResponse: status="
+                            + UserHalHelper.halCallbackStatusToString(status) + ", resp=" + resp);
+                }
+                UserInfo user = null; // user returned in the result
+                if (status != HalCallback.STATUS_OK) {
+                    Log.w(TAG, "invalid callback status ("
+                            + UserHalHelper.halCallbackStatusToString(status) + ") for response "
+                            + resp);
+                    EventLog.writeEvent(EventLogTags.CAR_USER_SVC_CREATE_USER_RESP, status,
+                            resultStatus, resp.errorMessage);
+                    removeUser(newUser, "HAL call failed with "
+                            + UserHalHelper.halCallbackStatusToString(status));
+                    sendUserCreationResult(receiver, resultStatus, user, /* errorMsg= */ null);
+                    return;
+                }
+
+                switch (resp.status) {
+                    case CreateUserStatus.SUCCESS:
+                        resultStatus = UserCreationResult.STATUS_SUCCESSFUL;
+                        user = newUser;
+                        break;
+                    case CreateUserStatus.FAILURE:
+                        // HAL failed to switch user
+                        resultStatus = UserCreationResult.STATUS_HAL_FAILURE;
+                        break;
+                    default:
+                        // Shouldn't happen because UserHalService validates the status
+                        Log.wtf(TAG, "Received invalid user switch status from HAL: " + resp);
+                }
+                EventLog.writeEvent(EventLogTags.CAR_USER_SVC_CREATE_USER_RESP, status,
+                        resultStatus, resp.errorMessage);
+                if (user == null) {
+                    removeUser(newUser, "HAL returned "
+                            + UserCreationResult.statusToString(resultStatus));
+                }
+                sendUserCreationResult(receiver, resultStatus, user, resp.errorMessage);
+            });
+        } catch (Exception e) {
+            Log.w(TAG, "mHal.createUser(" + request + ") failed", e);
+            removeUser(newUser, "mHal.createUser() failed");
+            sendUserCreationResultFailure(receiver, UserCreationResult.STATUS_HAL_INTERNAL_FAILURE);
+        }
+    }
+
+    private void removeUser(@NonNull UserInfo user, @NonNull String reason) {
+        EventLog.writeEvent(EventLogTags.CAR_USER_SVC_CREATE_USER_USER_REMOVED, user.id, reason);
+        try {
+            if (!mUserManager.removeUser(user.id)) {
+                Log.w(TAG, "Failed to remove user " + user.toFullString());
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to remove user " + user.toFullString(), e);
+        }
+    }
+
+    @Override
     public UserIdentificationAssociationResponse getUserIdentificationAssociation(int[] types) {
         Preconditions.checkArgument(!ArrayUtils.isEmpty(types), "must have at least one type");
         checkManageUsersPermission("getUserIdentificationAssociation");
@@ -993,16 +1167,30 @@
         }
     }
 
-    private void sendResult(@NonNull AndroidFuture<UserSwitchResult> receiver,
+    private void sendUserSwitchResult(@NonNull AndroidFuture<UserSwitchResult> receiver,
             @UserSwitchResult.Status int status) {
-        sendResult(receiver, status, /* errorMessage= */ null);
+        sendUserSwitchResult(receiver, status, /* errorMessage= */ null);
     }
 
-    private void sendResult(@NonNull AndroidFuture<UserSwitchResult> receiver,
+    private void sendUserSwitchResult(@NonNull AndroidFuture<UserSwitchResult> receiver,
             @UserSwitchResult.Status int status, @Nullable String errorMessage) {
         receiver.complete(new UserSwitchResult(status, errorMessage));
     }
 
+    private void sendUserCreationResultFailure(@NonNull AndroidFuture<UserCreationResult> receiver,
+            @UserCreationResult.Status int status) {
+        sendUserCreationResult(receiver, status, /* user= */ null, /* errorMessage= */ null);
+    }
+
+    private void sendUserCreationResult(@NonNull AndroidFuture<UserCreationResult> receiver,
+            @UserCreationResult.Status int status, @NonNull UserInfo user,
+            @Nullable String errorMessage) {
+        if (TextUtils.isEmpty(errorMessage)) {
+            errorMessage = null;
+        }
+        receiver.complete(new UserCreationResult(status, user, errorMessage));
+    }
+
     /**
      * Calls activity manager for user switch.
      *
@@ -1045,17 +1233,26 @@
             mRequestIdForUserSwitchInProcess = requestId;
         }
     }
-
     private void postSwitchHalResponse(int requestId, @UserIdInt int targetUserId) {
+        UsersInfo usersInfo = UserHalHelper.newUsersInfo(mUserManager);
+        EventLog.writeEvent(EventLogTags.CAR_USER_SVC_POST_SWITCH_USER_REQ, requestId,
+                targetUserId, usersInfo.currentUser.userId);
+        SwitchUserRequest request = createUserSwitchRequest(targetUserId, usersInfo);
+        request.requestId = requestId;
+        mHal.postSwitchResponse(request);
+    }
+
+    private SwitchUserRequest createUserSwitchRequest(@UserIdInt int targetUserId,
+            @NonNull UsersInfo usersInfo) {
         UserInfo targetUser = mUserManager.getUserInfo(targetUserId);
-        UsersInfo usersInfo = getUsersInfo();
         android.hardware.automotive.vehicle.V2_0.UserInfo halTargetUser =
                 new android.hardware.automotive.vehicle.V2_0.UserInfo();
         halTargetUser.userId = targetUser.id;
         halTargetUser.flags = UserHalHelper.convertFlags(targetUser);
-        EventLog.writeEvent(EventLogTags.CAR_USER_SVC_POST_SWITCH_USER_REQ, requestId,
-                targetUserId, usersInfo.currentUser.userId);
-        mHal.postSwitchResponse(requestId, halTargetUser, usersInfo);
+        SwitchUserRequest request = new SwitchUserRequest();
+        request.targetUser = halTargetUser;
+        request.usersInfo = usersInfo;
+        return request;
     }
 
     /**
@@ -1112,40 +1309,6 @@
         }
     }
 
-    // TODO(b/150413515): use helper to generate UsersInfo
-    private UsersInfo getUsersInfo() {
-        UserInfo currentUser;
-        try {
-            currentUser = mAm.getCurrentUser();
-        } catch (RemoteException e) {
-            // shouldn't happen
-            throw new IllegalStateException("Could not get current user: ", e);
-        }
-        return getUsersInfo(currentUser);
-    }
-
-    // TODO(b/150413515): use helper to generate UsersInfo
-    private UsersInfo getUsersInfo(@NonNull UserInfo currentUser) {
-        List<UserInfo> existingUsers = mUserManager.getUsers();
-        int size = existingUsers.size();
-
-        UsersInfo usersInfo = new UsersInfo();
-        usersInfo.numberUsers = size;
-        usersInfo.currentUser.userId = currentUser.id;
-        usersInfo.currentUser.flags = UserHalHelper.convertFlags(currentUser);
-
-        for (int i = 0; i < size; i++) {
-            UserInfo androidUser = existingUsers.get(i);
-            android.hardware.automotive.vehicle.V2_0.UserInfo halUser =
-                    new android.hardware.automotive.vehicle.V2_0.UserInfo();
-            halUser.userId = androidUser.id;
-            halUser.flags = UserHalHelper.convertFlags(androidUser);
-            usersInfo.existingUsers.add(halUser);
-        }
-
-        return usersInfo;
-    }
-
     private void updateDefaultUserRestriction() {
         // We want to set restrictions on system and guest users only once. These are persisted
         // onto disk, so it's sufficient to do it once + we minimize the number of disk writes.
@@ -1345,8 +1508,10 @@
             handleNotifyAppUserLifecycleListeners(event);
         });
 
-        // Finally, update metrics.
-        mUserMetrics.onEvent(eventType, timestampMs, fromUserId, toUserId);
+        if (timestampMs != 0) {
+            // Finally, update metrics.
+            mUserMetrics.onEvent(eventType, timestampMs, fromUserId, toUserId);
+        }
     }
 
     /**
@@ -1447,9 +1612,8 @@
         // Switch HAL users if user switch is not requested by CarUserService
         notifyHalLegacySwitch(fromUserId, toUserId);
 
-        if (!UserHelper.isHeadlessSystemUser(toUserId)) {
-            mCarUserManagerHelper.setLastActiveUser(toUserId);
-        }
+        mCarUserManagerHelper.setLastActiveUser(toUserId);
+
         if (mLastPassengerId != UserHandle.USER_NULL) {
             stopPassengerInternal(mLastPassengerId, false);
         }
@@ -1466,14 +1630,9 @@
         }
 
         // switch HAL user
-        UserInfo targetUser = mUserManager.getUserInfo(toUserId);
-        android.hardware.automotive.vehicle.V2_0.UserInfo halTargetUser =
-                new android.hardware.automotive.vehicle.V2_0.UserInfo();
-        halTargetUser.userId = targetUser.id;
-        halTargetUser.flags = UserHalHelper.convertFlags(targetUser);
-        UserInfo currentUser = mUserManager.getUserInfo(fromUserId);
-        UsersInfo usersInfo = getUsersInfo(currentUser);
-        mHal.legacyUserSwitch(halTargetUser, usersInfo);
+        UsersInfo usersInfo = UserHalHelper.newUsersInfo(mUserManager);
+        SwitchUserRequest request = createUserSwitchRequest(toUserId, usersInfo);
+        mHal.legacyUserSwitch(request);
     }
 
     /**
@@ -1583,6 +1742,12 @@
         checkAtLeastOnePermission(message, android.Manifest.permission.MANAGE_USERS);
     }
 
+    private static void checkManageOrCreateUsersPermission(String message) {
+        checkAtLeastOnePermission(message,
+                android.Manifest.permission.MANAGE_USERS,
+                android.Manifest.permission.CREATE_USERS);
+    }
+
     private static void checkManageUsersOrDumpPermission(String message) {
         checkAtLeastOnePermission(message,
                 android.Manifest.permission.MANAGE_USERS,
diff --git a/surround_view/service-impl/Android.bp b/surround_view/service-impl/Android.bp
index 8ae7bba..80c77a3 100644
--- a/surround_view/service-impl/Android.bp
+++ b/surround_view/service-impl/Android.bp
@@ -14,19 +14,89 @@
 // limitations under the License.
 //
 
-cc_binary {
-    name: "android.automotive.sv.service@1.0-impl",
-    vendor: true,
+cc_library {
+    name : "libobj_reader",
+    vendor : true,
     srcs: [
+        "MtlReader.cpp",
+        "ObjReader.cpp",
+    ],
+    shared_libs : [
+        "libbase",
+    ]
+}
+
+cc_test{
+    name : "obj_reader_tests",
+    test_suites : ["device-tests"],
+    vendor : true,
+    srcs : ["ObjReaderTests.cpp"],
+    shared_libs : [
+        "libobj_reader",
+        "libcutils",
+        "libbase",
+        "libutils",
+    ],
+    required: [
+        "VolvoXC40_low.obj",
+        "VolvoXC40_low.mtl",
+    ],
+}
+
+cc_library{
+    name : "libvhal_handler",
+    vendor : true,
+    srcs : [
+        "VhalHandler.cpp",
+    ],
+    shared_libs : [
+        "android.hardware.automotive.vehicle@2.0",
+        "android.hidl.memory@1.0",
+        "libcutils",
+        "libbase",
+        "libbinder",
+        "libhidlbase",
+        "libhardware",
+        "libhidlmemory",
+        "libui",
+        "libutils",
+    ],
+}
+
+cc_test{
+    name : "vhal_handler_tests",
+    test_suites : ["device-tests"],
+    vendor : true,
+    srcs : ["VhalHandlerTests.cpp"],
+    shared_libs : [
+        "android.hardware.automotive.vehicle@2.0",
+        "libvhal_handler",
+        "libcutils",
+        "libbase",
+        "libbinder",
+        "libhidlbase",
+        "libhardware",
+        "libhidlmemory",
+        "libui",
+        "libutils",
+    ],
+}
+
+cc_binary{
+    name : "android.automotive.sv.service@1.0-impl",
+    vendor : true,
+    srcs : [
         "CoreLibSetupHelper.cpp",
         "SurroundViewService.cpp",
         "SurroundView2dSession.cpp",
         "SurroundView3dSession.cpp",
         "service.cpp",
+
     ],
-    init_rc: ["android.automotive.sv.service@1.0-impl.rc"],
-    shared_libs: [
+    init_rc : ["android.automotive.sv.service@1.0-impl.rc"],
+    shared_libs : [
         "android.hardware.automotive.sv@1.0",
+        "android.hardware.automotive.vehicle@2.0",
         "android.hidl.memory@1.0",
         "libbase",
         "libbinder",
@@ -38,49 +108,43 @@
         "libui",
         "libutils",
     ],
-    required: [
+    required : [
         "cam0.png",
         "cam1.png",
         "cam2.png",
         "cam3.png",
     ],
     // Disable builds except for arm64 and emulator devices
-    enabled: false,
-    arch: {
-        arm64: {
-            enabled: true,
+    enabled : false,
+    arch : {
+        arm64 : {
+            enabled : true,
         },
-        x86: {
-            enabled: true,
+        x86 : {
+            enabled : true,
         },
-        x86_64: {
-            enabled: true,
+        x86_64 : {
+            enabled : true,
         },
     },
-    vintf_fragments: [
+    vintf_fragments : [
         "manifest_android.hardware.automotive.sv@1.0.xml",
     ],
 }
 
-cc_prebuilt_library_shared {
-    name: "libcore_lib_shared",
-    proprietary: true,
-    arch: {
-        arm64: {
-            srcs: ["lib/arm64/libcore_lib_shared.so"]
-        },
-        x86: {
-            srcs: ["lib/x86/libcore_lib_shared.so"]
-        },
-        x86_64: {
-            srcs: ["lib/x86-64/libcore_lib_shared.so"]
-        },
+cc_prebuilt_library_shared{
+    name : "libcore_lib_shared",
+    proprietary : true,
+    arch : {
+        arm64 : {srcs : ["lib/arm64/libcore_lib_shared.so"]},
+        x86 : {srcs : ["lib/x86/libcore_lib_shared.so"]},
+        x86_64 : {srcs : ["lib/x86-64/libcore_lib_shared.so"]},
     },
-    shared_libs: [
-	"libutils",
-	"libcutils",
-	"libbase",
-	"libEGL",
+    shared_libs : [
+        "libutils",
+        "libcutils",
+        "libbase",
+        "libEGL",
         "libGLESv2",
         "libGLESv3",
         "libc",
@@ -91,27 +155,37 @@
     ],
 }
 
+prebuilt_etc{
+    name : "cam0.png",
+    src : "test_data/0.png",
+    sub_dir : "automotive/sv",
+}
+
+prebuilt_etc{
+    name : "cam1.png",
+    src : "test_data/1.png",
+    sub_dir : "automotive/sv",
+}
+
+prebuilt_etc{
+    name : "cam2.png",
+    src : "test_data/2.png",
+    sub_dir : "automotive/sv",
+}
+
 prebuilt_etc {
-    name: "cam0.png",
-    src: "test_data/0.png",
+name:
+    "cam3.png", src : "test_data/3.png", sub_dir : "automotive/sv",
+}
+
+prebuilt_etc {
+    name: "cube.obj",
+    src: "test_data/cube.obj",
     sub_dir: "automotive/sv",
 }
 
 prebuilt_etc {
-    name: "cam1.png",
-    src: "test_data/1.png",
+    name: "cube.mtl",
+    src: "test_data/cube.mtl",
     sub_dir: "automotive/sv",
 }
-
-prebuilt_etc {
-    name: "cam2.png",
-    src: "test_data/2.png",
-    sub_dir: "automotive/sv",
-}
-
-prebuilt_etc {
-    name: "cam3.png",
-    src: "test_data/3.png",
-    sub_dir: "automotive/sv",
-}
-
diff --git a/surround_view/service-impl/MtlReader.cpp b/surround_view/service-impl/MtlReader.cpp
new file mode 100644
index 0000000..d86a15a
--- /dev/null
+++ b/surround_view/service-impl/MtlReader.cpp
@@ -0,0 +1,167 @@
+/*
+ * Copyright 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.
+ */
+#include "MtlReader.h"
+
+#include <android-base/logging.h>
+#include <cstdio>
+
+#define LOG_TAG "MtlReader"
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace sv {
+namespace V1_0 {
+namespace implementation {
+
+namespace {
+
+constexpr int kCharBufferSize = 128;
+
+void ReadFloat3(FILE* file, float* value) {
+    float temp[3];
+    int res = fscanf(file, "%f %f %f", &temp[0], &temp[1], &temp[2]);
+    3 == res ? std::memcpy(value, temp, 3 * sizeof(float)) : nullptr;
+}
+
+void ReadFloat(FILE* file, float* value) {
+    float temp;
+    int res = fscanf(file, "%f", &temp);
+    *value = res > 0 ? temp : -1;
+}
+
+void ReadInt(FILE* file, int* value) {
+    int temp;
+    int res = fscanf(file, "%d", &temp);
+    *value = res > 0 ? temp : -1;
+}
+
+void ReadString(FILE* file, std::string* value) {
+    char temp[kCharBufferSize];
+    fscanf(file, "%s", temp);
+    *value = temp;
+}
+}  // namespace
+
+bool ReadMtlFromFile(const std::string& mtlFilename,
+                     std::map<std::string, MtlConfigParams>* params) {
+    FILE* file = fopen(mtlFilename.c_str(), "r");
+    if (!file) {
+        LOG(ERROR) << "Failed to open mtl file: " << mtlFilename;
+        return false;
+    }
+
+    std::string currentConfig;
+    while (true) {
+        char lineHeader[kCharBufferSize];
+        // read the first word of the line
+        int res = fscanf(file, "%s", lineHeader);
+
+        if (res == EOF) {
+            break;  // EOF = End Of File. Quit the loop.
+        }
+
+        if (strcmp(lineHeader, "#") == 0) {
+            fgets(lineHeader, sizeof(lineHeader), file);
+            continue;
+        }
+        if (strcmp(lineHeader, "newmtl") == 0) {
+            res = fscanf(file, "%s", lineHeader);
+            if (params->find(lineHeader) != params->end()) {
+                fclose(file);
+                LOG(ERROR) << "Duplicated params of : " << lineHeader[0];
+                return false;
+            }
+            currentConfig = lineHeader;
+            continue;
+        }
+
+        if (strcmp(lineHeader, "Ns") == 0) {
+            ReadFloat(file, &((*params)[currentConfig].ns));
+            continue;
+        }
+        if (strcmp(lineHeader, "Ni") == 0) {
+            ReadFloat(file, &((*params)[currentConfig].ni));
+            continue;
+        }
+        if (strcmp(lineHeader, "d") == 0) {
+            ReadFloat(file, &((*params)[currentConfig].d));
+            continue;
+        }
+        if (strcmp(lineHeader, "Tr") == 0) {
+            ReadFloat(file, &((*params)[currentConfig].tr));
+            continue;
+        }
+        if (strcmp(lineHeader, "Tf") == 0) {
+            ReadFloat3(file, (*params)[currentConfig].tf);
+            continue;
+        }
+        if (strcmp(lineHeader, "illum") == 0) {
+            ReadInt(file, &((*params)[currentConfig].illum));
+            continue;
+        }
+        if (strcmp(lineHeader, "Ka") == 0) {
+            ReadFloat3(file, (*params)[currentConfig].ka);
+            continue;
+        }
+        if (strcmp(lineHeader, "Kd") == 0) {
+            ReadFloat3(file, (*params)[currentConfig].kd);
+            continue;
+        }
+        if (strcmp(lineHeader, "Ks") == 0) {
+            ReadFloat3(file, (*params)[currentConfig].ks);
+            continue;
+        }
+        if (strcmp(lineHeader, "Ke") == 0) {
+            ReadFloat3(file, (*params)[currentConfig].ke);
+            continue;
+        }
+        if (strcmp(lineHeader, "map_bump") == 0) {
+            ReadString(file, &((*params)[currentConfig].mapBump));
+            continue;
+        }
+        if (strcmp(lineHeader, "bump") == 0) {
+            ReadString(file, &((*params)[currentConfig].bump));
+            continue;
+        }
+        if (strcmp(lineHeader, "map_Ka") == 0) {
+            ReadString(file, &((*params)[currentConfig].mapKa));
+            continue;
+        }
+        if (strcmp(lineHeader, "map_Kd") == 0) {
+            ReadString(file, &((*params)[currentConfig].mapKd));
+            continue;
+        }
+        if (strcmp(lineHeader, "map_Ks") == 0) {
+            ReadString(file, &((*params)[currentConfig].mapKs));
+            continue;
+        } else {
+            LOG(WARNING) << "Unknown tag " << lineHeader << ". Skipped";
+            fgets(lineHeader, sizeof(lineHeader), file);
+            continue;
+        }
+    }
+
+    fclose(file);
+    return true;
+}
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace sv
+}  // namespace automotive
+}  // namespace hardware
+}  // namespace android
diff --git a/surround_view/service-impl/MtlReader.h b/surround_view/service-impl/MtlReader.h
new file mode 100644
index 0000000..3e19876
--- /dev/null
+++ b/surround_view/service-impl/MtlReader.h
@@ -0,0 +1,180 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef SURROUND_VIEW_SERVICE_IMPL_MTLREADER_H_
+#define SURROUND_VIEW_SERVICE_IMPL_MTLREADER_H_
+
+#include <map>
+#include <string>
+
+#include "core_lib.h"
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace sv {
+namespace V1_0 {
+namespace implementation {
+
+// Mtl defined params.
+struct MtlConfigParams {
+    // Ns exponent
+    // Specifies the specular exponent for the current material. This defines
+    // the focus of the specular highlight.
+    // Ns values normally range from 0 to 1000.
+    float ns = -1;
+
+    // optical_density
+    // Specifies the optical density for the surface. This is also known as
+    // index of refraction.
+    //  "optical_density" is the value for the optical density. The values can
+    // range from 0.001 to 10. A value of 1.0 means that light does not bend
+    // as it passes through an object. Increasing the optical_density
+    // increases the amount of bending. Glass has an index of refraction of
+    // about 1.5.  Values of less than 1.0 produce bizarre results and are not
+    // recommended.
+    float ni = -1;
+
+    // d defines the non-transparency of the material to be alpha.
+    // The default is 1.0 (not transparent at all).
+    // The quantities d and Tr are the opposites of each other.
+    float d = -1;
+
+    // The Tr statement specifies the transparency of the material to be alpha.
+    // The default is 0.0 (not transparent at all).
+    // The quantities d and Tr are the opposites of each other,
+    float tr = -1;
+
+    // The Tf statement specifies the transmission filter using RGB values.
+    // "r g b" are the values for the red, green, and blue components of the
+    // atmosphere.  The g and b arguments are optional. If only r is
+    // specified, then g, and b are assumed to be equal to r. The r g b values
+    // are normally in the range of 0.0 to 1.0. Values outside this range
+    // increase or decrease the relectivity accordingly.
+    float tf[3] = {-1, -1, -1};
+
+    // illum_#
+    // The "illum" statement specifies the illumination model to use in the
+    // material.  Illumination models are mathematical equations that represent
+    // various material lighting and shading effects.
+    //
+    // "illum_#"can be a number from 0 to 10. The illumination models are
+    // summarized below;
+    //
+    //  Illumination    Properties that are turned on in the
+    //  model           Property Editor
+    //
+    //  0 Color on and Ambient off
+    //  1 Color on and Ambient on
+    //  2 Highlight on
+    //  3 Reflection on and Ray trace on
+    //  4 Transparency: Glass on
+    //    Reflection: Ray trace on
+    //  5 Reflection: Fresnel on and Ray trace on
+    //  6 Transparency: Refraction on
+    //    Reflection: Fresnel off and Ray trace on
+    //  7 Transparency: Refraction on
+    //    Reflection: Fresnel on and Ray trace on
+    //  8 Reflection on and Ray trace off
+    //  9 Transparency: Glass on
+    //    Reflection: Ray trace off
+    // 10 Casts shadows onto invisible surfaces
+    int illum = -1;
+
+    // The Ka statement specifies the ambient reflectivity using RGB values.
+    // "r g b" are the values for the red, green, and blue components of the
+    // color.  The g and b arguments are optional. If only r is specified,
+    // then g, and b are assumed to be equal to r. The r g b values are
+    // normally in the range of 0.0 to 1.0. Values outside this range increase
+    // or decrease the relectivity accordingly.
+    float ka[3] = {-1, -1, -1};
+
+    // The Kd statement specifies the diffuse reflectivity using RGB values.
+    //  "r g b" are the values for the red, green, and blue components of the
+    // atmosphere.  The g and b arguments are optional.  If only r is
+    // specified, then g, and b are assumed to be equal to r. The r g b values
+    // are normally in the range of 0.0 to 1.0. Values outside this range
+    // increase or decrease the relectivity accordingly.
+    float kd[3] = {-1, -1, -1};
+
+    // The Ks statement specifies the specular reflectivity using RGB values.
+    //  "r g b" are the values for the red, green, and blue components of the
+    // atmosphere. The g and b arguments are optional. If only r is
+    // specified, then g, and b are assumed to be equal to r. The r g b values
+    // are normally in the range of 0.0 to 1.0. Values outside this range
+    // increase or decrease the relectivity accordingly.
+    float ks[3] = {-1, -1, -1};
+
+    // Emissive coeficient. It goes together with ambient, diffuse and specular
+    // and represents the amount of light emitted by the material.
+    float ke[3] = {-1, -1, -1};
+
+    // Specifies that a color texture file or color procedural texture file is
+    // linked to the specular reflectivity of the material. During rendering,
+    // the map_Ks value is multiplied by the Ks value.
+    std::string mapKs;
+
+    // Specifies that a color texture file or a color procedural texture file
+    // is applied to the ambient reflectivity of the material. During
+    // rendering, the "map_Ka" value is multiplied by the "Ka" value.
+    std::string mapKa;
+
+    // Specifies that a color texture file or color procedural texture file is
+    // linked to the diffuse reflectivity of the material. During rendering,
+    // the map_Kd value is multiplied by the Kd value.
+    std::string mapKd;
+
+    // Same as bump
+    std::string mapBump;
+
+    // Specifies that a bump texture file or a bump procedural texture file is
+    // linked to the material.
+    std::string bump;
+
+    MtlConfigParams& operator=(const MtlConfigParams& rhs) {
+        ns = rhs.ns;
+        ni = rhs.ni;
+        d = rhs.d;
+        tr = rhs.tr;
+        std::memcpy(tf, rhs.tf, 3 * sizeof(float));
+        illum = rhs.illum;
+        std::memcpy(ka, rhs.ka, 3 * sizeof(float));
+        std::memcpy(kd, rhs.kd, 3 * sizeof(float));
+        std::memcpy(ks, rhs.ks, 3 * sizeof(float));
+        std::memcpy(ke, rhs.ke, 3 * sizeof(float));
+        mapKs = rhs.mapKs;
+        mapKa = rhs.mapKa;
+        mapKd = rhs.mapKd;
+        mapBump = rhs.mapBump;
+        bump = rhs.bump;
+
+        return *this;
+    }
+};
+
+// Reads mtl file associated with obj file.
+// |filename| is the full path and name of the obj file.
+bool ReadMtlFromFile(const std::string& mtlFilename,
+                     std::map<std::string, MtlConfigParams>* params);
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace sv
+}  // namespace automotive
+}  // namespace hardware
+}  // namespace android
+
+#endif  // SURROUND_VIEW_SERVICE_IMPL_MTLREADER_H_
diff --git a/surround_view/service-impl/ObjReader.cpp b/surround_view/service-impl/ObjReader.cpp
new file mode 100644
index 0000000..174ef5b
--- /dev/null
+++ b/surround_view/service-impl/ObjReader.cpp
@@ -0,0 +1,304 @@
+/*
+ * Copyright 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.
+ */
+#include "ObjReader.h"
+
+#include <array>
+#include <cstdio>
+#include <filesystem>
+#include <vector>
+
+#include "MtlReader.h"
+#include "core_lib.h"
+
+#include <android-base/logging.h>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace sv {
+namespace V1_0 {
+namespace implementation {
+
+using android_auto::surround_view::CarMaterial;
+using android_auto::surround_view::CarVertex;
+
+namespace {
+
+constexpr int kNumberOfVerticesPerFace = 3;
+constexpr int kNumberOfAxes = 3;
+constexpr int kCharBufferSize = 128;
+
+const std::array<float, 16> kMat4Identity = {
+        /*row 0*/ 1, 0, 0, 0,
+        /*row 1*/ 0, 1, 0, 0,
+        /*row 2*/ 0, 0, 1, 0,
+        /*row 3*/ 0, 0, 0, 1};
+
+// Copies face vertices parsed from obj to car vertices.
+void CopyFaceToCarVertex(const std::vector<std::array<float, kNumberOfAxes>>& currentVertices,
+                         const std::vector<std::array<float, kNumberOfAxes>>& currentTextures,
+                         const std::vector<std::array<float, kNumberOfAxes>>& currentNormals,
+                         int vertexId, int textureId, int normalId, CarVertex* carVertex) {
+    std::memcpy(carVertex->pos.data(), currentVertices[vertexId - 1].data(),
+                currentVertices[vertexId - 1].size() * sizeof(float));
+
+    if (textureId != -1) {
+        std::memcpy(carVertex->tex_coord.data(), currentTextures[textureId - 1].data(),
+                    2 * sizeof(float));
+        // Set texture coodinates as invalid.
+        carVertex->tex_coord = {-1.0, -1.0};
+    }
+
+    std::memcpy(carVertex->normal.data(), currentNormals[normalId - 1].data(),
+                currentNormals[normalId - 1].size() * sizeof(float));
+}
+
+}  // namespace
+
+bool ReadObjFromFile(const std::string& objFilename, std::map<std::string, CarPart>* carPartsMap) {
+    return ReadObjFromFile(objFilename, ReadObjOptions(), carPartsMap);
+}
+
+bool ReadObjFromFile(const std::string& objFilename, const ReadObjOptions& option,
+                     std::map<std::string, CarPart>* carPartsMap) {
+    FILE* file = fopen(objFilename.c_str(), "r");
+    if (!file) {
+        LOG(ERROR) << "Failed to open obj file: " << objFilename;
+        return false;
+    }
+
+    for (int i = 0; i < kNumberOfAxes; ++i) {
+        if (option.coordinateMapping[i] >= kNumberOfAxes || option.coordinateMapping[i] < 0) {
+            fclose(file);
+            LOG(ERROR) << "coordinateMapping index must be less than 3 and greater or equal "
+                          "to 0.";
+            return false;
+        }
+    }
+
+    std::vector<std::array<float, kNumberOfAxes>> currentVertices;
+    std::vector<std::array<float, kNumberOfAxes>> currentNormals;
+    std::vector<std::array<float, kNumberOfAxes>> currentTextures;
+    std::map<std::string, MtlConfigParams> mtlConfigParamsMap;
+    std::string currentGroupName;
+    MtlConfigParams currentMtlConfig;
+
+    while (true) {
+        char lineHeader[kCharBufferSize];
+        // read the first word of the line
+        int res = fscanf(file, "%s", lineHeader);
+
+        if (res == EOF) {
+            break;  // EOF = End Of File. Quit the loop.
+        }
+        if (strcmp(lineHeader, "#") == 0) {
+            fgets(lineHeader, sizeof(lineHeader), file);
+            continue;
+        }
+
+        // TODO(b/156558814): add object type support.
+        // TODO(b/156559272): add document for supported format.
+        // Only single group per line is supported.
+        if (strcmp(lineHeader, "g") == 0) {
+            res = fscanf(file, "%s", lineHeader);
+            currentGroupName = lineHeader;
+            currentMtlConfig = MtlConfigParams();
+
+            if (carPartsMap->find(currentGroupName) != carPartsMap->end()) {
+                LOG(WARNING) << "Duplicate group name: " << currentGroupName
+                             << ". using car part name as: " << currentGroupName << "_dup";
+                currentGroupName.append("_dup");
+            }
+            carPartsMap->emplace(
+                    std::make_pair(currentGroupName,
+                                   CarPart((std::vector<CarVertex>()), CarMaterial(), kMat4Identity,
+                                           std::string(), std::vector<std::string>())));
+            continue;
+        }
+
+        // no "g" case, assign it as default.
+        if (currentGroupName.empty()) {
+            currentGroupName = "default";
+            currentMtlConfig = MtlConfigParams();
+            carPartsMap->emplace(
+                    std::make_pair(currentGroupName,
+                                   CarPart((std::vector<CarVertex>()), CarMaterial(), kMat4Identity,
+                                           std::string(), std::vector<std::string>())));
+        }
+
+        if (strcmp(lineHeader, "usemtl") == 0) {
+            res = fscanf(file, "%s", lineHeader);
+
+            // If material name not found.
+            if (mtlConfigParamsMap.find(lineHeader) == mtlConfigParamsMap.end()) {
+                carPartsMap->at(currentGroupName).material = CarMaterial();
+                LOG(ERROR) << "Material not found: $0" << lineHeader;
+                return false;
+            }
+
+            currentMtlConfig = mtlConfigParamsMap[lineHeader];
+
+            carPartsMap->at(currentGroupName).material.ka = {currentMtlConfig.ka[0],
+                                                             currentMtlConfig.ka[1],
+                                                             currentMtlConfig.ka[2]};
+
+            carPartsMap->at(currentGroupName).material.kd = {currentMtlConfig.kd[0],
+                                                             currentMtlConfig.kd[1],
+                                                             currentMtlConfig.kd[2]};
+
+            carPartsMap->at(currentGroupName).material.ks = {currentMtlConfig.ks[0],
+                                                             currentMtlConfig.ks[1],
+                                                             currentMtlConfig.ks[2]};
+
+            carPartsMap->at(currentGroupName).material.d = currentMtlConfig.d;
+
+            carPartsMap->at(currentGroupName).material.textures.clear();
+
+            continue;
+        }
+
+        if (strcmp(lineHeader, "mtllib") == 0) {
+            res = fscanf(file, "%s", lineHeader);
+            mtlConfigParamsMap.clear();
+            std::string mtlFilename;
+            if (option.mtlFilename.empty()) {
+                mtlFilename = objFilename.substr(0, objFilename.find_last_of("/"));
+                mtlFilename.append("/");
+                mtlFilename.append(lineHeader);
+            } else {
+                mtlFilename = option.mtlFilename;
+            }
+            if (!ReadMtlFromFile(mtlFilename, &mtlConfigParamsMap)) {
+                LOG(ERROR) << "Parse MTL file " << mtlFilename << " failed.";
+                return false;
+            }
+            continue;
+        }
+
+        if (strcmp(lineHeader, "v") == 0) {
+            std::array<float, kNumberOfAxes> pos;
+            fscanf(file, "%f %f %f\n", &pos[option.coordinateMapping[0]],
+                   &pos[option.coordinateMapping[1]], &pos[option.coordinateMapping[2]]);
+            for (int i = 0; i < kNumberOfAxes; ++i) {
+                pos[i] *= option.scales[i];
+                pos[i] += option.offsets[i];
+            }
+            currentVertices.push_back(pos);
+        } else if (strcmp(lineHeader, "vt") == 0) {
+            std::array<float, kNumberOfAxes> texture;
+            fscanf(file, "%f %f %f\n", &texture[0], &texture[1], &texture[2]);
+            currentTextures.push_back(texture);
+        } else if (strcmp(lineHeader, "vn") == 0) {
+            std::array<float, kNumberOfAxes> normal;
+            fscanf(file, "%f %f %f\n", &normal[option.coordinateMapping[0]],
+                   &normal[option.coordinateMapping[1]], &normal[option.coordinateMapping[2]]);
+            currentNormals.push_back(normal);
+        } else if (strcmp(lineHeader, "f") == 0) {
+            int vertexId[kNumberOfVerticesPerFace];
+            int textureId[kNumberOfVerticesPerFace] = {-1, -1, -1};
+            int normalId[kNumberOfVerticesPerFace];
+
+            // Face vertices supported formats:
+            // With texture:     pos/texture/normal
+            // Without texture:  pos//normal
+
+            // Scan first vertex position.
+            int matches = fscanf(file, "%d/", &vertexId[0]);
+
+            if (matches != 1) {
+                LOG(WARNING) << "Face index error. Skipped.";
+                fgets(lineHeader, sizeof(lineHeader), file);
+                continue;
+            }
+
+            // Try scanning first two face 2 vertices with texture format present.
+            bool isTexturePresent = true;
+            matches = fscanf(file, "%d/%d %d/%d/%d", &textureId[0], &normalId[0], &vertexId[1],
+                             &textureId[1], &normalId[1]);
+
+            // If 5 matches not found, try scanning first 2 face vertices without
+            // texture format.
+            if (matches != 5) {
+                matches = fscanf(file, "/%d %d//%d", &normalId[0], &vertexId[1], &normalId[1]);
+
+                // If 3 matches not found return with error.
+                if (matches != 3) {
+                    LOG(WARNING) << "Face format not supported. Skipped.";
+                    fgets(lineHeader, sizeof(lineHeader), file);
+                    continue;
+                }
+
+                isTexturePresent = false;
+            }
+
+            // Copy first two face vertices to car vertices.
+            std::array<CarVertex, kNumberOfVerticesPerFace> carVertices;
+            CopyFaceToCarVertex(currentVertices, currentTextures, currentNormals, vertexId[0],
+                                textureId[0], normalId[0], &carVertices[0]);
+            CopyFaceToCarVertex(currentVertices, currentTextures, currentNormals, vertexId[1],
+                                textureId[1], normalId[1], &carVertices[1]);
+
+            // Add a triangle that the first two vertices make with every subsequent
+            // face vertex 3 and onwards. Note this assumes the face is a convex
+            // polygon.
+            do {
+                if (isTexturePresent) {
+                    matches = fscanf(file, " %d/%d/%d", &vertexId[2], &textureId[2], &normalId[2]);
+                    // Warn if un-expected number of matches.
+                    if (matches != 3 && matches != 0) {
+                        LOG(WARNING) << "Face matches, expected 3, read: " << matches;
+                        break;
+                    }
+                } else {
+                    // Warn if un-expected number of matches.
+                    matches = fscanf(file, " %d//%d", &vertexId[2], &normalId[2]);
+                    if (matches != 2 && matches != 0) {
+                        LOG(WARNING) << "Face matches, expected 2, read: " << matches;
+                        break;
+                    }
+                }
+
+                if (matches == 0) {
+                    break;
+                }
+
+                CopyFaceToCarVertex(currentVertices, currentTextures, currentNormals, vertexId[2],
+                                    textureId[2], normalId[2], &carVertices[2]);
+
+                carPartsMap->at(currentGroupName).vertices.push_back(carVertices[0]);
+                carPartsMap->at(currentGroupName).vertices.push_back(carVertices[1]);
+                carPartsMap->at(currentGroupName).vertices.push_back(carVertices[2]);
+
+                carVertices[1] = carVertices[2];
+            } while (true);
+
+        } else {
+            // LOG(WARNING) << "Unknown tag " << lineHeader << ". Skipped";
+            fgets(lineHeader, sizeof(lineHeader), file);
+            continue;
+        }
+    }
+
+    fclose(file);
+    return true;
+}
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace sv
+}  // namespace automotive
+}  // namespace hardware
+}  // namespace android
diff --git a/surround_view/service-impl/ObjReader.h b/surround_view/service-impl/ObjReader.h
new file mode 100644
index 0000000..c19be14
--- /dev/null
+++ b/surround_view/service-impl/ObjReader.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef SURROUND_VIEW_SERVICE_IMPL_OBJREADER_H_
+#define SURROUND_VIEW_SERVICE_IMPL_OBJREADER_H_
+
+#include <map>
+#include <string>
+
+#include "core_lib.h"
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace sv {
+namespace V1_0 {
+namespace implementation {
+
+using android_auto::surround_view::CarPart;
+
+// ReadObjOptions for processing obj's vertex coordinates.
+// Sequence of processing ReadObjOptions:
+// 1. coordinate_mapping
+// 2. scales
+// 3. offsets
+struct ReadObjOptions {
+    // Maps obj coordinates to the output overlay coordinate.
+    // 0 <-> x, 1 <-> y, 2 <-> z
+    // Default is {0, 1, 2}, without coordinate changes.
+    int coordinateMapping[3] = {0, 1, 2};
+
+    // scale of each coordinate (after offsets).
+    float scales[3] = {1.0f, 1.0f, 1.0f};
+
+    // offset of each coordinate (after mapping).
+    float offsets[3] = {0, 0, 0};
+
+    // Optional mtl filename. String name is obj file is used if this is empty.
+    std::string mtlFilename;
+};
+
+// Reads obj file to vector of OverlayVertex.
+// |obj_filename| is the full path and name of the obj file.
+// |car_parts_map| is a map containing all car parts.
+// Now it only supports two face formats:
+// 1. f x/x/x x/x/x x/x/x ...
+// 2. f x//x x//x x//x ...
+// b/
+bool ReadObjFromFile(const std::string& objFilename, std::map<std::string, CarPart>* carPartsMap);
+
+// Reads obj file to vector of OverlayVertex.
+// |obj_filename| is the full path and name of the obj file.
+// |option| provides optional changes on the coordinates.
+// |car_parts_map| is a map containing all car parts.
+bool ReadObjFromFile(const std::string& obFilename, const ReadObjOptions& option,
+                     std::map<std::string, CarPart>* carPartsMap);
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace sv
+}  // namespace automotive
+}  // namespace hardware
+}  // namespace android
+
+#endif  // SURROUND_VIEW_SERVICE_IMPL_OBJREADER_H_
diff --git a/surround_view/service-impl/ObjReaderTests.cpp b/surround_view/service-impl/ObjReaderTests.cpp
new file mode 100644
index 0000000..9dae171
--- /dev/null
+++ b/surround_view/service-impl/ObjReaderTests.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright 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.
+ */
+
+#define LOG_TAG "ObjReaderTests"
+
+#include "ObjReader.h"
+
+#include "MtlReader.h"
+#include "core_lib.h"
+
+#include <gtest/gtest.h>
+#include <map>
+#include <string>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace sv {
+namespace V1_0 {
+namespace implementation {
+namespace {
+
+TEST(ObjParserTests, ReadCubeSuccess) {
+    std::map<std::string, CarPart> carPartsMap;
+    EXPECT_TRUE(ReadObjFromFile("/etc/automotive/sv/cube.obj", &carPartsMap));
+    EXPECT_NE(carPartsMap.size(), 0);
+}
+
+}  // namespace
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace sv
+}  // namespace automotive
+}  // namespace hardware
+}  // namespace android
diff --git a/surround_view/service-impl/VhalHandler.cpp b/surround_view/service-impl/VhalHandler.cpp
new file mode 100644
index 0000000..f1294d9
--- /dev/null
+++ b/surround_view/service-impl/VhalHandler.cpp
@@ -0,0 +1,228 @@
+/*
+ * Copyright 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.
+ */
+#define LOG_TAG "VhalHandler"
+
+#include "VhalHandler.h"
+
+#include <chrono>
+#include <cmath>
+#include <condition_variable>
+#include <mutex>
+
+#include <android-base/logging.h>
+#include <android/hardware/automotive/vehicle/2.0/IVehicle.h>
+#include <time.h>
+#include <utils/SystemClock.h>
+#include <utils/Timers.h>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace sv {
+namespace V1_0 {
+namespace implementation {
+
+using vehicle::V2_0::IVehicle;
+using vehicle::V2_0::StatusCode;
+using vehicle::V2_0::VehiclePropertyType;
+using vehicle::V2_0::VehiclePropValue;
+
+bool VhalHandler::initialize(UpdateMethod updateMethod, int rate) {
+    LOG(DEBUG) << __FUNCTION__;
+    std::scoped_lock<std::mutex> lock(mAccessLock);
+
+    if (mIsInitialized) {
+        LOG(ERROR) << "Vehicle Handler is already initialized.";
+        return false;
+    }
+
+    LOG(INFO) << "Connecting to Vehicle HAL";
+    mVhalServicePtr = IVehicle::getService();
+    if (mVhalServicePtr.get() == nullptr) {
+        LOG(ERROR) << "Vehicle HAL getService failed.";
+        return false;
+    }
+
+    if (rate < 1 || rate > 100) {
+        LOG(ERROR) << "Rate must be in the range [1, 100].";
+        return false;
+    }
+
+    if (mUpdateMethod == UpdateMethod::SUBSCRIBE) {
+        LOG(ERROR) << "Update method Subscribe is not currently implemented.";
+        return false;
+    }
+
+    mUpdateMethod = updateMethod;
+    mRate = rate;
+    mIsInitialized = true;
+    mIsUpdateActive = false;
+
+    return true;
+}
+
+void VhalHandler::pollProperties() {
+    LOG(DEBUG) << "Polling thread started.";
+    while (true) {
+        nsecs_t startTime = elapsedRealtimeNano();
+
+        // Copy properties to read.
+        std::vector<VehiclePropValue> propertiesToRead;
+        int rate;
+        {
+            std::scoped_lock<std::mutex> lock(mAccessLock);
+            if (!mIsUpdateActive) {
+                LOG(DEBUG) << "Exiting polling thread.";
+                break;
+            }
+            propertiesToRead = mPropertiesToRead;
+            rate = mRate;
+        }
+
+        // Make get call for each VHAL property.
+        // Write to back property values, note lock is not needed as only this thread uses it.
+        std::vector<VehiclePropValue> vehiclePropValuesUpdated;
+        for (auto& propertyToRead : propertiesToRead) {
+            // VehiclePropValue vehiclePropValue;
+            mVhalServicePtr->get(propertyToRead,
+                                 [&vehiclePropValuesUpdated](StatusCode status,
+                                                             const VehiclePropValue& propValue) {
+                                     if (status != StatusCode::OK) {
+                                         LOG(ERROR) << "Failed to read vhal property: "
+                                                    << propValue.prop << ", with status code: "
+                                                    << static_cast<int32_t>(status);
+                                     } else {
+                                         vehiclePropValuesUpdated.push_back(propValue);
+                                     }
+                                 });
+        }
+
+        // Update property values by swapping with updated property values.
+        {
+            std::scoped_lock<std::mutex> lock(mAccessLock);
+            std::swap(mPropertyValues, vehiclePropValuesUpdated);
+        }
+
+        std::unique_lock<std::mutex> sleepLock(mPollThreadSleepMutex);
+        // Sleep to generate frames at kTargetFrameRate.
+        // rate is number of updates per seconds,
+        // Target time period between two updates in nano-seconds = (10 ^ 9) / rate.
+        const nsecs_t kTargetRateNs = std::pow(10, 9) / mRate;
+        const nsecs_t now = elapsedRealtimeNano();
+        const nsecs_t workTimeNs = now - startTime;
+        const nsecs_t sleepDurationNs = kTargetRateNs - workTimeNs;
+        if (sleepDurationNs > 0) {
+            // Sleep for sleepDurationNs or until a stop signal is received.
+            mPollThreadCondition.wait_for(sleepLock, std::chrono::nanoseconds(sleepDurationNs),
+                                          [this]() { return mPollStopSleeping; });
+        }
+    }
+}
+
+bool VhalHandler::startPropertiesUpdate() {
+    LOG(DEBUG) << __FUNCTION__;
+    std::scoped_lock<std::mutex> lock(mAccessLock);
+
+    // Check Vhal service is initialized.
+    if (!mIsInitialized) {
+        LOG(ERROR) << "VHAL handler not initialized.";
+        return false;
+    }
+
+    if (mIsUpdateActive) {
+        LOG(ERROR) << "Polling is already started.";
+        return false;
+    }
+
+    mIsUpdateActive = true;
+
+    {
+        std::scoped_lock<std::mutex> sleepLock(mPollThreadSleepMutex);
+        mPollStopSleeping = false;
+    }
+
+    // Start polling thread if updated method is GET.
+    if (mUpdateMethod == UpdateMethod::GET) {
+        mPollingThread = std::thread([this]() { pollProperties(); });
+    }
+
+    return true;
+}
+
+bool VhalHandler::setPropertiesToRead(const std::vector<VehiclePropValue>& propertiesToRead) {
+    LOG(DEBUG) << __FUNCTION__;
+    std::scoped_lock<std::mutex> lock(mAccessLock);
+
+    // Replace property ids to read.
+    mPropertiesToRead = propertiesToRead;
+
+    return true;
+}
+
+bool VhalHandler::getPropertyValues(std::vector<VehiclePropValue>* property_values) {
+    LOG(DEBUG) << __FUNCTION__;
+    std::scoped_lock<std::mutex> lock(mAccessLock);
+
+    // Check Vhal service is initialized.
+    if (!mIsInitialized) {
+        LOG(ERROR) << "VHAL handler not initialized.";
+        return false;
+    }
+
+    // Copy current property values to argument.
+    *property_values = mPropertyValues;
+
+    return true;
+}
+
+bool VhalHandler::stopPropertiesUpdate() {
+    LOG(DEBUG) << __FUNCTION__;
+    {
+        std::scoped_lock<std::mutex> lock(mAccessLock);
+
+        // Check Vhal service is initialized.
+        if (!mIsInitialized) {
+            LOG(ERROR) << "VHAL handler not initialized.";
+            return false;
+        }
+
+        if (!mIsUpdateActive) {
+            LOG(ERROR) << "Polling is already stopped.";
+            return false;
+        }
+
+        mIsUpdateActive = false;
+    }
+
+    // Wake up the polling thread.
+    {
+        std::scoped_lock<std::mutex> sleepLock(mPollThreadSleepMutex);
+        mPollStopSleeping = true;
+    }
+    mPollThreadCondition.notify_one();
+
+    // Wait for polling thread to exit.
+    mPollingThread.join();
+
+    return true;
+}
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace sv
+}  // namespace automotive
+}  // namespace hardware
+}  // namespace android
diff --git a/surround_view/service-impl/VhalHandler.h b/surround_view/service-impl/VhalHandler.h
new file mode 100644
index 0000000..23941f0
--- /dev/null
+++ b/surround_view/service-impl/VhalHandler.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef SURROUND_VIEW_SERVICE_IMPL_VHALHANDLER_H_
+#define SURROUND_VIEW_SERVICE_IMPL_VHALHANDLER_H_
+
+#include <mutex>
+#include <thread>
+#include <vector>
+
+#include <android/hardware/automotive/vehicle/2.0/IVehicle.h>
+
+using android::sp;
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace sv {
+namespace V1_0 {
+namespace implementation {
+
+// Vhal handler cache vhal properties needed and updates them at a fixed rate.
+class VhalHandler {
+public:
+    // Enumeration for the method to use for updating the VHAL properties,
+    enum UpdateMethod {
+        // Makes a periodic get call in a polling thread.
+        // Use when VHAL implementation does not support multiple clients in subscribe calls.
+        GET = 0,
+
+        // Subscribes to the VHAL properties, to receive values periodically in a callback.
+        // Use when VHAL implementation support multiple clients in subscribe calls.
+        // NOTE: Currently not implemented.
+        SUBSCRIBE
+    };
+
+    // Empty vhal handler constructor.
+    VhalHandler() : mIsInitialized(false), mUpdateMethod(GET), mRate(0), mIsUpdateActive(false) {}
+
+    // Initializes the VHAL handler.
+    // Valid range of rate is [1, 100] Hz.
+    // For subscribe the rate must be within each properties min and maximum sampling rate.
+    // For get, higher rate may result in excessive binder calls and increased latency.
+    bool initialize(UpdateMethod updateMethod, int rate);
+
+    // List of VHAL properties to read, can include vendor specific VHAL properties.
+    // The updated method determines if properties are updated using get or subscribe calls.
+    bool setPropertiesToRead(const std::vector<vehicle::V2_0::VehiclePropValue>& propertiesToRead);
+
+    // Starts updating the VHAL properties with the specified rate.
+    bool startPropertiesUpdate();
+
+    // Gets the last updated VHAL property values.
+    // property_values is empty if startPropertiesUpdate() has not been called.
+    bool getPropertyValues(std::vector<vehicle::V2_0::VehiclePropValue>* property_values);
+
+    // Stops updating the VHAL properties.
+    // For Get method, waits for the polling thread to exit.
+    bool stopPropertiesUpdate();
+
+private:
+    // Thread function to poll properties.
+    void pollProperties();
+
+    // Pointer to VHAL service.
+    sp<vehicle::V2_0::IVehicle> mVhalServicePtr;
+
+    // Mutex for locking VHAL properties data.
+    std::mutex mAccessLock;
+
+    // Initialized parameters.
+    bool mIsInitialized;
+    UpdateMethod mUpdateMethod;
+    int mRate;
+    bool mIsUpdateActive;
+
+    // GET method related data members.
+    std::thread mPollingThread;
+    std::mutex mPollThreadSleepMutex;
+    std::condition_variable mPollThreadCondition;
+    bool mPollStopSleeping;
+
+    // List of properties to read.
+    std::vector<vehicle::V2_0::VehiclePropValue> mPropertiesToRead;
+
+    // Updated list of property values.
+    std::vector<vehicle::V2_0::VehiclePropValue> mPropertyValues;
+};
+
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace sv
+}  // namespace automotive
+}  // namespace hardware
+}  // namespace android
+
+#endif  // SURROUND_VIEW_SERVICE_IMPL_VHALHANDLER_H_
diff --git a/surround_view/service-impl/VhalHandlerTests.cpp b/surround_view/service-impl/VhalHandlerTests.cpp
new file mode 100644
index 0000000..2d0ef40
--- /dev/null
+++ b/surround_view/service-impl/VhalHandlerTests.cpp
@@ -0,0 +1,101 @@
+/*
+ * Copyright 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.
+ */
+
+#define LOG_TAG "VhalHandlerTests"
+
+#include <android/hardware/automotive/vehicle/2.0/IVehicle.h>
+#include "VhalHandler.h"
+
+#include <gtest/gtest.h>
+#include <time.h>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace sv {
+namespace V1_0 {
+namespace implementation {
+namespace {
+
+void SetSamplePropertiesToRead(VhalHandler* vhalHandler) {
+    std::vector<vehicle::V2_0::VehiclePropValue> properties_to_read;
+    vehicle::V2_0::VehiclePropValue property_read;
+    property_read.prop = static_cast<int32_t>(vehicle::V2_0::VehicleProperty::INFO_MODEL);
+    properties_to_read.push_back(property_read);
+    ASSERT_TRUE(vhalHandler->setPropertiesToRead(properties_to_read));
+}
+
+TEST(VhalhandlerTests, UninitializedStartFail) {
+    VhalHandler vhalHandler;
+    ASSERT_FALSE(vhalHandler.startPropertiesUpdate());
+}
+
+TEST(VhalhandlerTests, StartStopSuccess) {
+    VhalHandler vhalHandler;
+    ASSERT_TRUE(vhalHandler.initialize(VhalHandler::UpdateMethod::GET, 10));
+    SetSamplePropertiesToRead(&vhalHandler);
+    ASSERT_TRUE(vhalHandler.startPropertiesUpdate());
+    ASSERT_TRUE(vhalHandler.stopPropertiesUpdate());
+}
+
+TEST(VhalhandlerTests, StopTwiceFail) {
+    VhalHandler vhalHandler;
+    ASSERT_TRUE(vhalHandler.initialize(VhalHandler::UpdateMethod::GET, 10));
+    SetSamplePropertiesToRead(&vhalHandler);
+    ASSERT_TRUE(vhalHandler.startPropertiesUpdate());
+    ASSERT_TRUE(vhalHandler.stopPropertiesUpdate());
+    ASSERT_FALSE(vhalHandler.stopPropertiesUpdate());
+}
+
+TEST(VhalhandlerTests, NoStartFail) {
+    VhalHandler vhalHandler;
+    ASSERT_TRUE(vhalHandler.initialize(VhalHandler::UpdateMethod::GET, 10));
+    SetSamplePropertiesToRead(&vhalHandler);
+    ASSERT_FALSE(vhalHandler.stopPropertiesUpdate());
+}
+
+TEST(VhalhandlerTests, StartAgainSuccess) {
+    VhalHandler vhalHandler;
+    ASSERT_TRUE(vhalHandler.initialize(VhalHandler::UpdateMethod::GET, 10));
+    SetSamplePropertiesToRead(&vhalHandler);
+    ASSERT_TRUE(vhalHandler.startPropertiesUpdate());
+    ASSERT_TRUE(vhalHandler.stopPropertiesUpdate());
+    ASSERT_TRUE(vhalHandler.startPropertiesUpdate());
+    ASSERT_TRUE(vhalHandler.stopPropertiesUpdate());
+}
+
+TEST(VhalhandlerTests, GetMethodSuccess) {
+    VhalHandler vhalHandler;
+    ASSERT_TRUE(vhalHandler.initialize(VhalHandler::UpdateMethod::GET, 10));
+
+    SetSamplePropertiesToRead(&vhalHandler);
+
+    ASSERT_TRUE(vhalHandler.startPropertiesUpdate());
+    sleep(1);
+    std::vector<vehicle::V2_0::VehiclePropValue> property_values;
+    EXPECT_TRUE(vhalHandler.getPropertyValues(&property_values));
+    EXPECT_EQ(property_values.size(), 1);
+
+    EXPECT_TRUE(vhalHandler.stopPropertiesUpdate());
+}
+
+}  // namespace
+}  // namespace implementation
+}  // namespace V1_0
+}  // namespace sv
+}  // namespace automotive
+}  // namespace hardware
+}  // namespace android
diff --git a/surround_view/service-impl/lib/arm64/libcore_lib_shared.so b/surround_view/service-impl/lib/arm64/libcore_lib_shared.so
index 2421d41..3988762 100755
--- a/surround_view/service-impl/lib/arm64/libcore_lib_shared.so
+++ b/surround_view/service-impl/lib/arm64/libcore_lib_shared.so
Binary files differ
diff --git a/surround_view/service-impl/lib/x86-64/libcore_lib_shared.so b/surround_view/service-impl/lib/x86-64/libcore_lib_shared.so
index 34d1f6a..e9f94db 100755
--- a/surround_view/service-impl/lib/x86-64/libcore_lib_shared.so
+++ b/surround_view/service-impl/lib/x86-64/libcore_lib_shared.so
Binary files differ
diff --git a/surround_view/service-impl/lib/x86/libcore_lib_shared.so b/surround_view/service-impl/lib/x86/libcore_lib_shared.so
index c6ba2b6..b82d025 100755
--- a/surround_view/service-impl/lib/x86/libcore_lib_shared.so
+++ b/surround_view/service-impl/lib/x86/libcore_lib_shared.so
Binary files differ
diff --git a/surround_view/service-impl/test_data/cube.mtl b/surround_view/service-impl/test_data/cube.mtl
new file mode 100644
index 0000000..2f4e865
--- /dev/null
+++ b/surround_view/service-impl/test_data/cube.mtl
@@ -0,0 +1,5 @@
+newmtl flatwhite
+d 1.0000
+illum 1
+Ka 0.5000 0.5000 0.5000
+Kd 1.0000 1.0000 1.0000
diff --git a/surround_view/service-impl/test_data/cube.obj b/surround_view/service-impl/test_data/cube.obj
new file mode 100644
index 0000000..60ae1b2
--- /dev/null
+++ b/surround_view/service-impl/test_data/cube.obj
@@ -0,0 +1,32 @@
+mtllib cube.mtl
+g cube
+ 
+v 0.0 0.0 0.0
+v 0.0 0.0 1.0
+v 0.0 1.0 0.0
+v 0.0 1.0 1.0
+v 1.0 0.0 0.0
+v 1.0 0.0 1.0
+v 1.0 1.0 0.0
+v 1.0 1.0 1.0
+
+vn 0.0 0.0 1.0
+vn 0.0 0.0 -1.0
+vn 0.0 1.0 0.0
+vn 0.0 -1.0 0.0
+vn 1.0 0.0 0.0
+vn -1.0 0.0 0.0
+usemtl flatwhite 
+f 1//2 7//2 5//2
+f 1//2 3//2 7//2 
+f 1//6 4//6 3//6 
+f 1//6 2//6 4//6 
+f 3//3 8//3 7//3 
+f 3//3 4//3 8//3 
+f 5//5 7//5 8//5 
+f 5//5 8//5 6//5 
+f 1//4 5//4 6//4 
+f 1//4 6//4 2//4 
+f 2//1 6//1 8//1 
+f 2//1 8//1 4//1 
+
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/CarTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/CarPermisisonTest.java
similarity index 97%
rename from tests/CarSecurityPermissionTest/src/com/android/car/CarTest.java
rename to tests/CarSecurityPermissionTest/src/com/android/car/CarPermisisonTest.java
index 90378a8..07f4310 100644
--- a/tests/CarSecurityPermissionTest/src/com/android/car/CarTest.java
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/CarPermisisonTest.java
@@ -29,10 +29,10 @@
 import org.junit.runner.RunWith;
 
 /**
- * This class contains security permission tests for the {@link CarTest}'s system APIs.
+ * This class contains security permission tests for the {@link Car}'s system APIs.
  */
 @RunWith(AndroidJUnit4.class)
-public class CarTest {
+public class CarPermisisonTest {
     private Car mCar = null;
 
     @Before
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/CarPropertyManagerPublicTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/CarPropertyManagerPublicPermissionTest.java
similarity index 98%
rename from tests/CarSecurityPermissionTest/src/com/android/car/CarPropertyManagerPublicTest.java
rename to tests/CarSecurityPermissionTest/src/com/android/car/CarPropertyManagerPublicPermissionTest.java
index 92226a1..975bc74 100644
--- a/tests/CarSecurityPermissionTest/src/com/android/car/CarPropertyManagerPublicTest.java
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/CarPropertyManagerPublicPermissionTest.java
@@ -40,11 +40,11 @@
  * This class contains security permission tests for the {@link CarPropertyManager}'s public APIs.
  */
 @RunWith(AndroidJUnit4.class)
-public class CarPropertyManagerPublicTest {
+public class CarPropertyManagerPublicPermissionTest {
     private Car mCar = null;
     private CarPropertyManager mPropertyManager;
     private HashSet<Integer> mProps = new HashSet<>();
-    private static final String TAG = CarPropertyManagerPublicTest.class.getSimpleName();
+    private static final String TAG = CarPropertyManagerPublicPermissionTest.class.getSimpleName();
     private static final Integer DUMMY_AREA_ID = VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL;
     // Dummy values for setter test.
     private static final int DUMMY_PROPERTY_VALUE_INTEGER = 1;
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/CarPublicTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/CarPublicPermissionTest.java
similarity index 97%
rename from tests/CarSecurityPermissionTest/src/com/android/car/CarPublicTest.java
rename to tests/CarSecurityPermissionTest/src/com/android/car/CarPublicPermissionTest.java
index af1867b..16aef37 100644
--- a/tests/CarSecurityPermissionTest/src/com/android/car/CarPublicTest.java
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/CarPublicPermissionTest.java
@@ -29,10 +29,10 @@
 import org.junit.runner.RunWith;
 
 /**
- * This class contains security permission tests for the {@link CarTest}'s public APIs.
+ * This class contains security permission tests for the {@link CarPermisisonTest}'s public APIs.
  */
 @RunWith(AndroidJUnit4.class)
-public class CarPublicTest {
+public class CarPublicPermissionTest {
     private Car mCar = null;
 
     @Before
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/content/pm/CarPackageManagerPermissionTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/content/pm/CarPackageManagerPermissionTest.java
new file mode 100644
index 0000000..f1e23ba
--- /dev/null
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/content/pm/CarPackageManagerPermissionTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.car;
+
+import static org.testng.Assert.assertThrows;
+
+import android.car.Car;
+import android.car.content.pm.CarAppBlockingPolicy;
+import android.car.content.pm.CarPackageManager;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * This class contains security permission tests for {@link CarPackageManager}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CarPackageManagerPermissionTest {
+    private Car mCar;
+    private CarPackageManager mPm;
+
+    @Before
+    public void setUp() throws Exception {
+        mCar = Car.createCar(InstrumentationRegistry.getInstrumentation().getTargetContext());
+        mPm = (CarPackageManager) mCar.getCarManager(Car.PACKAGE_SERVICE);
+    }
+
+    @After
+    public void tearDown() {
+        mCar.disconnect();
+    }
+
+    @Test
+    public void testRestartTask() {
+        assertThrows(SecurityException.class, () -> mPm.restartTask(0));
+    }
+
+    @Test
+    public void testSetAppBlockingPolicy() {
+        String packageName = "com.android";
+        CarAppBlockingPolicy policy = new CarAppBlockingPolicy(null, null);
+        assertThrows(SecurityException.class, () -> mPm.setAppBlockingPolicy(packageName, policy,
+                0));
+    }
+
+}
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/input/CarInputManagerTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/input/CarInputManagerPermisisonTest.java
similarity index 97%
rename from tests/CarSecurityPermissionTest/src/com/android/car/input/CarInputManagerTest.java
rename to tests/CarSecurityPermissionTest/src/com/android/car/input/CarInputManagerPermisisonTest.java
index 354446e..9730d13 100644
--- a/tests/CarSecurityPermissionTest/src/com/android/car/input/CarInputManagerTest.java
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/input/CarInputManagerPermisisonTest.java
@@ -35,7 +35,7 @@
  * This class contains security permission tests for the {@link CarInputManager}'s system APIs.
  */
 @RunWith(AndroidJUnit4.class)
-public class CarInputManagerTest {
+public class CarInputManagerPermisisonTest {
     private Car mCar;
 
     private CarInputManager mCarInputManager;
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerPermissionTest.java
similarity index 87%
rename from tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerTest.java
rename to tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerPermissionTest.java
index 1bb81ec..1e4391b 100644
--- a/tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerTest.java
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerPermissionTest.java
@@ -25,16 +25,12 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.junit.Assume.assumeNotNull;
 import static org.testng.Assert.expectThrows;
 
 import android.car.Car;
 import android.car.media.CarAudioManager;
 import android.content.Context;
-import android.hardware.display.DisplayManager;
 import android.os.Handler;
-import android.view.Display;
-import android.view.DisplayAddress;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -43,15 +39,13 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.Arrays;
 import java.util.Objects;
-import java.util.Optional;
 
 /**
  * This class contains security permission tests for the {@link CarAudioManager}'s system APIs.
  */
 @RunWith(AndroidJUnit4.class)
-public final class CarAudioManagerTest {
+public final class CarAudioManagerPermissionTest {
     private static final int GROUP_ID = 0;
     private static final int UID = 10;
 
@@ -220,22 +214,6 @@
     }
 
     @Test
-    public void getZoneIdForDisplayPermission() {
-        Display display = getPhysicalDisplay();
-        assumeNotNull(display);
-        Exception e = expectThrows(SecurityException.class,
-                () -> mCarAudioManager.getZoneIdForDisplay(display));
-        assertThat(e.getMessage()).contains(PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
-    }
-
-    @Test
-    public void getZoneIdForDisplayPortIdPermission() {
-        Exception e = expectThrows(SecurityException.class,
-                () -> mCarAudioManager.getZoneIdForDisplayPortId(Byte.MAX_VALUE));
-        assertThat(e.getMessage()).contains(PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
-    }
-
-    @Test
     public void getOutputDeviceForUsagePermission() {
         Exception e = expectThrows(SecurityException.class,
                 () -> mCarAudioManager.getOutputDeviceForUsage(PRIMARY_AUDIO_ZONE, USAGE_MEDIA));
@@ -255,12 +233,4 @@
                 () -> mCarAudioManager.onCarDisconnected());
         assertThat(e.getMessage()).contains(PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
     }
-
-    private Display getPhysicalDisplay() {
-        DisplayManager displayManager = (DisplayManager) mContext.getSystemService(
-                Context.DISPLAY_SERVICE);
-        Optional<Display> physical = Arrays.stream(displayManager.getDisplays()).filter(
-                display -> (display.getAddress() instanceof DisplayAddress.Physical)).findFirst();
-        return physical.orElse(null);
-    }
 }
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerPublicTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerPublicPermissionTest.java
similarity index 96%
rename from tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerPublicTest.java
rename to tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerPublicPermissionTest.java
index 2877043..b9cafc4 100644
--- a/tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerPublicTest.java
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/media/CarAudioManagerPublicPermissionTest.java
@@ -39,7 +39,7 @@
  * This class contains security permission tests for the {@link CarAudioManager}'s public APIs.
  */
 @RunWith(AndroidJUnit4.class)
-public final class CarAudioManagerPublicTest {
+public final class CarAudioManagerPublicPermissionTest {
     private CarAudioManager mCarAudioManager;
 
     @Before
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/user/CarUserManagerPermissionTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/user/CarUserManagerPermissionTest.java
index 86a2853..3a5ea1b 100644
--- a/tests/CarSecurityPermissionTest/src/com/android/car/user/CarUserManagerPermissionTest.java
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/user/CarUserManagerPermissionTest.java
@@ -16,6 +16,7 @@
 
 package com.android.car.user;
 
+import static android.Manifest.permission.CREATE_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.Manifest.permission.MANAGE_USERS;
@@ -35,6 +36,7 @@
 import android.car.user.CarUserManager.UserLifecycleListener;
 import android.content.Context;
 import android.os.Handler;
+import android.os.UserManager;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -67,9 +69,21 @@
 
     @Test
     public void testSwitchUserPermission() throws Exception {
-        int target_uid = 100;
+        Exception e = expectThrows(SecurityException.class, () -> mCarUserManager.switchUser(100));
+        assertThat(e.getMessage()).contains(MANAGE_USERS);
+    }
+
+    @Test
+    public void testCreateUserPermission() throws Exception {
         Exception e = expectThrows(SecurityException.class,
-                () -> mCarUserManager.switchUser(target_uid));
+                () -> mCarUserManager.createUser(null, UserManager.USER_TYPE_FULL_SECONDARY, 0));
+        assertThat(e.getMessage()).contains(MANAGE_USERS);
+        assertThat(e.getMessage()).contains(CREATE_USERS);
+    }
+
+    @Test
+    public void testRemoveUserPermission() throws Exception {
+        Exception e = expectThrows(SecurityException.class, () -> mCarUserManager.removeUser(100));
         assertThat(e.getMessage()).contains(MANAGE_USERS);
     }
 
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/audio.xml b/tests/EmbeddedKitchenSinkApp/res/layout/audio.xml
index 7c77b1b..cfc3515 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/audio.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/audio.xml
@@ -28,6 +28,50 @@
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:orientation="horizontal"
+                android:layout_weight="1"
+                android:visibility="gone"
+                android:id="@+id/audio_select_device_address_layout">
+                <TextView
+                    android:id="@+id/select_device_title"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/select_device" />
+                <Space
+                    android:layout_width="3dp"
+                    android:layout_height="match_parent" />
+                <Spinner
+                    android:id="@+id/device_address_spinner"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" />
+                <Space
+                    android:layout_width="3dp"
+                    android:layout_height="match_parent" />
+                <Button
+                    android:id="@+id/button_device_media_play_start"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/play" />
+                <Space
+                    android:layout_width="3dp"
+                    android:layout_height="match_parent" />
+                <Button
+                    android:id="@+id/button_device_media_play_once"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/play_pcm_once" />
+                <Space
+                    android:layout_width="3dp"
+                    android:layout_height="match_parent" />
+                <Button
+                    android:id="@+id/button_device_media_play_stop"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stop" />
+            </LinearLayout>
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
                 android:layout_weight="1" >
                 <TextView
                     android:id="@+id/zone_selection_title"
@@ -72,50 +116,6 @@
             <LinearLayout
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:orientation="horizontal"
-                android:layout_weight="1"
-                android:visibility="gone"
-                android:id="@+id/audio_display_layout">
-                <TextView
-                    android:id="@+id/select_display_title"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:text="@string/select_display" />
-                <Space
-                    android:layout_width="3dp"
-                    android:layout_height="match_parent" />
-                <Spinner
-                    android:id="@+id/display_spinner"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content" />
-                <Space
-                    android:layout_width="3dp"
-                    android:layout_height="match_parent" />
-                <Button
-                    android:id="@+id/button_display_media_play_start"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:text="@string/play" />
-                <Space
-                    android:layout_width="3dp"
-                    android:layout_height="match_parent" />
-                <Button
-                    android:id="@+id/button_display_media_play_once"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:text="@string/play_pcm_once" />
-                <Space
-                    android:layout_width="3dp"
-                    android:layout_height="match_parent" />
-                <Button
-                    android:id="@+id/button_display_media_play_stop"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:text="@string/stop" />
-            </LinearLayout>
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
                 android:orientation="vertical"
                 android:layout_weight="1" >
                 <LinearLayout
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
index 80e6fc4..162c37d 100644
--- a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
@@ -137,7 +137,7 @@
     <string name="mock_audio_on" translatable="false">Audio Mocking On</string>
     <string name="mock_audio_off" translatable="false">Audio Mocking Off</string>
     <string name="select_zone_to_hear_on_speaker" translatable="false">Select Zone to hear on speaker</string>
-    <string name="select_display" translatable="false">Select Display</string>
+    <string name="select_device" translatable="false">Select Device</string>
     <string name="activity_current_zone_id" translatable="false">Activity\'s current audio zone id</string>
     <string name="no_zone" translatable="false">none</string>
     <string name="phone_audio_player" translatable="false">Phone Player</string>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java
index d4e30b7..bf5865a 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java
@@ -24,7 +24,6 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.hardware.display.DisplayManager;
 import android.media.AudioAttributes;
 import android.media.AudioDeviceInfo;
 import android.media.AudioFocusRequest;
@@ -36,8 +35,6 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.util.Log;
-import android.view.Display;
-import android.view.DisplayAddress;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -82,7 +79,7 @@
     private AudioPlayer mVrPlayer;
     private AudioPlayer mSystemPlayer;
     private AudioPlayer mWavPlayer;
-    private AudioPlayer mMusicPlayerForSelectedDisplay;
+    private AudioPlayer mMusicPlayerForSelectedDeviceAddress;
     private HwAudioSource mHwAudioSource;
     private AudioPlayer[] mAllPlayers;
 
@@ -97,15 +94,14 @@
     private AudioAttributes mVrAudioAttrib;
     private AudioAttributes mRadioAudioAttrib;
     private AudioAttributes mSystemSoundAudioAttrib;
-    private AudioAttributes mMusicAudioAttribForDisplay;
+    private AudioAttributes mMusicAudioAttribForDeviceAddress;
     private CarEmulator mCarEmulator;
     private CarAudioManager mCarAudioManager;
     private Spinner mZoneSpinner;
     ArrayAdapter<Integer> mZoneAdapter;
-    private Spinner mDisplaySpinner;
-    ArrayAdapter<Integer> mDisplayAdapter;
-    private LinearLayout mDisplayLayout;
-    private int mOldZonePosition;
+    private Spinner mDeviceAddressSpinner;
+    ArrayAdapter<CarAudioZoneDeviceInfo> mDeviceAddressAdapter;
+    private LinearLayout mDeviceAddressLayout;
 
     private final Object mLock = new Object();
 
@@ -114,8 +110,6 @@
     private OnAudioFocusChangeListener mMediaWithDelayedFocusListener;
     private TextView mDelayedStatusText;
 
-    private static int sDefaultExtraTestScreenPortId = 1;
-
     private final OnAudioFocusChangeListener mNavFocusListener = (focusChange) -> {
         Log.i(TAG, "Nav focus change:" + focusChange);
     };
@@ -160,9 +154,7 @@
 
                     handleSetUpZoneSelection();
 
-                    if (mCarAudioManager.isDynamicRoutingEnabled()) {
-                        setUpDisplayPlayer();
-                    }
+                    setUpDeviceAddressPlayer();
                 });
     }
 
@@ -173,8 +165,8 @@
         //Zone Spinner
         setUpZoneSpinnerView(view);
 
-        //Display layout
-        setUpDisplayLayoutView(view);
+        // Device Address layout
+        setUpDeviceAddressLayoutView(view);
 
         connectCar();
         initializePlayers();
@@ -293,17 +285,17 @@
             }
         });
 
-        // Manage buttons for audio player for displays
-        view.findViewById(R.id.button_display_media_play_start).setOnClickListener(v -> {
-            startDisplayAudio();
+        // Manage buttons for audio player for device address
+        view.findViewById(R.id.button_device_media_play_start).setOnClickListener(v -> {
+            startDeviceAudio();
         });
-        view.findViewById(R.id.button_display_media_play_once).setOnClickListener(v -> {
-            startDisplayAudio();
+        view.findViewById(R.id.button_device_media_play_once).setOnClickListener(v -> {
+            startDeviceAudio();
             // play only for 1 sec and stop
-            mHandler.postDelayed(() -> mMusicPlayerForSelectedDisplay.stop(), 1000);
+            mHandler.postDelayed(() -> mMusicPlayerForSelectedDeviceAddress.stop(), 1000);
         });
-        view.findViewById(R.id.button_display_media_play_stop)
-                .setOnClickListener(v -> mMusicPlayerForSelectedDisplay.stop());
+        view.findViewById(R.id.button_device_media_play_stop)
+                .setOnClickListener(v -> mMusicPlayerForSelectedDeviceAddress.stop());
 
         view.findViewById(R.id.media_delayed_focus_start)
                 .setOnClickListener(v -> handleDelayedMediaStart());
@@ -347,61 +339,62 @@
 
     private void initializePlayers() {
         mMusicAudioAttrib = new AudioAttributes.Builder()
-            .setUsage(AudioAttributes.USAGE_MEDIA)
-            .build();
+                .setUsage(AudioAttributes.USAGE_MEDIA)
+                .build();
         mNavAudioAttrib = new AudioAttributes.Builder()
-            .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
-            .build();
+                .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
+                .build();
         mPhoneAudioAttrib = new AudioAttributes.Builder()
                 .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
                 .build();
         mVrAudioAttrib = new AudioAttributes.Builder()
-            .setUsage(AudioAttributes.USAGE_ASSISTANT)
-            .build();
+                .setUsage(AudioAttributes.USAGE_ASSISTANT)
+                .build();
         mRadioAudioAttrib = new AudioAttributes.Builder()
-            .setUsage(AudioAttributes.USAGE_MEDIA)
-            .build();
+                .setUsage(AudioAttributes.USAGE_MEDIA)
+                .build();
         mSystemSoundAudioAttrib = new AudioAttributes.Builder()
-            .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
-            .build();
-        // Create a display audio attribute
-        mMusicAudioAttribForDisplay = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+                .build();
+        // Create an audio device address audio attribute
+        mMusicAudioAttribForDeviceAddress = new AudioAttributes.Builder()
                 .setUsage(AudioAttributes.USAGE_MEDIA)
                 .build();
 
-        mMusicPlayerForSelectedDisplay = new AudioPlayer(mContext, R.raw.well_worth_the_wait,
-                mMusicAudioAttribForDisplay);
+
+        mMusicPlayerForSelectedDeviceAddress = new AudioPlayer(mContext, R.raw.well_worth_the_wait,
+                mMusicAudioAttribForDeviceAddress);
         mMusicPlayer = new AudioPlayer(mContext, R.raw.well_worth_the_wait,
-            mMusicAudioAttrib);
+                mMusicAudioAttrib);
         mMusicPlayerWithDelayedFocus = new AudioPlayer(mContext, R.raw.well_worth_the_wait,
                 mMusicAudioAttrib);
         mMusicPlayerShort = new AudioPlayer(mContext, R.raw.ring_classic_01,
-            mMusicAudioAttrib);
+                mMusicAudioAttrib);
         mNavGuidancePlayer = new AudioPlayer(mContext, R.raw.turnright,
-            mNavAudioAttrib);
+                mNavAudioAttrib);
         mPhoneAudioPlayer = new AudioPlayer(mContext, R.raw.free_flight,
                 mPhoneAudioAttrib);
         mVrPlayer = new AudioPlayer(mContext, R.raw.one2six,
-            mVrAudioAttrib);
+                mVrAudioAttrib);
         mSystemPlayer = new AudioPlayer(mContext, R.raw.ring_classic_01,
-            mSystemSoundAudioAttrib);
+                mSystemSoundAudioAttrib);
         mWavPlayer = new AudioPlayer(mContext, R.raw.free_flight,
-            mMusicAudioAttrib);
+                mMusicAudioAttrib);
         final AudioDeviceInfo tuner = findTunerDevice(mContext);
         if (tuner != null) {
             mHwAudioSource = new HwAudioSource.Builder()
-                .setAudioAttributes(mMusicAudioAttrib)
-                .setAudioDeviceInfo(findTunerDevice(mContext))
-                .build();
+                    .setAudioAttributes(mMusicAudioAttrib)
+                    .setAudioDeviceInfo(findTunerDevice(mContext))
+                    .build();
         }
         mAllPlayers = new AudioPlayer[] {
-            mMusicPlayer,
-            mMusicPlayerShort,
-            mNavGuidancePlayer,
-            mVrPlayer,
-            mSystemPlayer,
-            mWavPlayer,
-            mMusicPlayerWithDelayedFocus
+                mMusicPlayer,
+                mMusicPlayerShort,
+                mNavGuidancePlayer,
+                mVrPlayer,
+                mSystemPlayer,
+                mWavPlayer,
+                mMusicPlayerWithDelayedFocus
         };
     }
 
@@ -494,14 +487,14 @@
         if (DBG) Log.d(TAG, "Media With Delayed Focus already stopped");
     }
 
-    private void setUpDisplayLayoutView(View view) {
-        mDisplayLayout = view.findViewById(R.id.audio_display_layout);
+    private void setUpDeviceAddressLayoutView(View view) {
+        mDeviceAddressLayout = view.findViewById(R.id.audio_select_device_address_layout);
 
-        mDisplaySpinner = view.findViewById(R.id.display_spinner);
-        mDisplaySpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+        mDeviceAddressSpinner = view.findViewById(R.id.device_address_spinner);
+        mDeviceAddressSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
             @Override
             public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
-                handleDisplaySelection();
+                handleDeviceAddressSelection();
             }
 
             @Override
@@ -642,72 +635,68 @@
         mAudioManager.abandonAudioFocus(mRadioFocusListener, mRadioAudioAttrib);
     }
 
-    private void setUpDisplayPlayer() {
-        DisplayManager displayManager =  (DisplayManager) mContext.getSystemService(
-                Context.DISPLAY_SERVICE);
-        Display[] displays = displayManager.getDisplays();
-        List<Integer> displayList = new ArrayList<>();
-        for (Display display : displays) {
-            DisplayAddress.Physical physical = (DisplayAddress.Physical) display.getAddress();
-            if (physical != null) {
-                displayList.add((int) physical.getPort());
-                Log.d(TAG, "Found Display Port " + physical.getPort());
-            } else {
-                Log.d(TAG, "Found Display with no physical " + display.getDisplayId());
-            }
+    private void setUpDeviceAddressPlayer() {
+        if (!mCarAudioManager.isDynamicRoutingEnabled()) {
+            mDeviceAddressLayout.setVisibility(View.GONE);
+            return;
         }
-        // If only one display is available add another display for testing
-        if (displayList.size() == 1) {
-            displayList.add(sDefaultExtraTestScreenPortId);
+        mDeviceAddressLayout.setVisibility(View.VISIBLE);
+        List<CarAudioZoneDeviceInfo> deviceList = new ArrayList<>();
+        for (int audioZoneId: mCarAudioManager.getAudioZoneIds()) {
+            AudioDeviceInfo deviceInfo = mCarAudioManager
+                    .getOutputDeviceForUsage(audioZoneId, AudioAttributes.USAGE_MEDIA);
+            CarAudioZoneDeviceInfo carAudioZoneDeviceInfo = new CarAudioZoneDeviceInfo();
+            carAudioZoneDeviceInfo.mDeviceInfo = deviceInfo;
+            carAudioZoneDeviceInfo.mAudioZoneId = audioZoneId;
+            deviceList.add(carAudioZoneDeviceInfo);
+            if (DBG) {
+                Log.d(TAG, "Found device address"
+                        + carAudioZoneDeviceInfo.mDeviceInfo.getAddress()
+                        + " for audio zone id " + audioZoneId);
+            }
+
         }
 
-        //take care of display selection
-        Integer[] displayArray = displayList.stream().toArray(Integer[]::new);
-        mDisplayAdapter = new ArrayAdapter<>(mContext,
-                android.R.layout.simple_spinner_item, displayArray);
-        mDisplayAdapter.setDropDownViewResource(
+        CarAudioZoneDeviceInfo[] deviceArray =
+                deviceList.stream().toArray(CarAudioZoneDeviceInfo[]::new);
+        mDeviceAddressAdapter = new ArrayAdapter<>(mContext,
+                android.R.layout.simple_spinner_item, deviceArray);
+        mDeviceAddressAdapter.setDropDownViewResource(
                 android.R.layout.simple_spinner_dropdown_item);
-        mDisplaySpinner.setAdapter(mDisplayAdapter);
-        createDisplayAudioPlayer();
+        mDeviceAddressSpinner.setAdapter(mDeviceAddressAdapter);
+        createDeviceAddressAudioPlayer();
     }
 
-    private void createDisplayAudioPlayer() {
-        byte selectedDisplayPortId = mDisplayAdapter.getItem(
-                mDisplaySpinner.getSelectedItemPosition()).byteValue();
-        int zoneIdForDisplayId = mCarAudioManager.getZoneIdForDisplayPortId(selectedDisplayPortId);
-        Log.d(TAG, "Setting Bundle to zone " + zoneIdForDisplayId);
+    private void createDeviceAddressAudioPlayer() {
+        CarAudioZoneDeviceInfo carAudioZoneDeviceInfo = mDeviceAddressAdapter.getItem(
+                mDeviceAddressSpinner.getSelectedItemPosition());
+        Log.d(TAG, "Setting Bundle to zone " + carAudioZoneDeviceInfo.mAudioZoneId);
         Bundle bundle = new Bundle();
         bundle.putInt(CarAudioManager.AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID,
-                zoneIdForDisplayId);
-        mMusicAudioAttribForDisplay = new AudioAttributes.Builder()
+                carAudioZoneDeviceInfo.mAudioZoneId);
+        mMusicAudioAttribForDeviceAddress = new AudioAttributes.Builder()
                 .setUsage(AudioAttributes.USAGE_MEDIA)
                 .addBundle(bundle)
                 .build();
 
-        AudioDeviceInfo audioDeviceInfo =
-                mCarAudioManager.getOutputDeviceForUsage(zoneIdForDisplayId,
-                        AudioAttributes.USAGE_MEDIA);
-
-        mMusicPlayerForSelectedDisplay = new AudioPlayer(mContext,
+        mMusicPlayerForSelectedDeviceAddress = new AudioPlayer(mContext,
                 R.raw.well_worth_the_wait,
-                mMusicAudioAttribForDisplay,
-                audioDeviceInfo);
-
-        mDisplayLayout.findViewById(R.id.audio_display_layout)
-                .setVisibility(View.VISIBLE);
+                mMusicAudioAttribForDeviceAddress,
+                carAudioZoneDeviceInfo.mDeviceInfo);
     }
 
-    private void startDisplayAudio() {
-        Log.d(TAG, "Starting display audio");
-        mMusicPlayerForSelectedDisplay.start(true, false,
+    private void startDeviceAudio() {
+        Log.d(TAG, "Starting device address audio");
+        mMusicPlayerForSelectedDeviceAddress.start(true, false,
                 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
     }
 
-    public void handleDisplaySelection() {
-        if (mMusicPlayerForSelectedDisplay != null && mMusicPlayerForSelectedDisplay.isPlaying()) {
-            mMusicPlayerForSelectedDisplay.stop();
+    public void handleDeviceAddressSelection() {
+        if (mMusicPlayerForSelectedDeviceAddress != null
+                && mMusicPlayerForSelectedDeviceAddress.isPlaying()) {
+            mMusicPlayerForSelectedDeviceAddress.stop();
         }
-        createDisplayAudioPlayer();
+        createDeviceAddressAudioPlayer();
     }
 
     /**
@@ -838,4 +827,19 @@
             }
         }
     }
+
+    private class CarAudioZoneDeviceInfo {
+        AudioDeviceInfo mDeviceInfo;
+        int mAudioZoneId;
+
+        @Override
+        public String toString() {
+            StringBuilder builder = new StringBuilder();
+            builder.append("Device Address : ");
+            builder.append(mDeviceInfo.getAddress());
+            builder.append(", Audio Zone Id: ");
+            builder.append(mAudioZoneId);
+            return builder.toString();
+        }
+    }
 }
diff --git a/tests/android_car_api_test/Android.mk b/tests/android_car_api_test/Android.mk
index 7ac74aa..44c38c4 100644
--- a/tests/android_car_api_test/Android.mk
+++ b/tests/android_car_api_test/Android.mk
@@ -41,6 +41,7 @@
         android.car.cluster.navigation \
         android.car.cluster.navigation \
         android.car.testapi \
+        android.car.test.utils \
         compatibility-device-util-axt \
         testng \
         truth-prebuilt \
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java b/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java
index 4106ccb..b3bf492 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java
@@ -16,14 +16,22 @@
 
 package android.car.apitest;
 
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+import static com.android.compatibility.common.util.TestUtils.BooleanSupplierWithThrow;
+
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.fail;
+
 import android.car.Car;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.ServiceConnection;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.util.Log;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
@@ -39,6 +47,15 @@
 
     protected static final long DEFAULT_WAIT_TIMEOUT_MS = 1_000;
 
+    /**
+     * Constant used to wait blindly, when there is no condition that can be checked.
+     */
+    private static final int SUSPEND_TIMEOUT_MS = 5_000;
+    /**
+     * How long to sleep (multiple times) while waiting for a condition.
+     */
+    private static final int SMALL_NAP_MS = 100;
+
     protected static final Context sContext = InstrumentationRegistry.getInstrumentation()
             .getTargetContext();
 
@@ -89,4 +106,36 @@
             assertMainThread();
         }
     }
+
+    protected static void suspendToRamAndResume() throws Exception {
+        Log.d(TAG, "Emulate suspend to RAM and resume");
+        PowerManager powerManager = sContext.getSystemService(PowerManager.class);
+        runShellCommand("cmd car_service suspend");
+        // Check for suspend success
+        waitUntil("Suspsend is not successful",
+                SUSPEND_TIMEOUT_MS, () -> !powerManager.isScreenOn());
+
+        // Force turn off garage mode
+        runShellCommand("cmd car_service garage-mode off");
+        runShellCommand("cmd car_service resume");
+    }
+
+    protected static boolean waitUntil(String msg, long timeoutMs,
+            BooleanSupplierWithThrow condition) {
+        long deadline = SystemClock.elapsedRealtime() + timeoutMs;
+        do {
+            try {
+                if (condition.getAsBoolean()) {
+                    return true;
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Exception in waitUntil: " + msg);
+                throw new RuntimeException(e);
+            }
+            SystemClock.sleep(SMALL_NAP_MS);
+        } while (SystemClock.elapsedRealtime() < deadline);
+
+        fail(msg + " after: " + timeoutMs + "ms");
+        return false;
+    }
 }
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarAppFocusManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarAppFocusManagerTest.java
index 4a4681b..8b64620 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarAppFocusManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarAppFocusManagerTest.java
@@ -54,7 +54,7 @@
         // Request all application focuses and abandon them to ensure no active context is present
         // when test starts.
         int[] activeTypes =  mManager.getActiveAppTypes();
-        FocusOwnershipCallback owner = new FocusOwnershipCallback();
+        FocusOwnershipCallback owner = new FocusOwnershipCallback(/* assertEventThread= */ false);
         for (int i = 0; i < activeTypes.length; i++) {
             mManager.requestAppFocus(activeTypes[i], owner);
             owner.waitForOwnershipGrantAndAssert(DEFAULT_WAIT_TIMEOUT_MS, activeTypes[i]);
@@ -165,9 +165,9 @@
         assertThat(manager1.getActiveAppTypes()).asList().containsExactly(expectedFocus).inOrder();
         assertThat(manager2.getActiveAppTypes()).asList().containsExactly(expectedFocus).inOrder();
         assertThat(change2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
-                APP_FOCUS_TYPE_NAVIGATION, true)).isFalse();
+                APP_FOCUS_TYPE_NAVIGATION, true)).isTrue();
         assertThat(change1.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS,
-                APP_FOCUS_TYPE_NAVIGATION, true)).isFalse();
+                APP_FOCUS_TYPE_NAVIGATION, true)).isTrue();
 
         assertThat(manager2.requestAppFocus(APP_FOCUS_TYPE_NAVIGATION, owner2))
                 .isEqualTo(CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED);
@@ -337,6 +337,15 @@
         private final Semaphore mLossEventWait = new Semaphore(0);
         private int mLastGrantEvent;
         private final Semaphore mGrantEventWait = new Semaphore(0);
+        private final boolean mAssertEventThread;
+
+        private FocusOwnershipCallback(boolean assertEventThread) {
+            mAssertEventThread = assertEventThread;
+        }
+
+        private FocusOwnershipCallback() {
+            this(true);
+        }
 
         boolean waitForOwnershipLossAndAssert(long timeoutMs, int expectedAppType)
                 throws Exception {
@@ -359,7 +368,9 @@
         @Override
         public void onAppFocusOwnershipLost(int appType) {
             Log.i(TAG, "onAppFocusOwnershipLost " + appType);
-            assertEventThread();
+            if (mAssertEventThread) {
+                assertEventThread();
+            }
             mLastLossEvent = appType;
             mLossEventWait.release();
         }
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarUserManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarUserManagerTest.java
index 08ccdd6..421dd44 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarUserManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarUserManagerTest.java
@@ -15,6 +15,9 @@
  */
 package android.car.apitest;
 
+import static android.car.test.util.UserTestingHelper.clearUserLockCredentials;
+import static android.car.test.util.UserTestingHelper.setMaxSupportedUsers;
+import static android.car.test.util.UserTestingHelper.setUserLockCredentials;
 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING;
 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STOPPED;
 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STOPPING;
@@ -42,6 +45,8 @@
 import android.os.UserManager;
 import android.util.Log;
 
+import androidx.test.filters.FlakyTest;
+
 import com.android.internal.infra.AndroidFuture;
 
 import org.junit.AfterClass;
@@ -49,6 +54,7 @@
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
@@ -58,6 +64,8 @@
 
     private static final String TAG = CarUserManagerTest.class.getSimpleName();
 
+    private static final int PIN = 2345;
+
     private static final int SWITCH_TIMEOUT_MS = 70_000;
     private static final int STOP_TIMEOUT_MS = 300_000;
 
@@ -69,8 +77,11 @@
 
     private static final UserManager sUserManager = UserManager.get(sContext);
 
+    private static final int sMaxNumberUsersBefore = UserManager.getMaxSupportedUsers();
+
+    private static final List<Integer> sCreatedUsers = new ArrayList<>();
+
     private static int sInitialUserId = UserHandle.USER_NULL;
-    private static int sNewUserId = UserHandle.USER_NULL;
 
     private CarUserManager mCarUserManager;
 
@@ -78,25 +89,20 @@
     public static void setupUsers() {
         sInitialUserId = ActivityManager.getCurrentUser();
         Log.i(TAG, "Running test as user " + sInitialUserId);
-
-        sNewUserId = createNewUser("Main", /* isGuest= */ false).id;
+        setMaxSupportedUsers(8); // Total 6 users will be created for all tests
     }
 
     @AfterClass
     public static void cleanupUsers() {
+        setMaxSupportedUsers(sMaxNumberUsersBefore);
         switchUserDirectly(sInitialUserId);
 
-        if (sNewUserId == UserHandle.USER_NULL) {
-            Log.w(TAG, "No need to remove user" + sNewUserId);
-            return;
-        }
-
-        Log.i(TAG, "Switching back to " + sInitialUserId);
-        switchUserDirectly(sInitialUserId);
-
-        Log.i(TAG, "Removing user" + sNewUserId);
-        if (!sUserManager.removeUser(sNewUserId)) {
-            Log.wtf(TAG, "Failed to remove user " + sNewUserId);
+        for (int i = 0; i < sCreatedUsers.size(); i++) {
+            int id = sCreatedUsers.get(i);
+            Log.d(TAG, "removeCreatedUsers: " + id);
+            if (!sUserManager.removeUser(id)) {
+                Log.wtf(TAG, "Failed to remove user " + id);
+            }
         }
     }
 
@@ -107,8 +113,8 @@
 
     @Test
     public void testLifecycleListener() throws Exception {
-        int oldUserId = sInitialUserId;
-        int newUserId = sNewUserId;
+        int oldUserId = ActivityManager.getCurrentUser();
+        int newUserId = createNewUser("Test", /* isGuest= */ false).id;
 
         BlockingUserLifecycleListener startListener = BlockingUserLifecycleListener
                 .forSpecificEvents()
@@ -132,14 +138,16 @@
         // Switch while listener is registered
         switchUser(newUserId);
 
-        List<UserLifecycleEvent> startEvents = startListener.waitForEvents();
+        List<UserLifecycleEvent> startEvents  = startListener.waitForEvents();
         Log.d(TAG, "Received start events: " + startEvents);
 
         // Make sure listener callback was executed in the proper threaqd
         assertWithMessage("not executed on executor").that(executedRef.get()).isTrue();
 
         // Assert user ids
-        for (UserLifecycleEvent event : startEvents) {
+        List<UserLifecycleEvent> expectedEvents = startListener.waitForEvents();
+        Log.d(TAG, "Received expected events: " + expectedEvents);
+        for (UserLifecycleEvent event : expectedEvents) {
             assertWithMessage("wrong userId on %s", event)
                 .that(event.getUserId()).isEqualTo(newUserId);
             assertWithMessage("wrong userHandle on %s", event)
@@ -166,8 +174,8 @@
         Log.d(TAG, "registering stop listener: " + stopListener);
         mCarUserManager.addListener(mExecutor, stopListener);
 
-        // Switch back to the previous user
-        switchUser(oldUserId);
+        // Switch back to the initial user
+        switchUser(sInitialUserId);
 
         if (TEST_STOP) {
             // Must force stop the user, otherwise it can take minutes for its process to finish
@@ -188,8 +196,7 @@
             Log.w(TAG, "NOT testing user stop events");
         }
 
-        // Make sure unregistered listener din't receive any more events
-
+        // Make sure unregistered listener didn't receive any more events
         List<UserLifecycleEvent> allStartEvents = startListener.getAllReceivedEvents();
         Log.d(TAG, "All start events: " + startEvents);
         assertThat(allStartEvents).containsAllIn(startEvents).inOrder();
@@ -198,13 +205,129 @@
         mCarUserManager.removeListener(stopListener);
     }
 
+    /**
+     * Tests resume behavior when current user is ephemeral guest, a new guest user should be
+     * created and switched to.
+     */
+    @Test
+    @FlakyTest // TODO(b/158050171) remove once process is stable on user switching.
+    public void testGuestUserResumeToNewGuestUser() throws Exception {
+        // Create new guest user
+        UserInfo guestUser = createNewUser("Guest", /* isGuestUser= */ true);
+        int guestUserId = guestUser.id;
+
+        // Wait for this user to be active
+        BlockingUserLifecycleListener listener1 = BlockingUserLifecycleListener
+                .forSpecificEvents()
+                .forUser(guestUserId)
+                .setTimeout(SWITCH_TIMEOUT_MS)
+                .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED)
+                .build();
+        mCarUserManager.addListener(Runnable::run, listener1);
+        switchUser(guestUserId);
+        listener1.waitForEvents();
+        mCarUserManager.removeListener(listener1);
+
+        BlockingUserLifecycleListener listener2 = BlockingUserLifecycleListener
+                .forSpecificEvents()
+                .forPreviousUser(guestUserId)
+                .setTimeout(SWITCH_TIMEOUT_MS)
+                .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING)
+                .build();
+        // Make sure listener callback was executed in the proper threaqd
+        mCarUserManager.addListener(Runnable::run, listener2);
+
+        // Emulate suspend to RAM
+        suspendToRamAndResume();
+        UserLifecycleEvent event = listener2.waitForEvents().get(0);
+
+        int newGuestId = event.getUserId();
+        sCreatedUsers.add(newGuestId);
+
+        assertWithMessage("wrong user on event %s", event).that(newGuestId)
+                .isNotEqualTo(guestUserId);
+        assertWithMessage("wrong current user").that(newGuestId)
+                .isEqualTo(ActivityManager.getCurrentUser());
+        UserInfo newGuest = sUserManager.getUserInfo(newGuestId);
+        assertWithMessage("new user (%s) is not a guest", newGuest.toFullString())
+                .that(newGuest.isGuest()).isTrue();
+        assertWithMessage("wrong name on new guest(%s)", newGuest.toFullString())
+                .that(newGuest.name).isNotEqualTo(guestUser.name);
+        mCarUserManager.removeListener(listener2);
+    }
+
+    /**
+     * Tests resume behavior when current user is guest guest but with secured lock screen,
+     * resume to same guest user.
+     */
+    @Test
+    @FlakyTest // TODO(b/158050171) remove once process is stable on user switching.
+    public void testSecuredGuestUserResumeToSameUser() throws Exception {
+        // Create new guest user
+        UserInfo guestUser = createNewUser("Guest", /* isGuestUser= */ true);
+        int guestUserId = guestUser.id;
+
+        // Wait for this user to be active
+        BlockingUserLifecycleListener listener = BlockingUserLifecycleListener
+                .forSpecificEvents()
+                .forUser(guestUserId)
+                .setTimeout(SWITCH_TIMEOUT_MS)
+                .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED)
+                .build();
+        mCarUserManager.addListener(Runnable::run, listener);
+
+        switchUser(guestUserId);
+
+        listener.waitForEvents();
+        mCarUserManager.removeListener(listener);
+
+        setUserLockCredentials(guestUserId, PIN);
+        try {
+            // Emulate suspend to RAM
+            suspendToRamAndResume();
+
+            assertWithMessage("not resumed to previous user: %s", guestUser)
+                    .that(ActivityManager.getCurrentUser()).isEqualTo(guestUserId);
+        } finally {
+            clearUserLockCredentials(guestUserId, PIN);
+        }
+
+    }
+
+    /**
+     * Tests resume behavior when current user is persistent user.
+     */
+    @Test
+    @FlakyTest // TODO(b/158050171) remove once process is stable on user switching.
+    public void testPersistentUserResumeToUser() throws Exception {
+        int newUserId = createNewUser("Test", /* isGuest= */ false).id;
+        BlockingUserLifecycleListener listener = BlockingUserLifecycleListener
+                .forSpecificEvents()
+                .forUser(newUserId)
+                .setTimeout(SWITCH_TIMEOUT_MS)
+                .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED)
+                .build();
+        mCarUserManager.addListener(Runnable::run, listener);
+        switchUser(newUserId);
+        listener.waitForEvents();
+
+        // Emulate suspend to RAM
+        suspendToRamAndResume();
+
+        listener.waitForEvents();
+        assertWithMessage("not resumed to previous user: %s", newUserId)
+                .that(ActivityManager.getCurrentUser()).isEqualTo(newUserId);
+
+        mCarUserManager.removeListener(listener);
+    }
+
     @NonNull
     private static UserInfo createNewUser(String name, boolean isGuest) {
         name = "CarUserManagerTest." + name;
         Log.i(TAG, "Creating new user " + name);
         UserInfo newUser = isGuest ? sUserManager.createGuest(sContext, name)
                 : sUserManager.createUser(name, /* flags= */ 0);
-
+        sCreatedUsers.add(newUser.id);
         Log.i(TAG, "Created new user: " + newUser.toFullString());
         return newUser;
     }
diff --git a/tests/carservice_test/res/raw/car_audio_configuration.xml b/tests/carservice_test/res/raw/car_audio_configuration.xml
index b4bb08a..e1d75df 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration.xml
@@ -21,10 +21,6 @@
                     </device>
                 </group>
             </volumeGroups>
-            <displays>
-                <display port="1"/>
-                <display port="2"/>
-            </displays>
         </zone>
         <zone name="rear seat zone" audioZoneId="2">
             <volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_V1.xml b/tests/carservice_test/res/raw/car_audio_configuration_V1.xml
index 4aaaa6c..26b1c08 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_V1.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_V1.xml
@@ -33,10 +33,6 @@
                     </device>
                 </group>
             </volumeGroups>
-            <displays>
-                <display port="1"/>
-                <display port="2"/>
-            </displays>
         </zone>
         <zone name="rear seat zone">
             <volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_V1_with_non_legacy_contexts.xml b/tests/carservice_test/res/raw/car_audio_configuration_V1_with_non_legacy_contexts.xml
index 03e9a0f..5c39ceb 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_V1_with_non_legacy_contexts.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_V1_with_non_legacy_contexts.xml
@@ -33,10 +33,6 @@
                     </device>
                 </group>
             </volumeGroups>
-            <displays>
-                <display port="1"/>
-                <display port="2"/>
-            </displays>
         </zone>
         <zone name="rear seat zone">
             <volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_duplicate_audio_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_duplicate_audio_zone_id.xml
index 4b5abc5..8561968 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_duplicate_audio_zone_id.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_duplicate_audio_zone_id.xml
@@ -37,10 +37,6 @@
                     </device>
                 </group>
             </volumeGroups>
-            <displays>
-                <display port="1"/>
-                <display port="2"/>
-            </displays>
         </zone>
         <zone name="rear seat zone" audioZoneId="1" occupantZoneId="2">
             <volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_duplicate_occupant_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_duplicate_occupant_zone_id.xml
index 1d15b36..8c1d0e8 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_duplicate_occupant_zone_id.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_duplicate_occupant_zone_id.xml
@@ -37,10 +37,6 @@
                     </device>
                 </group>
             </volumeGroups>
-            <displays>
-                <display port="1"/>
-                <display port="2"/>
-            </displays>
         </zone>
         <zone name="rear seat zone" audioZoneId="2" occupantZoneId="1">
             <volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_duplicate_ports.xml b/tests/carservice_test/res/raw/car_audio_configuration_duplicate_ports.xml
index 288e8fb..dba13a2 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_duplicate_ports.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_duplicate_ports.xml
@@ -17,10 +17,6 @@
                     </device>
                 </group>
             </volumeGroups>
-            <displays>
-                <display port="1"/>
-                <display port="1"/>
-            </displays>
         </zone>
     </zones>
 </carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_empty_occupant_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_empty_occupant_zone_id.xml
index 182e606..972da37 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_empty_occupant_zone_id.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_empty_occupant_zone_id.xml
@@ -37,10 +37,6 @@
                     </device>
                 </group>
             </volumeGroups>
-            <displays>
-                <display port="1"/>
-                <display port="2"/>
-            </displays>
         </zone>
         <zone name="rear seat zone" audioZoneId="2">
             <volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_negative_audio_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_negative_audio_zone_id.xml
index acdb784..060934b 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_negative_audio_zone_id.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_negative_audio_zone_id.xml
@@ -37,10 +37,6 @@
                     </device>
                 </group>
             </volumeGroups>
-            <displays>
-                <display port="1"/>
-                <display port="2"/>
-            </displays>
         </zone>
         <zone name="rear seat zone" audioZoneId="2">
             <volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_negative_occupant_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_negative_occupant_zone_id.xml
index b7d7a62..cffad51 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_negative_occupant_zone_id.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_negative_occupant_zone_id.xml
@@ -37,10 +37,6 @@
                     </device>
                 </group>
             </volumeGroups>
-            <displays>
-                <display port="1"/>
-                <display port="2"/>
-            </displays>
         </zone>
         <zone name="rear seat zone" audioZoneId="2">
             <volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_audio_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_audio_zone_id.xml
index 09168a6..8e1c41b 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_audio_zone_id.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_audio_zone_id.xml
@@ -37,10 +37,6 @@
                     </device>
                 </group>
             </volumeGroups>
-            <displays>
-                <display port="1"/>
-                <display port="2"/>
-            </displays>
         </zone>
         <zone name="rear seat zone" audioZoneId="2">
             <volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_occupant_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_occupant_zone_id.xml
index af57c24..f00b169 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_occupant_zone_id.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_occupant_zone_id.xml
@@ -37,10 +37,6 @@
                     </device>
                 </group>
             </volumeGroups>
-            <displays>
-                <display port="1"/>
-                <display port="2"/>
-            </displays>
         </zone>
         <zone name="rear seat zone" audioZoneId="2">
             <volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_port.xml b/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_port.xml
index f6995bc..9adef24 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_port.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_non_numerical_port.xml
@@ -21,9 +21,6 @@
                     </device>
                 </group>
             </volumeGroups>
-            <displays>
-                <display port="one"/>
-            </displays>
         </zone>
     </zones>
 </carAudioConfiguration>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_non_primary_zone_with_primary_audio_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_non_primary_zone_with_primary_audio_zone_id.xml
index f822469..4364a1d 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_non_primary_zone_with_primary_audio_zone_id.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_non_primary_zone_with_primary_audio_zone_id.xml
@@ -33,10 +33,6 @@
                     </device>
                 </group>
             </volumeGroups>
-            <displays>
-                <display port="1"/>
-                <display port="2"/>
-            </displays>
         </zone>
         <zone name="rear seat zone" audioZoneId="0">
             <volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_output_address_does_not_exist.xml b/tests/carservice_test/res/raw/car_audio_configuration_output_address_does_not_exist.xml
index 83c054c..a359d36 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_output_address_does_not_exist.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_output_address_does_not_exist.xml
@@ -37,10 +37,6 @@
                     </device>
                 </group>
             </volumeGroups>
-            <displays>
-                <display port="1"/>
-                <display port="2"/>
-            </displays>
         </zone>
         <zone name="rear seat zone" audioZoneId="2">
             <volumeGroups>
diff --git a/tests/carservice_test/res/raw/car_audio_configuration_primary_zone_with_non_zero_audio_zone_id.xml b/tests/carservice_test/res/raw/car_audio_configuration_primary_zone_with_non_zero_audio_zone_id.xml
index 6f845e7..2d25716 100644
--- a/tests/carservice_test/res/raw/car_audio_configuration_primary_zone_with_non_zero_audio_zone_id.xml
+++ b/tests/carservice_test/res/raw/car_audio_configuration_primary_zone_with_non_zero_audio_zone_id.xml
@@ -37,10 +37,6 @@
                     </device>
                 </group>
             </volumeGroups>
-            <displays>
-                <display port="1"/>
-                <display port="2"/>
-            </displays>
         </zone>
         <zone name="rear seat zone" audioZoneId="2">
             <volumeGroups>
diff --git a/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java b/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java
index a37c0e4..fce0a78 100644
--- a/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java
+++ b/tests/carservice_test/src/com/android/car/audio/CarAudioZonesHelperTest.java
@@ -28,7 +28,6 @@
 import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
 import android.util.SparseIntArray;
-import android.view.DisplayAddress;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -293,56 +292,6 @@
     }
 
     @Test
-    public void loadAudioZones_parsesPhysicalDisplayAddresses() throws Exception {
-        CarAudioZonesHelper cazh = new CarAudioZonesHelper(mCarAudioSettings, mInputStream,
-                mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
-
-        CarAudioZone[] zones = cazh.loadAudioZones();
-
-        CarAudioZone primaryZone = zones[0];
-        List<DisplayAddress.Physical> primaryPhysicals = primaryZone.getPhysicalDisplayAddresses();
-        assertThat(primaryPhysicals).hasSize(2);
-        assertThat(primaryPhysicals.get(0).getPort()).isEqualTo(1);
-        assertThat(primaryPhysicals.get(1).getPort()).isEqualTo(2);
-    }
-
-    @Test
-    public void loadAudioZones_defaultsDisplayAddressesToEmptyList() throws Exception {
-        CarAudioZonesHelper cazh = new CarAudioZonesHelper(mCarAudioSettings, mInputStream,
-                mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
-
-        CarAudioZone[] zones = cazh.loadAudioZones();
-
-        CarAudioZone rseZone = zones[1];
-        List<DisplayAddress.Physical> rsePhysicals = rseZone.getPhysicalDisplayAddresses();
-        assertThat(rsePhysicals).isEmpty();
-    }
-
-    @Test(expected = RuntimeException.class)
-    public void loadAudioZones_throwsOnDuplicatePorts() throws Exception {
-        try (InputStream duplicatePortStream = mContext.getResources().openRawResource(
-                R.raw.car_audio_configuration_duplicate_ports)) {
-            CarAudioZonesHelper cazh =
-                    new CarAudioZonesHelper(mCarAudioSettings, duplicatePortStream,
-                            mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
-
-            cazh.loadAudioZones();
-        }
-    }
-
-    @Test
-    public void loadAudioZones_throwsOnNonNumericalPort() {
-        InputStream duplicatePortStream = mContext.getResources().openRawResource(
-                R.raw.car_audio_configuration_non_numerical_port);
-        CarAudioZonesHelper cazh = new CarAudioZonesHelper(mCarAudioSettings, duplicatePortStream,
-                mCarAudioOutputDeviceInfos, mInputAudioDeviceInfos);
-
-        IllegalArgumentException exception = expectThrows(IllegalArgumentException.class,
-                cazh::loadAudioZones);
-        assertThat(exception).hasMessageThat().contains("Port one is not a number");
-    }
-
-    @Test
     public void loadAudioZones_passesOnMissingAudioZoneIdForPrimary() throws Exception {
         try (InputStream missingAudioZoneIdStream = mContext.getResources().openRawResource(
                 R.raw.car_audio_configuration_no_audio_zone_id_for_primary_zone)) {
diff --git a/tests/carservice_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java b/tests/carservice_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java
index fb65ff0..28d70e6 100644
--- a/tests/carservice_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java
+++ b/tests/carservice_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java
@@ -17,7 +17,7 @@
 package com.android.car.watchdog;
 
 import static android.car.test.mocks.AndroidMockitoHelper.mockQueryService;
-import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetUsers;
+import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetAllUsers;
 import static android.car.test.mocks.AndroidMockitoHelper.mockUmIsUserRunning;
 import static android.car.test.util.UserTestingHelper.UserInfoBuilder;
 import static android.car.watchdog.CarWatchdogManager.TIMEOUT_CRITICAL;
@@ -100,7 +100,7 @@
         when(mCar.getEventHandler()).thenReturn(mMainHandler);
         when(mServiceBinder.queryLocalInterface(anyString())).thenReturn(mCarWatchdogService);
         when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
-        mockUmGetUsers(mUserManager, mUserInfos);
+        mockUmGetAllUsers(mUserManager, mUserInfos);
         mockUmIsUserRunning(mUserManager, 10, true);
         mockUmIsUserRunning(mUserManager, 11, false);
 
diff --git a/tests/carservice_unit_test/src/android/car/userlib/CarUserManagerHelperTest.java b/tests/carservice_unit_test/src/android/car/userlib/CarUserManagerHelperTest.java
index 5fe5670..62cf6d4 100644
--- a/tests/carservice_unit_test/src/android/car/userlib/CarUserManagerHelperTest.java
+++ b/tests/carservice_unit_test/src/android/car/userlib/CarUserManagerHelperTest.java
@@ -16,6 +16,8 @@
 
 package android.car.userlib;
 
+import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetSystemUser;
+import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetUserInfo;
 import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetUsers;
 import static android.car.test.util.UserTestingHelper.newUser;
 import static android.os.UserHandle.USER_SYSTEM;
@@ -31,6 +33,7 @@
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
+import android.car.settings.CarSettings;
 import android.car.test.mocks.AbstractExtendedMockitoTestCase;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -38,7 +41,6 @@
 import android.content.res.Resources;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.provider.Settings;
 import android.sysprop.CarProperties;
 
 import androidx.test.InstrumentationRegistry;
@@ -192,12 +194,31 @@
     }
 
     @Test
-    public void testGetInitialUser_WithNonExistLastActiveUser_ReturnsSmallestUserId() {
-        setLastActiveUser(12);
-        mockGetUsers(USER_SYSTEM, 10, 10 + 1);
+    public void testGetInitialUser_WithNonExistLastActiveUser_ReturnsLastPersistentUser() {
+        setLastActiveUser(120);
+        setLastPersistentActiveUser(110);
+        mockGetUsers(USER_SYSTEM, 100, 110);
 
         assertThat(mCarUserManagerHelper.getInitialUser(/* usesOverrideUserIdProperty= */ true))
-                .isEqualTo(10);
+                .isEqualTo(110);
+        // should have reset last active user
+        assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_USER_ID))
+                .isEqualTo(UserHandle.USER_NULL);
+    }
+
+    @Test
+    public void testGetInitialUser_WithNonExistLastActiveAndPersistentUsers_ReturnsSmallestUser() {
+        setLastActiveUser(120);
+        setLastPersistentActiveUser(120);
+        mockGetUsers(USER_SYSTEM, 100, 110);
+
+        assertThat(mCarUserManagerHelper.getInitialUser(/* usesOverrideUserIdProperty= */ true))
+                .isEqualTo(100);
+        // should have reset both settions
+        assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_USER_ID))
+                .isEqualTo(UserHandle.USER_NULL);
+        assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID))
+                .isEqualTo(UserHandle.USER_NULL);
     }
 
     @Test
@@ -289,6 +310,65 @@
         assertThat(mCarUserManagerHelper.hasInitialUser()).isFalse();
     }
 
+    @Test
+    public void testSetLastActiveUser_headlessSystem() {
+        mockIsHeadlessSystemUserMode(true);
+        mockUmGetSystemUser(mUserManager);
+
+        mCarUserManagerHelper.setLastActiveUser(UserHandle.USER_SYSTEM);
+
+        assertSettingsNotSet(CarSettings.Global.LAST_ACTIVE_USER_ID);
+        assertSettingsNotSet(CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID);
+    }
+
+    @Test
+    public void testSetLastActiveUser_nonHeadlessSystem() {
+        mockIsHeadlessSystemUserMode(false);
+        mockUmGetSystemUser(mUserManager);
+
+        mCarUserManagerHelper.setLastActiveUser(UserHandle.USER_SYSTEM);
+
+        assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_USER_ID))
+                .isEqualTo(UserHandle.USER_SYSTEM);
+        assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID))
+                .isEqualTo(UserHandle.USER_SYSTEM);
+    }
+
+    @Test
+    public void testSetLastActiveUser_nonExistingUser() {
+        // Don't need to mock um.getUser(), it will return null by default
+        mCarUserManagerHelper.setLastActiveUser(42);
+
+        assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_USER_ID)).isEqualTo(42);
+        assertSettingsNotSet(CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID);
+    }
+
+    @Test
+    public void testSetLastActiveUser_ephemeralUser() {
+        int persistentUserId = 42;
+        int ephemeralUserid = 108;
+        mockUmGetUserInfo(mUserManager, persistentUserId, NO_FLAGS);
+        mockUmGetUserInfo(mUserManager, ephemeralUserid, UserInfo.FLAG_EPHEMERAL);
+
+        mCarUserManagerHelper.setLastActiveUser(persistentUserId);
+        mCarUserManagerHelper.setLastActiveUser(ephemeralUserid);
+
+        assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_USER_ID))
+                .isEqualTo(ephemeralUserid);
+        assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID))
+                .isEqualTo(persistentUserId);
+    }
+
+    @Test
+    public void testSetLastActiveUser_nonEphemeralUser() {
+        mockUmGetUserInfo(mUserManager, 42, NO_FLAGS);
+
+        mCarUserManagerHelper.setLastActiveUser(42);
+
+        assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_USER_ID)).isEqualTo(42);
+        assertThat(getSettingsInt(CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID)).isEqualTo(42);
+    }
+
     private void mockGetUsers(@NonNull @UserIdInt int... userIds) {
         mockUmGetUsers(mUserManager, userIds);
     }
@@ -298,7 +378,11 @@
     }
 
     private void setLastActiveUser(@UserIdInt int userId) {
-        putSettingsInt(Settings.Global.LAST_ACTIVE_USER_ID, userId);
+        putSettingsInt(CarSettings.Global.LAST_ACTIVE_USER_ID, userId);
+    }
+
+    private void setLastPersistentActiveUser(@UserIdInt int userId) {
+        putSettingsInt(CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID, userId);
     }
 
     private void setDefaultBootUserOverride(@UserIdInt int userId) {
diff --git a/tests/carservice_unit_test/src/android/car/userlib/UserHalHelperTest.java b/tests/carservice_unit_test/src/android/car/userlib/UserHalHelperTest.java
index 6397c3c..316cd32 100644
--- a/tests/carservice_unit_test/src/android/car/userlib/UserHalHelperTest.java
+++ b/tests/carservice_unit_test/src/android/car/userlib/UserHalHelperTest.java
@@ -17,6 +17,8 @@
 package android.car.userlib;
 
 import static android.car.userlib.UserHalHelper.CREATE_USER_PROPERTY;
+import static android.car.userlib.UserHalHelper.REMOVE_USER_PROPERTY;
+import static android.car.userlib.UserHalHelper.SWITCH_USER_PROPERTY;
 import static android.car.userlib.UserHalHelper.USER_IDENTIFICATION_ASSOCIATION_PROPERTY;
 import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationSetValue.ASSOCIATE_CURRENT_USER;
 import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationSetValue.DISASSOCIATE_ALL_USERS;
@@ -49,6 +51,9 @@
 import android.hardware.automotive.vehicle.V2_0.InitialUserInfoRequestType;
 import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
 import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
+import android.hardware.automotive.vehicle.V2_0.RemoveUserRequest;
+import android.hardware.automotive.vehicle.V2_0.SwitchUserMessageType;
+import android.hardware.automotive.vehicle.V2_0.SwitchUserRequest;
 import android.hardware.automotive.vehicle.V2_0.UserFlags;
 import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociation;
 import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationType;
@@ -861,6 +866,62 @@
     }
 
     @Test
+    public void testRemoveUserRequestToVehiclePropValue_null() {
+        assertThrows(NullPointerException.class,
+                () -> UserHalHelper.toVehiclePropValue((RemoveUserRequest) null));
+    }
+
+    @Test
+    public void testRemoveUserRequestToVehiclePropValue_emptyRequest() {
+        RemoveUserRequest request = new RemoveUserRequest();
+
+        assertThrows(IllegalArgumentException.class,
+                () -> UserHalHelper.toVehiclePropValue(request));
+    }
+
+    @Test
+    public void testRemoveUserRequestToVehiclePropValue_missingRequestId() {
+        RemoveUserRequest request = new RemoveUserRequest();
+        request.removedUserInfo.userId = 11;
+        request.usersInfo.existingUsers.add(request.removedUserInfo);
+
+        assertThrows(IllegalArgumentException.class,
+                () -> UserHalHelper.toVehiclePropValue(request));
+    }
+
+    @Test
+    public void testRemoveUserRequestToVehiclePropValue_ok() {
+        RemoveUserRequest request = new RemoveUserRequest();
+        request.requestId = 42;
+
+        android.hardware.automotive.vehicle.V2_0.UserInfo user10 =
+                new android.hardware.automotive.vehicle.V2_0.UserInfo();
+        user10.userId = 10;
+        user10.flags = UserFlags.ADMIN;
+
+        // existing users
+        request.usersInfo.numberUsers = 1;
+        request.usersInfo.existingUsers.add(user10);
+
+        // current user
+        request.usersInfo.currentUser = user10;
+        // user to remove
+        request.removedUserInfo = user10;
+
+        VehiclePropValue propValue = UserHalHelper.toVehiclePropValue(request);
+
+        assertWithMessage("wrong prop on %s", propValue).that(propValue.prop)
+                .isEqualTo(REMOVE_USER_PROPERTY);
+        assertWithMessage("wrong int32values on %s", propValue).that(propValue.value.int32Values)
+                .containsExactly(42, // request id
+                        10, UserFlags.ADMIN, // user to remove
+                        10, UserFlags.ADMIN, // current user
+                        1, // number of users
+                        10, UserFlags.ADMIN  // existing user 1
+                        ).inOrder();
+    }
+
+    @Test
     public void testCreateUserRequestToVehiclePropValue_null() {
         assertThrows(NullPointerException.class,
                 () -> UserHalHelper.toVehiclePropValue((CreateUserRequest) null));
@@ -885,6 +946,24 @@
     }
 
     @Test
+    public void testCreateUserRequestToVehiclePropValue_nullNewUserName() {
+        CreateUserRequest request = new CreateUserRequest();
+        request.requestId = 42;
+
+        request.newUserInfo.userId = 10;
+        request.newUserInfo.flags = UserFlags.ADMIN;
+        request.newUserName = null;
+
+        request.usersInfo.numberUsers = 1;
+        request.usersInfo.currentUser.userId = request.newUserInfo.userId;
+        request.usersInfo.currentUser.flags = request.newUserInfo.flags;
+        request.usersInfo.existingUsers.add(request.usersInfo.currentUser);
+
+        assertThrows(IllegalArgumentException.class,
+                () -> UserHalHelper.toVehiclePropValue(request));
+    }
+
+    @Test
     public void testCreateUserRequestToVehiclePropValue_usersInfoDoesNotContainNewUser() {
         CreateUserRequest request = new CreateUserRequest();
         request.requestId = 42;
@@ -966,6 +1045,84 @@
     }
 
     @Test
+    public void testSwitchUserRequestToVehiclePropValue_null() {
+        assertThrows(NullPointerException.class,
+                () -> UserHalHelper.toVehiclePropValue((SwitchUserRequest) null));
+    }
+
+    @Test
+    public void testSwitchUserRequestToVehiclePropValue_emptyRequest() {
+        SwitchUserRequest request = new SwitchUserRequest();
+
+        assertThrows(IllegalArgumentException.class,
+                () -> UserHalHelper.toVehiclePropValue(request));
+    }
+
+    @Test
+    public void testSwitchUserRequestToVehiclePropValue_missingMessageType() {
+        SwitchUserRequest request = new SwitchUserRequest();
+        request.requestId = 42;
+        android.hardware.automotive.vehicle.V2_0.UserInfo user10 =
+                new android.hardware.automotive.vehicle.V2_0.UserInfo();
+        user10.userId = 10;
+        request.usersInfo.numberUsers = 1;
+        request.usersInfo.existingUsers.add(user10);
+        request.usersInfo.currentUser = user10;
+        request.targetUser = user10;
+
+        assertThrows(IllegalArgumentException.class,
+                () -> UserHalHelper.toVehiclePropValue(request));
+    }
+
+    @Test
+    public void testSwitchUserRequestToVehiclePropValue_incorrectMessageType() {
+        SwitchUserRequest request = new SwitchUserRequest();
+        request.requestId = 42;
+        request.messageType = -1;
+        android.hardware.automotive.vehicle.V2_0.UserInfo user10 =
+                new android.hardware.automotive.vehicle.V2_0.UserInfo();
+        user10.userId = 10;
+        request.usersInfo.numberUsers = 1;
+        request.usersInfo.existingUsers.add(user10);
+        request.usersInfo.currentUser = user10;
+        request.targetUser = user10;
+
+        assertThrows(IllegalArgumentException.class,
+                () -> UserHalHelper.toVehiclePropValue(request));
+    }
+
+    @Test
+    public void tesSwitchUserRequestToVehiclePropValue_ok() {
+        SwitchUserRequest request = new SwitchUserRequest();
+        request.requestId = 42;
+        android.hardware.automotive.vehicle.V2_0.UserInfo user10 =
+                new android.hardware.automotive.vehicle.V2_0.UserInfo();
+        user10.userId = 10;
+        user10.flags = UserFlags.ADMIN;
+        // existing users
+        request.usersInfo.numberUsers = 1;
+        request.usersInfo.existingUsers.add(user10);
+        // current user
+        request.usersInfo.currentUser = user10;
+        // user to remove
+        request.targetUser = user10;
+        request.messageType = SwitchUserMessageType.ANDROID_SWITCH;
+
+        VehiclePropValue propValue = UserHalHelper.toVehiclePropValue(request);
+
+        assertWithMessage("wrong prop on %s", propValue).that(propValue.prop)
+                .isEqualTo(SWITCH_USER_PROPERTY);
+        assertWithMessage("wrong int32values on %s", propValue).that(propValue.value.int32Values)
+                .containsExactly(42, // request id
+                        SwitchUserMessageType.ANDROID_SWITCH, // message type
+                        10, UserFlags.ADMIN, // target user
+                        10, UserFlags.ADMIN, // current user
+                        1, // number of users
+                        10, UserFlags.ADMIN  // existing user 1
+                        ).inOrder();
+    }
+
+    @Test
     public void testNewUsersInfo_nullUm() {
         assertThrows(IllegalArgumentException.class, () -> UserHalHelper.newUsersInfo(null));
     }
diff --git a/tests/carservice_unit_test/src/com/android/car/CarInputServiceTest.java b/tests/carservice_unit_test/src/com/android/car/CarInputServiceTest.java
index 9f360e1..cabab88 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarInputServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarInputServiceTest.java
@@ -30,16 +30,25 @@
 import static org.mockito.Mockito.ignoreStubs;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import android.annotation.UserIdInt;
+import android.app.IActivityManager;
 import android.car.CarProjectionManager;
 import android.car.input.CarInputHandlingService.InputFilter;
 import android.car.input.ICarInputListener;
+import android.car.testapi.BlockingUserLifecycleListener;
+import android.car.user.CarUserManager;
+import android.car.userlib.CarUserManagerHelper;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -48,17 +57,25 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
 import android.service.voice.VoiceInteractionSession;
 import android.telecom.TelecomManager;
+import android.test.mock.MockContentResolver;
 import android.view.KeyEvent;
 
 import androidx.test.core.app.ApplicationProvider;
 
 import com.android.car.hal.InputHalService;
+import com.android.car.hal.UserHalService;
+import com.android.car.user.CarUserService;
 import com.android.internal.app.AssistUtils;
+import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.internal.util.test.FakeSettingsProvider;
 
 import com.google.common.collect.Range;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -73,6 +90,9 @@
 
 @RunWith(MockitoJUnitRunner.class)
 public class CarInputServiceTest {
+    // TODO(b/152069895): decrease value once refactored. In fact, it should not even use
+    // runWithScissors(), but only rely on CountdownLatches
+    private static final long DEFAULT_TIMEOUT_MS = 5_000;
 
     @Mock InputHalService mInputHalService;
     @Mock TelecomManager mTelecomManager;
@@ -84,12 +104,54 @@
     @Spy Context mContext = ApplicationProvider.getApplicationContext();
     @Spy Handler mHandler = new Handler(Looper.getMainLooper());
 
+    private MockContext mMockContext;
+    private CarUserService mCarUserService;
     private CarInputService mCarInputService;
 
+    /**
+     * A mock {@link Context}.
+     * This class uses a mock {@link ContentResolver} and {@link android.content.ContentProvider} to
+     * avoid changing real system settings. Besides, to emulate the case where the OEM changes
+     * {@link R.string.rotaryService} to empty in the resource file (e.g., the OEM doesn't want to
+     * start RotaryService), this class allows to return a given String when retrieving {@link
+     * R.string.rotaryService}.
+     */
+    private static class MockContext extends BroadcastInterceptingContext {
+        private final MockContentResolver mContentResolver;
+        private final FakeSettingsProvider mContentProvider;
+        private final Resources mResources;
+
+        MockContext(Context base, String rotaryService) {
+            super(base);
+            FakeSettingsProvider.clearSettingsProvider();
+            mContentResolver = new MockContentResolver(this);
+            mContentProvider = new FakeSettingsProvider();
+            mContentResolver.addProvider(Settings.AUTHORITY, mContentProvider);
+
+            mResources = spy(base.getResources());
+            doReturn(rotaryService).when(mResources).getString(R.string.rotaryService);
+        }
+
+        void release() {
+            FakeSettingsProvider.clearSettingsProvider();
+        }
+
+        @Override
+        public ContentResolver getContentResolver() {
+            return mContentResolver;
+        }
+
+        @Override
+        public Resources getResources() {
+            return mResources;
+        }
+    }
+
     @Before
     public void setUp() {
-        mCarInputService = new CarInputService(mContext, mInputHalService, mHandler,
-                mTelecomManager, mAssistUtils, mDefaultMainListener, mLastCallSupplier,
+        mCarUserService = mock(CarUserService.class);
+        mCarInputService = new CarInputService(mContext, mInputHalService, mCarUserService,
+                mHandler, mTelecomManager, mAssistUtils, mDefaultMainListener, mLastCallSupplier,
                 /* customInputServiceComponent= */ null, mLongPressDelaySupplier);
 
         when(mInputHalService.isKeyInputSupported()).thenReturn(true);
@@ -100,6 +162,69 @@
     }
 
     @Test
+    public void rotaryServiceSettingsUpdated_whenRotaryServiceIsNotEmpty() throws Exception {
+        final String rotaryService = "com.android.car.rotary/com.android.car.rotary.RotaryService";
+        init(rotaryService);
+        assertThat(mMockContext.getString(R.string.rotaryService)).isEqualTo(rotaryService);
+
+        final int userId = 11;
+
+        // By default RotaryService is not enabled.
+        String enabledServices = Settings.Secure.getStringForUser(
+                mMockContext.getContentResolver(),
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                userId);
+        assertThat(enabledServices == null ? "" : enabledServices).doesNotContain(rotaryService);
+
+        String enabled = Settings.Secure.getStringForUser(
+                mMockContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_ENABLED,
+                userId);
+        assertThat(enabled).isNull();
+
+        // Enable RotaryService by sending user switch event.
+        sendUserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING, userId);
+
+        enabledServices = Settings.Secure.getStringForUser(
+                mMockContext.getContentResolver(),
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                userId);
+        assertThat(enabledServices).contains(rotaryService);
+
+        enabled = Settings.Secure.getStringForUser(
+                mMockContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_ENABLED,
+                userId);
+        assertThat(enabled).isEqualTo("1");
+    }
+
+    @Test
+    public void rotaryServiceSettingsNotUpdated_whenRotaryServiceIsEmpty() throws Exception {
+        final String rotaryService = "";
+        init(rotaryService);
+        assertThat(mMockContext.getString(R.string.rotaryService)).isEqualTo(rotaryService);
+
+        final int userId = 11;
+
+        // By default the Accessibility is disabled.
+        String enabled = Settings.Secure.getStringForUser(
+                mMockContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_ENABLED,
+                userId);
+        assertThat(enabled).isNull();
+
+        sendUserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING, userId);
+
+        // Sending user switch event shouldn't enable the Accessibility because RotaryService is
+        // empty.
+        enabled = Settings.Secure.getStringForUser(
+                mMockContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_ENABLED,
+                userId);
+        assertThat(enabled).isNull();
+    }
+
+    @Test
     public void ordinaryEvents_onMainDisplay_routedToInputManager() {
         KeyEvent event = send(Key.DOWN, KeyEvent.KEYCODE_ENTER, Display.MAIN);
 
@@ -458,6 +583,54 @@
         assertThat(timeCaptor.getValue()).isIn(Range.closed(then + systemDelay, now + systemDelay));
     }
 
+    @After
+    public void tearDown() {
+        if (mMockContext != null) {
+            mMockContext.release();
+            mMockContext = null;
+        }
+    }
+
+    /**
+     * Initializes {@link #mMockContext}, {@link #mCarUserService}, and {@link #mCarInputService}.
+     */
+    private void init(String rotaryService) {
+        mMockContext = new MockContext(mContext, rotaryService);
+
+        UserManager userManager = mock(UserManager.class);
+        UserInfo userInfo = mock(UserInfo.class);
+        doReturn(userInfo).when(userManager).getUserInfo(anyInt());
+        UserHalService userHal = mock(UserHalService.class);
+        CarUserManagerHelper carUserManagerHelper = mock(CarUserManagerHelper.class);
+        IActivityManager iActivityManager = mock(IActivityManager.class);
+        mCarUserService = new CarUserService(mMockContext, userHal, carUserManagerHelper,
+                userManager, iActivityManager, /* maxRunningUsers= */ 2);
+
+        mCarInputService = new CarInputService(mMockContext, mInputHalService, mCarUserService,
+                mHandler, mTelecomManager, mAssistUtils, mDefaultMainListener, mLastCallSupplier,
+                /* customInputServiceComponent= */ null, mLongPressDelaySupplier);
+        mCarInputService.init();
+    }
+
+    private void sendUserLifecycleEvent(@CarUserManager.UserLifecycleEventType int eventType,
+            @UserIdInt int userId) throws InterruptedException {
+        // Add a blocking listener to ensure CarUserService event notification is completed
+        // before proceeding with test execution.
+        BlockingUserLifecycleListener blockingListener =
+                BlockingUserLifecycleListener.forAnyEvent().build();
+        mCarUserService.addUserLifecycleListener(blockingListener);
+
+        runOnMainThreadAndWaitForIdle(() -> mCarUserService.onUserLifecycleEvent(eventType,
+                /* timestampMs= */ 0, /* fromUserId= */ UserHandle.USER_NULL, userId));
+        blockingListener.waitForAnyEvent();
+    }
+
+    private static void runOnMainThreadAndWaitForIdle(Runnable r) {
+        Handler.getMain().runWithScissors(r, DEFAULT_TIMEOUT_MS);
+        // Run empty runnable to make sure that all posted handlers are done.
+        Handler.getMain().runWithScissors(() -> { }, DEFAULT_TIMEOUT_MS);
+    }
+
     private enum Key {DOWN, UP}
 
     private enum Display {MAIN, INSTRUMENT_CLUSTER}
diff --git a/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java b/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java
index afd6f57..3cbfffc 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarPowerManagementServiceTest.java
@@ -62,6 +62,7 @@
 import com.android.car.systeminterface.WakeLockInterface;
 import com.android.car.test.utils.TemporaryDirectory;
 import com.android.car.user.CarUserService;
+import com.android.internal.app.IVoiceInteractionManagerService;
 
 import org.junit.After;
 import org.junit.Before;
@@ -107,6 +108,9 @@
     private CarUserService mUserService;
     @Mock
     private InitialUserSetter mInitialUserSetter;
+    @Mock
+    private IVoiceInteractionManagerService mVoiceInteractionManagerService;
+
 
     @Override
     protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
@@ -150,7 +154,8 @@
                 + ", maxGarageModeRunningDurationInSecs="
                 + mResources.getInteger(R.integer.maxGarageModeRunningDurationInSecs));
         mService = new CarPowerManagementService(mContext, mResources, mPowerHal,
-                mSystemInterface, mUserManager, mUserService, mInitialUserSetter);
+                mSystemInterface, mUserManager, mUserService, mInitialUserSetter,
+                mVoiceInteractionManagerService);
         mService.init();
         mService.setShutdownTimersForTest(0, 0);
         mPowerHal.setSignalListener(mPowerSignalListener);
@@ -180,7 +185,7 @@
                 new PowerState(
                         VehicleApPowerStateReq.SHUTDOWN_PREPARE,
                         VehicleApPowerStateShutdownParam.SHUTDOWN_ONLY));
-        assertStateReceived(PowerHalService.SET_SHUTDOWN_START, WAKE_UP_DELAY);
+        assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_SHUTDOWN_START);
         assertThat(mService.garageModeShouldExitImmediately()).isFalse();
         assertThat(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS)).isFalse();
         mPowerSignalListener.waitForShutdown(WAIT_TIMEOUT_MS);
@@ -266,10 +271,10 @@
                 new PowerState(
                         VehicleApPowerStateReq.SHUTDOWN_PREPARE,
                         VehicleApPowerStateShutdownParam.SHUTDOWN_IMMEDIATELY));
-        assertStateReceived(PowerHalService.SET_SHUTDOWN_START, 0);
+        assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_SHUTDOWN_START, 0);
         // Cancel the shutdown
         mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.CANCEL_SHUTDOWN, 0));
-        assertStateReceived(PowerHalService.SET_SHUTDOWN_CANCELLED, 0);
+        assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_SHUTDOWN_CANCELLED);
         // Go to suspend
         mPowerHal.setCurrentPowerState(
                 new PowerState(
@@ -288,7 +293,7 @@
                 new PowerState(
                         VehicleApPowerStateReq.SHUTDOWN_PREPARE,
                         VehicleApPowerStateShutdownParam.SLEEP_IMMEDIATELY));
-        assertStateReceived(PowerHalService.SET_DEEP_SLEEP_ENTRY, 0);
+        assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_DEEP_SLEEP_ENTRY, 0);
         assertThat(mService.garageModeShouldExitImmediately()).isTrue();
         mPowerSignalListener.waitForSleepEntry(WAIT_TIMEOUT_MS);
 
@@ -501,6 +506,7 @@
                 VehicleApPowerStateShutdownParam.CAN_SLEEP));
         assertThat(mDisplayInterface.waitForDisplayStateChange(WAIT_TIMEOUT_MS)).isFalse();
         assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_DEEP_SLEEP_ENTRY);
+        assertVoiceInteractionDisabled();
         mPowerSignalListener.waitForSleepEntry(WAIT_TIMEOUT_MS);
 
         // Send the finished signal
@@ -530,6 +536,7 @@
         mSystemStateInterface.waitForSleepEntryAndWakeup(WAIT_TIMEOUT_MS);
         // Since we just woke up from shutdown, wake up time will be 0
         assertStateReceived(PowerHalService.SET_DEEP_SLEEP_EXIT, 0);
+        assertVoiceInteractionEnabled();
         assertThat(mDisplayInterface.getDisplayState()).isFalse();
     }
 
@@ -573,6 +580,14 @@
         assertStateReceivedForShutdownOrSleepWithPostpone(lastState, expectedSecondParameter);
     }
 
+    private void assertVoiceInteractionEnabled() throws Exception {
+        verify(mVoiceInteractionManagerService).setDisabled(false);
+    }
+
+    private void assertVoiceInteractionDisabled() throws Exception {
+        verify(mVoiceInteractionManagerService).setDisabled(true);
+    }
+
     private static void waitForSemaphore(Semaphore semaphore, long timeoutMs)
             throws InterruptedException {
         if (!semaphore.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) {
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/UserHalServiceTest.java b/tests/carservice_unit_test/src/com/android/car/hal/UserHalServiceTest.java
index 2b11bd7..e6a81b6 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/UserHalServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/UserHalServiceTest.java
@@ -18,6 +18,7 @@
 import static android.car.VehiclePropertyIds.CREATE_USER;
 import static android.car.VehiclePropertyIds.CURRENT_GEAR;
 import static android.car.VehiclePropertyIds.INITIAL_USER_INFO;
+import static android.car.VehiclePropertyIds.REMOVE_USER;
 import static android.car.VehiclePropertyIds.SWITCH_USER;
 import static android.car.VehiclePropertyIds.USER_IDENTIFICATION_ASSOCIATION;
 import static android.car.test.mocks.CarArgumentMatchers.isProperty;
@@ -53,7 +54,9 @@
 import android.hardware.automotive.vehicle.V2_0.CreateUserStatus;
 import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
 import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
+import android.hardware.automotive.vehicle.V2_0.RemoveUserRequest;
 import android.hardware.automotive.vehicle.V2_0.SwitchUserMessageType;
+import android.hardware.automotive.vehicle.V2_0.SwitchUserRequest;
 import android.hardware.automotive.vehicle.V2_0.SwitchUserResponse;
 import android.hardware.automotive.vehicle.V2_0.SwitchUserStatus;
 import android.hardware.automotive.vehicle.V2_0.UserFlags;
@@ -413,28 +416,28 @@
 
     @Test
     public void testSwitchUser_invalidTimeout() {
-        assertThrows(IllegalArgumentException.class,
-                () -> mUserHalService.switchUser(mUser10, 0, mUsersInfo, noOpCallback()));
-        assertThrows(IllegalArgumentException.class,
-                () -> mUserHalService.switchUser(mUser10, -1, mUsersInfo, noOpCallback()));
+        assertThrows(IllegalArgumentException.class, () -> mUserHalService
+                .switchUser(createUserSwitchRequest(mUser10, mUsersInfo), 0, noOpCallback()));
+        assertThrows(IllegalArgumentException.class, () -> mUserHalService
+                .switchUser(createUserSwitchRequest(mUser10, mUsersInfo), -1, noOpCallback()));
     }
 
     @Test
     public void testSwitchUser_noUsersInfo() {
-        assertThrows(IllegalArgumentException.class,
-                () -> mUserHalService.switchUser(mUser10, TIMEOUT_MS, null, noOpCallback()));
+        assertThrows(IllegalArgumentException.class, () -> mUserHalService
+                .switchUser(createUserSwitchRequest(mUser10, null), TIMEOUT_MS, noOpCallback()));
     }
 
     @Test
     public void testSwitchUser_noCallback() {
-        assertThrows(NullPointerException.class,
-                () -> mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, null));
+        assertThrows(NullPointerException.class, () -> mUserHalService
+                .switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS, null));
     }
 
     @Test
     public void testSwitchUser_noTarget() {
-        assertThrows(NullPointerException.class,
-                () -> mUserHalService.switchUser(null, TIMEOUT_MS, mUsersInfo, noOpCallback()));
+        assertThrows(NullPointerException.class, () -> mUserHalService
+                .switchUser(createUserSwitchRequest(null, mUsersInfo), TIMEOUT_MS, noOpCallback()));
     }
 
     @Test
@@ -443,7 +446,8 @@
 
         GenericHalCallback<SwitchUserResponse> callback = new GenericHalCallback<>(
                 CALLBACK_TIMEOUT_TIMEOUT);
-        mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback);
+        mUserHalService.switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS,
+                callback);
 
         callback.assertCalled();
         assertCallbackStatus(callback, HalCallback.STATUS_HAL_SET_TIMEOUT);
@@ -458,7 +462,8 @@
     public void testSwitchUser_halDidNotReply() throws Exception {
         GenericHalCallback<SwitchUserResponse> callback = new GenericHalCallback<>(
                 CALLBACK_TIMEOUT_TIMEOUT);
-        mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback);
+        mUserHalService.switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS,
+                callback);
 
         callback.assertCalled();
         assertCallbackStatus(callback, HalCallback.STATUS_HAL_RESPONSE_TIMEOUT);
@@ -475,7 +480,8 @@
 
         GenericHalCallback<SwitchUserResponse> callback = new GenericHalCallback<>(
                 CALLBACK_TIMEOUT_TIMEOUT);
-        mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback);
+        mUserHalService.switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS,
+                callback);
 
         callback.assertCalled();
         assertCallbackStatus(callback, HalCallback.STATUS_HAL_RESPONSE_TIMEOUT);
@@ -493,7 +499,8 @@
 
         GenericHalCallback<SwitchUserResponse> callback = new GenericHalCallback<>(
                 CALLBACK_TIMEOUT_SUCCESS);
-        mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback);
+        mUserHalService.switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS,
+                callback);
 
         callback.assertCalled();
 
@@ -517,7 +524,8 @@
 
         GenericHalCallback<SwitchUserResponse> callback = new GenericHalCallback<>(
                 CALLBACK_TIMEOUT_SUCCESS);
-        mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback);
+        mUserHalService.switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS,
+                callback);
 
         callback.assertCalled();
 
@@ -545,7 +553,8 @@
 
         GenericHalCallback<SwitchUserResponse> callback = new GenericHalCallback<>(
                 CALLBACK_TIMEOUT_SUCCESS);
-        mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback);
+        mUserHalService.switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS,
+                callback);
 
         callback.assertCalled();
 
@@ -567,8 +576,10 @@
                 CALLBACK_TIMEOUT_TIMEOUT);
         GenericHalCallback<SwitchUserResponse> callback2 = new GenericHalCallback<>(
                 CALLBACK_TIMEOUT_TIMEOUT);
-        mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback1);
-        mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback2);
+        mUserHalService.switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS,
+                callback1);
+        mUserHalService.switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS,
+                callback2);
 
         callback1.assertCalled();
         assertCallbackStatus(callback1, HalCallback.STATUS_HAL_RESPONSE_TIMEOUT);
@@ -590,7 +601,8 @@
 
         GenericHalCallback<SwitchUserResponse> callback = new GenericHalCallback<>(
                 CALLBACK_TIMEOUT_SUCCESS);
-        mUserHalService.switchUser(mUser10, TIMEOUT_MS, mUsersInfo, callback);
+        mUserHalService.switchUser(createUserSwitchRequest(mUser10, mUsersInfo), TIMEOUT_MS,
+                callback);
 
         callback.assertCalled();
 
@@ -634,30 +646,113 @@
 
     @Test
     public void testPostSwitchResponse_noUsersInfo() {
-        assertThrows(NullPointerException.class,
-                () -> mUserHalService.postSwitchResponse(42, mUser10, null));
+        SwitchUserRequest request = createUserSwitchRequest(mUser10, null);
+        request.requestId = 42;
+        assertThrows(NullPointerException.class, () -> mUserHalService.postSwitchResponse(request));
     }
 
     @Test
     public void testPostSwitchResponse_HalCalledWithCorrectProp() {
-        mUserHalService.postSwitchResponse(42, mUser10, mUsersInfo);
+        SwitchUserRequest request = createUserSwitchRequest(mUser10, mUsersInfo);
+        request.requestId = 42;
+        mUserHalService.postSwitchResponse(request);
         ArgumentCaptor<VehiclePropValue> propCaptor =
                 ArgumentCaptor.forClass(VehiclePropValue.class);
         verify(mVehicleHal).set(propCaptor.capture());
         VehiclePropValue prop = propCaptor.getValue();
-        assertHalSetSwitchUserRequest(prop, SwitchUserMessageType.ANDROID_POST_SWITCH,
-                mUser10);
+        assertHalSetSwitchUserRequest(prop, SwitchUserMessageType.ANDROID_POST_SWITCH, mUser10);
+    }
+
+    @Test
+    public void testLegacyUserSwitch_nullRequest() {
+        assertThrows(NullPointerException.class, () -> mUserHalService.legacyUserSwitch(null));
+    }
+
+    @Test
+    public void testLegacyUserSwitch_noMessageType() {
+        SwitchUserRequest request = new SwitchUserRequest();
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mUserHalService.legacyUserSwitch(request));
+    }
+
+    @Test
+    public void testLegacyUserSwitch_noTargetUserInfo() {
+        SwitchUserRequest request = new SwitchUserRequest();
+        request.messageType = SwitchUserMessageType.ANDROID_SWITCH;
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mUserHalService.legacyUserSwitch(request));
+    }
+
+    @Test
+    public void testRemoveUser_nullRequest() {
+        RemoveUserRequest request = null;
+
+        assertThrows(NullPointerException.class,
+                () -> mUserHalService.removeUser(request));
+    }
+
+    @Test
+    public void testRemoveUser_noRequestId() {
+        RemoveUserRequest request = new RemoveUserRequest();
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mUserHalService.removeUser(request));
+    }
+
+    @Test
+    public void testRemoveUser_noRemovedUserInfo() {
+        RemoveUserRequest request = new RemoveUserRequest();
+        request.requestId = 1;
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mUserHalService.removeUser(request));
+    }
+
+    @Test
+    public void testRemoveUser_noUsersInfo() {
+        RemoveUserRequest request = new RemoveUserRequest();
+        request.requestId = 1;
+        request.removedUserInfo = mUser10;
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mUserHalService.removeUser(request));
+    }
+
+    @Test
+    public void testRemoveUser_HalCalledWithCorrectProp() {
+        RemoveUserRequest request = new RemoveUserRequest();
+        request.removedUserInfo = mUser10;
+        request.usersInfo = mUsersInfo;
+        ArgumentCaptor<VehiclePropValue> propCaptor =
+                ArgumentCaptor.forClass(VehiclePropValue.class);
+
+        mUserHalService.removeUser(request);
+
+        verify(mVehicleHal).set(propCaptor.capture());
+        assertHalSetRemoveUserRequest(propCaptor.getValue(), mUser10);
     }
 
     @Test
     public void testLegacyUserSwitch_noUsersInfo() {
+        SwitchUserRequest request = new SwitchUserRequest();
+        request.messageType = SwitchUserMessageType.ANDROID_SWITCH;
+        request.targetUser = mUser10;
+
         assertThrows(IllegalArgumentException.class,
-                () -> mUserHalService.legacyUserSwitch(mUser10, null));
+                () -> mUserHalService.legacyUserSwitch(request));
     }
 
     @Test
     public void testLegacyUserSwitch_HalCalledWithCorrectProp() {
-        mUserHalService.legacyUserSwitch(mUser10, mUsersInfo);
+        SwitchUserRequest request = new SwitchUserRequest();
+        request.messageType = SwitchUserMessageType.LEGACY_ANDROID_SWITCH;
+        request.requestId = 1;
+        request.targetUser = mUser10;
+        request.usersInfo = mUsersInfo;
+
+        mUserHalService.legacyUserSwitch(request);
         ArgumentCaptor<VehiclePropValue> propCaptor =
                 ArgumentCaptor.forClass(VehiclePropValue.class);
         verify(mVehicleHal).set(propCaptor.capture());
@@ -1240,6 +1335,15 @@
                 "PropId: 0x" + Integer.toHexString(prop))).when(mVehicleHal).set(isProperty(prop));
     }
 
+    @NonNull
+    private SwitchUserRequest createUserSwitchRequest(@NonNull UserInfo targetUser,
+            @NonNull UsersInfo usersInfo) {
+        SwitchUserRequest request = new SwitchUserRequest();
+        request.targetUser = targetUser;
+        request.usersInfo = usersInfo;
+        return request;
+    }
+
     /**
      * Creates and set expectations for a valid request.
      */
@@ -1313,6 +1417,17 @@
         assertUsersInfo(req, mUsersInfo, 4);
     }
 
+    private void assertHalSetRemoveUserRequest(VehiclePropValue req, UserInfo userInfo) {
+        assertThat(req.prop).isEqualTo(REMOVE_USER);
+        assertWithMessage("wrong request Id on %s", req).that(req.value.int32Values.get(0))
+                .isAtLeast(1);
+        assertWithMessage("user.id mismatch on %s", req).that(req.value.int32Values.get(1))
+                .isEqualTo(userInfo.userId);
+        assertWithMessage("user.flags mismatch on %s", req).that(req.value.int32Values.get(2))
+                .isEqualTo(userInfo.flags);
+        assertUsersInfo(req, mUsersInfo, 3);
+    }
+
     private void assertHalSetCreateUserRequest(VehiclePropValue prop, CreateUserRequest request) {
         assertThat(prop.prop).isEqualTo(CREATE_USER);
         assertWithMessage("wrong request Id on %s", prop).that(prop.value.int32Values.get(0))
diff --git a/tests/carservice_unit_test/src/com/android/car/hardware/power/CarPowerManagerUnitTest.java b/tests/carservice_unit_test/src/com/android/car/hardware/power/CarPowerManagerUnitTest.java
index 6100dbc..ae86874 100644
--- a/tests/carservice_unit_test/src/com/android/car/hardware/power/CarPowerManagerUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hardware/power/CarPowerManagerUnitTest.java
@@ -41,6 +41,7 @@
 import com.android.car.systeminterface.DisplayInterface;
 import com.android.car.systeminterface.SystemInterface;
 import com.android.car.systeminterface.SystemStateInterface;
+import com.android.internal.app.IVoiceInteractionManagerService;
 
 import org.junit.After;
 import org.junit.Before;
@@ -73,6 +74,8 @@
     private Resources mResources;
     @Mock
     private Car mCar;
+    @Mock
+    private IVoiceInteractionManagerService mVoiceInteractionManagerService;
 
     @Before
     public void setUp() throws Exception {
@@ -204,7 +207,7 @@
                 + ", maxGarageModeRunningDurationInSecs="
                 + mResources.getInteger(R.integer.maxGarageModeRunningDurationInSecs));
         mService = new CarPowerManagementService(mContext, mResources, mPowerHal,
-                mSystemInterface, null, null, null);
+                mSystemInterface, null, null, null, mVoiceInteractionManagerService);
         mService.init();
         mService.setShutdownTimersForTest(0, 0);
         assertStateReceived(MockedPowerHalService.SET_WAIT_FOR_VHAL, 0);
diff --git a/tests/carservice_unit_test/src/com/android/car/user/CarUserManagerUnitTest.java b/tests/carservice_unit_test/src/com/android/car/user/CarUserManagerUnitTest.java
index 13cebbd..1003957 100644
--- a/tests/carservice_unit_test/src/com/android/car/user/CarUserManagerUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/user/CarUserManagerUnitTest.java
@@ -17,7 +17,6 @@
 
 import static android.car.test.mocks.AndroidMockitoHelper.getResult;
 import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetUsers;
-import static android.car.test.util.UserTestingHelper.newUsers;
 import static android.car.testapi.CarMockitoHelper.mockHandleRemoteExceptionFromCarServiceWithDefaultValue;
 import static android.os.UserHandle.USER_SYSTEM;
 
@@ -34,17 +33,22 @@
 import static org.mockito.Mockito.when;
 import static org.testng.Assert.assertThrows;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.car.Car;
 import android.car.ICarUserService;
 import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.car.test.util.UserTestingHelper;
 import android.car.user.CarUserManager;
 import android.car.user.CarUserManager.UserLifecycleListener;
 import android.car.user.CarUserManager.UserSwitchUiCallback;
+import android.car.user.UserCreationResult;
 import android.car.user.UserIdentificationAssociationResponse;
+import android.car.user.UserRemovalResult;
 import android.car.user.UserSwitchResult;
 import android.content.pm.UserInfo;
+import android.content.pm.UserInfo.UserInfoFlag;
 import android.os.RemoteException;
 import android.os.UserManager;
 
@@ -54,8 +58,6 @@
 import org.junit.Test;
 import org.mockito.Mock;
 
-import java.util.List;
-
 public final class CarUserManagerUnitTest extends AbstractExtendedMockitoTestCase {
 
     @Mock
@@ -175,7 +177,7 @@
 
     @Test
     public void testSwitchUser_remoteException() throws Exception {
-        expectServiceSwitchUserSucceeds(11);
+        expectServiceSwitchUserFails(11);
         mockHandleRemoteExceptionFromCarServiceWithDefaultValue(mCar);
 
         AndroidFuture<UserSwitchResult> future = mMgr.switchUser(11);
@@ -187,6 +189,28 @@
     }
 
     @Test
+    public void testRemoveUser_success() throws Exception {
+        int userId = 11;
+        int status = UserRemovalResult.STATUS_SUCCESSFUL;
+        when(mService.removeUser(userId)).thenReturn(new UserRemovalResult(status));
+
+        UserRemovalResult result = mMgr.removeUser(11);
+
+        assertThat(result.getStatus()).isEqualTo(UserRemovalResult.STATUS_SUCCESSFUL);
+    }
+
+    @Test
+    public void testRemoveUser_remoteException() throws Exception {
+        int userId = 11;
+        doThrow(new RemoteException("D'OH!")).when(mService).removeUser(eq(userId));
+        mockHandleRemoteExceptionFromCarServiceWithDefaultValue(mCar);
+
+        UserRemovalResult result = mMgr.removeUser(11);
+
+        assertThat(result.getStatus()).isEqualTo(UserRemovalResult.STATUS_HAL_INTERNAL_FAILURE);
+    }
+
+    @Test
     public void testSetSwitchUserUICallback_success() throws Exception {
         UserSwitchUiCallback callback = (u)-> { };
 
@@ -201,6 +225,42 @@
     }
 
     @Test
+    public void testCreateUser_success() throws Exception {
+        expectServiceCreateUserSucceeds("dude", "sweet", 42, UserCreationResult.STATUS_SUCCESSFUL,
+                108);
+
+        AndroidFuture<UserCreationResult> future = mMgr.createUser("dude", "sweet", 42);
+
+        assertThat(future).isNotNull();
+        UserCreationResult result = getResult(future);
+        assertThat(result.getStatus()).isEqualTo(UserCreationResult.STATUS_SUCCESSFUL);
+        assertThat(result.isSuccess()).isTrue();
+        assertThat(result.getErrorMessage()).isNull();
+
+        UserInfo newUser = result.getUser();
+        assertThat(newUser).isNotNull();
+        assertThat(newUser.id).isEqualTo(108);
+        assertThat(newUser.name).isEqualTo("dude");
+        assertThat(newUser.userType).isEqualTo("sweet");
+        assertThat(newUser.flags).isEqualTo(42);
+    }
+
+    @Test
+    public void testCreateUser_remoteException() throws Exception {
+        expectServiceCreateUserFails("dude", "sweet", 42);
+        mockHandleRemoteExceptionFromCarServiceWithDefaultValue(mCar);
+
+        AndroidFuture<UserCreationResult> future = mMgr.createUser("dude", "sweet", 42);
+
+        assertThat(future).isNotNull();
+        UserCreationResult result = getResult(future);
+        assertThat(result.getStatus()).isEqualTo(UserCreationResult.STATUS_HAL_INTERNAL_FAILURE);
+        assertThat(result.isSuccess()).isFalse();
+        assertThat(result.getErrorMessage()).isNull();
+        assertThat(result.getUser()).isNull();
+    }
+
+    @Test
     public void testGetUserIdentificationAssociation_nullTypes() throws Exception {
         assertThrows(IllegalArgumentException.class,
                 () -> mMgr.getUserIdentificationAssociation(null));
@@ -321,13 +381,32 @@
         }).when(mService).switchUser(eq(userId), anyInt(), notNull());
     }
 
-    private void expectServiceSwitchUserSucceeds(@UserIdInt int userId) throws RemoteException {
+    private void expectServiceSwitchUserFails(@UserIdInt int userId) throws RemoteException {
         doThrow(new RemoteException("D'OH!")).when(mService)
             .switchUser(eq(userId), anyInt(), notNull());
     }
 
+    private void expectServiceCreateUserSucceeds(@Nullable String name,
+            @NonNull String userType, @UserInfoFlag int flags,
+            @UserCreationResult.Status int status, @UserIdInt int userId) throws RemoteException {
+        doAnswer((invocation) -> {
+            @SuppressWarnings("unchecked")
+            AndroidFuture<UserCreationResult> future =
+                    (AndroidFuture<UserCreationResult>) invocation.getArguments()[4];
+            UserInfo newUser = new UserTestingHelper.UserInfoBuilder(108)
+                    .setName(name).setType(userType).setFlags(flags).build();
+            future.complete(new UserCreationResult(status, newUser, /* errorMessage= */ null));
+            return null;
+        }).when(mService).createUser(eq(name), eq(userType), eq(flags), anyInt(), notNull());
+    }
+
+    private void expectServiceCreateUserFails(@Nullable String name,
+            @NonNull String userType, @UserInfoFlag int flags) throws RemoteException {
+        doThrow(new RemoteException("D'OH!")).when(mService)
+                .createUser(eq(name), eq(userType), eq(flags), anyInt(), notNull());
+    }
+
     private void setExistingUsers(int... userIds) {
-        List<UserInfo> users = newUsers(userIds);
-        mockUmGetUsers(mUserManager, users);
+        mockUmGetUsers(mUserManager, userIds);
     }
 }
diff --git a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
index 4dc2923..b0f1c29 100644
--- a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
@@ -17,10 +17,11 @@
 package com.android.car.user;
 
 import static android.car.test.mocks.AndroidMockitoHelper.getResult;
-import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetSystemUser;
+import static android.car.test.mocks.AndroidMockitoHelper.mockUmCreateUser;
 import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetUserInfo;
 import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetUsers;
 import static android.car.test.util.UserTestingHelper.UserInfoBuilder;
+import static android.content.pm.UserInfo.FLAG_ADMIN;
 import static android.content.pm.UserInfo.FLAG_EPHEMERAL;
 import static android.content.pm.UserInfo.FLAG_GUEST;
 
@@ -63,10 +64,13 @@
 import android.car.user.CarUserManager.UserLifecycleEvent;
 import android.car.user.CarUserManager.UserLifecycleEventType;
 import android.car.user.CarUserManager.UserLifecycleListener;
+import android.car.user.UserCreationResult;
 import android.car.user.UserIdentificationAssociationResponse;
+import android.car.user.UserRemovalResult;
 import android.car.user.UserSwitchResult;
 import android.car.userlib.CarUserManagerHelper;
 import android.car.userlib.HalCallback;
+import android.car.userlib.HalCallback.HalCallbackStatus;
 import android.car.userlib.UserHalHelper;
 import android.car.userlib.UserHelper;
 import android.content.Context;
@@ -74,9 +78,14 @@
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
+import android.hardware.automotive.vehicle.V2_0.CreateUserRequest;
+import android.hardware.automotive.vehicle.V2_0.CreateUserResponse;
+import android.hardware.automotive.vehicle.V2_0.CreateUserStatus;
 import android.hardware.automotive.vehicle.V2_0.InitialUserInfoRequestType;
 import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
 import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
+import android.hardware.automotive.vehicle.V2_0.RemoveUserRequest;
+import android.hardware.automotive.vehicle.V2_0.SwitchUserRequest;
 import android.hardware.automotive.vehicle.V2_0.SwitchUserResponse;
 import android.hardware.automotive.vehicle.V2_0.SwitchUserStatus;
 import android.hardware.automotive.vehicle.V2_0.UserFlags;
@@ -136,6 +145,8 @@
 
     private static final int NON_EXISTING_USER = 55; // must not be on mExistingUsers
 
+    private static final long DEFAULT_LIFECYCLE_TIMESTAMP = 1;
+
     @Mock private Context mMockContext;
     @Mock private Context mApplicationContext;
     @Mock private LocationManager mLocationManager;
@@ -160,6 +171,7 @@
 
     private final int mGetUserInfoRequestType = InitialUserInfoRequestType.COLD_BOOT;
     private final AndroidFuture<UserSwitchResult> mUserSwitchFuture = new AndroidFuture<>();
+    private final AndroidFuture<UserCreationResult> mUserCreationFuture = new AndroidFuture<>();
     private final AndroidFuture<UserIdentificationAssociationResponse> mUserAssociationRespFuture =
             new AndroidFuture<>();
     private final int mAsyncCallTimeoutMs = 100;
@@ -308,21 +320,6 @@
     }
 
     /**
-     * Test that the {@link CarUserService} doesn't update last active user on user switch in
-     * headless system user mode.
-     */
-    @Test
-    public void testLastActiveUserUpdatedOnUserSwitch_headlessSystemUser() throws Exception {
-        mockIsHeadlessSystemUser(mRegularUser.id, true);
-        mockUmGetSystemUser(mMockedUserManager);
-        mockExistingUsers();
-
-        sendUserSwitchingEvent(mAdminUser.id, mRegularUser.id);
-
-        verifyLastActiveUserNotSet();
-    }
-
-    /**
      * Test that the {@link CarUserService} sets default guest restrictions on first boot.
      */
     @Test
@@ -664,6 +661,81 @@
     }
 
     @Test
+    public void testRemoveUser_currentUserCannotBeRemoved() throws Exception {
+        mockCurrentUser(mAdminUser);
+
+        UserRemovalResult result = mCarUserService.removeUser(mAdminUser.id);
+
+        assertThat(result.getStatus())
+                .isEqualTo(UserRemovalResult.STATUS_TARGET_USER_IS_CURRENT_USER);
+    }
+
+    @Test
+    public void testRemoveUser_userNotExist() throws Exception {
+        UserRemovalResult result = mCarUserService.removeUser(15);
+
+        assertThat(result.getStatus())
+                .isEqualTo(UserRemovalResult.STATUS_USER_DOES_NOT_EXIST);
+    }
+
+    @Test
+    public void testRemoveUser_lastAdminUser() throws Exception {
+        mockCurrentUser(mRegularUser);
+        mockExistingUsers();
+
+        UserRemovalResult result = mCarUserService.removeUser(mAdminUser.id);
+
+        assertThat(result.getStatus())
+                .isEqualTo(UserRemovalResult.STATUS_TARGET_USER_IS_LAST_ADMIN_USER);
+    }
+
+    @Test
+    public void testRemoveUser_notLastAdminUser_success() throws Exception {
+        // Give admin rights to regular user.
+        UserInfo currentUser = mRegularUser;
+        currentUser.flags = currentUser.flags | FLAG_ADMIN;
+        mockExistingUsersAndCurrentUser(currentUser);
+        int removeUserId = mAdminUser.id;
+        when(mMockedUserManager.removeUser(removeUserId)).thenReturn(true);
+
+        UserRemovalResult result = mCarUserService.removeUser(removeUserId);
+
+        assertThat(result.getStatus()).isEqualTo(UserRemovalResult.STATUS_SUCCESSFUL);
+        assertHalRemove(currentUser.id, removeUserId);
+    }
+
+    @Test
+    public void testRemoveUser_success() throws Exception {
+        mockExistingUsersAndCurrentUser(mAdminUser);
+        int removeUserId = mRegularUser.id;
+        when(mMockedUserManager.removeUser(removeUserId)).thenReturn(true);
+
+        UserRemovalResult result = mCarUserService.removeUser(removeUserId);
+
+        assertThat(result.getStatus()).isEqualTo(UserRemovalResult.STATUS_SUCCESSFUL);
+        assertHalRemove(mAdminUser.id, removeUserId);
+    }
+
+    @Test
+    public void testRemoveUser_androidFailure() throws Exception {
+        mockExistingUsersAndCurrentUser(mAdminUser);
+        int targetUserId = mRegularUser.id;
+        when(mMockedUserManager.removeUser(targetUserId)).thenReturn(false);
+
+        UserRemovalResult result = mCarUserService.removeUser(targetUserId);
+
+        assertThat(result.getStatus()).isEqualTo(UserRemovalResult.STATUS_ANDROID_FAILURE);
+    }
+
+    @Test
+    public void testSwitchUser_nullReceiver() throws Exception {
+        mockExistingUsersAndCurrentUser(mAdminUser);
+
+        assertThrows(NullPointerException.class, () -> mCarUserService
+                .switchUser(mAdminUser.id, mAsyncCallTimeoutMs, null));
+    }
+
+    @Test
     public void testSwitchUser_nonExistingTarget() throws Exception {
         assertThrows(IllegalArgumentException.class, () -> mCarUserService
                 .switchUser(NON_EXISTING_USER, mAsyncCallTimeoutMs, mUserSwitchFuture));
@@ -671,9 +743,10 @@
 
     @Test
     public void testSwitchUser_targetSameAsCurrentUser() throws Exception {
-        mockExistingUsers();
-        mockGetCurrentUser(mAdminUser.id);
+        mockExistingUsersAndCurrentUser(mAdminUser);
+
         mCarUserService.switchUser(mAdminUser.id, mAsyncCallTimeoutMs, mUserSwitchFuture);
+
         assertThat(getUserSwitchResult().getStatus())
                 .isEqualTo(UserSwitchResult.STATUS_ALREADY_REQUESTED_USER);
     }
@@ -961,17 +1034,12 @@
     }
 
     @Test
-    public void testHalUserSwitchOnAndroidSwitch_successfulNoExitingUserSwitch() {
-        mockExistingUsers();
+    public void testHalUserSwitchOnAndroidSwitch_successfulNoExitingUserSwitch() throws Exception {
+        mockExistingUsersAndCurrentUser(mAdminUser);
 
         sendUserSwitchingEvent(mAdminUser.id, mRegularUser.id);
 
-        ArgumentCaptor<android.hardware.automotive.vehicle.V2_0.UserInfo> targetUser =
-                ArgumentCaptor.forClass(android.hardware.automotive.vehicle.V2_0.UserInfo.class);
-        ArgumentCaptor<UsersInfo> usersInfo = ArgumentCaptor.forClass(UsersInfo.class);
-        verify(mUserHal).legacyUserSwitch(targetUser.capture(), usersInfo.capture());
-        assertThat(targetUser.getValue().userId).isEqualTo(mRegularUser.id);
-        assertThat(usersInfo.getValue().currentUser.userId).isEqualTo(mAdminUser.id);
+        verify(mUserHal).legacyUserSwitch(isSwitchUserRequest(0, mAdminUser.id, mRegularUser.id));
     }
 
     @Test
@@ -986,7 +1054,7 @@
 
         sendUserSwitchingEvent(mAdminUser.id, mGuestUser.id);
 
-        verify(mUserHal, never()).legacyUserSwitch(any(), any());
+        verify(mUserHal, never()).legacyUserSwitch(any());
     }
 
     @Test
@@ -1041,6 +1109,164 @@
     }
 
     @Test
+    public void testCreateUser_nullType() throws Exception {
+        assertThrows(NullPointerException.class, () -> mCarUserService
+                .createUser("dude", null, 108, mAsyncCallTimeoutMs, mUserCreationFuture));
+    }
+
+    @Test
+    public void testCreateUser_nullReceiver() throws Exception {
+        assertThrows(NullPointerException.class, () -> mCarUserService
+                .createUser("dude", "TypeONegative", 108, mAsyncCallTimeoutMs, null));
+    }
+
+    @Test
+    public void testCreateUser_umCreateReturnsNull() throws Exception {
+        // No need to mock um.createUser() to return null
+
+        mCarUserService.createUser("dude", "TypeONegative", 108, mAsyncCallTimeoutMs,
+                mUserCreationFuture);
+
+        UserCreationResult result = getUserCreationResult();
+        assertThat(result.getStatus()).isEqualTo(UserCreationResult.STATUS_ANDROID_FAILURE);
+        assertThat(result.getUser()).isNull();
+        assertThat(result.getErrorMessage()).isNull();
+        assertNoHalUserCreation();
+        verifyNoUserRemoved();
+    }
+
+    @Test
+    public void testCreateUser_umCreateThrowsException() throws Exception {
+        mockUmCreateUser(mMockedUserManager, "dude", "TypeONegative", 108,
+                new RuntimeException("D'OH!"));
+
+        mCarUserService.createUser("dude", "TypeONegative", 108, mAsyncCallTimeoutMs,
+                mUserCreationFuture);
+
+        UserCreationResult result = getUserCreationResult();
+        assertThat(result.getStatus()).isEqualTo(UserCreationResult.STATUS_ANDROID_FAILURE);
+        assertThat(result.getUser()).isNull();
+        assertThat(result.getErrorMessage()).isNull();
+        assertNoHalUserCreation();
+        verifyNoUserRemoved();
+    }
+
+    @Test
+    public void testCreateUser_internalHalFailure() throws Exception {
+        mockUmCreateUser(mMockedUserManager, "dude", "TypeONegative", 108, 42);
+        mockHalCreateUser(HalCallback.STATUS_INVALID, /* not_used_status= */ -1);
+
+        mCarUserService.createUser("dude", "TypeONegative", 108, mAsyncCallTimeoutMs,
+                mUserCreationFuture);
+
+        UserCreationResult result = getUserCreationResult();
+        assertThat(result.getStatus()).isEqualTo(UserCreationResult.STATUS_HAL_INTERNAL_FAILURE);
+        assertThat(result.getUser()).isNull();
+        assertThat(result.getErrorMessage()).isNull();
+        verifyUserRemoved(42);
+    }
+
+    @Test
+    public void testCreateUser_halFailure() throws Exception {
+        mockUmCreateUser(mMockedUserManager, "dude", "TypeONegative", 108, 42);
+        mockHalCreateUser(HalCallback.STATUS_OK, CreateUserStatus.FAILURE);
+
+        mCarUserService.createUser("dude", "TypeONegative", 108, mAsyncCallTimeoutMs,
+                mUserCreationFuture);
+
+        UserCreationResult result = getUserCreationResult();
+        assertThat(result.getStatus()).isEqualTo(UserCreationResult.STATUS_HAL_FAILURE);
+        assertThat(result.getUser()).isNull();
+        assertThat(result.getErrorMessage()).isNull();
+
+        verifyUserRemoved(42);
+    }
+
+    @Test
+    public void testCreateUser_halServiceThrowsRuntimeException() throws Exception {
+        mockUmCreateUser(mMockedUserManager, "dude", "TypeONegative", 108, 42);
+        mockHalCreateUserThrowsRuntimeException();
+
+        mCarUserService.createUser("dude", "TypeONegative", 108, mAsyncCallTimeoutMs,
+                mUserCreationFuture);
+
+        UserCreationResult result = getUserCreationResult();
+        assertThat(result.getStatus()).isEqualTo(UserCreationResult.STATUS_HAL_INTERNAL_FAILURE);
+        assertThat(result.getUser()).isNull();
+        assertThat(result.getErrorMessage()).isNull();
+
+        verifyUserRemoved(42);
+    }
+
+    @Test
+    public void testCreateUser_success() throws Exception {
+        mockExistingUsersAndCurrentUser(mAdminUser);
+        int userId = mGuestUser.id;
+        mockUmCreateUser(mMockedUserManager, "dude", UserManager.USER_TYPE_FULL_GUEST,
+                UserInfo.FLAG_EPHEMERAL, userId);
+        ArgumentCaptor<CreateUserRequest> requestCaptor =
+                mockHalCreateUser(HalCallback.STATUS_OK, CreateUserStatus.SUCCESS);
+
+        mCarUserService.createUser("dude", UserManager.USER_TYPE_FULL_GUEST,
+                UserInfo.FLAG_EPHEMERAL, mAsyncCallTimeoutMs, mUserCreationFuture);
+
+        // Assert request
+        CreateUserRequest request = requestCaptor.getValue();
+        Log.d(TAG, "createUser() request: " + request);
+        assertThat(request.newUserName).isEqualTo("dude");
+        assertThat(request.newUserInfo.userId).isEqualTo(userId);
+        assertThat(request.newUserInfo.flags).isEqualTo(UserFlags.GUEST | UserFlags.EPHEMERAL);
+        assertDefaultUsersInfo(request.usersInfo, mAdminUser);
+
+        UserCreationResult result = getUserCreationResult();
+        assertThat(result.getStatus()).isEqualTo(UserCreationResult.STATUS_SUCCESSFUL);
+        assertThat(result.getErrorMessage()).isNull();
+        UserInfo newUser = result.getUser();
+        assertThat(newUser).isNotNull();
+        assertThat(newUser.id).isEqualTo(userId);
+        assertThat(newUser.name).isEqualTo("dude");
+        assertThat(newUser.userType).isEqualTo(UserManager.USER_TYPE_FULL_GUEST);
+        assertThat(newUser.flags).isEqualTo(UserInfo.FLAG_EPHEMERAL);
+
+        verifyNoUserRemoved();
+    }
+
+    @Test
+    public void testCreateUser_success_nullName() throws Exception {
+        String nullName = null;
+        mockExistingUsersAndCurrentUser(mAdminUser);
+        int userId = mGuestUser.id;
+        mockUmCreateUser(mMockedUserManager, nullName, UserManager.USER_TYPE_FULL_GUEST,
+                UserInfo.FLAG_EPHEMERAL, userId);
+        ArgumentCaptor<CreateUserRequest> requestCaptor =
+                mockHalCreateUser(HalCallback.STATUS_OK, CreateUserStatus.SUCCESS);
+
+        mCarUserService.createUser(nullName, UserManager.USER_TYPE_FULL_GUEST,
+                UserInfo.FLAG_EPHEMERAL, mAsyncCallTimeoutMs, mUserCreationFuture);
+
+        // Assert request
+        CreateUserRequest request = requestCaptor.getValue();
+        Log.d(TAG, "createUser() request: " + request);
+        assertThat(request.newUserName).isEmpty();
+        assertThat(request.newUserInfo.userId).isEqualTo(userId);
+        assertThat(request.newUserInfo.flags).isEqualTo(UserFlags.GUEST | UserFlags.EPHEMERAL);
+        assertDefaultUsersInfo(request.usersInfo, mAdminUser);
+
+        UserCreationResult result = getUserCreationResult();
+        assertThat(result.getStatus()).isEqualTo(UserCreationResult.STATUS_SUCCESSFUL);
+        assertThat(result.getErrorMessage()).isNull();
+
+        UserInfo newUser = result.getUser();
+        assertThat(newUser).isNotNull();
+        assertThat(newUser.id).isEqualTo(userId);
+        assertThat(newUser.name).isNull();
+        assertThat(newUser.userType).isEqualTo(UserManager.USER_TYPE_FULL_GUEST);
+        assertThat(newUser.flags).isEqualTo(UserInfo.FLAG_EPHEMERAL);
+
+        verifyNoUserRemoved();
+    }
+
+    @Test
     public void testGetUserInfo_nullReceiver() throws Exception {
         assertThrows(NullPointerException.class, () -> mCarUserService
                 .getInitialUserInfo(mGetUserInfoRequestType, mAsyncCallTimeoutMs, null));
@@ -1341,7 +1567,7 @@
         sendUserSwitchingEvent(mAdminUser.id, mRegularUser.id);
 
         verify(mUserMetrics).onEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING,
-                0, mAdminUser.id, mRegularUser.id);
+                DEFAULT_LIFECYCLE_TIMESTAMP, mAdminUser.id, mRegularUser.id);
     }
 
     @Test
@@ -1361,6 +1587,11 @@
     }
 
     @NonNull
+    private UserCreationResult getUserCreationResult() throws Exception {
+        return getResult(mUserCreationFuture);
+    }
+
+    @NonNull
     private UserIdentificationAssociationResponse getUserAssociationRespResult()
             throws Exception {
         return getResult(mUserAssociationRespFuture);
@@ -1427,6 +1658,28 @@
         mockHalSwitch(currentUserId, HalCallback.STATUS_OK, response, androidTargetUser);
     }
 
+    @NonNull
+    private ArgumentCaptor<CreateUserRequest> mockHalCreateUser(
+            @HalCallbackStatus int callbackStatus, int responseStatus) {
+        CreateUserResponse response = new CreateUserResponse();
+        response.status = responseStatus;
+        ArgumentCaptor<CreateUserRequest> captor = ArgumentCaptor.forClass(CreateUserRequest.class);
+        doAnswer((invocation) -> {
+            Log.d(TAG, "Answering " + invocation + " with " + response);
+            @SuppressWarnings("unchecked")
+            HalCallback<CreateUserResponse> callback =
+                    (HalCallback<CreateUserResponse>) invocation.getArguments()[2];
+            callback.onResponse(callbackStatus, response);
+            return null;
+        }).when(mUserHal).createUser(captor.capture(), eq(mAsyncCallTimeoutMs), notNull());
+        return captor;
+    }
+
+    private void mockHalCreateUserThrowsRuntimeException() {
+        doThrow(new RuntimeException("D'OH!"))
+                .when(mUserHal).createUser(any(), eq(mAsyncCallTimeoutMs), notNull());
+    }
+
     private void mockCallerUid(int uid, boolean returnCorrectUid) throws Exception {
         String packageName = "packageName";
         String className = "className";
@@ -1448,18 +1701,20 @@
         halTargetUser.userId = androidTargetUser.id;
         halTargetUser.flags = UserHalHelper.convertFlags(androidTargetUser);
         UsersInfo usersInfo = newUsersInfo(currentUserId);
+        SwitchUserRequest request = new SwitchUserRequest();
+        request.targetUser = halTargetUser;
+        request.usersInfo = usersInfo;
 
         BlockingAnswer<Void> blockingAnswer = BlockingAnswer.forVoidReturn(10_000, (invocation) -> {
             Log.d(TAG, "Answering " + invocation + " with " + response);
             @SuppressWarnings("unchecked")
             HalCallback<SwitchUserResponse> callback = (HalCallback<SwitchUserResponse>) invocation
-                    .getArguments()[3];
+                    .getArguments()[2];
             callback.onResponse(HalCallback.STATUS_OK, response);
         });
-        doAnswer(blockingAnswer).when(mUserHal).switchUser(eq(halTargetUser),
-                eq(mAsyncCallTimeoutMs), eq(usersInfo), notNull());
+        doAnswer(blockingAnswer).when(mUserHal).switchUser(eq(request), eq(mAsyncCallTimeoutMs),
+                notNull());
         return blockingAnswer;
-
     }
 
     private void mockHalSwitch(@UserIdInt int currentUserId,
@@ -1470,15 +1725,18 @@
         halTargetUser.userId = androidTargetUser.id;
         halTargetUser.flags = UserHalHelper.convertFlags(androidTargetUser);
         UsersInfo usersInfo = newUsersInfo(currentUserId);
+        SwitchUserRequest request = new SwitchUserRequest();
+        request.targetUser = halTargetUser;
+        request.usersInfo = usersInfo;
+
         doAnswer((invocation) -> {
             Log.d(TAG, "Answering " + invocation + " with " + response);
             @SuppressWarnings("unchecked")
             HalCallback<SwitchUserResponse> callback =
-                    (HalCallback<SwitchUserResponse>) invocation.getArguments()[3];
+                    (HalCallback<SwitchUserResponse>) invocation.getArguments()[2];
             callback.onResponse(callbackStatus, response);
             return null;
-        }).when(mUserHal).switchUser(eq(halTargetUser), eq(mAsyncCallTimeoutMs), eq(usersInfo),
-                notNull());
+        }).when(mUserHal).switchUser(eq(request), eq(mAsyncCallTimeoutMs), notNull());
     }
 
     private void mockHalGetUserIdentificationAssociation(@NonNull UserInfo user,
@@ -1569,7 +1827,6 @@
         for (int i = 0; i < actual.numberUsers; i++) {
             assertSameUser(actual.existingUsers.get(i), mExistingUsers.get(i));
         }
-
     }
 
     private void assertSameUser(android.hardware.automotive.vehicle.V2_0.UserInfo halUser,
@@ -1581,6 +1838,14 @@
             .that(halUser.flags).isEqualTo(UserHalHelper.convertFlags(androidUser));
     }
 
+    private void verifyUserRemoved(@UserIdInt int userId) {
+        verify(mMockedUserManager).removeUser(userId);
+    }
+
+    private void verifyNoUserRemoved() {
+        verify(mMockedUserManager, never()).removeUser(anyInt());
+    }
+
     @NonNull
     private UsersInfo newUsersInfo(@UserIdInt int currentUserId) {
         UsersInfo infos = new UsersInfo();
@@ -1666,34 +1931,34 @@
     }
 
     private void assertNoPostSwitch() {
-        verify(mUserHal, never()).postSwitchResponse(anyInt(), any(), any());
+        verify(mUserHal, never()).postSwitchResponse(any());
     }
 
     private void assertPostSwitch(int requestId, int currentId, int targetId) {
-        // verify post switch response
-        ArgumentCaptor<android.hardware.automotive.vehicle.V2_0.UserInfo> targetUser =
-                ArgumentCaptor.forClass(android.hardware.automotive.vehicle.V2_0.UserInfo.class);
-        ArgumentCaptor<UsersInfo> usersInfo = ArgumentCaptor.forClass(UsersInfo.class);
-        verify(mUserHal).postSwitchResponse(eq(requestId), targetUser.capture(),
-                usersInfo.capture());
-        assertThat(targetUser.getValue().userId).isEqualTo(targetId);
-        assertThat(usersInfo.getValue().currentUser.userId).isEqualTo(currentId);
+        verify(mUserHal).postSwitchResponse(isSwitchUserRequest(requestId, currentId, targetId));
     }
 
     private void assertHalSwitch(int currentId, int targetId) {
-        verify(mUserHal).switchUser(isHalUser(targetId), eq(mAsyncCallTimeoutMs),
-                isHalCurrentUser(currentId), any());
+        verify(mUserHal).switchUser(isSwitchUserRequest(0, currentId, targetId),
+                eq(mAsyncCallTimeoutMs), any());
+    }
+
+    private void assertNoHalUserCreation() {
+        verify(mUserHal, never()).createUser(any(), eq(mAsyncCallTimeoutMs), any());
+    }
+
+    private void assertHalRemove(int currentId, int removeUserId) {
+        ArgumentCaptor<RemoveUserRequest> request =
+                ArgumentCaptor.forClass(RemoveUserRequest.class);
+        verify(mUserHal).removeUser(request.capture());
+        assertThat(request.getValue().removedUserInfo.userId).isEqualTo(removeUserId);
+        assertThat(request.getValue().usersInfo.currentUser.userId).isEqualTo(currentId);
     }
 
     @NonNull
-    private static android.hardware.automotive.vehicle.V2_0.UserInfo isHalUser(
-            @UserIdInt int userId) {
-        return argThat(new UserInfoMatcher(userId));
-    }
-
-    @NonNull
-    private static UsersInfo isHalCurrentUser(@UserIdInt int userId) {
-        return argThat(new UsersInfoCurrentUserIdMatcher(userId));
+    private static SwitchUserRequest isSwitchUserRequest(int requestId,
+            @UserIdInt int currentUserId, @UserIdInt int targetUserId) {
+        return argThat(new SwitchUserRequestMatcher(requestId, currentUserId, targetUserId));
     }
 
     static final class FakeCarOccupantZoneService {
@@ -1740,7 +2005,8 @@
 
     private void sendUserLifecycleEvent(@UserIdInt int fromUserId, @UserIdInt int toUserId,
             @UserLifecycleEventType int eventType) {
-        mCarUserService.onUserLifecycleEvent(eventType, /* timestampMs= */ 0, fromUserId, toUserId);
+        mCarUserService.onUserLifecycleEvent(eventType, DEFAULT_LIFECYCLE_TIMESTAMP, fromUserId,
+                toUserId);
     }
 
     private void sendUserUnlockedEvent(@UserIdInt int userId) {
@@ -1818,53 +2084,46 @@
         }
     }
 
-    private static final class UserInfoMatcher
-            implements ArgumentMatcher<android.hardware.automotive.vehicle.V2_0.UserInfo> {
-
-        private static final String MY_TAG =
-                android.hardware.automotive.vehicle.V2_0.UserInfo.class.getSimpleName();
-
-        private final @UserIdInt int mUserId;
-
-        private UserInfoMatcher(@UserIdInt int userId) {
-            mUserId = userId;
-        }
-
-        @Override
-        public boolean matches(android.hardware.automotive.vehicle.V2_0.UserInfo argument) {
-            if (argument == null) {
-                Log.w(MY_TAG, "null argument");
-                return false;
-            }
-            if (argument.userId != mUserId) {
-                Log.w(MY_TAG, "wrong user id on " + argument + "; expected " + mUserId);
-                return false;
-            }
-            Log.d(MY_TAG, "Good News, Everyone! " + argument + " matches " + this);
-            return true;
-        }
-    }
-
-    private static final class UsersInfoCurrentUserIdMatcher implements ArgumentMatcher<UsersInfo> {
-
+    private static final class SwitchUserRequestMatcher
+            implements ArgumentMatcher<SwitchUserRequest> {
         private static final String MY_TAG = UsersInfo.class.getSimpleName();
 
-        private final @UserIdInt int mUserId;
+        private final int mRequestId;
+        private final @UserIdInt int mCurrentUserId;
+        private final @UserIdInt int mTargetUserId;
 
-        private UsersInfoCurrentUserIdMatcher(@UserIdInt int userId) {
-            mUserId = userId;
+
+        private SwitchUserRequestMatcher(int requestId, @UserIdInt int currentUserId,
+                @UserIdInt int targetUserId) {
+            mCurrentUserId = currentUserId;
+            mTargetUserId = targetUserId;
+            mRequestId = requestId;
         }
 
         @Override
-        public boolean matches(UsersInfo argument) {
+        public boolean matches(SwitchUserRequest argument) {
             if (argument == null) {
                 Log.w(MY_TAG, "null argument");
                 return false;
             }
-            if (argument.currentUser.userId != mUserId) {
-                Log.w(MY_TAG, "wrong user id on " + argument + "; expected " + mUserId);
+            if (argument.usersInfo.currentUser.userId != mCurrentUserId) {
+                Log.w(MY_TAG,
+                        "wrong current user id on " + argument + "; expected " + mCurrentUserId);
                 return false;
             }
+
+            if (argument.targetUser.userId != mTargetUserId) {
+                Log.w(MY_TAG,
+                        "wrong target user id on " + argument + "; expected " + mTargetUserId);
+                return false;
+            }
+
+            if (argument.requestId != mRequestId) {
+                Log.w(MY_TAG,
+                        "wrong request Id on " + argument + "; expected " + mTargetUserId);
+                return false;
+            }
+
             Log.d(MY_TAG, "Good News, Everyone! " + argument + " matches " + this);
             return true;
         }
diff --git a/tests/carservice_unit_test/src/com/android/car/user/UserCreationResultTest.java b/tests/carservice_unit_test/src/com/android/car/user/UserCreationResultTest.java
new file mode 100644
index 0000000..7aba561
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/user/UserCreationResultTest.java
@@ -0,0 +1,38 @@
+/*
+ * 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.car.user;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.user.UserCreationResult;
+
+import org.junit.Test;
+
+public final class UserCreationResultTest {
+
+    @Test
+    public void testIsSuccess() {
+        assertThat(new UserCreationResult(UserCreationResult.STATUS_SUCCESSFUL, null, null)
+                .isSuccess()).isTrue();
+        assertThat(new UserCreationResult(UserCreationResult.STATUS_HAL_FAILURE, null, null)
+                .isSuccess()).isFalse();
+        assertThat(
+                new UserCreationResult(UserCreationResult.STATUS_HAL_INTERNAL_FAILURE, null, null)
+                        .isSuccess()).isFalse();
+        assertThat(new UserCreationResult(UserCreationResult.STATUS_ANDROID_FAILURE, null, null)
+                .isSuccess()).isFalse();
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/user/UserSwitchResultTest.java b/tests/carservice_unit_test/src/com/android/car/user/UserSwitchResultTest.java
new file mode 100644
index 0000000..385bb4e
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/user/UserSwitchResultTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.car.user;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.user.UserSwitchResult;
+
+import org.junit.Test;
+
+public final class UserSwitchResultTest {
+
+    @Test
+    public void testIUserSwitchResult_checkStatusAndMessage() {
+        String msg = "Test Message";
+        UserSwitchResult result =
+                new UserSwitchResult(UserSwitchResult.STATUS_SUCCESSFUL, msg);
+        assertThat(result.getStatus()).isEqualTo(UserSwitchResult.STATUS_SUCCESSFUL);
+        assertThat(result.getErrorMessage()).isEqualTo(msg);
+    }
+
+    @Test
+    public void testIUserSwitchResult_isSuccess_failure() {
+        UserSwitchResult result =
+                new UserSwitchResult(UserSwitchResult.STATUS_ANDROID_FAILURE, null);
+        assertThat(result.isSuccess()).isFalse();
+    }
+
+    @Test
+    public void testIUserSwitchResult_isSuccess_success() {
+        UserSwitchResult result =
+                new UserSwitchResult(UserSwitchResult.STATUS_SUCCESSFUL, null);
+        assertThat(result.isSuccess()).isTrue();
+    }
+
+    @Test
+    public void testIUserSwitchResult_isSuccess_requestedState() {
+        UserSwitchResult result =
+                new UserSwitchResult(UserSwitchResult.STATUS_ALREADY_REQUESTED_USER, null);
+        assertThat(result.isSuccess()).isTrue();
+    }
+}
diff --git a/user/car-user-lib/Android.bp b/user/car-user-lib/Android.bp
index a9a5fc1..12bc950 100644
--- a/user/car-user-lib/Android.bp
+++ b/user/car-user-lib/Android.bp
@@ -18,6 +18,7 @@
         "src/**/*.java",
     ],
     static_libs: [
+        "android.car.settings",
         "android.hardware.automotive.vehicle-V2.0-java",
     ],
     product_variables: {
diff --git a/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java b/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java
index 4079e28..15a7d1a 100644
--- a/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java
+++ b/user/car-user-lib/src/android/car/userlib/CarUserManagerHelper.java
@@ -17,10 +17,12 @@
 package android.car.userlib;
 
 import android.Manifest;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
+import android.car.settings.CarSettings;
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.graphics.Bitmap;
@@ -30,7 +32,6 @@
 import android.sysprop.CarProperties;
 import android.util.Log;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.UserIcons;
 
 import com.google.android.collect.Sets;
@@ -57,6 +58,7 @@
 public final class CarUserManagerHelper {
     private static final String TAG = "CarUserManagerHelper";
 
+    private static final boolean DEBUG = false;
     private static final int BOOT_USER_NOT_FOUND = -1;
 
     /**
@@ -97,14 +99,41 @@
      * Sets the last active user.
      */
     public void setLastActiveUser(@UserIdInt int userId) {
-        Settings.Global.putInt(
-                mContext.getContentResolver(), Settings.Global.LAST_ACTIVE_USER_ID, userId);
+        if (UserHelper.isHeadlessSystemUser(userId)) {
+            if (DEBUG) Log.d(TAG, "setLastActiveUser(): ignoring headless system user " + userId);
+            return;
+        }
+        setUserIdGlobalProperty(CarSettings.Global.LAST_ACTIVE_USER_ID, userId);
+
+        // TODO(b/155918094): change method to receive a UserInfo instead
+        UserInfo user = mUserManager.getUserInfo(userId);
+        if (user == null) {
+            Log.w(TAG, "setLastActiveUser(): user " + userId + " doesn't exist");
+            return;
+        }
+        if (!user.isEphemeral()) {
+            setUserIdGlobalProperty(CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID, userId);
+        }
     }
 
-    private int getLastActiveUser() {
-        return Settings.Global.getInt(
-            mContext.getContentResolver(), Settings.Global.LAST_ACTIVE_USER_ID,
-            /* default user id= */ UserHandle.USER_SYSTEM);
+    private void setUserIdGlobalProperty(@NonNull String name, @UserIdInt int userId) {
+        if (DEBUG) Log.d(TAG, "setting global property " + name + " to " + userId);
+
+        Settings.Global.putInt(mContext.getContentResolver(), name, userId);
+    }
+
+    private int getUserIdGlobalProperty(@NonNull String name) {
+        int userId = Settings.Global.getInt(mContext.getContentResolver(), name,
+                UserHandle.USER_NULL);
+        if (DEBUG) Log.d(TAG, "getting global property " + name + ": " + userId);
+
+        return userId;
+    }
+
+    private void resetUserIdGlobalProperty(@NonNull String name) {
+        if (DEBUG) Log.d(TAG, "resetting global property " + name);
+
+        Settings.Global.putInt(mContext.getContentResolver(), name, UserHandle.USER_NULL);
     }
 
     /**
@@ -125,7 +154,6 @@
      * @return user id of the initial user to boot into on the device, or
      * {@link UserHandle#USER_NULL} if there is no user available.
      */
-    @VisibleForTesting
     int getInitialUser(boolean usesOverrideUserIdProperty) {
 
         List<Integer> allUsers = userInfoListToUserIdList(getAllUsers());
@@ -148,18 +176,29 @@
         }
 
         // If the last active user is not the SYSTEM user and is a real user, return it
-        int lastActiveUser = getLastActiveUser();
-        if (lastActiveUser != UserHandle.USER_SYSTEM
-                && allUsers.contains(lastActiveUser)) {
-            Log.i(TAG, "Last active user loaded for initial user, user id: "
-                    + lastActiveUser);
+        int lastActiveUser = getUserIdGlobalProperty(CarSettings.Global.LAST_ACTIVE_USER_ID);
+        if (allUsers.contains(lastActiveUser)) {
+            Log.i(TAG, "Last active user loaded for initial user: " + lastActiveUser);
             return lastActiveUser;
         }
+        resetUserIdGlobalProperty(CarSettings.Global.LAST_ACTIVE_USER_ID);
+
+        int lastPersistentUser = getUserIdGlobalProperty(
+                CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID);
+        if (allUsers.contains(lastPersistentUser)) {
+            Log.i(TAG, "Last active, persistent user loaded for initial user: "
+                    + lastPersistentUser);
+            return lastPersistentUser;
+        }
+        resetUserIdGlobalProperty(CarSettings.Global.LAST_ACTIVE_PERSISTENT_USER_ID);
 
         // If all else fails, return the smallest user id
         int returnId = Collections.min(allUsers);
-        Log.i(TAG, "Saved ids were invalid. Returning smallest user id, user id: "
-                + returnId);
+        // TODO(b/158101909): the smallest user id is not always the initial user; a better approach
+        // would be looking for the first ADMIN user, or keep track of all last active users (not
+        // just the very last)
+        Log.w(TAG, "Last active user (" + lastActiveUser + ") not found. Returning smallest user id"
+                + " instead: " + returnId);
         return returnId;
     }
 
diff --git a/user/car-user-lib/src/android/car/userlib/UserHalHelper.java b/user/car-user-lib/src/android/car/userlib/UserHalHelper.java
index a9ddf0b..1d5cd67 100644
--- a/user/car-user-lib/src/android/car/userlib/UserHalHelper.java
+++ b/user/car-user-lib/src/android/car/userlib/UserHalHelper.java
@@ -27,6 +27,8 @@
 import android.hardware.automotive.vehicle.V2_0.InitialUserInfoRequestType;
 import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
 import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
+import android.hardware.automotive.vehicle.V2_0.RemoveUserRequest;
+import android.hardware.automotive.vehicle.V2_0.SwitchUserRequest;
 import android.hardware.automotive.vehicle.V2_0.UserFlags;
 import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociation;
 import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationSetValue;
@@ -60,9 +62,12 @@
     private static final boolean DEBUG = false;
 
     public static final int INITIAL_USER_INFO_PROPERTY = 299896583;
+    public static final int SWITCH_USER_PROPERTY = 299896584;
     public static final int CREATE_USER_PROPERTY = 299896585;
+    public static final int REMOVE_USER_PROPERTY = 299896586;
     public static final int USER_IDENTIFICATION_ASSOCIATION_PROPERTY = 299896587;
 
+
     private static final String STRING_SEPARATOR = "\\|\\|";
 
     /**
@@ -486,6 +491,8 @@
         Objects.requireNonNull(request, "request cannot be null");
         checkArgument(request.requestId > 0, "invalid requestId on %s", request);
         checkValid(request.usersInfo);
+        checkArgument(request.newUserName != null, "newUserName cannot be null (should be empty "
+                + "instead) on %s", request);
 
         boolean hasNewUser = false;
         int newUserFlags = UserFlags.NONE;
@@ -513,6 +520,48 @@
     }
 
     /**
+     * Creates a generic {@link VehiclePropValue} (that can be sent to HAL) from a
+     * {@link SwitchUserRequest}.
+     *
+     * @throws IllegalArgumentException if the request doesn't have the proper format.
+     */
+    @NonNull
+    public static VehiclePropValue toVehiclePropValue(@NonNull SwitchUserRequest request) {
+        Objects.requireNonNull(request, "request cannot be null");
+        checkArgument(request.messageType > 0, "invalid messageType on %s", request);
+        android.hardware.automotive.vehicle.V2_0.UserInfo targetInfo = request.targetUser;
+        UsersInfo usersInfo = request.usersInfo;
+        Objects.requireNonNull(targetInfo);
+        checkValid(usersInfo);
+
+        VehiclePropValue propValue = createPropRequest(SWITCH_USER_PROPERTY, request.requestId,
+                request.messageType);
+        addUserInfo(propValue, targetInfo);
+        addUsersInfo(propValue, usersInfo);
+        return propValue;
+    }
+
+    /**
+     * Creates a generic {@link VehiclePropValue} (that can be sent to HAL) from a
+     * {@link RemoveUserRequest}.
+     *
+     * @throws IllegalArgumentException if the request doesn't have the proper format.
+     */
+    @NonNull
+    public static VehiclePropValue toVehiclePropValue(@NonNull RemoveUserRequest request) {
+        checkArgument(request.requestId > 0, "invalid requestId on %s", request);
+        android.hardware.automotive.vehicle.V2_0.UserInfo removedUserInfo = request.removedUserInfo;
+        Objects.requireNonNull(removedUserInfo);
+        UsersInfo usersInfo = request.usersInfo;
+        checkValid(usersInfo);
+
+        VehiclePropValue propValue = createPropRequest(REMOVE_USER_PROPERTY, request.requestId);
+        addUserInfo(propValue, removedUserInfo);
+        addUsersInfo(propValue, usersInfo);
+        return propValue;
+    }
+
+    /**
      * Creates a {@link UsersInfo} instance populated with the current users.
      */
     @NonNull