Merge "Update to the latest core lib .so" 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 0c634ae..f404172 100644
--- a/car-internal-lib/src/com/android/internal/car/EventLogTags.logtags
+++ b/car-internal-lib/src/com/android/internal/car/EventLogTags.logtags
@@ -82,6 +82,8 @@
 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)
@@ -96,15 +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 df5e6ab..c5a4c98 100644
--- a/car-lib/src/android/car/ICarUserService.aidl
+++ b/car-lib/src/android/car/ICarUserService.aidl
@@ -18,6 +18,7 @@
 
 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;
@@ -32,6 +33,7 @@
     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);
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/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 54bda19..971f1fc 100644
--- a/car-lib/src/android/car/hardware/hvac/CarHvacManager.java
+++ b/car-lib/src/android/car/hardware/hvac/CarHvacManager.java
@@ -25,8 +25,6 @@
 import android.car.hardware.property.CarPropertyManager;
 import android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback;
 import android.car.hardware.property.ICarProperty;
-import android.car.hardware.property.PropertyAccessDeniedSecurityException;
-import android.car.hardware.property.PropertyNotAvailableException;
 import android.os.IBinder;
 import android.util.ArraySet;
 import android.util.Log;
@@ -351,12 +349,9 @@
                     mCarPropertyMgr.unregisterCallback(mListenerToBase, c.getPropertyId());
 
                 }
-            } catch (PropertyAccessDeniedSecurityException
-                    | PropertyNotAvailableException
-                    | IllegalArgumentException e) {
+            } catch (RuntimeException e) {
                 Log.e(TAG, "getPropertyList exception ", e);
             }
-
             if (mCallbacks.isEmpty()) {
                 mCarPropertyMgr.unregisterCallback(mListenerToBase);
                 mListenerToBase = null;
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/user/CarUserManager.java b/car-lib/src/android/car/user/CarUserManager.java
index 1525889..fcdc89a 100644
--- a/car-lib/src/android/car/user/CarUserManager.java
+++ b/car-lib/src/android/car/user/CarUserManager.java
@@ -209,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);
@@ -217,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) {
@@ -264,6 +264,28 @@
         }
     }
 
+     /**
+     * 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}.
      *
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.java b/car-lib/src/android/car/user/UserCreationResult.java
index d0f7aa1..fa29cf4 100644
--- a/car-lib/src/android/car/user/UserCreationResult.java
+++ b/car-lib/src/android/car/user/UserCreationResult.java
@@ -42,7 +42,7 @@
      *
      * @hide
      */
-    public static final int STATUS_SUCCESSFUL = 1;
+    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
@@ -50,7 +50,7 @@
      *
      * @hide
      */
-    public static final int STATUS_ANDROID_FAILURE = 2;
+    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
@@ -58,7 +58,7 @@
      *
      * @hide
      */
-    public static final int STATUS_HAL_FAILURE = 3;
+    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
@@ -66,7 +66,7 @@
      *
      * @hide
      */
-    public static final int STATUS_HAL_INTERNAL_FAILURE = 4;
+    public static final int STATUS_HAL_INTERNAL_FAILURE = CommonResults.STATUS_HAL_INTERNAL_FAILURE;
 
     /**
      * Gets the user switch result status.
@@ -77,7 +77,7 @@
      *         or
      *         {@link UserCreationResult#STATUS_HAL_INTERNAL_FAILURE}
      */
-    private final int mStatus;
+    private final @Status int mStatus;
 
     /**
      * Gets the created user.
@@ -98,6 +98,8 @@
         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.
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 8c4c535..55e2dbc 100644
--- a/car-lib/src/android/car/user/UserSwitchResult.java
+++ b/car-lib/src/android/car/user/UserSwitchResult.java
@@ -38,63 +38,50 @@
 
     /**
      * When user switch is successful for both HAL and Android.
-     *
-     * @hide
      */
-    public static final int STATUS_SUCCESSFUL = 1;
+    public static final int STATUS_SUCCESSFUL = CommonResults.STATUS_SUCCESSFUL;
 
     /**
      * When user switch is only successful for Hal but not for Android. Hal user switch rollover
      * message have been sent.
-     *
-     * @hide
      */
-    public static final int STATUS_ANDROID_FAILURE = 2;
+    public static final int STATUS_ANDROID_FAILURE = CommonResults.STATUS_ANDROID_FAILURE;
 
     /**
      * When user switch fails for HAL. User switch for Android is not called.
-     *
-     * @hide
      */
-    public static final int STATUS_HAL_FAILURE = 3;
+    public static final int STATUS_HAL_FAILURE = CommonResults.STATUS_HAL_FAILURE;
 
     /**
      * When user switch fails for HAL for some internal error. User switch for Android is not
      * called.
-     *
-     * @hide
      */
-    public static final int STATUS_HAL_INTERNAL_FAILURE = 4;
-
-    /**
-     * When target user is same as current user.
-     *
-     * @hide
-     */
-    public static final int STATUS_ALREADY_REQUESTED_USER = 5;
-
-    /**
-     * When another user switch request for the same target user is in process.
-     *
-     * @hide
-     */
-    public static final int STATUS_TARGET_USER_ALREADY_BEING_SWITCHED_TO = 6;
-
-    /**
-     * When another user switch request for a new different target user is received. Previous
-     * request is abandoned.
-     *
-     * @hide
-     */
-    public static final int STATUS_TARGET_USER_ABANDONED_DUE_TO_A_NEW_REQUEST = 7;
+    public static final int STATUS_HAL_INTERNAL_FAILURE = CommonResults.STATUS_HAL_INTERNAL_FAILURE;
 
     /**
      * When given parameters or environment states are invalid for switching user. HAL or Android
      * user switch is not requested.
-     *
-     * @hide
      */
