Merge changes from topic "per_user_volume_settings" into rvc-dev

* changes:
  Update Volume Embedded Kitchen Sink Fragment.
  Fixed car audio volume group settings for user.
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 5d6f494..60a174d 100644
--- a/car-internal-lib/src/com/android/internal/car/EventLogTags.logtags
+++ b/car-internal-lib/src/com/android/internal/car/EventLogTags.logtags
@@ -74,6 +74,7 @@
 150108 car_user_svc_post_switch_user_req (target_user_id|1),(current_user_id|1)
 150109 car_user_svc_get_user_auth_req (uid|1),(user_id|1),(number_types|1)
 150110 car_user_svc_get_user_auth_resp (number_values|1)
+150111 car_user_svc_switch_user_ui_req (user_id|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)
@@ -83,6 +84,8 @@
 150145 car_user_hal_get_user_auth_req (int32values|4)
 150146 car_user_hal_get_user_auth_resp (int32values|4),(error_message|3)
 150147 car_user_hal_legacy_switch_user_req (request_id|1),(target_user_id|1),(current_user_id|1)
+150148 car_user_hal_set_user_auth_req (int32values|4)
+150149 car_user_hal_set_user_auth_resp (int32values|4),(error_message|3)
 
 150171 car_user_mgr_add_listener (uid|1)
 150172 car_user_mgr_remove_listener (uid|1)
diff --git a/car-lib/native/include/CarPowerManager.h b/car-lib/native/include/CarPowerManager.h
index 57bbd50..b02c886 100644
--- a/car-lib/native/include/CarPowerManager.h
+++ b/car-lib/native/include/CarPowerManager.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef CAR_POWER_MANAGER
-#define CAR_POWER_MANAGER
+#ifndef CAR_LIB_NATIVE_INCLUDE_CARPOWERMANAGER_H_
+#define CAR_LIB_NATIVE_INCLUDE_CARPOWERMANAGER_H_
 
 #include <binder/Status.h>
 #include <utils/RefBase.h>
@@ -38,6 +38,7 @@
     //  NOTE:  The entries in this enum must match the ones in CarPowerStateListener located in
     //      packages/services/Car/car-lib/src/android/car/hardware/power/CarPowerManager.java
     enum class State {
+        kInvalid = 0,
         kWaitForVhal = 1,
         kSuspendEnter = 2,
         kSuspendExit = 3,
@@ -46,8 +47,7 @@
         kShutdownPrepare = 7,
         kShutdownCancelled = 8,
 
-
-        kFirst = kWaitForVhal,
+        kFirst = kInvalid,
         kLast = kShutdownCancelled,
     };
 
@@ -74,7 +74,7 @@
 private:
     class CarPowerStateListener final : public BnCarPowerStateListener {
     public:
-        explicit CarPowerStateListener(CarPowerManager* parent) : mParent(parent) {};
+        explicit CarPowerStateListener(CarPowerManager* parent) : mParent(parent) {}
 
         Status onStateChanged(int state) override {
             sp<CarPowerManager> parent = mParent;
@@ -102,9 +102,9 @@
     sp<CarPowerStateListener> mListenerToService;
 };
 
-} // namespace power
-} // namespace hardware
-} // namespace car
-} // namespace android
+}  // namespace power
+}  // namespace hardware
+}  // namespace car
+}  // namespace android
 
-#endif // CAR_POWER_MANAGER
+#endif  // CAR_LIB_NATIVE_INCLUDE_CARPOWERMANAGER_H_
diff --git a/car-lib/src/android/car/Car.java b/car-lib/src/android/car/Car.java
index 3160e9e..099d56a 100644
--- a/car-lib/src/android/car/Car.java
+++ b/car-lib/src/android/car/Car.java
@@ -988,6 +988,10 @@
 
     /**
      * A factory method that creates Car instance for all Car API access.
+     *
+     * <p>Instance created with this should be disconnected from car service by calling
+     * {@link #disconnect()} before the passed {code Context} is released.
+     *
      * @param context App's Context. This should not be null. If you are passing
      *                {@link ContextWrapper}, make sure that its base Context is non-null as well.
      *                Otherwise it will throw {@link java.lang.NullPointerException}.
@@ -1020,6 +1024,9 @@
      * A factory method that creates Car instance for all Car API access using main thread {@code
      * Looper}.
      *
+     * <p>Instance created with this should be disconnected from car service by calling
+     * {@link #disconnect()} before the passed {code Context} is released.
+     *
      * @see #createCar(Context, ServiceConnection, Handler)
      *
      * @deprecated use {@link #createCar(Context, Handler)} instead.
@@ -1032,6 +1039,9 @@
     /**
      * Creates new {@link Car} object which connected synchronously to Car Service and ready to use.
      *
+     * <p>Instance created with this should be disconnected from car service by calling
+     * {@link #disconnect()} before the passed {code Context} is released.
+     *
      * @param context application's context
      *
      * @return Car object if operation succeeded, otherwise null.
@@ -1044,6 +1054,9 @@
     /**
      * Creates new {@link Car} object which connected synchronously to Car Service and ready to use.
      *
+     * <p>Instance created with this should be disconnected from car service by calling
+     * {@link #disconnect()} before the passed {code Context} is released.
+     *
      * @param context App's Context. This should not be null. If you are passing
      *                {@link ContextWrapper}, make sure that its base Context is non-null as well.
      *                Otherwise it will throw {@link java.lang.NullPointerException}.
@@ -1111,6 +1124,9 @@
     /**
      * Creates new {@link Car} object with {@link CarServiceLifecycleListener}.
      *
+     * <p>Instance created with this should be disconnected from car service by calling
+     * {@link #disconnect()} before the passed {code Context} is released.
+     *
      * <p> If car service is ready inside this call and if the caller is running in the main thread,
      * {@link CarServiceLifecycleListener#onLifecycleChanged(Car, boolean)} will be called
      * with ready set to be true. Otherwise,
diff --git a/car-lib/src/android/car/ICarUserService.aidl b/car-lib/src/android/car/ICarUserService.aidl
index 9e49790..2eccdbb 100644
--- a/car-lib/src/android/car/ICarUserService.aidl
+++ b/car-lib/src/android/car/ICarUserService.aidl
@@ -37,4 +37,5 @@
     oneway void resetLifecycleListenerForUid();
     oneway void getInitialUserInfo(int requestType, int timeoutMs, in IResultReceiver receiver);
     GetUserIdentificationAssociationResponse getUserIdentificationAssociation(in int[] types);
+    oneway void setUserSwitchUiCallback(in IResultReceiver callback);
 }
diff --git a/car-lib/src/android/car/hardware/power/CarPowerManager.java b/car-lib/src/android/car/hardware/power/CarPowerManager.java
index 7c2a323..d9ff33d 100644
--- a/car-lib/src/android/car/hardware/power/CarPowerManager.java
+++ b/car-lib/src/android/car/hardware/power/CarPowerManager.java
@@ -16,6 +16,7 @@
 
 package android.car.hardware.power;
 
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.car.Car;
 import android.car.CarManagerBase;
@@ -56,10 +57,15 @@
     public interface CarPowerStateListener {
         /**
          * onStateChanged() states.  These definitions must match the ones located in the native
-         * CarPowerManager:  packages/services/Car/car-lib/native/CarPowerManager/CarPowerManager.h
+         * CarPowerManager:  packages/services/Car/car-lib/native/include/CarPowerManager.h
          */
 
         /**
+         * The current power state is unavailable, unknown, or invalid
+         * @hide
+         */
+        int INVALID = 0;
+        /**
          * Android is up, but vendor is controlling the audio / display
          * @hide
          */
@@ -162,6 +168,20 @@
     }
 
     /**
+     * Returns the current power state
+     * @return One of the values defined in {@link CarPowerStateListener}
+     * @hide
+     */
+    @RequiresPermission(Car.PERMISSION_CAR_POWER)
+    public int getPowerState() {
+        try {
+            return mService.getPowerState();
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, CarPowerStateListener.INVALID);
+        }
+    }
+
+    /**
      * Sets a listener to receive power state changes. Only one listener may be set at a
      * time for an instance of CarPowerManager.
      * The listener is assumed to completely handle the 'onStateChanged' before returning.
diff --git a/car-lib/src/android/car/hardware/power/ICarPower.aidl b/car-lib/src/android/car/hardware/power/ICarPower.aidl
index 581e736..505b075 100644
--- a/car-lib/src/android/car/hardware/power/ICarPower.aidl
+++ b/car-lib/src/android/car/hardware/power/ICarPower.aidl
@@ -31,4 +31,6 @@
     void scheduleNextWakeupTime(int seconds) = 4;
 
     void registerListenerWithCompletion(in ICarPowerStateListener listener) = 5;
+
+    int getPowerState() = 6;
 }
diff --git a/car-lib/src/android/car/navigation/CarNavigationInstrumentCluster.java b/car-lib/src/android/car/navigation/CarNavigationInstrumentCluster.java
index b41c7de..444f82a 100644
--- a/car-lib/src/android/car/navigation/CarNavigationInstrumentCluster.java
+++ b/car-lib/src/android/car/navigation/CarNavigationInstrumentCluster.java
@@ -57,25 +57,31 @@
 
     private final Bundle mExtra;
 
-    public static final Parcelable.Creator<CarNavigationInstrumentCluster> CREATOR
-            = new Parcelable.Creator<CarNavigationInstrumentCluster>() {
-        public CarNavigationInstrumentCluster createFromParcel(Parcel in) {
-            return new CarNavigationInstrumentCluster(in);
-        }
+    public static final Parcelable.Creator<CarNavigationInstrumentCluster> CREATOR =
+                new Parcelable.Creator<CarNavigationInstrumentCluster>() {
+            public CarNavigationInstrumentCluster createFromParcel(Parcel in) {
+                return new CarNavigationInstrumentCluster(in);
+            }
 
-        public CarNavigationInstrumentCluster[] newArray(int size) {
-            return new CarNavigationInstrumentCluster[size];
-        }
-    };
+            public CarNavigationInstrumentCluster[] newArray(int size) {
+                return new CarNavigationInstrumentCluster[size];
+            }
+        };
 
+    /**
+     * Creates a new {@link CarNavigationInstrumentCluster}.
+     */
     public static CarNavigationInstrumentCluster createCluster(int minIntervalMillis) {
         return new CarNavigationInstrumentCluster(minIntervalMillis, CLUSTER_TYPE_IMAGE_CODES_ONLY,
                 0, 0, 0);
     }
 
-    public static CarNavigationInstrumentCluster createCustomImageCluster(int minIntervalMs,
+    /**
+     * Creates a new {@link CarNavigationInstrumentCluster}.
+     */
+    public static CarNavigationInstrumentCluster createCustomImageCluster(int minIntervalMillis,
             int imageWidth, int imageHeight, int imageColorDepthBits) {
-        return new CarNavigationInstrumentCluster(minIntervalMs,
+        return new CarNavigationInstrumentCluster(minIntervalMillis,
                 CLUSTER_TYPE_CUSTOM_IMAGES_SUPPORTED,
                 imageWidth, imageHeight, imageColorDepthBits);
     }
@@ -108,7 +114,9 @@
      * Contains extra information about instrument cluster.
      * @hide
      */
-    public Bundle getExtra() { return mExtra; }
+    public Bundle getExtra() {
+        return mExtra;
+    }
 
     /**
      * If instrument cluster is image, number of bits of colour depth it supports (8, 16, or 32).
@@ -127,10 +135,9 @@
 
     /**
      * Whether cluster support custom image or not.
-     * @return
      */
     public boolean supportsCustomImages() {
-      return mType == CLUSTER_TYPE_CUSTOM_IMAGES_SUPPORTED;
+        return mType == CLUSTER_TYPE_CUSTOM_IMAGES_SUPPORTED;
     }
 
     private CarNavigationInstrumentCluster(
@@ -174,12 +181,12 @@
     /** Converts to string for debug purpose */
     @Override
     public String toString() {
-        return CarNavigationInstrumentCluster.class.getSimpleName() + "{ " +
-                "minIntervalMillis: " + mMinIntervalMillis + ", " +
-                "type: " + mType + ", " +
-                "imageWidth: " + mImageWidth + ", " +
-                "imageHeight: " + mImageHeight + ", " +
-                "imageColourDepthBits: " + mImageColorDepthBits +
-                "extra: " + mExtra + " }";
+        return CarNavigationInstrumentCluster.class.getSimpleName() + "{ "
+                + "minIntervalMillis: " + mMinIntervalMillis + ", "
+                + "type: " + mType + ", "
+                + "imageWidth: " + mImageWidth + ", "
+                + "imageHeight: " + mImageHeight + ", "
+                + "imageColourDepthBits: " + mImageColorDepthBits
+                + "extra: " + mExtra + " }";
     }
 }
diff --git a/car-lib/src/android/car/storagemonitoring/IoStats.java b/car-lib/src/android/car/storagemonitoring/IoStats.java
index d620169..5154196 100644
--- a/car-lib/src/android/car/storagemonitoring/IoStats.java
+++ b/car-lib/src/android/car/storagemonitoring/IoStats.java
@@ -70,7 +70,7 @@
         mUptimeTimestamp = in.getInt("uptime");
         JSONArray statsArray = in.getJSONArray("stats");
         mStats = new ArrayList<>();
-        for(int i = 0; i < statsArray.length(); ++i) {
+        for (int i = 0; i < statsArray.length(); ++i) {
             mStats.add(new IoStatsEntry(statsArray.getJSONObject(i)));
         }
     }
@@ -113,6 +113,11 @@
         return Objects.hash(mStats, mUptimeTimestamp);
     }
 
+    /**
+     * Returns user's stats ({@link IoStatsEntry}).
+     *
+     * @param uid Android's user id
+     */
     public IoStatsEntry getUserIdStats(int uid) {
         for (IoStatsEntry stats : getStats()) {
             if (stats.uid == uid) {
@@ -123,6 +128,10 @@
         return null;
     }
 
+    /**
+     * Returns the following foreground total metrics: bytes written and read, bytes read from and
+     * written to storage, and number of sync calls.
+     */
     public IoStatsEntry.Metrics getForegroundTotals() {
         long bytesRead = 0;
         long bytesWritten = 0;
@@ -145,6 +154,10 @@
                 fsyncCalls);
     }
 
+    /**
+     * Returns the following background total metrics: bytes written and read, bytes read from and
+     * written to storage, and number of sync calls.
+     */
     public IoStatsEntry.Metrics getBackgroundTotals() {
         long bytesRead = 0;
         long bytesWritten = 0;
@@ -167,6 +180,10 @@
             fsyncCalls);
     }
 
+    /**
+     * Returns the sum of all foreground and background metrics (bytes written, bytes read from
+     * storage, bytes written to storage and number of sync calls).
+     */
     public IoStatsEntry.Metrics getTotals() {
         IoStatsEntry.Metrics foreground = getForegroundTotals();
         IoStatsEntry.Metrics background = getBackgroundTotals();
@@ -181,9 +198,9 @@
     @Override
     public boolean equals(Object other) {
         if (other instanceof IoStats) {
-            IoStats delta = (IoStats)other;
-            return delta.getTimestamp() == getTimestamp() &&
-                delta.getStats().equals(getStats());
+            IoStats delta = (IoStats) other;
+            return delta.getTimestamp() == getTimestamp()
+                && delta.getStats().equals(getStats());
         }
         return false;
     }
diff --git a/car-lib/src/android/car/user/CarUserManager.java b/car-lib/src/android/car/user/CarUserManager.java
index 8d7bfa1..784ee48 100644
--- a/car-lib/src/android/car/user/CarUserManager.java
+++ b/car-lib/src/android/car/user/CarUserManager.java
@@ -338,6 +338,40 @@
     }
 
     /**
+     * Sets a callback to be notified before user switch. It should only be used by Car System UI.
+     *
+     * @hide
+     */
+    public void setUserSwitchUiCallback(@NonNull UserSwitchUiCallback callback) {
+        Preconditions.checkArgument(callback != null, "Null callback");
+        UserSwitchUiCallbackReceiver userSwitchUiCallbackReceiver =
+                new UserSwitchUiCallbackReceiver(callback);
+        try {
+            mService.setUserSwitchUiCallback(userSwitchUiCallbackReceiver);
+        } catch (RemoteException e) {
+            handleRemoteExceptionFromCarService(e);
+        }
+    }
+
+    /**
+     * {@code IResultReceiver} used to receive user switch UI Callback.
+     */
+    // TODO(b/154958003): use mReceiver instead as now there are two binder objects
+    private final class UserSwitchUiCallbackReceiver extends IResultReceiver.Stub {
+
+        private final UserSwitchUiCallback mUserSwitchUiCallback;
+
+        UserSwitchUiCallbackReceiver(UserSwitchUiCallback callback) {
+            mUserSwitchUiCallback = callback;
+        }
+
+        @Override
+        public void send(int userId, Bundle unused) throws RemoteException {
+            mUserSwitchUiCallback.showUserSwitchDialog(userId);
+        }
+    }
+
+    /**
      * {@code IResultReceiver} used to receive lifecycle events and dispatch to the proper listener.
      */
     private class LifecycleResultReceiver extends IResultReceiver.Stub {
@@ -560,4 +594,20 @@
          */
         void onEvent(@NonNull UserLifecycleEvent event);
     }
+
+    /**
+     * Callback for notifying user switch before switch started.
+     *
+     * <p> It should only be user by Car System UI. The purpose of this callback is notify the
+     * Car System UI to display the user switch UI.
+     *
+     * @hide
+     */
+    public interface UserSwitchUiCallback {
+
+        /**
+         * Called to notify that user switch dialog should be shown now.
+         */
+        void showUserSwitchDialog(@UserIdInt int userId);
+    }
 }
diff --git a/car-test-lib/src/android/car/test/mocks/CarArgumentMatchers.java b/car-test-lib/src/android/car/test/mocks/CarArgumentMatchers.java
index 62328c0..1d1c399 100644
--- a/car-test-lib/src/android/car/test/mocks/CarArgumentMatchers.java
+++ b/car-test-lib/src/android/car/test/mocks/CarArgumentMatchers.java
@@ -158,6 +158,11 @@
             }
             return true;
         }
+
+        @Override
+        public String toString() {
+            return "prop: " + mProp + " values: " + Arrays.toString(mValues);
+        }
     }
 
     private CarArgumentMatchers() {
diff --git a/car-usb-handler/AndroidManifest.xml b/car-usb-handler/AndroidManifest.xml
index caa93bd..bcd9975 100644
--- a/car-usb-handler/AndroidManifest.xml
+++ b/car-usb-handler/AndroidManifest.xml
@@ -14,22 +14,36 @@
      limitations under the License.
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-        package="android.car.usb.handler">
-    <uses-sdk android:minSdkVersion="25" />
-    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
-    <uses-permission android:name="android.permission.MANAGE_USB" />
-    <uses-permission android:name="android.permission.MANAGE_USERS" />
-    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
-    <application android:label="@string/app_name" android:icon="@drawable/ic_launcher"
-                 android:directBootAware="true">
+          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+          package="android.car.usb.handler">
+    <uses-sdk
+        android:minSdkVersion="25"
+        android:targetSdkVersion="29"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
+    <uses-permission android:name="android.permission.MANAGE_USB"/>
+    <uses-permission android:name="android.permission.MANAGE_USERS"/>
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+
+    <!-- "queries" to specify what car-usb-handler will query for due to  Android 11's
+         package visibility update. -->
+    <queries>
+        <intent>
+            <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"/>
+        </intent>
+    </queries>
+
+    <application
+        android:label="@string/app_name"
+        android:icon="@drawable/ic_launcher"
+        android:directBootAware="true">
         <activity android:name=".UsbHostManagementActivity"
                   android:theme="@android:style/Theme.DeviceDefault.Dialog"
                   android:launchMode="standard">
             <meta-data
                 android:name="distractionOptimized"
-                android:value="true" />
+                android:value="true"/>
         </activity>
         <service android:name=".BootUsbService"
                  android:exported="false"
@@ -38,7 +52,7 @@
         <receiver android:name=".BootUsbScanner"
                   android:directBootAware="true">
             <intent-filter>
-                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
+                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED"/>
             </intent-filter>
         </receiver>
     </application>
diff --git a/car_product/build/car.mk b/car_product/build/car.mk
index bc61fca..64d6bec 100644
--- a/car_product/build/car.mk
+++ b/car_product/build/car.mk
@@ -16,8 +16,8 @@
 
 # Common make file for all car builds
 
-PRODUCT_PUBLIC_SEPOLICY_DIRS += packages/services/Car/car_product/sepolicy/public
-PRODUCT_PRIVATE_SEPOLICY_DIRS += packages/services/Car/car_product/sepolicy/private
+BOARD_PLAT_PUBLIC_SEPOLICY_DIR += packages/services/Car/car_product/sepolicy/public
+BOARD_PLAT_PRIVATE_SEPOLICY_DIR += packages/services/Car/car_product/sepolicy/private
 
 PRODUCT_PACKAGES += \
     Bluetooth \
diff --git a/car_product/occupant_awareness/OccupantAwareness.mk b/car_product/occupant_awareness/OccupantAwareness.mk
index 3d27ed8..43d904f 100644
--- a/car_product/occupant_awareness/OccupantAwareness.mk
+++ b/car_product/occupant_awareness/OccupantAwareness.mk
@@ -1,5 +1,7 @@
 # Occupant Awareness SELinux policy variable definitions
 LOCAL_PATH:= $(call my-dir)
 
-PRODUCT_PUBLIC_SEPOLICY_DIRS += $(LOCAL_PATH)/sepolicy/public
-PRODUCT_PRIVATE_SEPOLICY_DIRS += $(LOCAL_PATH)/sepolicy/private
+BOARD_PLAT_PUBLIC_SEPOLICY_DIR += $(LOCAL_PATH)/sepolicy/public
+BOARD_PLAT_PRIVATE_SEPOLICY_DIR += $(LOCAL_PATH)/sepolicy/private
+
+BOARD_SEPOLICY_DIRS += $(LOCAL_PATH)/sepolicy
diff --git a/computepipe/products/computepipe.mk b/computepipe/products/computepipe.mk
index 44c93f9..d5fe7f7 100644
--- a/computepipe/products/computepipe.mk
+++ b/computepipe/products/computepipe.mk
@@ -29,7 +29,7 @@
 
 
 # Selinux public policies for computepipe services
-PRODUCT_PUBLIC_SEPOLICY_DIRS += packages/services/Car/computepipe/sepolicy/public
+BOARD_PLAT_PUBLIC_SEPOLICY_DIR += packages/services/Car/computepipe/sepolicy/public
 
 # Selinux private policies for computepipe services
-PRODUCT_PRIVATE_SEPOLICY_DIRS += packages/services/Car/computepipe/sepolicy/private
+BOARD_PLAT_PRIVATE_SEPOLICY_DIR += packages/services/Car/computepipe/sepolicy/private
diff --git a/service/src/com/android/car/CarPowerManagementService.java b/service/src/com/android/car/CarPowerManagementService.java
index b2331ca..d130c43 100644
--- a/service/src/com/android/car/CarPowerManagementService.java
+++ b/service/src/com/android/car/CarPowerManagementService.java
@@ -878,6 +878,7 @@
 
     @Override
     public void scheduleNextWakeupTime(int seconds) {
+        ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_POWER);
         if (seconds < 0) {
             Log.w(CarLog.TAG_POWER, "Next wake up time is negative. Ignoring!");
             return;
@@ -899,6 +900,15 @@
         }
     }
 