-    public static final int STATUS_INVALID_REQUEST = 8;
+    public static final int STATUS_INVALID_REQUEST = CommonResults.STATUS_INVALID_REQUEST;
+
+    /**
+     * When target user is same as current user.
+     */
+    public static final int STATUS_ALREADY_REQUESTED_USER =
+            CommonResults.LAST_COMMON_STATUS + 1;
+
+    /**
+     * When another user switch request for the same target user is in process.
+     */
+    public static final int STATUS_TARGET_USER_ALREADY_BEING_SWITCHED_TO =
+            CommonResults.LAST_COMMON_STATUS + 2;
+
+    /**
+     * When another user switch request for a new different target user is received. Previous
+     * request is abandoned.
+     */
+    public static final int STATUS_TARGET_USER_ABANDONED_DUE_TO_A_NEW_REQUEST =
+            CommonResults.LAST_COMMON_STATUS + 3;
 
     /**
      * Gets the user switch result status.
@@ -108,7 +95,7 @@
      *         {@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.
@@ -123,6 +110,8 @@
         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.
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 65610ae..c89a337 100644
--- a/car-test-lib/src/android/car/test/mocks/AbstractExtendedMockitoTestCase.java
+++ b/car-test-lib/src/android/car/test/mocks/AbstractExtendedMockitoTestCase.java
@@ -86,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()
@@ -159,7 +159,7 @@
             }
         }
         ArrayList<SyncRunnable> syncs = new ArrayList<>(handlerThreads.size());
-        Log.i(TAG, "will wait for HandlerThreads:" + 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(() -> { });
@@ -203,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()}.
      *
@@ -522,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));
+            }
+        }
     }
 
     /**
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/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 f9e15cc..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) {
diff --git a/service/src/com/android/car/CarShellCommand.java b/service/src/com/android/car/CarShellCommand.java
index 86253dc..0a8c420 100644
--- a/service/src/com/android/car/CarShellCommand.java
+++ b/service/src/com/android/car/CarShellCommand.java
@@ -36,6 +36,7 @@
 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;
@@ -46,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;
@@ -124,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 =
@@ -154,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,
@@ -373,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.");
@@ -608,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;
@@ -953,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",
@@ -978,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();
@@ -1107,6 +1123,52 @@
         }
     }
 
+    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,
             @NonNull AndroidFuture<T> future, int timeoutMs) {
         T result = null;
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/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index d17afed..bd45abb 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -35,6 +35,7 @@
 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;
@@ -53,6 +54,8 @@
 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;
@@ -301,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,
@@ -312,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.
      */
@@ -793,11 +806,9 @@
         }
 
         UsersInfo usersInfo = UserHalHelper.newUsersInfo(mUserManager);
-        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) -> {
+        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);
@@ -870,6 +881,62 @@
         });
     }
 
+    @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.");
@@ -1166,17 +1233,26 @@
             mRequestIdForUserSwitchInProcess = requestId;
         }
     }
-
     private void postSwitchHalResponse(int requestId, @UserIdInt int targetUserId) {
-        UserInfo targetUser = mUserManager.getUserInfo(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);
         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;
     }
 
     /**
@@ -1536,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);
         }
@@ -1555,13 +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);
         UsersInfo usersInfo = UserHalHelper.newUsersInfo(mUserManager);
-        mHal.legacyUserSwitch(halTargetUser, usersInfo);
+        SwitchUserRequest request = createUserSwitchRequest(toUserId, usersInfo);
+        mHal.legacyUserSwitch(request);
     }
 
     /**
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 b0edd13..3a5ea1b 100644
--- a/tests/CarSecurityPermissionTest/src/com/android/car/user/CarUserManagerPermissionTest.java
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/user/CarUserManagerPermissionTest.java
@@ -82,6 +82,12 @@
     }
 
     @Test
+    public void testRemoveUserPermission() throws Exception {
+        Exception e = expectThrows(SecurityException.class, () -> mCarUserManager.removeUser(100));
+        assertThat(e.getMessage()).contains(MANAGE_USERS);
+    }
+
+    @Test
     public void testAddListenerPermission() {
         UserLifecycleListener listener = (e) -> { };
 
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/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_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 b7816c2..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));
@@ -984,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 f146dcb..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);
@@ -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 540e1d1..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
@@ -45,6 +45,7 @@
 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;
@@ -188,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)-> { };
 
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 4e82740..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
@@ -18,10 +18,10 @@
 
 import static android.car.test.mocks.AndroidMockitoHelper.getResult;
 import static android.car.test.mocks.AndroidMockitoHelper.mockUmCreateUser;
-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.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;
 
@@ -66,6 +66,7 @@
 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;
@@ -83,6 +84,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.SwitchUserResponse;
 import android.hardware.automotive.vehicle.V2_0.SwitchUserStatus;
 import android.hardware.automotive.vehicle.V2_0.UserFlags;
@@ -317,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
@@ -673,6 +661,73 @@
     }
 
     @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);
 
@@ -984,12 +1039,7 @@
 
         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
@@ -1004,7 +1054,7 @@
 
         sendUserSwitchingEvent(mAdminUser.id, mGuestUser.id);
 
-        verify(mUserHal, never()).legacyUserSwitch(any(), any());
+        verify(mUserHal, never()).legacyUserSwitch(any());
     }
 
     @Test
@@ -1651,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,
@@ -1673,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,
@@ -1772,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,
@@ -1877,38 +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());
     }
 
-    @NonNull
-    private static android.hardware.automotive.vehicle.V2_0.UserInfo isHalUser(
-            @UserIdInt int userId) {
-        return argThat(new UserInfoMatcher(userId));
+    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 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 {
@@ -2034,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/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 e57b452..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 = "\\|\\|";
 
     /**
@@ -515,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