+    @Override
+    public int getPowerState() {
+        ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_POWER);
+        synchronized (mLock) {
+            return (mCurrentState == null) ? CarPowerStateListener.INVALID
+                    : mCurrentState.mCarPowerStateListenerState;
+        }
+    }
+
     private void finishedImpl(IBinder binder) {
         boolean allAreComplete = false;
         synchronized (mLock) {
diff --git a/service/src/com/android/car/CarShellCommand.java b/service/src/com/android/car/CarShellCommand.java
index 633a4bf..871e18c 100644
--- a/service/src/com/android/car/CarShellCommand.java
+++ b/service/src/com/android/car/CarShellCommand.java
@@ -15,6 +15,9 @@
  */
 package com.android.car;
 
+import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationSetValue.ASSOCIATE_CURRENT_USER;
+import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationSetValue.DISASSOCIATE_ALL_USERS;
+import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationSetValue.DISASSOCIATE_CURRENT_USER;
 import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationType.CUSTOM_1;
 import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationType.CUSTOM_2;
 import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationType.CUSTOM_3;
@@ -41,10 +44,13 @@
 import android.hardware.automotive.vehicle.V2_0.SwitchUserMessageType;
 import android.hardware.automotive.vehicle.V2_0.SwitchUserStatus;
 import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociation;
+import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationSetValue;
 import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationType;
 import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationValue;
 import android.hardware.automotive.vehicle.V2_0.UserIdentificationGetRequest;
 import android.hardware.automotive.vehicle.V2_0.UserIdentificationResponse;
+import android.hardware.automotive.vehicle.V2_0.UserIdentificationSetAssociation;
+import android.hardware.automotive.vehicle.V2_0.UserIdentificationSetRequest;
 import android.hardware.automotive.vehicle.V2_0.UserInfo;
 import android.hardware.automotive.vehicle.V2_0.UsersInfo;
 import android.hardware.automotive.vehicle.V2_0.VehicleArea;
@@ -117,6 +123,8 @@
             "reset-user-in-occupant-zone";
     private static final String COMMAND_GET_USER_AUTH_ASSOCIATION =
             "get-user-auth-association";
+    private static final String COMMAND_SET_USER_AUTH_ASSOCIATION =
+            "set-user-auth-association";
 
     // Whitelist of commands allowed in user build. All these command should be protected with
     // a permission. K: command, V: required permission.
@@ -139,6 +147,8 @@
                 android.Manifest.permission.MANAGE_USERS);
         USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_GET_USER_AUTH_ASSOCIATION,
                 android.Manifest.permission.MANAGE_USERS);
+        USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_SET_USER_AUTH_ASSOCIATION,
+                android.Manifest.permission.MANAGE_USERS);
     }
 
     private static final String DEVICE_POWER_PERMISSION = "android.permission.DEVICE_POWER";
@@ -155,10 +165,16 @@
     private static final int RESULT_OK = 0;
     private static final int RESULT_ERROR = -1; // Arbitrary value, any non-0 is fine
 
+    private static final int DEFAULT_HAL_TIMEOUT_MS = 1_000;
+
+    private static final int INVALID_USER_AUTH_TYPE_OR_VALUE = -1;
+
     private static final SparseArray<String> VALID_USER_AUTH_TYPES;
-    private static final int INVALID_USER_AUTH_TYPE = -1;
     private static final String VALID_USER_AUTH_TYPES_HELP;
 
+    private static final SparseArray<String> VALID_USER_AUTH_SET_VALUES;
+    private static final String VALID_USER_AUTH_SET_VALUES_HELP;
+
     static {
         VALID_USER_AUTH_TYPES = new SparseArray<String>(5);
         VALID_USER_AUTH_TYPES.put(KEY_FOB, UserIdentificationAssociationType.toString(KEY_FOB));
@@ -166,16 +182,29 @@
         VALID_USER_AUTH_TYPES.put(CUSTOM_2, UserIdentificationAssociationType.toString(CUSTOM_2));
         VALID_USER_AUTH_TYPES.put(CUSTOM_3, UserIdentificationAssociationType.toString(CUSTOM_3));
         VALID_USER_AUTH_TYPES.put(CUSTOM_4, UserIdentificationAssociationType.toString(CUSTOM_4));
+        VALID_USER_AUTH_TYPES_HELP = getHelpString("types", VALID_USER_AUTH_TYPES);
 
-        StringBuilder help = new StringBuilder("Valid types are: ");
-        int size = VALID_USER_AUTH_TYPES.size();
+        VALID_USER_AUTH_SET_VALUES = new SparseArray<String>(3);
+        VALID_USER_AUTH_SET_VALUES.put(ASSOCIATE_CURRENT_USER,
+                UserIdentificationAssociationSetValue.toString(ASSOCIATE_CURRENT_USER));
+        VALID_USER_AUTH_SET_VALUES.put(DISASSOCIATE_CURRENT_USER,
+                UserIdentificationAssociationSetValue.toString(DISASSOCIATE_CURRENT_USER));
+        VALID_USER_AUTH_SET_VALUES.put(DISASSOCIATE_ALL_USERS,
+                UserIdentificationAssociationSetValue.toString(DISASSOCIATE_ALL_USERS));
+        VALID_USER_AUTH_SET_VALUES_HELP = getHelpString("values", VALID_USER_AUTH_SET_VALUES);
+    }
+
+    @NonNull
+    private static String getHelpString(@NonNull String name, @NonNull SparseArray<String> values) {
+        StringBuilder help = new StringBuilder("Valid ").append(name).append(" are: ");
+        int size = values.size();
         for (int i = 0; i < size; i++) {
-            help.append(VALID_USER_AUTH_TYPES.valueAt(i));
+            help.append(values.valueAt(i));
             if (i != size - 1) {
                 help.append(", ");
             }
         }
-        VALID_USER_AUTH_TYPES_HELP = help.append('.').toString();
+        return help.append('.').toString();
     }
 
     private final Context mContext;
@@ -337,13 +366,14 @@
         pw.printf("\t%s [occupantZoneId]\n", COMMAND_RESET_USER_ID_IN_OCCUPANT_ZONE);
         pw.println("\t  Unmaps the user assigned to occupant zone id.");
 
-        pw.printf("\t%s [--hal-only] [--user USER_ID] (TYPE1) [...TYPE_N]\n",
-                COMMAND_GET_USER_AUTH_ASSOCIATION);
-        pw.println("\t  Checks whether the given user authentication types are associated with ");
-        pw.println("\t  the given user (or current user when not specified).");
-        pw.println("\t  By defalut it calls CarUserManager, but using --hal-only will call just "
+        pw.printf("\t%s [--hal-only] [--user USER_ID] TYPE1 VALUE1 [..TYPE_N VALUE_N]\n",
+                COMMAND_SET_USER_AUTH_ASSOCIATION);
+        pw.println("\t  Sets the N user authentication types with the N values for the given user");
+        pw.println("\t  (or current user when not specified).");
+        pw.println("\t  By defautt it calls CarUserManager, but using --hal-only will call just "
                 + "UserHalService.");
         pw.printf("\t  %s\n", VALID_USER_AUTH_TYPES_HELP);
+        pw.printf("\t  %s\n", VALID_USER_AUTH_SET_VALUES_HELP);
     }
 
     private static int showInvalidArguments(PrintWriter pw) {
@@ -559,6 +589,9 @@
             case COMMAND_GET_USER_AUTH_ASSOCIATION:
                 getUserAuthAssociation(args, writer);
                 break;
+            case COMMAND_SET_USER_AUTH_ASSOCIATION:
+                setUserAuthAssociation(args, writer);
+                break;
             default:
                 writer.println("Unknown command: \"" + cmd + "\"");
                 showHelp(writer);
@@ -782,7 +815,7 @@
         String typeArg = args[1];
         int requestType = UserHalHelper.parseInitialUserInfoRequestType(typeArg);
 
-        int timeout = 1_000;
+        int timeout = DEFAULT_HAL_TIMEOUT_MS;
         for (int i = 2; i < args.length; i++) {
             String arg = args[i];
             switch (arg) {
@@ -841,7 +874,7 @@
         }
 
         int targetUserId = Integer.parseInt(args[1]);
-        int timeout = 1_000;
+        int timeout = DEFAULT_HAL_TIMEOUT_MS;
         boolean halOnly = false;
 
         for (int i = 2; i < args.length; i++) {
@@ -948,8 +981,8 @@
                     halOnly = true;
                     break;
                 default:
-                    int type = parseAuthType(arg);
-                    if (type == INVALID_USER_AUTH_TYPE) {
+                    int type = parseAuthArg(VALID_USER_AUTH_TYPES, arg);
+                    if (type == INVALID_USER_AUTH_TYPE_OR_VALUE) {
                         writer.printf("Invalid type at index %d (from %s): %s. %s\n", i + 1,
                                 Arrays.toString(args), arg, VALID_USER_AUTH_TYPES_HELP);
                         return;
@@ -1024,13 +1057,100 @@
         }
     }
 
-    private static int parseAuthType(@NonNull String type) {
-        for (int i = 0; i < VALID_USER_AUTH_TYPES.size(); i++) {
-            if (VALID_USER_AUTH_TYPES.valueAt(i).equals(type)) {
-                return VALID_USER_AUTH_TYPES.keyAt(i);
+    private void setUserAuthAssociation(String[] args, PrintWriter writer) {
+        if (args.length < 4) {
+            writer.println("invalid usage, must pass at least 4 arguments");
+            return;
+        }
+
+        boolean halOnly = false;
+        int timeout = DEFAULT_HAL_TIMEOUT_MS;
+        int userId = UserHandle.USER_CURRENT;
+
+        UserIdentificationSetRequest request = new UserIdentificationSetRequest();
+        for (int i = 1; i < args.length; i++) {
+            String arg = args[i];
+            switch (arg) {
+                case "--user":
+                    try {
+                        userId = Integer.parseInt(args[++i]);
+                    } catch (Exception e) {
+                        writer.printf("Invalid user id at index %d (from %s): %s\n", i + 1,
+                                Arrays.toString(args), arg);
+                    }
+                    break;
+                case "--hal-only":
+                    halOnly = true;
+                    break;
+                case "--timeout":
+                    timeout = Integer.parseInt(args[++i]);
+                    break;
+                default:
+                    UserIdentificationSetAssociation association =
+                            new UserIdentificationSetAssociation();
+                    association.type = parseAuthArg(VALID_USER_AUTH_TYPES, arg);
+                    if (association.type == INVALID_USER_AUTH_TYPE_OR_VALUE) {
+                        writer.printf("Invalid type at index %d (from %s): %s. %s\n", i + 1,
+                                Arrays.toString(args), arg, VALID_USER_AUTH_TYPES_HELP);
+                        return;
+                    }
+                    association.value = parseAuthArg(VALID_USER_AUTH_SET_VALUES, args[++i]);
+                    if (association.value == INVALID_USER_AUTH_TYPE_OR_VALUE) {
+                        writer.printf("Invalid value at index %d (from %s): %s. %s\n", i + 1,
+                                Arrays.toString(args), arg, VALID_USER_AUTH_SET_VALUES_HELP);
+                        return;
+                    }
+                    request.associations.add(association);
+            }
+
+        }
+        if (userId == UserHandle.USER_CURRENT) {
+            userId = ActivityManager.getCurrentUser();
+        }
+        int requestSize = request.associations.size();
+        if (halOnly) {
+            request.numberAssociations = requestSize;
+            // TODO(b/150413515): use UserHalHelper to set user flags
+            request.userInfo.userId = userId;
+
+            Log.d(TAG, "setUserAuthAssociation(): user=" + userId + ", halOnly=" + halOnly
+                    + ", request=" + request);
+            CountDownLatch latch = new CountDownLatch(1);
+            mHal.getUserHal().setUserAssociation(timeout, request, (status, response) -> {
+                Log.d(TAG, "setUserAuthAssociation(): response=" + response);
+                try {
+                    if (response == null) {
+                        writer.println("null response");
+                        return;
+                    }
+
+                    if (!TextUtils.isEmpty(response.errorMessage)) {
+                        writer.printf("Error message: %s\n", response.errorMessage);
+                    }
+                    int numberAssociations = response.associations.size();
+                    writer.printf("%d associations:\n", numberAssociations);
+                    for (int i = 0; i < numberAssociations; i++) {
+                        UserIdentificationAssociation association = response.associations.get(i);
+                        writer.printf("  %s\n", association);
+                    }
+                } finally {
+                    latch.countDown();
+                }
+            });
+            waitForHal(writer, latch, timeout);
+            return;
+        }
+        // TODO(b/150409351): implement it...
+        throw new UnsupportedOperationException("must set --hal-only");
+    }
+
+    private static int parseAuthArg(@NonNull SparseArray<String> types, @NonNull String type) {
+        for (int i = 0; i < types.size(); i++) {
+            if (types.valueAt(i).equals(type)) {
+                return types.keyAt(i);
             }
         }
-        return INVALID_USER_AUTH_TYPE;
+        return INVALID_USER_AUTH_TYPE_OR_VALUE;
     }
 
     private void forceDayNightMode(String arg, PrintWriter writer) {
diff --git a/service/src/com/android/car/audio/CarAudioService.java b/service/src/com/android/car/audio/CarAudioService.java
index c14ac2f..c880709 100644
--- a/service/src/com/android/car/audio/CarAudioService.java
+++ b/service/src/com/android/car/audio/CarAudioService.java
@@ -51,6 +51,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.UserHandle;
+import android.telephony.Annotation.CallState;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
@@ -139,13 +140,21 @@
             new AudioPolicy.AudioPolicyVolumeCallback() {
         @Override
         public void onVolumeAdjustment(int adjustment) {
-            final int usage = getSuggestedAudioUsage();
-            Log.v(CarLog.TAG_AUDIO,
-                    "onVolumeAdjustment: " + AudioManager.adjustToString(adjustment)
-                            + " suggested usage: " + AudioAttributes.usageToString(usage));
-            // TODO: Pass zone id into this callback.
-            final int zoneId = CarAudioManager.PRIMARY_AUDIO_ZONE;
-            final int groupId = getVolumeGroupIdForUsage(zoneId, usage);
+            int zoneId = CarAudioManager.PRIMARY_AUDIO_ZONE;
+            @AudioContext int suggestedContext = getSuggestedAudioContext();
+
+            int groupId;
+            synchronized (mImplLock) {
+                groupId = getVolumeGroupIdForAudioContextLocked(zoneId, suggestedContext);
+            }
+
+            if (Log.isLoggable(CarLog.TAG_AUDIO, Log.VERBOSE)) {
+                Log.v(CarLog.TAG_AUDIO, "onVolumeAdjustment: "
+                        + AudioManager.adjustToString(adjustment) + " suggested audio context: "
+                        + CarAudioContext.toString(suggestedContext) + " suggested volume group: "
+                        + groupId);
+            }
+
             final int currentVolume = getGroupVolume(zoneId, groupId);
             final int flags = AudioManager.FLAG_FROM_KEY | AudioManager.FLAG_SHOW_UI;
             switch (adjustment) {
@@ -791,17 +800,22 @@
             Preconditions.checkArgumentInRange(zoneId, 0, mCarAudioZones.length - 1,
                     "zoneId out of range: " + zoneId);
 
-            CarVolumeGroup[] groups = mCarAudioZones[zoneId].getVolumeGroups();
-            for (int i = 0; i < groups.length; i++) {
-                int[] contexts = groups[i].getContexts();
-                for (int context : contexts) {
-                    if (CarAudioContext.getContextForUsage(usage) == context) {
-                        return i;
-                    }
+            @AudioContext int audioContext = CarAudioContext.getContextForUsage(usage);
+            return getVolumeGroupIdForAudioContextLocked(zoneId, audioContext);
+        }
+    }
+
+    private int getVolumeGroupIdForAudioContextLocked(int zoneId, @AudioContext int audioContext) {
+        CarVolumeGroup[] groups = mCarAudioZones[zoneId].getVolumeGroups();
+        for (int i = 0; i < groups.length; i++) {
+            int[] groupAudioContexts = groups[i].getContexts();
+            for (int groupAudioContext : groupAudioContexts) {
+                if (audioContext == groupAudioContext) {
+                    return i;
                 }
             }
-            return INVALID_VOLUME_GROUP_ID;
         }
+        return INVALID_VOLUME_GROUP_ID;
     }
 
     @Override
@@ -1112,29 +1126,11 @@
         return group.getAudioDevicePortForContext(CarAudioContext.getContextForUsage(usage));
     }
 
-    /**
-     * @return The suggested {@link AudioAttributes} usage to which the volume key events apply
-     */
-    private @AudioAttributes.AttributeUsage int getSuggestedAudioUsage() {
-        int callState = mTelephonyManager.getCallState();
-        if (callState == TelephonyManager.CALL_STATE_RINGING) {
-            return AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
-        } else if (callState == TelephonyManager.CALL_STATE_OFFHOOK) {
-            return AudioAttributes.USAGE_VOICE_COMMUNICATION;
-        } else {
-            List<AudioPlaybackConfiguration> playbacks = mAudioManager
-                    .getActivePlaybackConfigurations()
-                    .stream()
-                    .filter(AudioPlaybackConfiguration::isActive)
-                    .collect(Collectors.toList());
-            if (!playbacks.isEmpty()) {
-                // Get audio usage from active playbacks if there is any, last one if multiple
-                return playbacks.get(playbacks.size() - 1).getAudioAttributes().getSystemUsage();
-            } else {
-                // TODO(b/72695246): Otherwise, get audio usage from foreground activity/window
-                return DEFAULT_AUDIO_USAGE;
-            }
-        }
+    private @AudioContext int getSuggestedAudioContext() {
+        @CallState int callState = mTelephonyManager.getCallState();
+        List<AudioPlaybackConfiguration> configurations =
+                mAudioManager.getActivePlaybackConfigurations();
+        return CarVolume.getSuggestedAudioContext(configurations, callState);
     }
 
     /**
@@ -1143,7 +1139,7 @@
      * @return volume group id mapped from stream type
      */
     private int getVolumeGroupIdForStreamType(int streamType) {
-        int groupId = -1;
+        int groupId = INVALID_VOLUME_GROUP_ID;
         for (int i = 0; i < CarAudioDynamicRouting.STREAM_TYPES.length; i++) {
             if (streamType == CarAudioDynamicRouting.STREAM_TYPES[i]) {
                 groupId = i;
diff --git a/service/src/com/android/car/audio/CarVolume.java b/service/src/com/android/car/audio/CarVolume.java
new file mode 100644
index 0000000..39f2c59
--- /dev/null
+++ b/service/src/com/android/car/audio/CarVolume.java
@@ -0,0 +1,105 @@
+/*
+ * 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.audio;
+
+import static com.android.car.audio.CarAudioService.DEFAULT_AUDIO_CONTEXT;
+
+import android.media.AudioAttributes;
+import android.media.AudioAttributes.AttributeUsage;
+import android.media.AudioPlaybackConfiguration;
+import android.telephony.Annotation.CallState;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.util.SparseIntArray;
+
+import com.android.car.audio.CarAudioContext.AudioContext;
+
+import java.util.List;
+
+/**
+ * CarVolume is responsible for determining which audio contexts to prioritize when adjusting volume
+ */
+final class CarVolume {
+    private static final String TAG = CarVolume.class.getSimpleName();
+    private static final int CONTEXT_NOT_PRIORITIZED = -1;
+
+    private static final int[] AUDIO_CONTEXT_VOLUME_PRIORITY = {
+            CarAudioContext.NAVIGATION,
+            CarAudioContext.CALL,
+            CarAudioContext.MUSIC,
+            CarAudioContext.ANNOUNCEMENT,
+            CarAudioContext.VOICE_COMMAND,
+            CarAudioContext.CALL_RING,
+            CarAudioContext.SYSTEM_SOUND,
+            CarAudioContext.SAFETY,
+            CarAudioContext.ALARM,
+            CarAudioContext.NOTIFICATION,
+            CarAudioContext.VEHICLE_STATUS,
+            CarAudioContext.EMERGENCY,
+            // CarAudioContext.INVALID is intentionally not prioritized as it is not routed by
+            // CarAudioService and is not expected to be used.
+    };
+
+    private static final SparseIntArray VOLUME_PRIORITY_BY_AUDIO_CONTEXT = new SparseIntArray();
+
+    static {
+        for (int priority = 0; priority < AUDIO_CONTEXT_VOLUME_PRIORITY.length; priority++) {
+            VOLUME_PRIORITY_BY_AUDIO_CONTEXT.append(AUDIO_CONTEXT_VOLUME_PRIORITY[priority],
+                    priority);
+        }
+    }
+
+    /**
+     * Suggests a {@link AudioContext} that should be adjusted based on the current
+     * {@link AudioPlaybackConfiguration}s and {@link CallState}.
+     */
+    static @AudioContext int getSuggestedAudioContext(
+            List<AudioPlaybackConfiguration> configurations, @CallState int callState) {
+        int currentContext = DEFAULT_AUDIO_CONTEXT;
+        int currentPriority = AUDIO_CONTEXT_VOLUME_PRIORITY.length;
+
+        if (callState == TelephonyManager.CALL_STATE_RINGING) {
+            currentContext = CarAudioContext.CALL_RING;
+            currentPriority = VOLUME_PRIORITY_BY_AUDIO_CONTEXT.get(CarAudioContext.CALL_RING);
+        } else if (callState == TelephonyManager.CALL_STATE_OFFHOOK) {
+            currentContext = CarAudioContext.CALL;
+            currentPriority = VOLUME_PRIORITY_BY_AUDIO_CONTEXT.get(CarAudioContext.CALL);
+        }
+
+        for (AudioPlaybackConfiguration configuration : configurations) {
+            if (!configuration.isActive()) {
+                continue;
+            }
+
+            @AttributeUsage int usage = configuration.getAudioAttributes().getSystemUsage();
+            @AudioContext int context = CarAudioContext.getContextForUsage(usage);
+            int priority = VOLUME_PRIORITY_BY_AUDIO_CONTEXT.get(context, CONTEXT_NOT_PRIORITIZED);
+            if (priority == CONTEXT_NOT_PRIORITIZED) {
+                Log.w(TAG, "Usage " + AudioAttributes.usageToString(usage) + " mapped to context "
+                        + CarAudioContext.toString(context) + " which is not prioritized");
+                continue;
+            }
+
+            if (priority < currentPriority) {
+                currentContext = context;
+                currentPriority = priority;
+            }
+        }
+
+        return currentContext;
+    }
+}
diff --git a/service/src/com/android/car/hal/PropertyHalService.java b/service/src/com/android/car/hal/PropertyHalService.java
index 192b7ec..15038cd 100644
--- a/service/src/com/android/car/hal/PropertyHalService.java
+++ b/service/src/com/android/car/hal/PropertyHalService.java
@@ -32,6 +32,7 @@
 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyType;
+import android.os.Build;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -374,6 +375,12 @@
                     Log.e(TAG, "Property is not supported: 0x" + toHexString(v.prop));
                     continue;
                 }
+                // Check payload if it is a userdebug build.
+                if (Build.IS_DEBUGGABLE && !mPropIds.checkPayload(v)) {
+                    Log.e(TAG, "Drop event for property: " + v + " because it is failed "
+                            + "in payload checking.");
+                    continue;
+                }
                 int mgrPropId = halToManagerPropId(v.prop);
                 CarPropertyValue<?> propVal;
                 if (isMixedTypeProperty(v.prop)) {
diff --git a/service/src/com/android/car/hal/PropertyHalServiceIds.java b/service/src/com/android/car/hal/PropertyHalServiceIds.java
index b9f3134..3772843 100644
--- a/service/src/com/android/car/hal/PropertyHalServiceIds.java
+++ b/service/src/com/android/car/hal/PropertyHalServiceIds.java
@@ -21,15 +21,34 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.car.Car;
+import android.car.VehicleHvacFanDirection;
 import android.car.hardware.property.VehicleVendorPermission;
+import android.hardware.automotive.vehicle.V2_0.EvConnectorType;
+import android.hardware.automotive.vehicle.V2_0.FuelType;
+import android.hardware.automotive.vehicle.V2_0.PortLocationType;
+import android.hardware.automotive.vehicle.V2_0.VehicleAreaSeat;
+import android.hardware.automotive.vehicle.V2_0.VehicleGear;
+import android.hardware.automotive.vehicle.V2_0.VehicleIgnitionState;
+import android.hardware.automotive.vehicle.V2_0.VehicleLightState;
+import android.hardware.automotive.vehicle.V2_0.VehicleLightSwitch;
+import android.hardware.automotive.vehicle.V2_0.VehicleOilLevel;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyGroup;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropertyType;
+import android.hardware.automotive.vehicle.V2_0.VehicleSeatOccupancyState;
+import android.hardware.automotive.vehicle.V2_0.VehicleTurnSignal;
+import android.hardware.automotive.vehicle.V2_0.VehicleUnit;
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
 
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Helper class to define which property IDs are used by PropertyHalService.  This class binds the
@@ -45,7 +64,37 @@
      */
     private final SparseArray<Pair<String, String>> mProps;
     private final HashSet<Integer> mPropForUnits;
+    // Key: propId, Value: possible value for the property
+    private final HashMap<Integer, Set<Integer>> mPropToValidValue;
+    private final HashMap<Integer, Integer> mPropToValidBitFlag;
     private static final String TAG = "PropertyHalServiceIds";
+    // Enums are used as return value in Vehicle HAL.
+    private static final Set<Integer> FUEL_TYPE =
+            new HashSet<>(getIntegersFromDataEnums(FuelType.class));
+    private static final Set<Integer> EV_CONNECTOR_TYPE =
+            new HashSet<>(getIntegersFromDataEnums(EvConnectorType.class));
+    private static final Set<Integer> PORT_LOCATION =
+            new HashSet<>(getIntegersFromDataEnums(PortLocationType.class));
+    private static final Set<Integer> VEHICLE_SEAT =
+            new HashSet<>(getIntegersFromDataEnums(VehicleAreaSeat.class));
+    private static final Set<Integer> OIL_LEVEL =
+            new HashSet<>(getIntegersFromDataEnums(VehicleOilLevel.class));
+    private static final Set<Integer> VEHICLE_GEAR =
+            new HashSet<>(getIntegersFromDataEnums(VehicleGear.class));
+    private static final Set<Integer> TURN_SIGNAL =
+            new HashSet<>(getIntegersFromDataEnums(VehicleTurnSignal.class));
+    private static final Set<Integer> IGNITION_STATE =
+            new HashSet<>(getIntegersFromDataEnums(VehicleIgnitionState.class));
+    private static final Set<Integer> VEHICLE_UNITS =
+            new HashSet<>(getIntegersFromDataEnums(VehicleUnit.class));
+    private static final Set<Integer> SEAT_OCCUPANCY_STATE =
+            new HashSet<>(getIntegersFromDataEnums(VehicleSeatOccupancyState.class));
+    private static final Set<Integer> VEHICLE_LIGHT_STATE =
+            new HashSet<>(getIntegersFromDataEnums(VehicleLightState.class));
+    private static final Set<Integer> VEHICLE_LIGHT_SWITCH =
+            new HashSet<>(getIntegersFromDataEnums(VehicleLightSwitch.class));
+    private static final int HVAC_FAN_DIRECTION_COMBINATIONS =
+            generateAllCombination(VehicleHvacFanDirection.class);
 
     // default vendor permission
     private static final int PERMISSION_CAR_VENDOR_DEFAULT = 0x00000000;
@@ -103,6 +152,8 @@
     public PropertyHalServiceIds() {
         mProps = new SparseArray<>();
         mPropForUnits = new HashSet<>();
+        mPropToValidValue = new HashMap<>();
+        mPropToValidBitFlag = new HashMap<>();
         // Add propertyId and read/write permissions
         // Cabin Properties
         mProps.put(VehicleProperty.DOOR_POS, new Pair<>(
@@ -485,6 +536,42 @@
         mProps.put(VehicleProperty.SUPPORT_CUSTOMIZE_VENDOR_PERMISSION, new Pair<>(
                 Car.PERMISSION_READ_CAR_VENDOR_PERMISSION_INFO,
                 null));
+
+        // mPropToValidValue should contain all properties which has @data_enum in types.hal
+        mPropToValidValue.put(VehicleProperty.INFO_FUEL_TYPE, FUEL_TYPE);
+        mPropToValidValue.put(VehicleProperty.INFO_EV_CONNECTOR_TYPE, EV_CONNECTOR_TYPE);
+        mPropToValidValue.put(VehicleProperty.INFO_FUEL_DOOR_LOCATION, PORT_LOCATION);
+        mPropToValidValue.put(VehicleProperty.INFO_DRIVER_SEAT, VEHICLE_SEAT);
+        mPropToValidValue.put(VehicleProperty.INFO_MULTI_EV_PORT_LOCATIONS, PORT_LOCATION);
+        mPropToValidValue.put(VehicleProperty.ENGINE_OIL_LEVEL, OIL_LEVEL);
+        mPropToValidValue.put(VehicleProperty.GEAR_SELECTION, VEHICLE_GEAR);
+        mPropToValidValue.put(VehicleProperty.CURRENT_GEAR, VEHICLE_GEAR);
+        mPropToValidValue.put(VehicleProperty.TURN_SIGNAL_STATE, TURN_SIGNAL);
+        mPropToValidValue.put(VehicleProperty.IGNITION_STATE, IGNITION_STATE);
+        mPropToValidValue.put(VehicleProperty.HVAC_TEMPERATURE_DISPLAY_UNITS, VEHICLE_UNITS);
+        mPropToValidValue.put(VehicleProperty.DISTANCE_DISPLAY_UNITS, VEHICLE_UNITS);
+        mPropToValidValue.put(VehicleProperty.FUEL_VOLUME_DISPLAY_UNITS, VEHICLE_UNITS);
+        mPropToValidValue.put(VehicleProperty.TIRE_PRESSURE_DISPLAY_UNITS, VEHICLE_UNITS);
+        mPropToValidValue.put(VehicleProperty.EV_BATTERY_DISPLAY_UNITS, VEHICLE_UNITS);
+        mPropToValidValue.put(VehicleProperty.SEAT_OCCUPANCY, SEAT_OCCUPANCY_STATE);
+        mPropToValidValue.put(VehicleProperty.HIGH_BEAM_LIGHTS_STATE, VEHICLE_LIGHT_STATE);
+        mPropToValidValue.put(VehicleProperty.HEADLIGHTS_STATE, VEHICLE_LIGHT_STATE);
+        mPropToValidValue.put(VehicleProperty.FOG_LIGHTS_STATE, VEHICLE_LIGHT_STATE);
+        mPropToValidValue.put(VehicleProperty.HAZARD_LIGHTS_STATE, VEHICLE_LIGHT_STATE);
+        mPropToValidValue.put(VehicleProperty.CABIN_LIGHTS_STATE, VEHICLE_LIGHT_STATE);
+        mPropToValidValue.put(VehicleProperty.READING_LIGHTS_STATE, VEHICLE_LIGHT_STATE);
+        mPropToValidValue.put(VehicleProperty.HEADLIGHTS_SWITCH, VEHICLE_LIGHT_SWITCH);
+        mPropToValidValue.put(VehicleProperty.HIGH_BEAM_LIGHTS_SWITCH, VEHICLE_LIGHT_SWITCH);
+        mPropToValidValue.put(VehicleProperty.FOG_LIGHTS_SWITCH, VEHICLE_LIGHT_SWITCH);
+        mPropToValidValue.put(VehicleProperty.HAZARD_LIGHTS_SWITCH, VEHICLE_LIGHT_SWITCH);
+        mPropToValidValue.put(VehicleProperty.CABIN_LIGHTS_SWITCH, VEHICLE_LIGHT_SWITCH);
+        mPropToValidValue.put(VehicleProperty.READING_LIGHTS_SWITCH, VEHICLE_LIGHT_SWITCH);
+
+        // mPropToValidBitFlag contains all properties which return values are combinations of bits
+        mPropToValidBitFlag.put(VehicleProperty.HVAC_FAN_DIRECTION_AVAILABLE,
+                HVAC_FAN_DIRECTION_COMBINATIONS);
+        mPropToValidBitFlag.put(VehicleProperty.HVAC_FAN_DIRECTION,
+                HVAC_FAN_DIRECTION_COMBINATIONS);
     }
 
     /**
@@ -666,4 +753,108 @@
         }
     }
 
+    /**
+     * Checks property value's format for all properties. Checks property value range if property
+     * has @data_enum flag in types.hal.
+     * @return true if property value's payload is valid.
+     */
+    public boolean checkPayload(VehiclePropValue propValue) {
+        // Mixed property uses config array to indicate the data format. Checked it when convert it
+        // to CarPropertyValue.
+        if ((propValue.prop & VehiclePropertyType.MASK) == VehiclePropertyType.MIXED) {
+            return true;
+        }
+        if (!checkFormatForAllProperties(propValue)) {
+            Log.e(TAG, "Property value" + propValue + "has an invalid data format");
+            return false;
+        }
+        if (mPropToValidValue.containsKey(propValue.prop)) {
+            return checkDataEnum(propValue);
+        }
+        if (mPropToValidBitFlag.containsKey(propValue.prop)) {
+            return checkValidBitFlag(propValue);
+        }
+        return true;
+    }
+
+    private boolean checkValidBitFlag(VehiclePropValue propValue) {
+        int flagCombination = mPropToValidBitFlag.get(propValue.prop);
+        for (int value : propValue.value.int32Values) {
+            if ((value & flagCombination) != value) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean checkFormatForAllProperties(VehiclePropValue propValue) {
+        int propId = propValue.prop;
+        VehiclePropValue.RawValue rawValue = propValue.value;
+        //Records sum size of int32values, floatValue, int64Values, bytes, String
+        int sizeOfAllValue = rawValue.int32Values.size() + rawValue.floatValues.size()
+                + rawValue.int64Values.size() + rawValue.bytes.size()
+                + rawValue.stringValue.length();
+        if (sizeOfAllValue == 0) {
+            Log.e(TAG, "Property value is empty: " + propValue);
+            return false;
+        }
+        switch (propId & VehiclePropertyType.MASK) {
+            case VehiclePropertyType.BOOLEAN:
+            case VehiclePropertyType.INT32:
+                return sizeOfAllValue == 1 && rawValue.int32Values.size() == 1;
+            case VehiclePropertyType.FLOAT:
+                return sizeOfAllValue == 1 && rawValue.floatValues.size() == 1;
+            case VehiclePropertyType.INT64:
+                return sizeOfAllValue == 1 && rawValue.int64Values.size() == 1;
+            case VehiclePropertyType.FLOAT_VEC:
+                return sizeOfAllValue == rawValue.floatValues.size();
+            case VehiclePropertyType.INT64_VEC:
+                return sizeOfAllValue == rawValue.int64Values.size();
+            case VehiclePropertyType.INT32_VEC:
+                return sizeOfAllValue == rawValue.int32Values.size();
+            case VehiclePropertyType.BYTES:
+                return sizeOfAllValue == rawValue.bytes.size();
+            case VehiclePropertyType.STRING:
+                return sizeOfAllValue == rawValue.stringValue.length();
+            default:
+                throw new IllegalArgumentException("Unexpected property type for propId: "
+                        + Integer.toHexString(propId));
+        }
+    }
+    private boolean checkDataEnum(VehiclePropValue propValue) {
+        int propId = propValue.prop;
+        VehiclePropValue.RawValue rawValue = propValue.value;
+        Set<Integer> validValue = mPropToValidValue.get(propId);
+        for (int value : rawValue.int32Values) {
+            if (!validValue.contains(value)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static List<Integer> getIntegersFromDataEnums(Class clazz) {
+        Field[] fields = clazz.getDeclaredFields();
+        List<Integer> integerList = new ArrayList<>(5);
+        for (Field f : fields) {
+            if (f.getType() == int.class) {
+                try {
+                    integerList.add(f.getInt(clazz));
+                } catch (Exception e) {
+                    Log.w(TAG, "Failed to get value");
+                }
+            }
+        }
+        return integerList;
+    }
+
+    // Generate all combinations at once
+    private static int generateAllCombination(Class clazz) {
+        List<Integer> allBits = getIntegersFromDataEnums(clazz);
+        int combination = allBits.get(0);
+        for (int i = 1; i < allBits.size(); i++) {
+            combination |= allBits.get(i);
+        }
+        return combination;
+    }
 }
diff --git a/service/src/com/android/car/hal/UserHalService.java b/service/src/com/android/car/hal/UserHalService.java
index ea5a2e8..f995047 100644
--- a/service/src/com/android/car/hal/UserHalService.java
+++ b/service/src/com/android/car/hal/UserHalService.java
@@ -37,6 +37,7 @@
 import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationType;
 import android.hardware.automotive.vehicle.V2_0.UserIdentificationGetRequest;
 import android.hardware.automotive.vehicle.V2_0.UserIdentificationResponse;
+import android.hardware.automotive.vehicle.V2_0.UserIdentificationSetRequest;
 import android.hardware.automotive.vehicle.V2_0.UserInfo;
 import android.hardware.automotive.vehicle.V2_0.UsersInfo;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
@@ -49,16 +50,17 @@
 import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.Log;
-import android.util.Pair;
-import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.car.EventLogTags;
+import com.android.internal.util.FunctionalUtils;
 import com.android.internal.util.Preconditions;
 
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
@@ -105,7 +107,7 @@
      * Map of callbacks by request id.
      */
     @GuardedBy("mLock")
-    private SparseArray<Pair<Class<?>, HalCallback<?>>> mPendingCallbacks = new SparseArray<>();
+    private final SparseArray<PendingRequest<?, ?>> mPendingRequests = new SparseArray<>();
 
     public UserHalService(VehicleHal hal) {
         mHal = hal;
@@ -150,10 +152,11 @@
                             UserHalService::handleOnSwitchUserResponse, this, value));
                     break;
                 case USER_IDENTIFICATION_ASSOCIATION:
-                    Slog.w(TAG, "HAL updated event for USER_IDENTIFICATION_ASSOCIATION: " + value);
+                    mHandler.sendMessage(obtainMessage(
+                            UserHalService::handleOnUserIdentificationAssociation, this, value));
                     break;
                 default:
-                    Slog.w(TAG, "received unsupported event from HAL: " + value);
+                    Log.w(TAG, "received unsupported event from HAL: " + value);
             }
         }
     }
@@ -224,24 +227,29 @@
         synchronized (mLock) {
             checkSupportedLocked();
             if (hasPendingRequestLocked(InitialUserInfoResponse.class, callback)) return;
-            requestId = mNextRequestId++;
+            requestId = getNextRequestId();
             EventLog.writeEvent(EventLogTags.CAR_USER_HAL_INITIAL_USER_INFO_REQ, requestId,
                     requestType, timeoutMs);
-            propRequest = UserHalHelper.createPropRequest(requestId, requestType,
-                    INITIAL_USER_INFO);
+            propRequest = UserHalHelper.createPropRequest(INITIAL_USER_INFO, requestId,
+                    requestType);
             UserHalHelper.addUsersInfo(propRequest, usersInfo);
             addPendingRequestLocked(requestId, InitialUserInfoResponse.class, callback);
         }
 
+        sendHalRequest(requestId, timeoutMs, propRequest, callback);
+    }
+
+    private void sendHalRequest(int requestId, int timeoutMs, @NonNull VehiclePropValue request,
+            @NonNull HalCallback<?> callback) {
         mHandler.sendMessageDelayed(obtainMessage(
                 UserHalService::handleCheckIfRequestTimedOut, this, requestId).setWhat(requestId),
                 timeoutMs);
         try {
-            if (DBG) Log.d(TAG, "Calling hal.set(): " + propRequest);
-            mHal.set(propRequest);
+            if (DBG) Log.d(TAG, "Calling hal.set(): " + request);
+            mHal.set(request);
         } catch (ServiceSpecificException e) {
             handleRemovePendingRequest(requestId);
-            Log.w(TAG, "Failed to set INITIAL_USER_INFO", e);
+            Log.w(TAG, "Failed to set " + request, e);
             callback.onResponse(HalCallback.STATUS_HAL_SET_TIMEOUT, null);
         }
     }
@@ -271,7 +279,7 @@
         synchronized (mLock) {
             checkSupportedLocked();
             if (hasPendingRequestLocked(SwitchUserResponse.class, callback)) return;
-            requestId = mNextRequestId++;
+            requestId = getNextRequestId();
             EventLog.writeEvent(EventLogTags.CAR_USER_HAL_SWITCH_USER_REQ, requestId,
                     targetInfo.userId, timeoutMs);
             propRequest = getPropRequestForSwitchUserLocked(requestId,
@@ -279,19 +287,7 @@
             addPendingRequestLocked(requestId, SwitchUserResponse.class, callback);
         }
 
-        mHandler.sendMessageDelayed(
-                obtainMessage(UserHalService::handleCheckIfRequestTimedOut, this, requestId)
-                        .setWhat(requestId),
-                timeoutMs);
-
-        try {
-            if (DBG) Log.d(TAG, "Calling hal.set(): " + propRequest);
-            mHal.set(propRequest);
-        } catch (ServiceSpecificException e) {
-            handleRemovePendingRequest(requestId);
-            Log.w(TAG, "Failed to set ANDROID SWITCH", e);
-            callback.onResponse(HalCallback.STATUS_HAL_SET_TIMEOUT, null);
-        }
+        sendHalRequest(requestId, timeoutMs, propRequest, callback);
     }
 
     /**
@@ -355,12 +351,11 @@
         }
     }
 
-    private VehiclePropValue getPropRequestForSwitchUserLocked(int requestId, int requestType,
-            @NonNull UserInfo targetInfo, @NonNull UsersInfo usersInfo) {
+    private static VehiclePropValue getPropRequestForSwitchUserLocked(int requestId,
+            int requestType, @NonNull UserInfo targetInfo, @NonNull UsersInfo usersInfo) {
         VehiclePropValue propRequest =
-                UserHalHelper.createPropRequest(requestId, requestType, SWITCH_USER);
-        propRequest.value.int32Values.add(targetInfo.userId);
-        propRequest.value.int32Values.add(targetInfo.flags);
+                UserHalHelper.createPropRequest(SWITCH_USER, requestId, requestType);
+        UserHalHelper.addUserInfo(propRequest, targetInfo);
         UserHalHelper.addUsersInfo(propRequest, usersInfo);
         return propRequest;
     }
@@ -388,7 +383,10 @@
             types.put(type, true);
         }
 
+        request.requestId = getNextRequestId();
+
         if (DBG) Log.d(TAG, "getUserAssociation(): req=" + request);
+
         VehiclePropValue requestAsPropValue = UserHalHelper.toVehiclePropValue(request);
         EventLog.writeEvent(EventLogTags.CAR_USER_HAL_GET_USER_AUTH_REQ,
                 requestAsPropValue.value.int32Values.toArray());
@@ -399,22 +397,12 @@
             return null;
         }
 
-        if (TextUtils.isEmpty(responseAsPropValue.value.stringValue)) {
-            EventLog.writeEvent(EventLogTags.CAR_USER_HAL_GET_USER_AUTH_RESP,
-                    responseAsPropValue.value.int32Values.toArray());
-        } else {
-            // Must manually append the error message to the array of values
-            int size = responseAsPropValue.value.int32Values.size();
-            Object[] list = new Object[size + 1];
-            responseAsPropValue.value.int32Values.toArray(list);
-            list[list.length - 1] = responseAsPropValue.value.stringValue;
-            EventLog.writeEvent(EventLogTags.CAR_USER_HAL_GET_USER_AUTH_RESP, list);
-        }
+        logEventWithErrorMessage(EventLogTags.CAR_USER_HAL_GET_USER_AUTH_RESP, responseAsPropValue);
         if (DBG) Log.d(TAG, "getUserAssociation(): responseAsPropValue=" + responseAsPropValue);
 
         UserIdentificationResponse response;
         try {
-            response = UserHalHelper.toUserIdentificationGetResponse(responseAsPropValue);
+            response = UserHalHelper.toUserIdentificationResponse(responseAsPropValue);
         } catch (IllegalArgumentException e) {
             Log.w(TAG, "invalid response from HAL for " + requestAsPropValue, e);
             return null;
@@ -422,6 +410,11 @@
         if (DBG) Log.d(TAG, "getUserAssociation(): response=" + response);
 
         // Validate the response according to the request
+        if (response.requestId != request.requestId) {
+            Log.w(TAG, "invalid request id (should be " + request.requestId + ") on HAL response: "
+                    + response);
+            return null;
+        }
         if (response.numberAssociation != request.numberAssociationTypes) {
             Log.w(TAG, "Wrong number of association types on HAL response (expected "
                     + request.numberAssociationTypes + ") for request " + requestAsPropValue
@@ -443,14 +436,138 @@
         return response;
     }
 
-    @GuardedBy("mLock")
-    private void addPendingRequestLocked(int requestId, @NonNull Class<?> responseClass,
-            @NonNull HalCallback<?> callback) {
-        if (DBG) {
-            Log.d(TAG, "adding pending callback (of type " + responseClass.getName()
-                    + ") for request " + requestId);
+    /**
+     * Calls HAL to set the value of the user identifications associated with the given user.
+     *
+     * @throws IllegalArgumentException if request is invalid (mismatch on number of associations,
+     *   duplicated association, invalid association type values, etc).
+     */
+    public void setUserAssociation(int timeoutMs, @NonNull UserIdentificationSetRequest request,
+            @NonNull HalCallback<UserIdentificationResponse> callback) {
+        if (DBG) Log.d(TAG, "setUserAssociation(" + request + ")");
+        Preconditions.checkArgumentPositive(timeoutMs, "timeout must be positive");
+        Objects.requireNonNull(request, "request cannot be null");
+        Objects.requireNonNull(callback, "callback cannot be null");
+
+        // Check that it doesn't have dupes
+        SparseBooleanArray types = new SparseBooleanArray(request.numberAssociations);
+        for (int i = 0; i < request.numberAssociations; i++) {
+            int type = request.associations.get(i).type;
+            Preconditions.checkArgument(!types.get(type), "type %s found more than once on %s",
+                    UserIdentificationAssociationType.toString(type), request);
+            types.put(type, true);
         }
-        mPendingCallbacks.put(requestId, new Pair<>(responseClass, callback));
+
+        VehiclePropValue propRequest;
+        int requestId;
+        synchronized (mLock) {
+            checkSupportedLocked();
+            if (hasPendingRequestLocked(UserIdentificationResponse.class, callback)) return;
+            requestId = request.requestId = getNextRequestId();
+            propRequest = UserHalHelper.toVehiclePropValue(request);
+            EventLog.writeEvent(EventLogTags.CAR_USER_HAL_SET_USER_AUTH_REQ,
+                    propRequest.value.int32Values.toArray());
+
+            addPendingRequestLocked(requestId, UserIdentificationSetRequest.class,
+                    UserIdentificationResponse.class, request, callback);
+        }
+        sendHalRequest(requestId, timeoutMs, propRequest, callback);
+    }
+
+    private void handleOnUserIdentificationAssociation(@NonNull VehiclePropValue value) {
+        logEventWithErrorMessage(EventLogTags.CAR_USER_HAL_SET_USER_AUTH_RESP, value);
+        if (DBG) Log.d(TAG, "handleOnUserIdentificationAssociation(): " + value);
+
+        int requestId = value.value.int32Values.get(0);
+        HalCallback<UserIdentificationResponse> callback = handleGetPendingCallback(requestId,
+                UserIdentificationResponse.class);
+        if (callback == null) {
+            Log.w(TAG, "no callback for requestId " + requestId + ": " + value);
+            return;
+        }
+        PendingRequest<?, ?> pendingRequest = handleRemovePendingRequest(requestId);
+        UserIdentificationResponse response;
+        try {
+            response = UserHalHelper.toUserIdentificationResponse(value);
+        } catch (RuntimeException e) {
+            Log.w(TAG, "error parsing UserIdentificationResponse (" + value + ")", e);
+            callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
+            return;
+        }
+
+        // Validate the response according to the request
+        UserIdentificationSetRequest request = PendingRequest.getRequest(pendingRequest,
+                UserIdentificationSetRequest.class, requestId);
+
+        if (request == null) {
+            // already logged on PendingRequest.getRequest
+            callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
+        }
+
+        if (response.numberAssociation != request.numberAssociations) {
+            Log.w(TAG, "Wrong number of association types on HAL response (expected "
+                    + request.numberAssociations + ") for request " + request
+                    + ": " + response);
+            callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
+            return;
+        }
+
+        for (int i = 0; i < request.numberAssociations; i++) {
+            int expectedType = request.associations.get(i).type;
+            int actualType = response.associations.get(i).type;
+            if (actualType != expectedType) {
+                Log.w(TAG, "Wrong type on index " + i + " of HAL response (" + response + ") for "
+                        + "request " + value + " : expected "
+                        + UserIdentificationAssociationType.toString(expectedType)
+                        + ", got " + UserIdentificationAssociationType.toString(actualType));
+                callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
+                return;
+            }
+        }
+
+
+        if (DBG) Log.d(TAG, "replying to request " + requestId + " with " + response);
+        callback.onResponse(HalCallback.STATUS_OK, response);
+    }
+
+    private static void logEventWithErrorMessage(int eventTag, @NonNull VehiclePropValue value) {
+        if (TextUtils.isEmpty(value.value.stringValue)) {
+            EventLog.writeEvent(eventTag, value.value.int32Values.toArray());
+        } else {
+            // Must manually append the error message to the array of values
+            int size = value.value.int32Values.size();
+            Object[] list = new Object[size + 1];
+            value.value.int32Values.toArray(list);
+            list[list.length - 1] = value.value.stringValue;
+            EventLog.writeEvent(eventTag, list);
+        }
+    }
+
+    @VisibleForTesting
+    int getNextRequestId() {
+        synchronized (mLock) {
+            return ++mNextRequestId;
+        }
+    }
+
+    @GuardedBy("mLock")
+    private <REQ, RESP> void addPendingRequestLocked(int requestId,
+            @NonNull Class<REQ> requestClass, @NonNull Class<RESP> responseClass,
+            @NonNull REQ request, @NonNull HalCallback<RESP> callback) {
+        PendingRequest<?, RESP> pendingRequest = new PendingRequest<>(responseClass, request,
+                callback);
+        if (DBG) {
+            Log.d(TAG, "adding pending request (" + pendingRequest + ") for requestId "
+                    + requestId);
+        }
+        mPendingRequests.put(requestId, pendingRequest);
+    }
+
+    @GuardedBy("mLock")
+    private <RESP> void addPendingRequestLocked(int requestId, @NonNull Class<RESP> responseClass,
+            @NonNull HalCallback<RESP> callback) {
+        addPendingRequestLocked(requestId, Void.class, responseClass, /* request= */ null,
+                callback);
     }
 
     /**
@@ -458,12 +575,12 @@
      * with {@link HalCallback#STATUS_CONCURRENT_OPERATION} when there is.
      */
     @GuardedBy("mLock")
-    private boolean hasPendingRequestLocked(@NonNull Class<?> requestClass,
+    private boolean hasPendingRequestLocked(@NonNull Class<?> responseClass,
             @NonNull HalCallback<?> callback) {
-        for (int i = 0; i < mPendingCallbacks.size(); i++) {
-            Pair<Class<?>, HalCallback<?>> pair = mPendingCallbacks.valueAt(i);
-            if (pair.first == requestClass) {
-                Log.w(TAG, "Already have pending request of type " + requestClass);
+        for (int i = 0; i < mPendingRequests.size(); i++) {
+            PendingRequest<?, ?> pendingRequest = mPendingRequests.valueAt(i);
+            if (pendingRequest.responseClass == responseClass) {
+                Log.w(TAG, "Already have pending request of type " + responseClass);
                 callback.onResponse(HalCallback.STATUS_CONCURRENT_OPERATION, null);
                 return true;
             }
@@ -474,27 +591,31 @@
     /**
      * Removes the pending request and its timeout callback.
      */
-    private void handleRemovePendingRequest(int requestId) {
+    @Nullable
+    private PendingRequest<?, ?> handleRemovePendingRequest(int requestId) {
         if (DBG) Log.d(TAG, "Removing pending request #" + requestId);
         mHandler.removeMessages(requestId);
+        PendingRequest<?, ?> pendingRequest;
         synchronized (mLock) {
-            mPendingCallbacks.remove(requestId);
+            pendingRequest = mPendingRequests.get(requestId);
+            mPendingRequests.remove(requestId);
         }
+        return pendingRequest;
     }
 
     private void handleCheckIfRequestTimedOut(int requestId) {
-        Pair<Class<?>, HalCallback<?>> pair = getPendingCallback(requestId);
-        if (pair == null) return;
+        PendingRequest<?, ?> pendingRequest = getPendingResponse(requestId);
+        if (pendingRequest == null) return;
 
         Log.w(TAG, "Request #" + requestId + " timed out");
         handleRemovePendingRequest(requestId);
-        pair.second.onResponse(HalCallback.STATUS_HAL_RESPONSE_TIMEOUT, null);
+        pendingRequest.callback.onResponse(HalCallback.STATUS_HAL_RESPONSE_TIMEOUT, null);
     }
 
     @Nullable
-    private Pair<Class<?>, HalCallback<?>> getPendingCallback(int requestId) {
+    private PendingRequest<?, ?> getPendingResponse(int requestId) {
         synchronized (mLock) {
-            return mPendingCallbacks.get(requestId);
+            return mPendingRequests.get(requestId);
         }
     }
 
@@ -582,17 +703,17 @@
     }
 
     private <T> HalCallback<T> handleGetPendingCallback(int requestId, Class<T> clazz) {
-        Pair<Class<?>, HalCallback<?>> pair = getPendingCallback(requestId);
-        if (pair == null) return null;
+        PendingRequest<?, ?> pendingRequest = getPendingResponse(requestId);
+        if (pendingRequest == null) return null;
 
-        if (pair.first != clazz) {
-            Slog.e(TAG, "Invalid callback class for request " + requestId + ": expected" + clazz
-                    + ", but got is " + pair.first);
+        if (pendingRequest.responseClass != clazz) {
+            Log.e(TAG, "Invalid callback class for request " + requestId + ": expected" + clazz
+                    + ", but got is " + pendingRequest.responseClass);
             // TODO(b/150413515): add unit test for this scenario once it supports other properties
             return null;
         }
         @SuppressWarnings("unchecked")
-        HalCallback<T> callback = (HalCallback<T>) pair.second;
+        HalCallback<T> callback = (HalCallback<T>) pendingRequest.callback;
         return callback;
     }
 
@@ -617,18 +738,81 @@
             }
             writer.printf("next request id: %d\n", mNextRequestId);
 
-            if (mPendingCallbacks.size() == 0) {
+            int numberPendingCallbacks = mPendingRequests.size();
+            if (numberPendingCallbacks == 0) {
                 writer.println("no pending callbacks");
             } else {
-                writer.printf("pending callbacks: %s\n", mPendingCallbacks);
+                writer.printf("%d pending callbacks: %s\n", numberPendingCallbacks);
+                for (int i = 0; i < numberPendingCallbacks; i++) {
+                    writer.print(indent);
+                    mPendingRequests.valueAt(i).dump(writer);
+                    writer.println();
+                }
             }
         }
     }
 
-    private void dumpSystemProperty(@NonNull PrintWriter writer, @NonNull String indent,
+    private static void dumpSystemProperty(@NonNull PrintWriter writer, @NonNull String indent,
             @NonNull String name, Optional<?> prop) {
         String value = prop.isPresent() ? prop.get().toString() : "<NOT SET>";
         writer.printf("%s%s=%s\n", indent, name, value);
     }
 
+    private static final class PendingRequest<REQ, RESP> {
+        @NonNull
+        public final Class<RESP> responseClass;
+
+        @Nullable
+        public final REQ request;
+
+        @NonNull
+        public final HalCallback<RESP> callback;
+
+        PendingRequest(@NonNull Class<RESP> responseClass, @Nullable REQ request,
+                @NonNull HalCallback<RESP> callback) {
+            this.responseClass = responseClass;
+            this.request = request;
+            this.callback = callback;
+        }
+
+        /**
+         * Gets the safely cast request for a given pending request.
+         */
+        @Nullable
+        private static <T> T getRequest(@Nullable PendingRequest<?, ?> pendingRequest,
+                @NonNull Class<T> clazz, int requestId) {
+            if (pendingRequest == null) {
+                Log.e(TAG, "No pending request for id " + requestId);
+                return null;
+
+            }
+            Object request = pendingRequest.request;
+            if (!clazz.isInstance(request)) {
+                Log.e(TAG, "Wrong pending request for id " + requestId + ": " + pendingRequest);
+                return null;
+            }
+            return clazz.cast(request);
+        }
+
+        public void dump(@NonNull PrintWriter pw) {
+            pw.printf("Class: %s Callback: %s", responseClass.getSimpleName(),
+                    FunctionalUtils.getLambdaName(callback));
+            if (request != null) {
+                pw.printf(" Request: %s", request);
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringWriter sw = new StringWriter();
+            PrintWriter pw = new PrintWriter(sw);
+            pw.print("[PendingRequest: ");
+            dump(pw);
+            pw.print("]");
+            pw.flush();
+            return sw.toString();
+        }
+
+    }
+
 }
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index e708b24..2ae56cb 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -36,6 +36,7 @@
 import android.car.user.GetUserIdentificationAssociationResponse;
 import android.car.user.UserSwitchResult;
 import android.car.userlib.CarUserManagerHelper;
+import android.car.userlib.CommonConstants.CarUserServiceConstants;
 import android.car.userlib.HalCallback;
 import android.car.userlib.UserHalHelper;
 import android.content.Context;
@@ -103,13 +104,14 @@
     private static final String TAG = TAG_USER;
 
     /** {@code int} extra used to represent a user id in a {@link IResultReceiver} response. */
-    public static final String BUNDLE_USER_ID = "user.id";
+    public static final String BUNDLE_USER_ID = CarUserServiceConstants.BUNDLE_USER_ID;
     /** {@code int} extra used to represent user flags in a {@link IResultReceiver} response. */
-    public static final String BUNDLE_USER_FLAGS = "user.flags";
+    public static final String BUNDLE_USER_FLAGS = CarUserServiceConstants.BUNDLE_USER_FLAGS;
     /** {@code String} extra used to represent a user name in a {@link IResultReceiver} response. */
-    public static final String BUNDLE_USER_NAME = "user.name";
+    public static final String BUNDLE_USER_NAME = CarUserServiceConstants.BUNDLE_USER_NAME;
     /** {@code int} extra used to represent the info action {@link IResultReceiver} response. */
-    public static final String BUNDLE_INITIAL_INFO_ACTION = "initial_info.action";
+    public static final String BUNDLE_INITIAL_INFO_ACTION =
+            CarUserServiceConstants.BUNDLE_INITIAL_INFO_ACTION;
 
     private final Context mContext;
     private final CarUserManagerHelper mCarUserManagerHelper;
@@ -178,6 +180,8 @@
 
     private UserMetrics mUserMetrics;
 
+    private IResultReceiver mUserSwitchUiReceiver;
+
     /** Interface for callbaks related to passenger activities. */
     public interface PassengerCallback {
         /** Called when passenger is started at a certain zone. */
@@ -248,6 +252,7 @@
         writer.println("*CarUserService*");
         String indent = "  ";
         handleDumpListeners(writer, indent);
+        writer.printf("User switch UI receiver %s\n", mUserSwitchUiReceiver);
         synchronized (mLockUser) {
             writer.println("User0Unlocked: " + mUser0Unlocked);
             writer.println("BackgroundUsersToRestart: " + mBackgroundUsersToRestart);
@@ -820,6 +825,7 @@
                         try {
                             switched = mAm.switchUser(targetUserId);
                             if (switched) {
+                                sendUserSwitchUiCallback(targetUserId);
                                 resultStatus = UserSwitchResult.STATUS_SUCCESSFUL;
                                 mRequestIdForUserSwitchInProcess = resp.requestId;
                             } else {
@@ -846,6 +852,20 @@
         });
     }
 
+    private void sendUserSwitchUiCallback(@UserIdInt int targetUserId) {
+        if (mUserSwitchUiReceiver == null) {
+            Log.w(TAG_USER, "No User switch UI receiver.");
+            return;
+        }
+
+        try {
+            EventLog.writeEvent(EventLogTags.CAR_USER_SVC_SWITCH_USER_UI_REQ, targetUserId);
+            mUserSwitchUiReceiver.send(targetUserId, null);
+        } catch (RemoteException e) {
+            Log.e(TAG_USER, "Error calling user switch UI receiver.", e);
+        }
+    }
+
     @Override
     public GetUserIdentificationAssociationResponse getUserIdentificationAssociation(int[] types) {
         Preconditions.checkArgument(!ArrayUtils.isEmpty(types), "must have at least one type");
@@ -930,6 +950,19 @@
         return mHal.isSupported();
     }
 
+    /**
+     * Sets a callback which is invoked before user switch.
+     *
+     * <p>
+     * This method should only be called by the Car System UI. The purpose of this call is to notify
+     * Car System UI to show the user switch UI before the user switch.
+     */
+    @Override
+    public void setUserSwitchUiCallback(@NonNull IResultReceiver receiver) {
+        // TODO(b/154958003): check UID, only carSysUI should be allowed to set it.
+        mUserSwitchUiReceiver = receiver;
+    }
+
     // TODO(b/144120654): use helper to generate UsersInfo
     private UsersInfo getUsersInfo() {
         UserInfo currentUser;
diff --git a/tests/carservice_test/src/com/android/car/CarDrivingRestrictionsTest.java b/tests/carservice_test/src/com/android/car/CarDrivingRestrictionsTest.java
index 520e910..890981a 100644
--- a/tests/carservice_test/src/com/android/car/CarDrivingRestrictionsTest.java
+++ b/tests/carservice_test/src/com/android/car/CarDrivingRestrictionsTest.java
@@ -29,6 +29,7 @@
 import android.car.drivingstate.CarUxRestrictionsManager;
 import android.hardware.automotive.vehicle.V2_0.VehicleGear;
 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
+import android.os.Build;
 import android.os.SystemClock;
 import android.util.Log;
 
@@ -211,9 +212,14 @@
                         .setTimestamp(SystemClock.elapsedRealtimeNanos())
                         .build());
         drivingEvent = listener.waitForDrivingStateChange();
-        assertNotNull(drivingEvent);
-        assertThat(drivingEvent.eventValue).isEqualTo(CarDrivingStateEvent.DRIVING_STATE_IDLING);
-
+        if (Build.IS_DEBUGGABLE) {
+            // In userdebug build, payloadChecker in HAL drops the invalid event.
+            assertNull(drivingEvent);
+        } else {
+            assertNotNull(drivingEvent);
+            assertThat(drivingEvent.eventValue).isEqualTo(
+                    CarDrivingStateEvent.DRIVING_STATE_IDLING);
+        }
         // Now, send in an invalid speed value as well, now the driving state will be unknown and
         // the UX restrictions will change to fully restricted.
         listener.reset();
@@ -224,13 +230,19 @@
                         .setTimestamp(SystemClock.elapsedRealtimeNanos())
                         .build());
         drivingEvent = listener.waitForDrivingStateChange();
-        assertNotNull(drivingEvent);
-        assertThat(drivingEvent.eventValue).isEqualTo(CarDrivingStateEvent.DRIVING_STATE_UNKNOWN);
-        restrictions = listener.waitForUxRestrictionsChange();
-        assertNotNull(restrictions);
-        assertTrue(restrictions.isRequiresDistractionOptimization());
-        assertThat(restrictions.getActiveRestrictions())
-                .isEqualTo(CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED);
+        if (Build.IS_DEBUGGABLE) {
+            // In userdebug build, payloadChecker in HAL drops the invalid event.
+            assertNull(drivingEvent);
+        } else {
+            assertNotNull(drivingEvent);
+            assertThat(drivingEvent.eventValue).isEqualTo(
+                    CarDrivingStateEvent.DRIVING_STATE_UNKNOWN);
+            restrictions = listener.waitForUxRestrictionsChange();
+            assertNotNull(restrictions);
+            assertTrue(restrictions.isRequiresDistractionOptimization());
+            assertThat(restrictions.getActiveRestrictions())
+                    .isEqualTo(CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED);
+        }
         mCarDrivingStateManager.unregisterListener();
         mCarUxRManager.unregisterListener();
     }
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 6c0b286..33a852a 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,9 @@
 package android.car.userlib;
 
 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;
+import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationSetValue.DISASSOCIATE_CURRENT_USER;
 import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationType.CUSTOM_1;
 import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationType.CUSTOM_2;
 import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationType.CUSTOM_3;
@@ -42,11 +45,16 @@
 import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationValue;
 import android.hardware.automotive.vehicle.V2_0.UserIdentificationGetRequest;
 import android.hardware.automotive.vehicle.V2_0.UserIdentificationResponse;
+import android.hardware.automotive.vehicle.V2_0.UserIdentificationSetAssociation;
+import android.hardware.automotive.vehicle.V2_0.UserIdentificationSetRequest;
 import android.hardware.automotive.vehicle.V2_0.UsersInfo;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
 
+import com.google.common.collect.Range;
+
 import org.junit.Test;
 
 public final class UserHalHelperTest {
@@ -164,17 +172,42 @@
     }
 
     @Test
-    public void testCreatePropRequest() {
-        int requestId = 1;
-        int requestType = 2;
-        int requestProp = 3;
-        VehiclePropValue propRequest = UserHalHelper.createPropRequest(requestId, requestType,
-                requestProp);
+    public void testCreatePropRequest_withType() {
+        int prop = 1;
+        int requestId = 2;
+        int requestType = 3;
+        long before = SystemClock.elapsedRealtime();
+        VehiclePropValue propRequest = UserHalHelper.createPropRequest(prop, requestId,
+                requestType);
+        long after = SystemClock.elapsedRealtime();
 
         assertThat(propRequest.value.int32Values)
                 .containsExactly(requestId, requestType)
                 .inOrder();
-        assertThat(propRequest.prop).isEqualTo(requestProp);
+        assertThat(propRequest.prop).isEqualTo(prop);
+        assertThat(propRequest.timestamp).isIn(Range.closed(before, after));
+    }
+
+    @Test
+    public void testCreatePropRequest() {
+        int prop = 1;
+        int requestId = 2;
+        long before = SystemClock.elapsedRealtime();
+        VehiclePropValue propRequest = UserHalHelper.createPropRequest(prop, requestId);
+        long after = SystemClock.elapsedRealtime();
+
+        assertThat(propRequest.value.int32Values)
+                .containsExactly(requestId)
+                .inOrder();
+        assertThat(propRequest.prop).isEqualTo(prop);
+        assertThat(propRequest.timestamp).isIn(Range.closed(before, after));
+    }
+
+    @Test
+    public void testAddUsersInfo_nullProp() {
+        UsersInfo infos = new UsersInfo();
+
+        assertThrows(NullPointerException.class, () -> UserHalHelper.addUsersInfo(null, infos));
     }
 
     @Test
@@ -223,7 +256,39 @@
     }
 
     @Test
-    public void testVsValidUserIdentificationAssociationType_valid() {
+    public void testAddUserInfo_nullProp() {
+        android.hardware.automotive.vehicle.V2_0.UserInfo userInfo =
+                new android.hardware.automotive.vehicle.V2_0.UserInfo();
+
+        assertThrows(NullPointerException.class, () -> UserHalHelper.addUserInfo(null, userInfo));
+    }
+
+    @Test
+    public void testAddUserInfo_nullCurrentUser() {
+        VehiclePropValue prop = new VehiclePropValue();
+
+        assertThrows(NullPointerException.class, () -> UserHalHelper.addUserInfo(prop, null));
+    }
+
+    @Test
+    public void testAddUserInfo_success() {
+        VehiclePropValue propRequest = new VehiclePropValue();
+        propRequest.value.int32Values.add(99);
+
+        android.hardware.automotive.vehicle.V2_0.UserInfo userInfo =
+                new android.hardware.automotive.vehicle.V2_0.UserInfo();
+        userInfo.userId = 42;
+        userInfo.flags = 1;
+
+        UserHalHelper.addUserInfo(propRequest, userInfo);
+
+        assertThat(propRequest.value.int32Values)
+                .containsExactly(99, 42, 1)
+                .inOrder();
+    }
+
+    @Test
+    public void testIsValidUserIdentificationAssociationType_valid() {
         assertThat(UserHalHelper.isValidUserIdentificationAssociationType(KEY_FOB)).isTrue();
         assertThat(UserHalHelper.isValidUserIdentificationAssociationType(CUSTOM_1)).isTrue();
         assertThat(UserHalHelper.isValidUserIdentificationAssociationType(CUSTOM_2)).isTrue();
@@ -253,6 +318,21 @@
     }
 
     @Test
+    public void testIsValidUserIdentificationAssociationSetValue_valid() {
+        assertThat(UserHalHelper
+                .isValidUserIdentificationAssociationSetValue(ASSOCIATE_CURRENT_USER)).isTrue();
+        assertThat(UserHalHelper
+                .isValidUserIdentificationAssociationSetValue(DISASSOCIATE_CURRENT_USER)).isTrue();
+        assertThat(UserHalHelper
+                .isValidUserIdentificationAssociationSetValue(DISASSOCIATE_ALL_USERS)).isTrue();
+    }
+
+    @Test
+    public void testIsValidUserIdentificationAssociationSetValue_invalid() {
+        assertThat(UserHalHelper.isValidUserIdentificationAssociationSetValue(0)).isFalse();
+    }
+
+    @Test
     public void testUserIdentificationGetRequestToVehiclePropValue_null() {
         assertThrows(NullPointerException.class,
                 () -> UserHalHelper.toVehiclePropValue((UserIdentificationGetRequest) null));
@@ -261,6 +341,7 @@
     @Test
     public void testUserIdentificationGetRequestToVehiclePropValue_emptyRequest() {
         UserIdentificationGetRequest request = new UserIdentificationGetRequest();
+
         assertThrows(IllegalArgumentException.class,
                 () -> UserHalHelper.toVehiclePropValue(request));
     }
@@ -269,6 +350,7 @@
     public void testUserIdentificationGetRequestToVehiclePropValue_wrongNumberOfAssociations() {
         UserIdentificationGetRequest request = new UserIdentificationGetRequest();
         request.numberAssociationTypes = 1;
+
         assertThrows(IllegalArgumentException.class,
                 () -> UserHalHelper.toVehiclePropValue(request));
     }
@@ -278,6 +360,19 @@
         UserIdentificationGetRequest request = new UserIdentificationGetRequest();
         request.numberAssociationTypes = 1;
         request.associationTypes.add(CUSTOM_4 + 1);
+
+        assertThrows(IllegalArgumentException.class,
+                () -> UserHalHelper.toVehiclePropValue(request));
+    }
+
+    @Test
+    public void testUserIdentificationGetRequestToVehiclePropValue_missingRequestId() {
+        UserIdentificationGetRequest request = new UserIdentificationGetRequest();
+        request.userInfo.userId = 42;
+        request.userInfo.flags = 108;
+        request.numberAssociationTypes = 1;
+        request.associationTypes.add(KEY_FOB);
+
         assertThrows(IllegalArgumentException.class,
                 () -> UserHalHelper.toVehiclePropValue(request));
     }
@@ -285,6 +380,7 @@
     @Test
     public void testUserIdentificationGetRequestToVehiclePropValue_ok() {
         UserIdentificationGetRequest request = new UserIdentificationGetRequest();
+        request.requestId = 1;
         request.userInfo.userId = 42;
         request.userInfo.flags = 108;
         request.numberAssociationTypes = 2;
@@ -295,67 +391,77 @@
         assertWithMessage("wrong prop on %s", propValue).that(propValue.prop)
                 .isEqualTo(USER_IDENTIFICATION_ASSOCIATION_PROPERTY);
         assertWithMessage("wrong int32values on %s", propValue).that(propValue.value.int32Values)
-                .containsExactly(42, 108, 2, KEY_FOB, CUSTOM_1).inOrder();
+                .containsExactly(1, 42, 108, 2, KEY_FOB, CUSTOM_1).inOrder();
     }
 
     @Test
-    public void testToUserIdentificationGetResponse_null() {
+    public void testToUserIdentificationResponse_null() {
         assertThrows(NullPointerException.class,
-                () -> UserHalHelper.toUserIdentificationGetResponse(null));
+                () -> UserHalHelper.toUserIdentificationResponse(null));
     }
 
     @Test
     public void testToUserIdentificationGetResponse_invalidPropType() {
         VehiclePropValue prop = new VehiclePropValue();
+
         assertThrows(IllegalArgumentException.class,
-                () -> UserHalHelper.toUserIdentificationGetResponse(prop));
+                () -> UserHalHelper.toUserIdentificationResponse(prop));
     }
 
     @Test
-    public void testToUserIdentificationGetResponse_invalidSize() {
+    public void testToUserIdentificationResponse_invalidSize() {
+        VehiclePropValue prop = new VehiclePropValue();
+        prop.prop = UserHalHelper.USER_IDENTIFICATION_ASSOCIATION_PROPERTY;
+        // need at least 4: request_id, number associations, type1, value1
+        prop.value.int32Values.add(1);
+        prop.value.int32Values.add(2);
+        prop.value.int32Values.add(3);
+
+        assertThrows(IllegalArgumentException.class,
+                () -> UserHalHelper.toUserIdentificationResponse(prop));
+    }
+
+    @Test
+    public void testToUserIdentificationResponse_invalidRequest() {
         VehiclePropValue prop = new VehiclePropValue();
         prop.prop = UserHalHelper.USER_IDENTIFICATION_ASSOCIATION_PROPERTY;
         prop.value.int32Values.add(0);
+
         assertThrows(IllegalArgumentException.class,
-                () -> UserHalHelper.toUserIdentificationGetResponse(prop));
+                () -> UserHalHelper.toUserIdentificationResponse(prop));
     }
 
     @Test
-    public void testToUserIdentificationGetResponse_sizeMismatch() {
+    public void testToUserIdentificationResponse_invalidType() {
         VehiclePropValue prop = new VehiclePropValue();
         prop.prop = UserHalHelper.USER_IDENTIFICATION_ASSOCIATION_PROPERTY;
-        prop.value.int32Values.add(1); // number of associations
-        prop.value.int32Values.add(KEY_FOB);
-        assertThrows(IllegalArgumentException.class,
-                () -> UserHalHelper.toUserIdentificationGetResponse(prop));
-    }
-
-    @Test
-    public void testToUserIdentificationGetResponse_invalidType() {
-        VehiclePropValue prop = new VehiclePropValue();
-        prop.prop = UserHalHelper.USER_IDENTIFICATION_ASSOCIATION_PROPERTY;
+        prop.value.int32Values.add(42); // request id
         prop.value.int32Values.add(1); // number of associations
         prop.value.int32Values.add(CUSTOM_4 + 1);
         prop.value.int32Values.add(ASSOCIATED_ANOTHER_USER);
+
         assertThrows(IllegalArgumentException.class,
-                () -> UserHalHelper.toUserIdentificationGetResponse(prop));
+                () -> UserHalHelper.toUserIdentificationResponse(prop));
     }
 
     @Test
-    public void testToUserIdentificationGetResponse_invalidValue() {
+    public void testToUserIdentificationResponse_invalidValue() {
         VehiclePropValue prop = new VehiclePropValue();
         prop.prop = UserHalHelper.USER_IDENTIFICATION_ASSOCIATION_PROPERTY;
+        prop.value.int32Values.add(42); // request id
         prop.value.int32Values.add(1); // number of associations
         prop.value.int32Values.add(KEY_FOB);
         prop.value.int32Values.add(0);
+
         assertThrows(IllegalArgumentException.class,
-                () -> UserHalHelper.toUserIdentificationGetResponse(prop));
+                () -> UserHalHelper.toUserIdentificationResponse(prop));
     }
 
     @Test
-    public void testToUserIdentificationGetResponse_ok() {
+    public void testToUserIdentificationResponse_ok() {
         VehiclePropValue prop = new VehiclePropValue();
         prop.prop = UserHalHelper.USER_IDENTIFICATION_ASSOCIATION_PROPERTY;
+        prop.value.int32Values.add(42); // request id
         prop.value.int32Values.add(3); // number of associations
         prop.value.int32Values.add(KEY_FOB);
         prop.value.int32Values.add(ASSOCIATED_ANOTHER_USER);
@@ -364,10 +470,13 @@
         prop.value.int32Values.add(CUSTOM_2);
         prop.value.int32Values.add(NOT_ASSOCIATED_ANY_USER);
         prop.value.stringValue = "D'OH!";
-        UserIdentificationResponse response = UserHalHelper.toUserIdentificationGetResponse(prop);
+
+        UserIdentificationResponse response = UserHalHelper.toUserIdentificationResponse(prop);
+
+        assertWithMessage("Wrong request id on %s", response)
+            .that(response.requestId).isEqualTo(42);
         assertWithMessage("Wrong number of associations on %s", response)
             .that(response.numberAssociation).isEqualTo(3);
-
         assertAssociation(response, 0, KEY_FOB, ASSOCIATED_ANOTHER_USER);
         assertAssociation(response, 1, CUSTOM_1, ASSOCIATED_CURRENT_USER);
         assertAssociation(response, 2, CUSTOM_2, NOT_ASSOCIATED_ANY_USER);
@@ -375,6 +484,96 @@
             .that(response.errorMessage).isEqualTo("D'OH!");
     }
 
+    @Test
+    public void testUserIdentificationSetRequestToVehiclePropValue_null() {
+        assertThrows(NullPointerException.class,
+                () -> UserHalHelper.toVehiclePropValue((UserIdentificationSetRequest) null));
+    }
+
+    @Test
+    public void testUserIdentificationSetRequestToVehiclePropValue_emptyRequest() {
+        UserIdentificationSetRequest request = new UserIdentificationSetRequest();
+
+        assertThrows(IllegalArgumentException.class,
+                () -> UserHalHelper.toVehiclePropValue(request));
+    }
+
+    @Test
+    public void testUserIdentificationSetRequestToVehiclePropValue_wrongNumberOfAssociations() {
+        UserIdentificationSetRequest request = new UserIdentificationSetRequest();
+        request.numberAssociations = 1;
+
+        assertThrows(IllegalArgumentException.class,
+                () -> UserHalHelper.toVehiclePropValue(request));
+    }
+
+    @Test
+    public void testUserIdentificationSetRequestToVehiclePropValue_invalidType() {
+        UserIdentificationSetRequest request = new UserIdentificationSetRequest();
+        request.numberAssociations = 1;
+        UserIdentificationSetAssociation association1 = new UserIdentificationSetAssociation();
+        request.associations.add(association1);
+        association1.type = CUSTOM_4 + 1;
+        association1.value = ASSOCIATE_CURRENT_USER;
+
+        assertThrows(IllegalArgumentException.class,
+                () -> UserHalHelper.toVehiclePropValue(request));
+    }
+
+    @Test
+    public void testUserIdentificationSetRequestToVehiclePropValue_invalidValue() {
+        UserIdentificationSetRequest request = new UserIdentificationSetRequest();
+        request.numberAssociations = 1;
+        UserIdentificationSetAssociation association1 = new UserIdentificationSetAssociation();
+        request.associations.add(association1);
+        association1.type = KEY_FOB;
+        association1.value = -1;
+
+        assertThrows(IllegalArgumentException.class,
+                () -> UserHalHelper.toVehiclePropValue(request));
+    }
+
+    @Test
+    public void testUserIdentificationSetRequestToVehiclePropValue_missingRequestId() {
+        UserIdentificationSetRequest request = new UserIdentificationSetRequest();
+        request.userInfo.userId = 42;
+        request.userInfo.flags = 108;
+        request.numberAssociations = 1;
+        UserIdentificationSetAssociation association1 = new UserIdentificationSetAssociation();
+        association1.type = KEY_FOB;
+        association1.value = ASSOCIATE_CURRENT_USER;
+        request.associations.add(association1);
+
+        assertThrows(IllegalArgumentException.class,
+                () -> UserHalHelper.toVehiclePropValue(request));
+    }
+
+    @Test
+    public void testUserIdentificationSetRequestToVehiclePropValue_ok() {
+        UserIdentificationSetRequest request = new UserIdentificationSetRequest();
+        request.requestId = 1;
+        request.userInfo.userId = 42;
+        request.userInfo.flags = 108;
+        request.numberAssociations = 2;
+        UserIdentificationSetAssociation association1 = new UserIdentificationSetAssociation();
+        association1.type = KEY_FOB;
+        association1.value = ASSOCIATE_CURRENT_USER;
+        request.associations.add(association1);
+        UserIdentificationSetAssociation association2 = new UserIdentificationSetAssociation();
+        association2.type = CUSTOM_1;
+        association2.value = DISASSOCIATE_CURRENT_USER;
+        request.associations.add(association2);
+
+        VehiclePropValue propValue = UserHalHelper.toVehiclePropValue(request);
+        assertWithMessage("wrong prop on %s", propValue).that(propValue.prop)
+                .isEqualTo(USER_IDENTIFICATION_ASSOCIATION_PROPERTY);
+        assertWithMessage("wrong int32values on %s", propValue).that(propValue.value.int32Values)
+                .containsExactly(1, 42, 108, 2,
+                        KEY_FOB, ASSOCIATE_CURRENT_USER,
+                        CUSTOM_1, DISASSOCIATE_CURRENT_USER)
+                .inOrder();
+    }
+
     private void assertAssociation(@NonNull UserIdentificationResponse response, int index,
             int expectedType, int expectedValue) {
         UserIdentificationAssociation actualAssociation = response.associations.get(index);
@@ -389,5 +588,4 @@
                     + UserIdentificationAssociationValue.toString(actualAssociation.value));
         }
     }
-
 }
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeTest.java
new file mode 100644
index 0000000..06d2299
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeTest.java
@@ -0,0 +1,181 @@
+/*
+ * 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.audio;
+
+import static android.media.AudioAttributes.USAGE_ALARM;
+import static android.media.AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE;
+import static android.media.AudioAttributes.USAGE_MEDIA;
+import static android.media.AudioAttributes.USAGE_NOTIFICATION;
+import static android.media.AudioAttributes.USAGE_VIRTUAL_SOURCE;
+import static android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION;
+import static android.telephony.TelephonyManager.CALL_STATE_IDLE;
+import static android.telephony.TelephonyManager.CALL_STATE_OFFHOOK;
+import static android.telephony.TelephonyManager.CALL_STATE_RINGING;
+
+import static com.android.car.audio.CarAudioContext.ALARM;
+import static com.android.car.audio.CarAudioContext.CALL;
+import static com.android.car.audio.CarAudioContext.CALL_RING;
+import static com.android.car.audio.CarAudioContext.NAVIGATION;
+import static com.android.car.audio.CarAudioService.DEFAULT_AUDIO_CONTEXT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.media.AudioAttributes;
+import android.media.AudioAttributes.AttributeUsage;
+import android.media.AudioPlaybackConfiguration;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.car.audio.CarAudioContext.AudioContext;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class CarVolumeTest {
+    @Test
+    public void getSuggestedAudioContext_withNoConfigurationsAndIdleTelephony_returnsDefault() {
+        @AudioContext int suggestedContext = CarVolume.getSuggestedAudioContext(new ArrayList<>(),
+                CALL_STATE_IDLE);
+
+        assertThat(suggestedContext).isEqualTo(CarAudioService.DEFAULT_AUDIO_CONTEXT);
+    }
+
+    @Test
+    public void getSuggestedAudioContext_withOneConfiguration_returnsAssociatedContext() {
+        List<AudioPlaybackConfiguration> configurations = ImmutableList.of(
+                new Builder().setUsage(USAGE_ALARM).build()
+        );
+
+        @AudioContext int suggestedContext = CarVolume.getSuggestedAudioContext(configurations,
+                CALL_STATE_IDLE);
+
+        assertThat(suggestedContext).isEqualTo(ALARM);
+    }
+
+    @Test
+    public void getSuggestedAudioContext_withCallStateOffHook_returnsCallContext() {
+        @AudioContext int suggestedContext = CarVolume.getSuggestedAudioContext(new ArrayList<>(),
+                CALL_STATE_OFFHOOK);
+
+        assertThat(suggestedContext).isEqualTo(CALL);
+    }
+
+    @Test
+    public void getSuggestedAudioContext_withCallStateRinging_returnsCallRingContext() {
+        @AudioContext int suggestedContext = CarVolume.getSuggestedAudioContext(new ArrayList<>(),
+                CALL_STATE_RINGING);
+
+        assertThat(suggestedContext).isEqualTo(CALL_RING);
+    }
+
+    @Test
+    public void getSuggestedAudioContext_withConfigurations_returnsHighestPriorityContext() {
+        List<AudioPlaybackConfiguration> configurations = ImmutableList.of(
+                new Builder().setUsage(USAGE_ALARM).build(),
+                new Builder().setUsage(USAGE_VOICE_COMMUNICATION).build(),
+                new Builder().setUsage(USAGE_NOTIFICATION).build()
+        );
+
+        @AudioContext int suggestedContext = CarVolume.getSuggestedAudioContext(configurations,
+                CALL_STATE_IDLE);
+
+        assertThat(suggestedContext).isEqualTo(CALL);
+    }
+
+    @Test
+    public void getSuggestedAudioContext_ignoresInactiveConfigurations() {
+        List<AudioPlaybackConfiguration> configurations = ImmutableList.of(
+                new Builder().setUsage(USAGE_ALARM).build(),
+                new Builder().setUsage(USAGE_VOICE_COMMUNICATION).setInactive().build(),
+                new Builder().setUsage(USAGE_NOTIFICATION).build()
+        );
+
+        @AudioContext int suggestedContext = CarVolume.getSuggestedAudioContext(configurations,
+                CALL_STATE_IDLE);
+
+        assertThat(suggestedContext).isEqualTo(ALARM);
+    }
+
+    @Test
+    public void getSuggestedAudioContext_withLowerPriorityConfigurationsAndCall_returnsCall() {
+        List<AudioPlaybackConfiguration> configurations = ImmutableList.of(
+                new Builder().setUsage(USAGE_ALARM).build(),
+                new Builder().setUsage(USAGE_NOTIFICATION).build()
+        );
+
+        @AudioContext int suggestedContext = CarVolume.getSuggestedAudioContext(configurations,
+                CALL_STATE_OFFHOOK);
+
+        assertThat(suggestedContext).isEqualTo(CALL);
+    }
+
+    @Test
+    public void getSuggestedAudioContext_withNavigationConfigurationAndCall_returnsNavigation() {
+        List<AudioPlaybackConfiguration> configurations = ImmutableList.of(
+                new Builder().setUsage(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE).build()
+        );
+
+        @AudioContext int suggestedContext = CarVolume.getSuggestedAudioContext(configurations,
+                CALL_STATE_OFFHOOK);
+
+        assertThat(suggestedContext).isEqualTo(NAVIGATION);
+    }
+
+    @Test
+    public void getSuggestedAudioContext_withUnprioritizedUsage_returnsDefault() {
+        List<AudioPlaybackConfiguration> configurations = ImmutableList.of(
+                new Builder().setUsage(USAGE_VIRTUAL_SOURCE).build()
+        );
+
+        @AudioContext int suggestedContext = CarVolume.getSuggestedAudioContext(configurations,
+                CALL_STATE_IDLE);
+
+        assertThat(suggestedContext).isEqualTo(DEFAULT_AUDIO_CONTEXT);
+    }
+
+    private static class Builder {
+        private @AttributeUsage int mUsage = USAGE_MEDIA;
+        private boolean mIsActive = true;
+
+        Builder setUsage(@AttributeUsage int usage) {
+            mUsage = usage;
+            return this;
+        }
+
+        Builder setInactive() {
+            mIsActive = false;
+            return this;
+        }
+
+        AudioPlaybackConfiguration build() {
+            AudioPlaybackConfiguration configuration = mock(AudioPlaybackConfiguration.class);
+            AudioAttributes attributes = new AudioAttributes.Builder().setUsage(mUsage).build();
+            when(configuration.getAudioAttributes()).thenReturn(attributes);
+            when(configuration.isActive()).thenReturn(mIsActive);
+            return configuration;
+        }
+    }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/PropertyHalServiceIdsTest.java b/tests/carservice_unit_test/src/com/android/car/hal/PropertyHalServiceIdsTest.java
index 59498cb..e128b19 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/PropertyHalServiceIdsTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/PropertyHalServiceIdsTest.java
@@ -17,12 +17,22 @@
 package com.android.car.hal;
 
 import android.car.Car;
+import android.car.VehicleHvacFanDirection;
 import android.car.VehiclePropertyIds;
+import android.hardware.automotive.vehicle.V2_0.VehicleGear;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
+import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
+import android.hardware.automotive.vehicle.V2_0.VehicleUnit;
 import android.hardware.automotive.vehicle.V2_0.VehicleVendorPermission;
+import android.os.SystemClock;
 import android.util.Log;
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.car.vehiclehal.VehiclePropValueBuilder;
+
+import com.google.common.truth.Truth;
+
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
@@ -53,6 +63,32 @@
             VehiclePropertyIds.HVAC_FAN_SPEED, VehiclePropertyIds.DOOR_LOCK};
     private static final List<Integer> CONFIG_ARRAY = new ArrayList<>();
     private static final List<Integer> CONFIG_ARRAY_INVALID = new ArrayList<>();
+    //payload test
+    private static final VehiclePropValue GEAR_WITH_VALID_VALUE =
+            VehiclePropValueBuilder.newBuilder(VehicleProperty.GEAR_SELECTION)
+            .addIntValue(VehicleGear.GEAR_DRIVE)
+            .setTimestamp(SystemClock.elapsedRealtimeNanos()).build();
+    private static final VehiclePropValue GEAR_WITH_EXTRA_VALUE =
+            VehiclePropValueBuilder.newBuilder(VehicleProperty.GEAR_SELECTION)
+                    .addIntValue(VehicleGear.GEAR_DRIVE)
+                    .addIntValue(VehicleGear.GEAR_1)
+                    .setTimestamp(SystemClock.elapsedRealtimeNanos()).build();
+    private static final VehiclePropValue GEAR_WITH_INVALID_VALUE =
+            VehiclePropValueBuilder.newBuilder(VehicleProperty.GEAR_SELECTION)
+                    .addIntValue(VehicleUnit.KILOPASCAL)
+                    .setTimestamp(SystemClock.elapsedRealtimeNanos()).build();
+    private static final VehiclePropValue GEAR_WITH_INVALID_TYPE_VALUE =
+            VehiclePropValueBuilder.newBuilder(VehicleProperty.GEAR_SELECTION)
+                    .addFloatValue(1.0f)
+                    .setTimestamp(SystemClock.elapsedRealtimeNanos()).build();
+    private static final VehiclePropValue HVAC_FAN_DIRECTIONS_VALID =
+            VehiclePropValueBuilder.newBuilder(VehicleProperty.HVAC_FAN_DIRECTION)
+                    .addIntValue(VehicleHvacFanDirection.FACE | VehicleHvacFanDirection.FLOOR)
+                    .setTimestamp(SystemClock.elapsedRealtimeNanos()).build();
+    private static final VehiclePropValue HVAC_FAN_DIRECTIONS_INVALID =
+            VehiclePropValueBuilder.newBuilder(VehicleProperty.HVAC_FAN_DIRECTION)
+                    .addIntValue(VehicleHvacFanDirection.FACE | 0x100)
+                    .setTimestamp(SystemClock.elapsedRealtimeNanos()).build();
     @Before
     public void setUp() {
         mPropertyHalServiceIds = new PropertyHalServiceIds();
@@ -90,7 +126,6 @@
         Assert.assertEquals(Car.PERMISSION_CONTROL_CAR_CLIMATE,
                 mPropertyHalServiceIds.getWritePermission(VehiclePropertyIds.HVAC_FAN_SPEED));
     }
-
     /**
      * Test {@link PropertyHalServiceIds#customizeVendorPermission(List)}
      */
@@ -143,4 +178,19 @@
         }
     }
 
+    /**
+     * Test {@link PropertyHalServiceIds#checkPayload(VehiclePropValue)}
+     */
+    @Test
+    public void testPayload() {
+        Truth.assertThat(mPropertyHalServiceIds.checkPayload(GEAR_WITH_VALID_VALUE)).isTrue();
+        Truth.assertThat(mPropertyHalServiceIds.checkPayload(GEAR_WITH_EXTRA_VALUE)).isFalse();
+        Truth.assertThat(mPropertyHalServiceIds.checkPayload(GEAR_WITH_INVALID_VALUE)).isFalse();
+        Truth.assertThat(mPropertyHalServiceIds.checkPayload(GEAR_WITH_INVALID_TYPE_VALUE))
+                .isFalse();
+
+        Truth.assertThat(mPropertyHalServiceIds.checkPayload(HVAC_FAN_DIRECTIONS_VALID)).isTrue();
+        Truth.assertThat(mPropertyHalServiceIds.checkPayload(HVAC_FAN_DIRECTIONS_INVALID))
+                .isFalse();
+    }
 }
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 71e51f3..033bb9a 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
@@ -24,6 +24,7 @@
 import static android.car.test.util.VehicleHalTestingHelper.newConfig;
 import static android.car.test.util.VehicleHalTestingHelper.newSubscribableConfig;
 import static android.hardware.automotive.vehicle.V2_0.InitialUserInfoRequestType.COLD_BOOT;
+import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationSetValue.ASSOCIATE_CURRENT_USER;
 import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationType.CUSTOM_1;
 import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationType.KEY_FOB;
 import static android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationValue.ASSOCIATED_CURRENT_USER;
@@ -33,11 +34,14 @@
 
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.testng.Assert.assertThrows;
 
+import android.annotation.NonNull;
 import android.car.hardware.property.VehicleHalStatusCode;
 import android.car.userlib.HalCallback;
 import android.car.userlib.UserHalHelper;
@@ -50,6 +54,8 @@
 import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociation;
 import android.hardware.automotive.vehicle.V2_0.UserIdentificationGetRequest;
 import android.hardware.automotive.vehicle.V2_0.UserIdentificationResponse;
+import android.hardware.automotive.vehicle.V2_0.UserIdentificationSetAssociation;
+import android.hardware.automotive.vehicle.V2_0.UserIdentificationSetRequest;
 import android.hardware.automotive.vehicle.V2_0.UserInfo;
 import android.hardware.automotive.vehicle.V2_0.UsersInfo;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
@@ -98,10 +104,16 @@
     private static final int CALLBACK_TIMEOUT_TIMEOUT = TIMEOUT_MS + 450;
 
     // Used when crafting a request property - the real value will be set by the mock.
-    private static final int REQUEST_ID_PLACE_HOLDER = 42;
+    private static final int REQUEST_ID_PLACE_HOLDER = 1111;
+
+    private static final int DEFAULT_REQUEST_ID = 2222;
+
+    private static final int DEFAULT_USER_ID = 333;
+    private static final int DEFAULT_USER_FLAGS = 444;
 
     private static final int INITIAL_USER_INFO_RESPONSE_ACTION = 108;
 
+
     @Mock
     private VehicleHal mVehicleHal;
 
@@ -110,14 +122,14 @@
 
     private final UsersInfo mUsersInfo = new UsersInfo();
 
+    // Must be a spy so we can mock getNextRequestId()
     private UserHalService mUserHalService;
 
     @Before
     public void setFixtures() {
-        mUserHalService = new UserHalService(mVehicleHal);
-        mUserHalService.takeProperties(Arrays.asList(
-                newSubscribableConfig(INITIAL_USER_INFO),
-                newSubscribableConfig(SWITCH_USER)));
+        mUserHalService = spy(new UserHalService(mVehicleHal));
+        // Needs at least one property, otherwise isSupported() will return false
+        mUserHalService.takeProperties(Arrays.asList(newSubscribableConfig(INITIAL_USER_INFO)));
 
         mUser0.userId = 0;
         mUser0.flags = 100;
@@ -243,8 +255,8 @@
 
     @Test
     public void testGetUserInfo_halReplyWithWrongRequestId() throws Exception {
-        VehiclePropValue propResponse = UserHalHelper.createPropRequest(REQUEST_ID_PLACE_HOLDER,
-                    INITIAL_USER_INFO_RESPONSE_ACTION, INITIAL_USER_INFO);
+        VehiclePropValue propResponse = UserHalHelper.createPropRequest(INITIAL_USER_INFO,
+                    REQUEST_ID_PLACE_HOLDER, INITIAL_USER_INFO_RESPONSE_ACTION);
 
         replySetPropertyWithOnChangeEvent(INITIAL_USER_INFO, propResponse,
                 /* rightRequestId= */ false);
@@ -261,8 +273,8 @@
 
     @Test
     public void testGetUserInfo_halReturnedInvalidAction() throws Exception {
-        VehiclePropValue propResponse = UserHalHelper.createPropRequest(REQUEST_ID_PLACE_HOLDER,
-                    INITIAL_USER_INFO_RESPONSE_ACTION, INITIAL_USER_INFO);
+        VehiclePropValue propResponse = UserHalHelper.createPropRequest(INITIAL_USER_INFO,
+                    REQUEST_ID_PLACE_HOLDER, INITIAL_USER_INFO_RESPONSE_ACTION);
 
         AtomicReference<VehiclePropValue> reqCaptor = replySetPropertyWithOnChangeEvent(
                 INITIAL_USER_INFO, propResponse, /* rightRequestId= */ true);
@@ -284,8 +296,8 @@
 
     @Test
     public void testGetUserInfo_successDefault() throws Exception {
-        VehiclePropValue propResponse = UserHalHelper.createPropRequest(REQUEST_ID_PLACE_HOLDER,
-                    InitialUserInfoResponseAction.DEFAULT, INITIAL_USER_INFO);
+        VehiclePropValue propResponse = UserHalHelper.createPropRequest(INITIAL_USER_INFO,
+                    REQUEST_ID_PLACE_HOLDER, InitialUserInfoResponseAction.DEFAULT);
 
         AtomicReference<VehiclePropValue> reqCaptor = replySetPropertyWithOnChangeEvent(
                 INITIAL_USER_INFO, propResponse, /* rightRequestId= */ true);
@@ -313,8 +325,8 @@
     @Test
     public void testGetUserInfo_successSwitchUser() throws Exception {
         int userIdToSwitch = 42;
-        VehiclePropValue propResponse = UserHalHelper.createPropRequest(REQUEST_ID_PLACE_HOLDER,
-                    InitialUserInfoResponseAction.SWITCH, INITIAL_USER_INFO);
+        VehiclePropValue propResponse = UserHalHelper.createPropRequest(INITIAL_USER_INFO,
+                    REQUEST_ID_PLACE_HOLDER, InitialUserInfoResponseAction.SWITCH);
         propResponse.value.int32Values.add(userIdToSwitch);
 
         AtomicReference<VehiclePropValue> reqCaptor = replySetPropertyWithOnChangeEvent(
@@ -344,8 +356,8 @@
     public void testGetUserInfo_successCreateUser() throws Exception {
         int newUserFlags = 108;
         String newUserName = "Groot";
-        VehiclePropValue propResponse = UserHalHelper.createPropRequest(REQUEST_ID_PLACE_HOLDER,
-                    InitialUserInfoResponseAction.CREATE, INITIAL_USER_INFO);
+        VehiclePropValue propResponse = UserHalHelper.createPropRequest(INITIAL_USER_INFO,
+                    REQUEST_ID_PLACE_HOLDER, InitialUserInfoResponseAction.CREATE);
         propResponse.value.int32Values.add(newUserFlags);
         propResponse.value.stringValue = newUserName;
 
@@ -434,8 +446,8 @@
 
     @Test
     public void testSwitchUser_halReplyWithWrongRequestId() throws Exception {
-        VehiclePropValue propResponse = UserHalHelper.createPropRequest(REQUEST_ID_PLACE_HOLDER,
-                    InitialUserInfoResponseAction.SWITCH, SWITCH_USER);
+        VehiclePropValue propResponse = UserHalHelper.createPropRequest(SWITCH_USER,
+                    REQUEST_ID_PLACE_HOLDER, InitialUserInfoResponseAction.SWITCH);
 
         replySetPropertyWithOnChangeEvent(SWITCH_USER, propResponse,
                 /* rightRequestId= */ false);
@@ -451,8 +463,8 @@
 
     @Test
     public void testSwitchUser_halReturnedInvalidMessageType() throws Exception {
-        VehiclePropValue propResponse = UserHalHelper.createPropRequest(REQUEST_ID_PLACE_HOLDER,
-                    SwitchUserMessageType.VEHICLE_REQUEST, SWITCH_USER);
+        VehiclePropValue propResponse = UserHalHelper.createPropRequest(SWITCH_USER,
+                    REQUEST_ID_PLACE_HOLDER, SwitchUserMessageType.VEHICLE_REQUEST);
         propResponse.value.int32Values.add(SwitchUserStatus.SUCCESS);
 
         AtomicReference<VehiclePropValue> reqCaptor = replySetPropertyWithOnChangeEvent(
@@ -474,8 +486,8 @@
 
     @Test
     public void testUserSwitch_success() throws Exception {
-        VehiclePropValue propResponse = UserHalHelper.createPropRequest(REQUEST_ID_PLACE_HOLDER,
-                    SwitchUserMessageType.VEHICLE_RESPONSE, SWITCH_USER);
+        VehiclePropValue propResponse = UserHalHelper.createPropRequest(SWITCH_USER,
+                    REQUEST_ID_PLACE_HOLDER, SwitchUserMessageType.VEHICLE_RESPONSE);
         propResponse.value.int32Values.add(SwitchUserStatus.SUCCESS);
 
         AtomicReference<VehiclePropValue> reqCaptor = replySetPropertyWithOnChangeEvent(
@@ -499,8 +511,8 @@
 
     @Test
     public void testUserSwitch_failure() throws Exception {
-        VehiclePropValue propResponse = UserHalHelper.createPropRequest(REQUEST_ID_PLACE_HOLDER,
-                    SwitchUserMessageType.VEHICLE_RESPONSE, SWITCH_USER);
+        VehiclePropValue propResponse = UserHalHelper.createPropRequest(SWITCH_USER,
+                    REQUEST_ID_PLACE_HOLDER, SwitchUserMessageType.VEHICLE_RESPONSE);
         propResponse.value.int32Values.add(SwitchUserStatus.FAILURE);
 
         AtomicReference<VehiclePropValue> reqCaptor = replySetPropertyWithOnChangeEvent(
@@ -542,8 +554,8 @@
 
     @Test
     public void testSwitchUser_halReturnedInvalidStatus() throws Exception {
-        VehiclePropValue propResponse = UserHalHelper.createPropRequest(REQUEST_ID_PLACE_HOLDER,
-                    SwitchUserMessageType.VEHICLE_RESPONSE, SWITCH_USER);
+        VehiclePropValue propResponse = UserHalHelper.createPropRequest(SWITCH_USER,
+                    REQUEST_ID_PLACE_HOLDER, SwitchUserMessageType.VEHICLE_RESPONSE);
         propResponse.value.int32Values.add(/*status =*/ 110); // an invalid status
 
         AtomicReference<VehiclePropValue> reqCaptor = replySetPropertyWithOnChangeEvent(
@@ -615,104 +627,308 @@
 
     @Test
     public void testGetUserAssociation_invalidResponse() {
-        VehiclePropValue mockedResponse = new VehiclePropValue();
-        mockedResponse.prop = USER_IDENTIFICATION_ASSOCIATION;
-        mockedResponse.value.int32Values.add(1); // 1 associations
-        mockedResponse.value.int32Values.add(KEY_FOB); // type only, it's missing value
-        when(mVehicleHal.get(
-                isPropertyWithValues(USER_IDENTIFICATION_ASSOCIATION, 42, 108, 1, KEY_FOB)))
-                        .thenReturn(mockedResponse);
-
-        UserIdentificationGetRequest request = new UserIdentificationGetRequest();
-        request.userInfo.userId = 42;
-        request.userInfo.flags = 108;
-        request.numberAssociationTypes = 1;
-        request.associationTypes.add(KEY_FOB);
+        VehiclePropValue propResponse = new VehiclePropValue();
+        propResponse.prop = USER_IDENTIFICATION_ASSOCIATION;
+        propResponse.value.int32Values.add(DEFAULT_REQUEST_ID);
+        propResponse.value.int32Values.add(1); // 1 associations
+        propResponse.value.int32Values.add(KEY_FOB); // type only, it's missing value
+        UserIdentificationGetRequest request = replyToValidGetUserIdentificationRequest(
+                propResponse);
 
         assertThat(mUserHalService.getUserAssociation(request)).isNull();
     }
 
     @Test
     public void testGetUserAssociation_nullResponse() {
-        VehiclePropValue mockedResponse = new VehiclePropValue();
-        mockedResponse.prop = USER_IDENTIFICATION_ASSOCIATION;
-        mockedResponse.value.int32Values.add(1); // 1 association
-        mockedResponse.value.int32Values.add(KEY_FOB);
-        mockedResponse.value.int32Values.add(ASSOCIATED_CURRENT_USER);
-        when(mVehicleHal.get(
-                isPropertyWithValues(USER_IDENTIFICATION_ASSOCIATION, 42, 108, 1, KEY_FOB)))
-                        .thenReturn(null);
-
-        UserIdentificationGetRequest request = new UserIdentificationGetRequest();
-        request.userInfo.userId = 42;
-        request.userInfo.flags = 108;
-        request.numberAssociationTypes = 1;
-        request.associationTypes.add(KEY_FOB);
+        UserIdentificationGetRequest request = replyToValidGetUserIdentificationRequest(null);
 
         assertThat(mUserHalService.getUserAssociation(request)).isNull();
+
+        verifyValidGetUserIdentificationRequestMade();
     }
 
     @Test
     public void testGetUserAssociation_wrongNumberOfAssociationsOnResponse() {
-        VehiclePropValue mockedResponse = new VehiclePropValue();
-        mockedResponse.prop = USER_IDENTIFICATION_ASSOCIATION;
-        mockedResponse.value.int32Values.add(2); // 2 associations
-        mockedResponse.value.int32Values.add(KEY_FOB);
-        mockedResponse.value.int32Values.add(ASSOCIATED_CURRENT_USER);
-        mockedResponse.value.int32Values.add(CUSTOM_1);
-        mockedResponse.value.int32Values.add(ASSOCIATED_CURRENT_USER);
-        when(mVehicleHal.get(
-                isPropertyWithValues(USER_IDENTIFICATION_ASSOCIATION, 42, 108, 1, KEY_FOB)))
-                        .thenReturn(mockedResponse);
-
-        UserIdentificationGetRequest request = new UserIdentificationGetRequest();
-        request.userInfo.userId = 42;
-        request.userInfo.flags = 108;
-        request.numberAssociationTypes = 1;
-        request.associationTypes.add(KEY_FOB);
+        VehiclePropValue propResponse = new VehiclePropValue();
+        propResponse.prop = USER_IDENTIFICATION_ASSOCIATION;
+        propResponse.value.int32Values.add(DEFAULT_REQUEST_ID);
+        propResponse.value.int32Values.add(2); // 2 associations
+        propResponse.value.int32Values.add(KEY_FOB);
+        propResponse.value.int32Values.add(ASSOCIATED_CURRENT_USER);
+        propResponse.value.int32Values.add(CUSTOM_1);
+        propResponse.value.int32Values.add(ASSOCIATED_CURRENT_USER);
+        UserIdentificationGetRequest request = replyToValidGetUserIdentificationRequest(
+                propResponse);
 
         assertThat(mUserHalService.getUserAssociation(request)).isNull();
+
+        verifyValidGetUserIdentificationRequestMade();
     }
 
     @Test
     public void testGetUserAssociation_typesOnResponseMismatchTypesOnRequest() {
-        VehiclePropValue mockedResponse = new VehiclePropValue();
-        mockedResponse.prop = USER_IDENTIFICATION_ASSOCIATION;
-        mockedResponse.value.int32Values.add(1);
-        mockedResponse.value.int32Values.add(CUSTOM_1);
-        mockedResponse.value.int32Values.add(ASSOCIATED_CURRENT_USER);
-        when(mVehicleHal.get(
-                isPropertyWithValues(USER_IDENTIFICATION_ASSOCIATION, 42, 108, 1, KEY_FOB)))
-                        .thenReturn(mockedResponse);
-
-        UserIdentificationGetRequest request = new UserIdentificationGetRequest();
-        request.userInfo.userId = 42;
-        request.userInfo.flags = 108;
-        request.numberAssociationTypes = 1;
-        request.associationTypes.add(KEY_FOB);
+        VehiclePropValue propResponse = new VehiclePropValue();
+        propResponse.prop = USER_IDENTIFICATION_ASSOCIATION;
+        propResponse.value.int32Values.add(DEFAULT_REQUEST_ID);
+        propResponse.value.int32Values.add(1); // 1 association
+        propResponse.value.int32Values.add(CUSTOM_1);
+        propResponse.value.int32Values.add(ASSOCIATED_CURRENT_USER);
+        UserIdentificationGetRequest request = replyToValidGetUserIdentificationRequest(
+                propResponse);
 
         assertThat(mUserHalService.getUserAssociation(request)).isNull();
+
+        verifyValidGetUserIdentificationRequestMade();
+    }
+
+    @Test
+    public void testGetUserAssociation_requestIdMismatch() {
+        VehiclePropValue propResponse = new VehiclePropValue();
+        propResponse.prop = USER_IDENTIFICATION_ASSOCIATION;
+        propResponse.value.int32Values.add(DEFAULT_REQUEST_ID + 1);
+        propResponse.value.int32Values.add(1); // 1 association
+        propResponse.value.int32Values.add(KEY_FOB);
+        propResponse.value.int32Values.add(ASSOCIATED_CURRENT_USER);
+        UserIdentificationGetRequest request = replyToValidGetUserIdentificationRequest(
+                propResponse);
+
+        assertThat(mUserHalService.getUserAssociation(request)).isNull();
+
+        verifyValidGetUserIdentificationRequestMade();
     }
 
     @Test
     public void testGetUserAssociation_ok() {
-        VehiclePropValue mockedResponse = new VehiclePropValue();
-        mockedResponse.prop = USER_IDENTIFICATION_ASSOCIATION;
-        mockedResponse.value.int32Values.add(1); // 1 association
-        mockedResponse.value.int32Values.add(KEY_FOB);
-        mockedResponse.value.int32Values.add(ASSOCIATED_CURRENT_USER);
-        when(mVehicleHal.get(
-                isPropertyWithValues(USER_IDENTIFICATION_ASSOCIATION, 42, 108, 1, KEY_FOB)))
-                        .thenReturn(mockedResponse);
+        VehiclePropValue propResponse = new VehiclePropValue();
+        propResponse.prop = USER_IDENTIFICATION_ASSOCIATION;
+        propResponse.value.int32Values.add(DEFAULT_REQUEST_ID);
+        propResponse.value.int32Values.add(1); // 1 association
+        propResponse.value.int32Values.add(KEY_FOB);
+        propResponse.value.int32Values.add(ASSOCIATED_CURRENT_USER);
+        UserIdentificationGetRequest request = replyToValidGetUserIdentificationRequest(
+                propResponse);
 
-        UserIdentificationGetRequest request = new UserIdentificationGetRequest();
-        request.userInfo.userId = 42;
-        request.userInfo.flags = 108;
-        request.numberAssociationTypes = 1;
-        request.associationTypes.add(KEY_FOB);
+        UserIdentificationResponse response = mUserHalService.getUserAssociation(request);
 
-        UserIdentificationResponse actualResponse = mUserHalService.getUserAssociation(request);
+        assertThat(response.requestId).isEqualTo(DEFAULT_REQUEST_ID);
+        assertThat(response.numberAssociation).isEqualTo(1);
+        assertThat(response.associations).hasSize(1);
+        UserIdentificationAssociation actualAssociation = response.associations.get(0);
+        assertThat(actualAssociation.type).isEqualTo(KEY_FOB);
+        assertThat(actualAssociation.value).isEqualTo(ASSOCIATED_CURRENT_USER);
+    }
 
+    @Test
+    public void testSetUserAssociation_invalidTimeout() {
+        UserIdentificationSetRequest request = new UserIdentificationSetRequest();
+        assertThrows(IllegalArgumentException.class, () ->
+                mUserHalService.setUserAssociation(0, request, (i, r) -> { }));
+        assertThrows(IllegalArgumentException.class, () ->
+                mUserHalService.setUserAssociation(-1, request, (i, r) -> { }));
+    }
+
+    @Test
+    public void testSetUserAssociation_nullRequest() {
+        assertThrows(NullPointerException.class, () ->
+                mUserHalService.setUserAssociation(TIMEOUT_MS, null, (i, r) -> { }));
+    }
+
+    @Test
+    public void testSetUserAssociation_nullCallback() {
+        UserIdentificationSetRequest request = new UserIdentificationSetRequest();
+        assertThrows(NullPointerException.class, () ->
+                mUserHalService.setUserAssociation(TIMEOUT_MS, request, null));
+    }
+
+    @Test
+    public void testSetUserAssociation_requestWithDuplicatedTypes() {
+        UserIdentificationSetRequest request = new UserIdentificationSetRequest();
+        request.numberAssociations = 2;
+        UserIdentificationSetAssociation association1 = new UserIdentificationSetAssociation();
+        association1.type = KEY_FOB;
+        association1.value = ASSOCIATE_CURRENT_USER;
+        request.associations.add(association1);
+        request.associations.add(association1);
+
+        assertThrows(IllegalArgumentException.class, () ->
+                mUserHalService.setUserAssociation(TIMEOUT_MS, request, (i, r) -> { }));
+    }
+
+    @Test
+    public void testSetUserAssociation_halSetTimedOut() throws Exception {
+        UserIdentificationSetRequest request = validUserIdentificationSetRequest();
+        GenericHalCallback<UserIdentificationResponse> callback = new GenericHalCallback<>(
+                CALLBACK_TIMEOUT_TIMEOUT);
+        replySetPropertyWithTimeoutException(USER_IDENTIFICATION_ASSOCIATION);
+
+        mUserHalService.setUserAssociation(TIMEOUT_MS, request, callback);
+
+        callback.assertCalled();
+        assertCallbackStatus(callback, HalCallback.STATUS_HAL_SET_TIMEOUT);
+        assertThat(callback.response).isNull();
+
+        // Make sure the pending request was removed
+        SystemClock.sleep(CALLBACK_TIMEOUT_TIMEOUT);
+        callback.assertNotCalledAgain();
+    }
+
+    @Test
+    public void testSetUserAssociation_halDidNotReply() throws Exception {
+        UserIdentificationSetRequest request = validUserIdentificationSetRequest();
+        GenericHalCallback<UserIdentificationResponse> callback = new GenericHalCallback<>(
+                CALLBACK_TIMEOUT_TIMEOUT);
+
+        mUserHalService.setUserAssociation(TIMEOUT_MS, request, callback);
+
+        callback.assertCalled();
+        assertCallbackStatus(callback, HalCallback.STATUS_HAL_RESPONSE_TIMEOUT);
+        assertThat(callback.response).isNull();
+    }
+
+    @Test
+    public void testSetUserAssociation_secondCallFailWhilePending() throws Exception {
+        UserIdentificationSetRequest request = validUserIdentificationSetRequest();
+        GenericHalCallback<UserIdentificationResponse> callback1 = new GenericHalCallback<>(
+                CALLBACK_TIMEOUT_TIMEOUT);
+        GenericHalCallback<UserIdentificationResponse> callback2 = new GenericHalCallback<>(
+                CALLBACK_TIMEOUT_TIMEOUT);
+
+        mUserHalService.setUserAssociation(TIMEOUT_MS, request, callback1);
+        mUserHalService.setUserAssociation(TIMEOUT_MS, request, callback2);
+
+        callback1.assertCalled();
+        assertCallbackStatus(callback1, HalCallback.STATUS_HAL_RESPONSE_TIMEOUT);
+        assertThat(callback1.response).isNull();
+
+        callback2.assertCalled();
+        assertCallbackStatus(callback2, HalCallback.STATUS_CONCURRENT_OPERATION);
+        assertThat(callback1.response).isNull();
+    }
+
+    @Test
+    public void testSetUserAssociation_responseWithWrongRequestId() throws Exception {
+        VehiclePropValue propResponse = new VehiclePropValue();
+        propResponse.prop = USER_IDENTIFICATION_ASSOCIATION;
+        propResponse.value.int32Values.add(DEFAULT_REQUEST_ID + 1);
+        AtomicReference<VehiclePropValue> propRequest = replySetPropertyWithOnChangeEvent(
+                USER_IDENTIFICATION_ASSOCIATION, propResponse, /* rightRequestId= */ true);
+        UserIdentificationSetRequest request = replyToValidSetUserIdentificationRequest();
+        GenericHalCallback<UserIdentificationResponse> callback = new GenericHalCallback<>(
+                CALLBACK_TIMEOUT_TIMEOUT);
+
+        mUserHalService.setUserAssociation(TIMEOUT_MS, request, callback);
+
+        // Assert request
+        verifyValidSetUserIdentificationRequestMade(propRequest.get());
+        // Assert response
+        callback.assertCalled();
+        assertCallbackStatus(callback, HalCallback.STATUS_WRONG_HAL_RESPONSE);
+        assertThat(callback.response).isNull();
+    }
+
+    @Test
+    public void testSetUserAssociation_notEnoughValuesOnResponse() throws Exception {
+        VehiclePropValue propResponse = new VehiclePropValue();
+        propResponse.prop = USER_IDENTIFICATION_ASSOCIATION;
+        // need at least 4: requestId, number associations, type1, value1
+        propResponse.value.int32Values.add(1);
+        propResponse.value.int32Values.add(2);
+        propResponse.value.int32Values.add(3);
+
+        AtomicReference<VehiclePropValue> propRequest = replySetPropertyWithOnChangeEvent(
+                USER_IDENTIFICATION_ASSOCIATION, propResponse, /* rightRequestId= */ true);
+        UserIdentificationSetRequest request = replyToValidSetUserIdentificationRequest();
+        GenericHalCallback<UserIdentificationResponse> callback = new GenericHalCallback<>(
+                CALLBACK_TIMEOUT_TIMEOUT);
+
+        mUserHalService.setUserAssociation(TIMEOUT_MS, request, callback);
+
+        // Assert request
+        verifyValidSetUserIdentificationRequestMade(propRequest.get());
+        // Assert response
+        callback.assertCalled();
+        assertCallbackStatus(callback, HalCallback.STATUS_WRONG_HAL_RESPONSE);
+        assertThat(callback.response).isNull();
+    }
+
+    @Test
+    public void testSetUserAssociation_wrongNumberOfAssociationsOnResponse() throws Exception {
+        VehiclePropValue propResponse = new VehiclePropValue();
+        propResponse.prop = USER_IDENTIFICATION_ASSOCIATION;
+        propResponse.value.int32Values.add(DEFAULT_REQUEST_ID);
+        propResponse.value.int32Values.add(2); // 2 associations; request is just 1
+        propResponse.value.int32Values.add(KEY_FOB);
+        propResponse.value.int32Values.add(ASSOCIATED_CURRENT_USER);
+        propResponse.value.int32Values.add(CUSTOM_1);
+        propResponse.value.int32Values.add(ASSOCIATED_CURRENT_USER);
+
+        AtomicReference<VehiclePropValue> propRequest = replySetPropertyWithOnChangeEvent(
+                USER_IDENTIFICATION_ASSOCIATION, propResponse, /* rightRequestId= */ true);
+        UserIdentificationSetRequest request = replyToValidSetUserIdentificationRequest();
+        GenericHalCallback<UserIdentificationResponse> callback = new GenericHalCallback<>(
+                CALLBACK_TIMEOUT_TIMEOUT);
+
+        mUserHalService.setUserAssociation(TIMEOUT_MS, request, callback);
+
+        // Assert request
+        verifyValidSetUserIdentificationRequestMade(propRequest.get());
+        // Assert response
+        callback.assertCalled();
+        assertCallbackStatus(callback, HalCallback.STATUS_WRONG_HAL_RESPONSE);
+        assertThat(callback.response).isNull();
+    }
+
+    @Test
+    public void testSetUserAssociation_typeMismatchOnResponse() throws Exception {
+        VehiclePropValue propResponse = new VehiclePropValue();
+        propResponse.prop = USER_IDENTIFICATION_ASSOCIATION;
+        propResponse.value.int32Values.add(DEFAULT_REQUEST_ID);
+        propResponse.value.int32Values.add(1); // 1 association
+        propResponse.value.int32Values.add(CUSTOM_1); // request is KEY_FOB
+        propResponse.value.int32Values.add(ASSOCIATED_CURRENT_USER);
+
+        AtomicReference<VehiclePropValue> propRequest = replySetPropertyWithOnChangeEvent(
+                USER_IDENTIFICATION_ASSOCIATION, propResponse, /* rightRequestId= */ true);
+        UserIdentificationSetRequest request = replyToValidSetUserIdentificationRequest();
+        GenericHalCallback<UserIdentificationResponse> callback = new GenericHalCallback<>(
+                CALLBACK_TIMEOUT_TIMEOUT);
+
+        mUserHalService.setUserAssociation(TIMEOUT_MS, request, callback);
+
+        // Assert request
+        verifyValidSetUserIdentificationRequestMade(propRequest.get());
+        // Assert response
+        callback.assertCalled();
+        assertCallbackStatus(callback, HalCallback.STATUS_WRONG_HAL_RESPONSE);
+        assertThat(callback.response).isNull();
+    }
+
+    @Test
+    public void testSetUserAssociation_ok() throws Exception {
+        VehiclePropValue propResponse = new VehiclePropValue();
+        propResponse.prop = USER_IDENTIFICATION_ASSOCIATION;
+        propResponse.value.int32Values.add(DEFAULT_REQUEST_ID);
+        propResponse.value.int32Values.add(1); // 1 association
+        propResponse.value.int32Values.add(KEY_FOB);
+        propResponse.value.int32Values.add(ASSOCIATED_CURRENT_USER);
+
+        AtomicReference<VehiclePropValue> propRequest = replySetPropertyWithOnChangeEvent(
+                USER_IDENTIFICATION_ASSOCIATION, propResponse, /* rightRequestId= */ true);
+        UserIdentificationSetRequest request = replyToValidSetUserIdentificationRequest();
+        GenericHalCallback<UserIdentificationResponse> callback = new GenericHalCallback<>(
+                CALLBACK_TIMEOUT_TIMEOUT);
+
+        mUserHalService.setUserAssociation(TIMEOUT_MS, request, callback);
+
+        // Assert request
+        verifyValidSetUserIdentificationRequestMade(propRequest.get());
+        // Assert response
+        callback.assertCalled();
+        assertCallbackStatus(callback, HalCallback.STATUS_OK);
+
+        UserIdentificationResponse actualResponse = callback.response;
+
+        assertThat(actualResponse.requestId).isEqualTo(DEFAULT_REQUEST_ID);
         assertThat(actualResponse.numberAssociation).isEqualTo(1);
         assertThat(actualResponse.associations).hasSize(1);
         UserIdentificationAssociation actualAssociation = actualResponse.associations.get(0);
@@ -776,7 +992,8 @@
             int requestId = request.value.int32Values.get(0);
             int responseId = rightRequestId ? requestId : requestId + 1000;
             response.value.int32Values.set(0, responseId);
-            Log.d(TAG, "mockSetPropertyWithOnChange(): resp=" + response + " for req=" + request);
+            Log.d(TAG, "replySetPropertyWithOnChangeEvent(): resp=" + response + " for req="
+                    + request);
             mUserHalService.onHalEvents(Arrays.asList(response));
             return null;
         }).when(mVehicleHal).set(isProperty(prop));
@@ -791,6 +1008,54 @@
                 "PropId: 0x" + Integer.toHexString(prop))).when(mVehicleHal).set(isProperty(prop));
     }
 
+    /**
+     * Creates and set expectations for a valid request.
+     */
+    private UserIdentificationGetRequest replyToValidGetUserIdentificationRequest(
+            @NonNull VehiclePropValue response) {
+        mockNextRequestId(DEFAULT_REQUEST_ID);
+
+        UserIdentificationGetRequest request = new UserIdentificationGetRequest();
+        request.userInfo.userId = DEFAULT_USER_ID;
+        request.userInfo.flags = DEFAULT_USER_FLAGS;
+        request.numberAssociationTypes = 1;
+        request.associationTypes.add(KEY_FOB);
+
+        when(mVehicleHal.get(isPropertyWithValues(USER_IDENTIFICATION_ASSOCIATION,
+                DEFAULT_REQUEST_ID, DEFAULT_USER_ID, DEFAULT_USER_FLAGS,
+                /* numberAssociations= */ 1, KEY_FOB)))
+            .thenReturn(response);
+
+        return request;
+    }
+
+    /**
+     * Creates and set expectations for a valid request.
+     */
+    private UserIdentificationSetRequest replyToValidSetUserIdentificationRequest() {
+        mockNextRequestId(DEFAULT_REQUEST_ID);
+        return validUserIdentificationSetRequest();
+    }
+
+    /**
+     * Creates a valid request that can be used in test cases where its content is not asserted.
+     */
+    private UserIdentificationSetRequest validUserIdentificationSetRequest() {
+        UserIdentificationSetRequest request = new UserIdentificationSetRequest();
+        request.userInfo.userId = DEFAULT_USER_ID;
+        request.userInfo.flags = DEFAULT_USER_FLAGS;
+        request.numberAssociations = 1;
+        UserIdentificationSetAssociation association1 = new UserIdentificationSetAssociation();
+        association1.type = KEY_FOB;
+        association1.value = ASSOCIATE_CURRENT_USER;
+        request.associations.add(association1);
+        return request;
+    }
+
+    private void mockNextRequestId(int requestId) {
+        doReturn(requestId).when(mUserHalService).getNextRequestId();
+    }
+
     private void assertInitialUserInfoSetRequest(VehiclePropValue req, int requestType) {
         assertThat(req.value.int32Values.get(1)).isEqualTo(requestType);
         assertUsersInfo(req, mUsersInfo, 2);
@@ -806,8 +1071,7 @@
         assertUsersInfo(req, mUsersInfo, 4);
     }
 
-    private void assertCallbackStatus(GenericHalCallback callback,
-            int expectedStatus) {
+    private void assertCallbackStatus(GenericHalCallback<?> callback, int expectedStatus) {
         int actualStatus = callback.status;
         if (actualStatus == expectedStatus) return;
 
@@ -816,6 +1080,27 @@
                 + UserHalHelper.halCallbackStatusToString(actualStatus));
     }
 
+    /**
+     * Verifies {@code hal.get()} was called with the values used on
+     * {@link #replyToValidGetUserIdentificationRequest(VehiclePropValue)}.
+     */
+    private void verifyValidGetUserIdentificationRequestMade() {
+        verify(mVehicleHal).get(isPropertyWithValues(USER_IDENTIFICATION_ASSOCIATION,
+                DEFAULT_REQUEST_ID, DEFAULT_USER_ID, DEFAULT_USER_FLAGS,
+                /* numberAssociations= */ 1, KEY_FOB));
+    }
+
+    /**
+     * Verifies {@code hal.set()} was called with the values used on
+     * {@link #replyToValidSetUserIdentificationRequest(VehiclePropValue)}.
+     */
+    private void verifyValidSetUserIdentificationRequestMade(@NonNull VehiclePropValue request) {
+        assertThat(request.prop).isEqualTo(USER_IDENTIFICATION_ASSOCIATION);
+        assertThat(request.value.int32Values).containsExactly(DEFAULT_REQUEST_ID, DEFAULT_USER_ID,
+                DEFAULT_USER_FLAGS,
+                /* numberAssociations= */ 1, KEY_FOB, ASSOCIATE_CURRENT_USER);
+    }
+
     private final class GenericHalCallback<R> implements HalCallback<R> {
 
         private final CountDownLatch mLatch = new CountDownLatch(1);
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 23e1038..abfd48d 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
@@ -22,11 +22,13 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.notNull;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.testng.Assert.assertThrows;
 
@@ -37,6 +39,7 @@
 import android.car.ICarUserService;
 import android.car.test.mocks.AbstractExtendedMockitoTestCase;
 import android.car.user.CarUserManager;
+import android.car.user.CarUserManager.UserSwitchUiCallback;
 import android.car.user.GetUserIdentificationAssociationResponse;
 import android.car.user.UserSwitchResult;
 import android.content.pm.UserInfo;
@@ -140,6 +143,20 @@
     }
 
     @Test
+    public void testSetSwitchUserUICallback_success() throws Exception {
+        UserSwitchUiCallback callback = (u)-> { };
+
+        mMgr.setUserSwitchUiCallback(callback);
+
+        verify(mService).setUserSwitchUiCallback(any());
+    }
+
+    @Test
+    public void testSetSwitchUserUICallback_nullCallback() throws Exception {
+        assertThrows(IllegalArgumentException.class, () -> mMgr.setUserSwitchUiCallback(null));
+    }
+
+    @Test
     public void testGetUserIdentificationAssociation_nullTypes() throws Exception {
         assertThrows(IllegalArgumentException.class,
                 () -> mMgr.getUserIdentificationAssociation(null));
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 95bcc1a..f52e48d 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
@@ -93,6 +93,7 @@
 import com.android.car.hal.UserHalService;
 import com.android.internal.R;
 import com.android.internal.infra.AndroidFuture;
+import com.android.internal.os.IResultReceiver;
 import com.android.internal.util.Preconditions;
 
 import org.junit.Before;
@@ -139,6 +140,7 @@
     @Mock private Resources mMockedResources;
     @Mock private Drawable mMockedDrawable;
     @Mock private UserMetrics mUserMetrics;
+    @Mock IResultReceiver mSwitchUserUiReceiver;
 
     private final BlockingUserLifecycleListener mUserLifecycleListener =
             BlockingUserLifecycleListener.newDefaultListener();
@@ -935,6 +937,22 @@
     }
 
     @Test
+    public void testSetSwitchUserUI_receiverSetAndCalled() throws Exception {
+        mockExistingUsersAndCurrentUser(mAdminUser);
+        int requestId = 42;
+        mSwitchUserResponse.status = SwitchUserStatus.SUCCESS;
+        mSwitchUserResponse.requestId = requestId;
+        mockHalSwitch(mAdminUser.id, mGuestUser, mSwitchUserResponse);
+        mockAmSwitchUser(mGuestUser, true);
+
+        mCarUserService.setUserSwitchUiCallback(mSwitchUserUiReceiver);
+        mCarUserService.switchUser(mGuestUser.id, mAsyncCallTimeoutMs, mUserSwitchFuture);
+
+        // update current user due to successful user switch
+        verify(mSwitchUserUiReceiver).send(mGuestUser.id, null);
+    }
+
+    @Test
     public void testGetUserInfo_nullReceiver() throws Exception {
         assertThrows(NullPointerException.class, () -> mCarUserService
                 .getInitialUserInfo(mGetUserInfoRequestType, mAsyncCallTimeoutMs, null));
diff --git a/user/car-user-lib/src/android/car/userlib/CommonConstants.java b/user/car-user-lib/src/android/car/userlib/CommonConstants.java
new file mode 100644
index 0000000..ef9a354
--- /dev/null
+++ b/user/car-user-lib/src/android/car/userlib/CommonConstants.java
@@ -0,0 +1,42 @@
+/*
+ * 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.userlib;
+
+/**
+ * Provides constants used by both CarService and CarServiceHelper packages.
+ */
+public final class CommonConstants {
+
+    private CommonConstants() {
+        throw new UnsupportedOperationException("contains only static constants");
+    }
+
+    /**
+     * Constants used on {@link android.os.Bundle bundles} sent by
+     * {@link com.android.car.user.CarUserService} on binder calls.
+     */
+    public static final class CarUserServiceConstants {
+
+        public static final String BUNDLE_USER_ID = "user.id";
+        public static final String BUNDLE_USER_FLAGS = "user.flags";
+        public static final String BUNDLE_USER_NAME = "user.name";
+        public static final String BUNDLE_INITIAL_INFO_ACTION = "initial_info.action";
+
+        private CarUserServiceConstants() {
+            throw new UnsupportedOperationException("contains only static constants");
+        }
+    }
+}
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 33a62d9..109a55f 100644
--- a/user/car-user-lib/src/android/car/userlib/UserHalHelper.java
+++ b/user/car-user-lib/src/android/car/userlib/UserHalHelper.java
@@ -24,10 +24,13 @@
 import android.hardware.automotive.vehicle.V2_0.InitialUserInfoRequestType;
 import android.hardware.automotive.vehicle.V2_0.UserFlags;
 import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociation;
+import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationSetValue;
 import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationType;
 import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationValue;
 import android.hardware.automotive.vehicle.V2_0.UserIdentificationGetRequest;
 import android.hardware.automotive.vehicle.V2_0.UserIdentificationResponse;
+import android.hardware.automotive.vehicle.V2_0.UserIdentificationSetAssociation;
+import android.hardware.automotive.vehicle.V2_0.UserIdentificationSetRequest;
 import android.hardware.automotive.vehicle.V2_0.UsersInfo;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
 import android.os.SystemClock;
@@ -165,16 +168,26 @@
     }
 
     /**
-     * Creates VehiclePropValue from request.
+     * Creates a {@link VehiclePropValue} with the given {@code prop}, {@code requestId},
+     * and {@code requestType}.
      */
     @NonNull
-    public static VehiclePropValue createPropRequest(int requestId, int requestType,
-                int requestProp) {
+    public static VehiclePropValue createPropRequest(int prop, int requestId, int requestType) {
+        VehiclePropValue propRequest = createPropRequest(prop, requestId);
+        propRequest.value.int32Values.add(requestType);
+
+        return propRequest;
+    }
+
+    /**
+     * Creates a {@link VehiclePropValue} with the given {@code prop} and {@code requestId}.
+     */
+    @NonNull
+    public static VehiclePropValue createPropRequest(int prop, int requestId) {
         VehiclePropValue propRequest = new VehiclePropValue();
-        propRequest.prop = requestProp;
+        propRequest.prop = prop;
         propRequest.timestamp = SystemClock.elapsedRealtime();
         propRequest.value.int32Values.add(requestId);
-        propRequest.value.int32Values.add(requestType);
 
         return propRequest;
     }
@@ -184,24 +197,33 @@
      */
     public static void addUsersInfo(@NonNull VehiclePropValue propRequest,
                 @NonNull UsersInfo usersInfo) {
+        Objects.requireNonNull(propRequest, "VehiclePropValue cannot be null");
         Objects.requireNonNull(usersInfo.currentUser, "Current user cannot be null");
-
-        propRequest.value.int32Values.add(usersInfo.currentUser.userId);
-        propRequest.value.int32Values.add(usersInfo.currentUser.flags);
-
         checkArgument(usersInfo.numberUsers == usersInfo.existingUsers.size(),
                 "Number of existing users info does not match numberUsers");
 
+        addUserInfo(propRequest, usersInfo.currentUser);
         propRequest.value.int32Values.add(usersInfo.numberUsers);
         for (int i = 0; i < usersInfo.numberUsers; i++) {
             android.hardware.automotive.vehicle.V2_0.UserInfo userInfo =
                     usersInfo.existingUsers.get(i);
-            propRequest.value.int32Values.add(userInfo.userId);
-            propRequest.value.int32Values.add(userInfo.flags);
+            addUserInfo(propRequest, userInfo);
         }
     }
 
     /**
+     * Adds user information to prop value.
+     */
+    public static void addUserInfo(@NonNull VehiclePropValue propRequest,
+            @NonNull android.hardware.automotive.vehicle.V2_0.UserInfo userInfo) {
+        Objects.requireNonNull(propRequest, "VehiclePropValue cannot be null");
+        Objects.requireNonNull(userInfo, "UserInfo cannot be null");
+
+        propRequest.value.int32Values.add(userInfo.userId);
+        propRequest.value.int32Values.add(userInfo.flags);
+    }
+
+    /**
      * Checks if the given {@code value} is a valid {@link UserIdentificationAssociationType}.
      */
     public static boolean isValidUserIdentificationAssociationType(int type) {
@@ -231,29 +253,47 @@
     }
 
     /**
+     * Checks if the given {@code value} is a valid {@link UserIdentificationAssociationSetValue}.
+     */
+    public static boolean isValidUserIdentificationAssociationSetValue(int value) {
+        switch(value) {
+            case UserIdentificationAssociationSetValue.ASSOCIATE_CURRENT_USER:
+            case UserIdentificationAssociationSetValue.DISASSOCIATE_CURRENT_USER:
+            case UserIdentificationAssociationSetValue.DISASSOCIATE_ALL_USERS:
+                return true;
+        }
+        return false;
+    }
+
+    /**
      * Creates a {@link UserIdentificationResponse} from a generic {@link VehiclePropValue} sent by
      * HAL.
      */
     @NonNull
-    public static UserIdentificationResponse toUserIdentificationGetResponse(
+    public static UserIdentificationResponse toUserIdentificationResponse(
             @NonNull VehiclePropValue prop) {
         Objects.requireNonNull(prop, "prop cannot be null");
         checkArgument(prop.prop == USER_IDENTIFICATION_ASSOCIATION_PROPERTY,
                 "invalid prop on %s", prop);
-        checkArgument(prop.value.int32Values.size() > 1,
-                "not enough int32Values on %s", prop);
+        // need at least 4: request_id, number associations, type1, value1
+        checkArgument(prop.value.int32Values.size() >= 4, "not enough int32Values on %s", prop);
 
-        int numberAssociations = prop.value.int32Values.get(0);
+        int requestId = prop.value.int32Values.get(0);
+        checkArgument(requestId > 0, "invalid request id (%d) on %s", requestId, prop);
+
+        int numberAssociations = prop.value.int32Values.get(1);
         checkArgument(numberAssociations >= 1, "invalid number of items on %s", prop);
-        int numberItems = prop.value.int32Values.size() - 1;
+        int numberOfNonItems = 2; // requestId and size
+        int numberItems = prop.value.int32Values.size() - numberOfNonItems;
         checkArgument(numberItems == numberAssociations * 2, "number of items mismatch on %s",
                 prop);
 
         UserIdentificationResponse response = new UserIdentificationResponse();
+        response.requestId = requestId;
         response.errorMessage = prop.value.stringValue;
 
         response.numberAssociation = numberAssociations;
-        int i = 1;
+        int i = numberOfNonItems;
         for (int a = 0; a < numberAssociations; a++) {
             int index;
             UserIdentificationAssociation association = new UserIdentificationAssociation();
@@ -283,11 +323,11 @@
                 "invalid number of association types mismatch on %s", request);
         checkArgument(request.numberAssociationTypes == request.associationTypes.size(),
                 "number of association types mismatch on %s", request);
+        checkArgument(request.requestId > 0, "invalid requestId on %s", request);
 
-        VehiclePropValue propValue = new VehiclePropValue();
-        propValue.prop = USER_IDENTIFICATION_ASSOCIATION_PROPERTY;
-        propValue.value.int32Values.add(request.userInfo.userId);
-        propValue.value.int32Values.add(request.userInfo.flags);
+        VehiclePropValue propValue = createPropRequest(USER_IDENTIFICATION_ASSOCIATION_PROPERTY,
+                request.requestId);
+        addUserInfo(propValue, request.userInfo);
         propValue.value.int32Values.add(request.numberAssociationTypes);
 
         for (int i = 0; i < request.numberAssociationTypes; i++) {
@@ -300,6 +340,38 @@
         return propValue;
     }
 
+    /**
+     * Creates a generic {@link VehiclePropValue} (that can be sent to HAL) from a
+     * {@link UserIdentificationSetRequest}.
+     */
+    @NonNull
+    public static VehiclePropValue toVehiclePropValue(
+            @NonNull UserIdentificationSetRequest request) {
+        Objects.requireNonNull(request, "request cannot be null");
+        checkArgument(request.numberAssociations > 0,
+                "invalid number of associations  mismatch on %s", request);
+        checkArgument(request.numberAssociations == request.associations.size(),
+                "number of associations mismatch on %s", request);
+        checkArgument(request.requestId > 0, "invalid requestId on %s", request);
+
+        VehiclePropValue propValue = createPropRequest(USER_IDENTIFICATION_ASSOCIATION_PROPERTY,
+                request.requestId);
+        addUserInfo(propValue, request.userInfo);
+        propValue.value.int32Values.add(request.numberAssociations);
+
+        for (int i = 0; i < request.numberAssociations; i++) {
+            UserIdentificationSetAssociation association = request.associations.get(i);
+            checkArgument(isValidUserIdentificationAssociationType(association.type),
+                    "invalid type at index %d on %s", i, request);
+            propValue.value.int32Values.add(association.type);
+            checkArgument(isValidUserIdentificationAssociationSetValue(association.value),
+                    "invalid value at index %d on %s", i, request);
+            propValue.value.int32Values.add(association.value);
+        }
+
+        return propValue;
+    }
+
     private UserHalHelper() {
         throw new UnsupportedOperationException("contains only static methods");
     }
diff --git a/watchdog/product/carwatchdog.mk b/watchdog/product/carwatchdog.mk
index 028f67b..6829177 100644
--- a/watchdog/product/carwatchdog.mk
+++ b/watchdog/product/carwatchdog.mk
@@ -16,10 +16,10 @@
 PRODUCT_PACKAGES += carwatchdogd
 
 # SELinux public policies for car watchdog services
-PRODUCT_PUBLIC_SEPOLICY_DIRS += packages/services/Car/watchdog/sepolicy/public
+BOARD_PLAT_PUBLIC_SEPOLICY_DIR += packages/services/Car/watchdog/sepolicy/public
 
 # SELinux private policies for car watchdog services
-PRODUCT_PRIVATE_SEPOLICY_DIRS += packages/services/Car/watchdog/sepolicy/private
+BOARD_PLAT_PRIVATE_SEPOLICY_DIR += packages/services/Car/watchdog/sepolicy/private
 
 # Include carwatchdog testclient if the build is userdebug or eng
 ifneq (,$(filter userdebug eng, $(TARGET_BUILD_VARIANT)))
diff --git a/watchdog/server/src/IoPerfCollection.cpp b/watchdog/server/src/IoPerfCollection.cpp
index a97858b..2799417 100644
--- a/watchdog/server/src/IoPerfCollection.cpp
+++ b/watchdog/server/src/IoPerfCollection.cpp
@@ -55,6 +55,7 @@
 using android::base::Result;
 using android::base::Split;
 using android::base::StringAppendF;
+using android::base::StringPrintf;
 using android::base::WriteStringToFd;
 using android::content::pm::IPackageManagerNative;
 
@@ -76,6 +77,24 @@
 
 const std::string kDumpMajorDelimiter = std::string(100, '-') + "\n";
 
+constexpr const char* kHelpText =
+        "\nCustom I/O performance data collection dump options:\n"
+        "%s: Starts custom I/O performance data collection. Customize the collection behavior with "
+        "the following optional arguments:\n"
+        "\t%s <seconds>: Modifies the collection interval. Default behavior is to collect once "
+        "every %lld seconds.\n"
+        "\t%s <seconds>: Modifies the maximum collection duration. Default behavior is to collect "
+        "until %ld minutes before automatically stopping the custom collection and discarding "
+        "the collected data.\n"
+        "\t%s <package name>,<package, name>,...: Comma-separated value containing package names. "
+        "When provided, the results are filtered only to the provided package names. Default "
+        "behavior is to list the results for the top %d packages.\n"
+        "%s: Stops custom I/O performance data collection and generates a dump of "
+        "the collection report.\n\n"
+        "When no options are specified, the carwatchdog report contains the I/O performance "
+        "data collected during boot-time and over the last %ld minutes before the report "
+        "generation.";
+
 double percentage(uint64_t numer, uint64_t denom) {
     return denom == 0 ? 0.0 : (static_cast<double>(numer) / static_cast<double>(denom)) * 100.0;
 }
@@ -406,8 +425,8 @@
 
     if (args[0] == String16(kStartCustomCollectionFlag)) {
         if (args.size() > 7) {
-            return Error(INVALID_OPERATION) << "Number of arguments to start custom "
-                                            << "I/O performance data collection cannot exceed 7";
+            return Error(BAD_VALUE) << "Number of arguments to start custom I/O performance data "
+                                    << "collection cannot exceed 7";
         }
         std::chrono::nanoseconds interval = kCustomCollectionInterval;
         std::chrono::nanoseconds maxDuration = kCustomCollectionDuration;
@@ -416,7 +435,7 @@
             if (args[i] == String16(kIntervalFlag)) {
                 const auto& ret = parseSecondsFlag(args, i + 1);
                 if (!ret) {
-                    return Error(FAILED_TRANSACTION)
+                    return Error(BAD_VALUE)
                             << "Failed to parse " << kIntervalFlag << ": " << ret.error();
                 }
                 interval = std::chrono::duration_cast<std::chrono::nanoseconds>(*ret);
@@ -426,7 +445,7 @@
             if (args[i] == String16(kMaxDurationFlag)) {
                 const auto& ret = parseSecondsFlag(args, i + 1);
                 if (!ret) {
-                    return Error(FAILED_TRANSACTION)
+                    return Error(BAD_VALUE)
                             << "Failed to parse " << kMaxDurationFlag << ": " << ret.error();
                 }
                 maxDuration = std::chrono::duration_cast<std::chrono::nanoseconds>(*ret);
@@ -435,7 +454,7 @@
             }
             if (args[i] == String16(kFilterPackagesFlag)) {
                 if (args.size() < i + 1) {
-                    return Error(FAILED_TRANSACTION)
+                    return Error(BAD_VALUE)
                             << "Must provide value for '" << kFilterPackagesFlag << "' flag";
                 }
                 std::vector<std::string> packages =
@@ -447,12 +466,13 @@
             }
             ALOGW("Unknown flag %s provided to start custom I/O performance data collection",
                   String8(args[i]).string());
-            return Error(INVALID_OPERATION) << "Unknown flag " << String8(args[i]).string()
-                                            << " provided to start custom I/O performance data "
-                                            << "collection";
+            return Error(BAD_VALUE) << "Unknown flag " << String8(args[i]).string()
+                                    << " provided to start custom I/O performance data "
+                                    << "collection";
         }
         const auto& ret = startCustomCollection(interval, maxDuration, filterPackages);
         if (!ret) {
+            WriteStringToFd(ret.error().message(), fd);
             return ret;
         }
         return {};
@@ -460,19 +480,37 @@
 
     if (args[0] == String16(kEndCustomCollectionFlag)) {
         if (args.size() != 1) {
-            ALOGW("Number of arguments to end custom I/O performance data collection cannot "
-                  "exceed 1");
+            ALOGW("Number of arguments to stop custom I/O performance data collection cannot "
+                  "exceed 1. Stopping the data collection.");
+            WriteStringToFd("Number of arguments to stop custom I/O performance data collection "
+                            "cannot exceed 1. Stopping the data collection.",
+                            fd);
         }
-        const auto& ret = endCustomCollection(fd);
-        if (!ret) {
-            return ret;
-        }
-        return {};
+        return endCustomCollection(fd);
     }
 
-    return Error(INVALID_OPERATION)
-            << "Dump arguments start neither with " << kStartCustomCollectionFlag << " nor with "
-            << kEndCustomCollectionFlag << " flags";
+    return Error(BAD_VALUE) << "I/O perf collection dump arguments start neither with "
+                            << kStartCustomCollectionFlag << " nor with "
+                            << kEndCustomCollectionFlag << " flags";
+}
+
+bool IoPerfCollection::dumpHelpText(int fd) {
+    long periodicCacheMinutes =
+            (std::chrono::duration_cast<std::chrono::seconds>(mPeriodicCollection.interval)
+                     .count() *
+             mPeriodicCollection.maxCacheSize) /
+            60;
+    return WriteStringToFd(StringPrintf(kHelpText, kStartCustomCollectionFlag, kIntervalFlag,
+                                        std::chrono::duration_cast<std::chrono::seconds>(
+                                                kCustomCollectionInterval)
+                                                .count(),
+                                        kMaxDurationFlag,
+                                        std::chrono::duration_cast<std::chrono::minutes>(
+                                                kCustomCollectionDuration)
+                                                .count(),
+                                        kFilterPackagesFlag, mTopNStatsPerCategory,
+                                        kEndCustomCollectionFlag, periodicCacheMinutes),
+                           fd);
 }
 
 Result<void> IoPerfCollection::dumpCollection(int fd) {
diff --git a/watchdog/server/src/IoPerfCollection.h b/watchdog/server/src/IoPerfCollection.h
index 9cde199..4cccb05 100644
--- a/watchdog/server/src/IoPerfCollection.h
+++ b/watchdog/server/src/IoPerfCollection.h
@@ -190,6 +190,9 @@
     // Returns any error observed during the dump generation.
     virtual android::base::Result<void> dump(int fd, const Vector<String16>& args);
 
+    // Dumps the help text.
+    bool dumpHelpText(int fd);
+
 private:
     // Generates a dump from the boot-time and periodic collection events.
     android::base::Result<void> dumpCollection(int fd);
diff --git a/watchdog/server/src/WatchdogBinderMediator.cpp b/watchdog/server/src/WatchdogBinderMediator.cpp
index da4ea29..0a482fd 100644
--- a/watchdog/server/src/WatchdogBinderMediator.cpp
+++ b/watchdog/server/src/WatchdogBinderMediator.cpp
@@ -18,6 +18,7 @@
 
 #include "WatchdogBinderMediator.h"
 
+#include <android-base/file.h>
 #include <android-base/parseint.h>
 #include <android-base/stringprintf.h>
 #include <android/automotive/watchdog/BootPhase.h>
@@ -38,10 +39,19 @@
 using android::base::ParseUint;
 using android::base::Result;
 using android::base::StringPrintf;
+using android::base::WriteStringToFd;
 using android::binder::Status;
 
 namespace {
 
+constexpr const char* kHelpFlag = "--help";
+constexpr const char* kHelpShortFlag = "-h";
+constexpr const char* kHelpText =
+        "CarWatchdog daemon dumpsys help page:\n"
+        "Format: dumpsys android.automotive.watchdog.ICarWatchdog/default [options]\n\n"
+        "%s or %s: Displays this help text.\n"
+        "When no options are specified, carwatchdog report is generated.\n";
+
 Status checkSystemPermission() {
     if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
         return Status::fromExceptionCode(Status::EX_SECURITY,
@@ -94,21 +104,48 @@
         }
         return OK;
     }
+
+    if (args[0] == String16(kHelpFlag) || args[0] == String16(kHelpShortFlag)) {
+        if (!dumpHelpText(fd, "")) {
+            ALOGW("Failed to write help text to fd");
+            return FAILED_TRANSACTION;
+        }
+        return OK;
+    }
+
     if (args[0] == String16(kStartCustomCollectionFlag) ||
         args[0] == String16(kEndCustomCollectionFlag)) {
         auto ret = mIoPerfCollection->dump(fd, args);
-        std::string mode = args[0] == String16(kStartCustomCollectionFlag) ? "start" : "end";
         if (!ret.ok()) {
-            ALOGW("Failed to %s custom I/O perf collection: %s", mode.c_str(),
-                  ret.error().message().c_str());
+            std::string mode = args[0] == String16(kStartCustomCollectionFlag) ? "start" : "end";
+            std::string errorMsg = StringPrintf("Failed to %s custom I/O perf collection: %s",
+                                                mode.c_str(), ret.error().message().c_str());
+            if (ret.error().code() == BAD_VALUE) {
+                dumpHelpText(fd, errorMsg);
+            } else {
+                ALOGW("%s", errorMsg.c_str());
+            }
             return ret.error().code();
         }
         return OK;
     }
-    ALOGW("Invalid dump arguments");
+    dumpHelpText(fd, "Invalid dump arguments");
     return INVALID_OPERATION;
 }
 
+bool WatchdogBinderMediator::dumpHelpText(int fd, std::string errorMsg) {
+    if (!errorMsg.empty()) {
+        ALOGW("Error: %s", errorMsg.c_str());
+        if (!WriteStringToFd(StringPrintf("Error: %s\n\n", errorMsg.c_str()), fd)) {
+            ALOGW("Failed to write error message to fd");
+            return false;
+        }
+    }
+
+    return WriteStringToFd(StringPrintf(kHelpText, kHelpFlag, kHelpShortFlag), fd) &&
+            mIoPerfCollection->dumpHelpText(fd);
+}
+
 Status WatchdogBinderMediator::registerMediator(const sp<ICarWatchdogClient>& mediator) {
     Status status = checkSystemPermission();
     if (!status.isOk()) {
diff --git a/watchdog/server/src/WatchdogBinderMediator.h b/watchdog/server/src/WatchdogBinderMediator.h
index a3b19a2..530659b 100644
--- a/watchdog/server/src/WatchdogBinderMediator.h
+++ b/watchdog/server/src/WatchdogBinderMediator.h
@@ -83,6 +83,8 @@
     void binderDied(const android::wp<IBinder>& who) override {
         return mWatchdogProcessService->binderDied(who);
     }
+    bool dumpHelpText(int fd, std::string errorMsg);
+
     android::sp<WatchdogProcessService> mWatchdogProcessService;
     android::sp<IoPerfCollection> mIoPerfCollection;