Merge "Modifies HalCamera::clientStreamEnding() method" into rvc-dev
diff --git a/car-lib/src/android/car/ICarUserService.aidl b/car-lib/src/android/car/ICarUserService.aidl
index b3823b3..5b58b54 100644
--- a/car-lib/src/android/car/ICarUserService.aidl
+++ b/car-lib/src/android/car/ICarUserService.aidl
@@ -44,4 +44,5 @@
     UserIdentificationAssociationResponse getUserIdentificationAssociation(in int[] types);
     void setUserIdentificationAssociation(int timeoutMs, in int[] types, in int[] values,
       in AndroidFuture<UserIdentificationAssociationResponse> result);
+    boolean isUserHalUserAssociationSupported();
 }
diff --git a/car-lib/src/android/car/user/CarUserManager.java b/car-lib/src/android/car/user/CarUserManager.java
index a4daa42..c3b00f3 100644
--- a/car-lib/src/android/car/user/CarUserManager.java
+++ b/car-lib/src/android/car/user/CarUserManager.java
@@ -377,6 +377,19 @@
     }
 
     /**
+     * Check if user hal supports user association.
+     *
+     * @hide
+     */
+    public boolean isUserHalUserAssociationSupported() {
+        try {
+            return mService.isUserHalUserAssociationSupported();
+        } catch (RemoteException e) {
+            return handleRemoteExceptionFromCarService(e, false);
+        }
+    }
+
+    /**
      * Gets the user authentication types associated with this manager's user.
      *
      * @hide
diff --git a/evs/apps/default/RenderPixelCopy.cpp b/evs/apps/default/RenderPixelCopy.cpp
index 186269f..dde2d2f 100644
--- a/evs/apps/default/RenderPixelCopy.cpp
+++ b/evs/apps/default/RenderPixelCopy.cpp
@@ -90,7 +90,7 @@
             if (mStreamHandler->newFrameAvailable()) {
                 const BufferDesc& srcBuffer = mStreamHandler->getNewFrame();
                 const AHardwareBuffer_Desc* pSrcDesc =
-                    reinterpret_cast<const AHardwareBuffer_Desc *>(&tgtBuffer.buffer.description);
+                    reinterpret_cast<const AHardwareBuffer_Desc *>(&srcBuffer.buffer.description);
 
                 // Lock our source buffer for reading (current expectation are for this to be NV21 format)
                 sp<android::GraphicBuffer> src = new android::GraphicBuffer(srcBuffer.buffer.nativeHandle,
@@ -101,35 +101,37 @@
                                                                             pSrcDesc->layers,
                                                                             pSrcDesc->usage,
                                                                             pSrcDesc->stride);
+
                 unsigned char* srcPixels = nullptr;
                 src->lock(GRALLOC_USAGE_SW_READ_OFTEN, (void**)&srcPixels);
-                if (!srcPixels) {
+                if (srcPixels != nullptr) {
+                    // Make sure we don't run off the end of either buffer
+                    const unsigned width  = std::min(pTgtDesc->width,
+                                                     pSrcDesc->width);
+                    const unsigned height = std::min(pTgtDesc->height,
+                                                     pSrcDesc->height);
+
+                    if (pSrcDesc->format == HAL_PIXEL_FORMAT_YCRCB_420_SP) {   // 420SP == NV21
+                        copyNV21toRGB32(width, height,
+                                        srcPixels,
+                                        tgtPixels, pTgtDesc->stride);
+                    } else if (pSrcDesc->format == HAL_PIXEL_FORMAT_YV12) { // YUV_420P == YV12
+                        copyYV12toRGB32(width, height,
+                                        srcPixels,
+                                        tgtPixels, pTgtDesc->stride);
+                    } else if (pSrcDesc->format == HAL_PIXEL_FORMAT_YCBCR_422_I) { // YUYV
+                        copyYUYVtoRGB32(width, height,
+                                        srcPixels, pSrcDesc->stride,
+                                        tgtPixels, pTgtDesc->stride);
+                    } else if (pSrcDesc->format == pTgtDesc->format) {  // 32bit RGBA
+                        copyMatchedInterleavedFormats(width, height,
+                                                      srcPixels, pSrcDesc->stride,
+                                                      tgtPixels, pTgtDesc->stride,
+                                                      tgtBuffer.pixelSize);
+                    }
+                } else {
                     LOG(ERROR) << "Failed to get pointer into src image data";
-                }
-
-                // Make sure we don't run off the end of either buffer
-                const unsigned width  = std::min(pTgtDesc->width,
-                                                 pSrcDesc->width);
-                const unsigned height = std::min(pTgtDesc->height,
-                                                 pSrcDesc->height);
-
-                if (pSrcDesc->format == HAL_PIXEL_FORMAT_YCRCB_420_SP) {   // 420SP == NV21
-                    copyNV21toRGB32(width, height,
-                                    srcPixels,
-                                    tgtPixels, pTgtDesc->stride);
-                } else if (pSrcDesc->format == HAL_PIXEL_FORMAT_YV12) { // YUV_420P == YV12
-                    copyYV12toRGB32(width, height,
-                                    srcPixels,
-                                    tgtPixels, pTgtDesc->stride);
-                } else if (pSrcDesc->format == HAL_PIXEL_FORMAT_YCBCR_422_I) { // YUYV
-                    copyYUYVtoRGB32(width, height,
-                                    srcPixels, pSrcDesc->stride,
-                                    tgtPixels, pTgtDesc->stride);
-                } else if (pSrcDesc->format == pTgtDesc->format) {  // 32bit RGBA
-                    copyMatchedInterleavedFormats(width, height,
-                                                  srcPixels, pSrcDesc->stride,
-                                                  tgtPixels, pTgtDesc->stride,
-                                                  tgtBuffer.pixelSize);
+                    success = false;
                 }
 
                 mStreamHandler->doneWithFrame(srcBuffer);
diff --git a/service/src/com/android/car/CarPowerManagementService.java b/service/src/com/android/car/CarPowerManagementService.java
index aef68cd..da88468 100644
--- a/service/src/com/android/car/CarPowerManagementService.java
+++ b/service/src/com/android/car/CarPowerManagementService.java
@@ -512,6 +512,9 @@
                 case HalCallback.STATUS_WRONG_HAL_RESPONSE:
                     switchUserOnResumeUserHalFallback("wrong response");
                     return;
+                case HalCallback.STATUS_HAL_NOT_SUPPORTED:
+                    switchUserOnResumeUserHalFallback("Hal not supported");
+                    return;
                 case HalCallback.STATUS_OK:
                     if (response == null) {
                         switchUserOnResumeUserHalFallback("no response");
diff --git a/service/src/com/android/car/hal/UserHalService.java b/service/src/com/android/car/hal/UserHalService.java
index 645f563..2c88aed 100644
--- a/service/src/com/android/car/hal/UserHalService.java
+++ b/service/src/com/android/car/hal/UserHalService.java
@@ -81,14 +81,24 @@
     private static final String TAG = UserHalService.class.getSimpleName();
 
     private static final String UNSUPPORTED_MSG = "Vehicle HAL does not support user management";
+    private static final String USER_ASSOCIATION_UNSUPPORTED_MSG =
+            "Vehicle HAL does not support user association";
 
     private static final int[] SUPPORTED_PROPERTIES = new int[]{
-            INITIAL_USER_INFO,
-            SWITCH_USER,
             CREATE_USER,
+            INITIAL_USER_INFO,
+            REMOVE_USER,
+            SWITCH_USER,
             USER_IDENTIFICATION_ASSOCIATION
     };
 
+    private static final int[] CORE_PROPERTIES = new int[]{
+            CREATE_USER,
+            INITIAL_USER_INFO,
+            REMOVE_USER,
+            SWITCH_USER,
+    };
+
     private static final boolean DBG = false;
 
     private final Object mLock = new Object();
@@ -208,11 +218,29 @@
     }
 
     /**
-     * Checks if the Vehicle HAL supports user management.
+     * Checks if the Vehicle HAL supports core user management actions.
      */
     public boolean isSupported() {
         synchronized (mLock) {
-            return mProperties != null;
+            if (mProperties == null) return false;
+
+            for (int i = 0; i < CORE_PROPERTIES.length; i++) {
+                if (mProperties.get(CORE_PROPERTIES[i]) == null) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    /**
+     * Checks if the Vehicle HAL supports core user management actions.
+     */
+    public boolean isUserAssociationSupported() {
+        synchronized (mLock) {
+            if (mProperties == null) return false;
+            if (mProperties.get(USER_IDENTIFICATION_ASSOCIATION) == null) return false;
+            return true;
         }
     }
 
@@ -221,6 +249,12 @@
         Preconditions.checkState(isSupported(), UNSUPPORTED_MSG);
     }
 
+    @GuardedBy("mLock")
+    private void checkUserAssociationSupportedLocked() {
+        Preconditions.checkState(isUserAssociationSupported(), USER_ASSOCIATION_UNSUPPORTED_MSG);
+    }
+
+
     /**
      * Calls HAL to asynchronously get info about the initial user.
      *
@@ -372,7 +406,7 @@
     public void postSwitchResponse(@NonNull SwitchUserRequest request) {
         EventLog.writeEvent(EventLogTags.CAR_USER_HAL_POST_SWITCH_USER_REQ, request.requestId,
                 request.targetUser.userId, request.usersInfo.currentUser.userId);
-        if (DBG) Log.d(TAG, "postSwitchResponse(" + request.targetUser.userId + ")");
+        if (DBG) Log.d(TAG, "postSwitchResponse(" + request + ")");
 
         VehiclePropValue propRequest;
         synchronized (mLock) {
@@ -438,6 +472,9 @@
     public UserIdentificationResponse getUserAssociation(
             @NonNull UserIdentificationGetRequest request) {
         Objects.requireNonNull(request, "request cannot be null");
+        synchronized (mLock) {
+            checkUserAssociationSupportedLocked();
+        }
 
         // Check that it doesn't have dupes
         SparseBooleanArray types = new SparseBooleanArray(request.numberAssociationTypes);
@@ -509,10 +546,10 @@
      */
     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");
+        if (DBG) Log.d(TAG, "setUserAssociation(" + request + ")");
 
         // Check that it doesn't have dupes
         SparseBooleanArray types = new SparseBooleanArray(request.numberAssociations);
@@ -526,7 +563,7 @@
         VehiclePropValue propRequest;
         int requestId;
         synchronized (mLock) {
-            checkSupportedLocked();
+            checkUserAssociationSupportedLocked();
             if (hasPendingRequestLocked(UserIdentificationResponse.class, callback)) return;
             requestId = request.requestId = getNextRequestId();
             propRequest = UserHalHelper.toVehiclePropValue(request);
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index 8e08b28..b9c54a0 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -134,6 +134,8 @@
     public static final String BUNDLE_INITIAL_INFO_ACTION =
             CarUserServiceConstants.BUNDLE_INITIAL_INFO_ACTION;
 
+    public static final String VEHICLE_HAL_NOT_SUPPORTED = "Vehicle Hal not supported.";
+
     private final Context mContext;
     private final CarUserManagerHelper mCarUserManagerHelper;
     private final IActivityManager mAm;
@@ -627,6 +629,10 @@
                 timeoutMs);
         Objects.requireNonNull(receiver, "receiver cannot be null");
         checkManageUsersPermission("getInitialInfo");
+        if (!isUserHalSupported()) {
+            sendResult(receiver, HalCallback.STATUS_HAL_NOT_SUPPORTED, null);
+            return;
+        }
         UsersInfo usersInfo = UserHalHelper.newUsersInfo(mUserManager);
         mHal.getInitialUserInfo(requestType, timeoutMs, usersInfo, (status, resp) -> {
             Bundle resultData = null;
@@ -729,6 +735,10 @@
                 mHalTimeoutMs);
         Objects.requireNonNull(callback, "callback cannot be null");
         checkManageUsersPermission("getInitialUserInfo");
+        if (!isUserHalSupported()) {
+            callback.onResponse(HalCallback.STATUS_HAL_NOT_SUPPORTED, null);
+            return;
+        }
         UsersInfo usersInfo = UserHalHelper.newUsersInfo(mUserManager);
         mHal.getInitialUserInfo(requestType, mHalTimeoutMs, usersInfo, callback);
     }
@@ -790,6 +800,22 @@
             return;
         }
 
+        // If User Hal is not supported, just android user switch.
+        if (!isUserHalSupported()) {
+            try {
+                if (mAm.switchUser(targetUserId)) {
+                    sendUserSwitchResult(receiver, UserSwitchResult.STATUS_SUCCESSFUL);
+                    return;
+                }
+            } catch (RemoteException e) {
+                // ignore
+                Log.w(TAG_USER,
+                        "error while switching user " + targetUser.toFullString(), e);
+            }
+            sendUserSwitchResult(receiver, UserSwitchResult.STATUS_ANDROID_FAILURE);
+            return;
+        }
+
         synchronized (mLockUser) {
             if (Log.isLoggable(TAG_USER, Log.DEBUG)) {
                 Log.d(TAG_USER, "switchUser(" + targetUserId + "): currentuser=" + currentUser
@@ -937,10 +963,13 @@
             return logAndGetResults(userId, UserRemovalResult.STATUS_ANDROID_FAILURE);
         }
 
-        RemoveUserRequest request = new RemoveUserRequest();
-        request.removedUserInfo = halUser;
-        request.usersInfo = usersInfo;
-        mHal.removeUser(request);
+        if (isUserHalSupported()) {
+            RemoveUserRequest request = new RemoveUserRequest();
+            request.removedUserInfo = halUser;
+            request.usersInfo = usersInfo;
+            mHal.removeUser(request);
+        }
+
         return logAndGetResults(userId, UserRemovalResult.STATUS_SUCCESSFUL);
     }
 
@@ -994,6 +1023,11 @@
             return;
         }
 
+        if (!isUserHalSupported()) {
+            sendUserCreationResult(receiver, UserCreationResult.STATUS_SUCCESSFUL, newUser, null);
+            return;
+        }
+
         CreateUserRequest request = new CreateUserRequest();
         request.usersInfo = UserHalHelper.newUsersInfo(mUserManager);
         if (!TextUtils.isEmpty(name)) {
@@ -1066,6 +1100,10 @@
 
     @Override
     public UserIdentificationAssociationResponse getUserIdentificationAssociation(int[] types) {
+        if (!isUserHalUserAssociationSupported()) {
+            return UserIdentificationAssociationResponse.forFailure(VEHICLE_HAL_NOT_SUPPORTED);
+        }
+
         Preconditions.checkArgument(!ArrayUtils.isEmpty(types), "must have at least one type");
         checkManageUsersPermission("getUserIdentificationAssociation");
 
@@ -1101,6 +1139,12 @@
     @Override
     public void setUserIdentificationAssociation(int timeoutMs, int[] types, int[] values,
             AndroidFuture<UserIdentificationAssociationResponse> result) {
+        if (!isUserHalUserAssociationSupported()) {
+            result.complete(
+                    UserIdentificationAssociationResponse.forFailure(VEHICLE_HAL_NOT_SUPPORTED));
+            return;
+        }
+
         Preconditions.checkArgument(!ArrayUtils.isEmpty(types), "must have at least one type");
         Preconditions.checkArgument(!ArrayUtils.isEmpty(values), "must have at least one value");
         if (types.length != values.length) {
@@ -1246,7 +1290,10 @@
             mRequestIdForUserSwitchInProcess = requestId;
         }
     }
+
     private void postSwitchHalResponse(int requestId, @UserIdInt int targetUserId) {
+        if (!isUserHalSupported()) return;
+
         UsersInfo usersInfo = UserHalHelper.newUsersInfo(mUserManager);
         EventLog.writeEvent(EventLogTags.CAR_USER_SVC_POST_SWITCH_USER_REQ, requestId,
                 targetUserId, usersInfo.currentUser.userId);
@@ -1276,6 +1323,14 @@
     }
 
     /**
+     * Checks if the User HAL user association is supported.
+     */
+    @Override
+    public boolean isUserHalUserAssociationSupported() {
+        return mHal.isUserAssociationSupported();
+    }
+
+    /**
      * Sets a callback which is invoked before user switch.
      *
      * <p>
@@ -1655,6 +1710,8 @@
             }
         }
 
+        if (!isUserHalSupported()) return;
+
         // switch HAL user
         UsersInfo usersInfo = UserHalHelper.newUsersInfo(mUserManager, fromUserId);
         SwitchUserRequest request = createUserSwitchRequest(toUserId, usersInfo);
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/homepage/contextualcards/deviceinfo/EmergencyInfoSlice.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/homepage/contextualcards/deviceinfo/EmergencyInfoSlice.java
index d7ef7cd..5454a68 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/homepage/contextualcards/deviceinfo/EmergencyInfoSlice.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/homepage/contextualcards/deviceinfo/EmergencyInfoSlice.java
@@ -27,6 +27,7 @@
 import androidx.slice.builders.SliceAction;
 
 import com.android.car.developeroptions.R;
+import com.android.car.developeroptions.Utils;
 import com.android.car.developeroptions.accounts.EmergencyInfoPreferenceController;
 import com.android.car.developeroptions.slices.CustomSliceRegistry;
 import com.android.car.developeroptions.slices.CustomSliceable;
@@ -61,7 +62,8 @@
 
     @Override
     public Intent getIntent() {
-        return new Intent(EmergencyInfoPreferenceController.getIntentAction(mContext));
+        return new Intent(EmergencyInfoPreferenceController.getIntentAction(mContext))
+                .setPackage(Utils.SETTINGS_PACKAGE_NAME);
     }
 
     @Override
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 e6a81b6..a75239a 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
@@ -146,8 +146,12 @@
     @Before
     public void setFixtures() {
         mUserHalService = spy(new UserHalService(mVehicleHal, mHandler));
-        // Needs at least one property, otherwise isSupported() will return false
-        mUserHalService.takeProperties(Arrays.asList(newSubscribableConfig(INITIAL_USER_INFO)));
+        // Needs at least one property, otherwise isSupported() and isUserAssociationSupported()
+        // will return false
+        mUserHalService.takeProperties(Arrays.asList(newSubscribableConfig(INITIAL_USER_INFO),
+                newSubscribableConfig(CREATE_USER), newSubscribableConfig(REMOVE_USER),
+                newSubscribableConfig(SWITCH_USER),
+                newSubscribableConfig(USER_IDENTIFICATION_ASSOCIATION)));
 
         mUser0.userId = 0;
         mUser0.flags = 100;
@@ -169,42 +173,91 @@
     }
 
     @Test
-    public void testTakeSupportedProperties_unsupportedOnly() {
+    public void testTakeSupportedProperties_supportedNoProperties() {
         // Cannot use mUserHalService because it's already set with supported properties
         UserHalService myHalService = new UserHalService(mVehicleHal);
 
-        myHalService.takeProperties(Collections.EMPTY_LIST);
+        myHalService.takeProperties(Collections.emptyList());
         assertThat(myHalService.isSupported()).isFalse();
+        assertThat(myHalService.isUserAssociationSupported()).isFalse();
+    }
+
+    @Test
+    public void testTakeSupportedProperties_supportedFewProperties() {
+        // Cannot use mUserHalService because it's already set with supported properties
+        UserHalService myHalService = new UserHalService(mVehicleHal);
+        myHalService.takeProperties(Arrays.asList(newSubscribableConfig(INITIAL_USER_INFO),
+                newSubscribableConfig(CREATE_USER), newSubscribableConfig(REMOVE_USER)));
+
+        assertThat(myHalService.isSupported()).isFalse();
+        assertThat(myHalService.isUserAssociationSupported()).isFalse();
+    }
+
+    @Test
+    public void testTakeSupportedProperties_supportedAllCoreProperties() {
+        // Cannot use mUserHalService because it's already set with supported properties
+        UserHalService myHalService = new UserHalService(mVehicleHal);
+        myHalService.takeProperties(Arrays.asList(newSubscribableConfig(INITIAL_USER_INFO),
+                newSubscribableConfig(CREATE_USER), newSubscribableConfig(REMOVE_USER),
+                newSubscribableConfig(SWITCH_USER)));
+
+        assertThat(myHalService.isSupported()).isTrue();
+        assertThat(myHalService.isUserAssociationSupported()).isFalse();
+    }
+
+    @Test
+    public void testTakeSupportedProperties_supportedAllProperties() {
+        // Cannot use mUserHalService because it's already set with supported properties
+        UserHalService myHalService = new UserHalService(mVehicleHal);
+        myHalService.takeProperties(Arrays.asList(newSubscribableConfig(INITIAL_USER_INFO),
+                newSubscribableConfig(CREATE_USER), newSubscribableConfig(REMOVE_USER),
+                newSubscribableConfig(SWITCH_USER),
+                newSubscribableConfig(USER_IDENTIFICATION_ASSOCIATION)));
+
+        assertThat(myHalService.isSupported()).isTrue();
+        assertThat(myHalService.isUserAssociationSupported()).isTrue();
     }
 
     @Test
     public void testTakeSupportedPropertiesAndInit() {
         // Cannot use mUserHalService because it's already set with supported properties
         UserHalService myHalService = new UserHalService(mVehicleHal);
-
         VehiclePropConfig unsupportedConfig = newConfig(CURRENT_GEAR);
-        VehiclePropConfig userInfoConfig = newSubscribableConfig(INITIAL_USER_INFO);
-        List<VehiclePropConfig> input = Arrays.asList(unsupportedConfig, userInfoConfig);
-        myHalService.takeProperties(input);
-        assertThat(mUserHalService.isSupported()).isTrue();
+
+        myHalService.takeProperties(Arrays.asList(newSubscribableConfig(INITIAL_USER_INFO),
+                newSubscribableConfig(CREATE_USER), newSubscribableConfig(REMOVE_USER),
+                newSubscribableConfig(SWITCH_USER), unsupportedConfig,
+                newSubscribableConfig(USER_IDENTIFICATION_ASSOCIATION)));
+
 
         // Ideally there should be 2 test methods (one for takeSupportedProperties() and one for
         // init()), but on "real life" VehicleHal calls these 2 methods in sequence, and the latter
         // depends on the properties set by the former, so it's ok to test both here...
         myHalService.init();
         verify(mVehicleHal).subscribeProperty(myHalService, INITIAL_USER_INFO);
+        verify(mVehicleHal).subscribeProperty(myHalService, CREATE_USER);
+        verify(mVehicleHal).subscribeProperty(myHalService, REMOVE_USER);
+        verify(mVehicleHal).subscribeProperty(myHalService, SWITCH_USER);
+        verify(mVehicleHal).subscribeProperty(myHalService, USER_IDENTIFICATION_ASSOCIATION);
     }
 
     @Test
     public void testSupportedProperties() {
-        assertThat(mUserHalService.getAllSupportedProperties()).asList().containsAllOf(
-                INITIAL_USER_INFO,
-                CREATE_USER,
-                SWITCH_USER,
+        assertThat(mUserHalService.getAllSupportedProperties()).asList().containsExactly(
+                INITIAL_USER_INFO, CREATE_USER, REMOVE_USER, SWITCH_USER,
                 USER_IDENTIFICATION_ASSOCIATION);
     }
 
     @Test
+    public void testGetUserInfo_noHalSupported() {
+        // Cannot use mUserHalService because it's already set with supported properties
+        UserHalService myHalService = new UserHalService(mVehicleHal);
+
+        assertThrows(IllegalStateException.class, () -> myHalService.getInitialUserInfo(COLD_BOOT,
+                TIMEOUT_MS, mUsersInfo, noOpCallback()));
+    }
+
+    @Test
     public void testGetUserInfo_invalidTimeout() {
         assertThrows(IllegalArgumentException.class, () ->
                 mUserHalService.getInitialUserInfo(COLD_BOOT, 0, mUsersInfo, noOpCallback()));
@@ -415,6 +468,16 @@
     }
 
     @Test
+    public void testSwitchUser_noHalSupported() {
+        // Cannot use mUserHalService because it's already set with supported properties
+        UserHalService myHalService = new UserHalService(mVehicleHal);
+
+        assertThrows(IllegalStateException.class,
+                () -> myHalService.switchUser(createUserSwitchRequest(mUser10, mUsersInfo),
+                        TIMEOUT_MS, noOpCallback()));
+    }
+
+    @Test
     public void testSwitchUser_invalidTimeout() {
         assertThrows(IllegalArgumentException.class, () -> mUserHalService
                 .switchUser(createUserSwitchRequest(mUser10, mUsersInfo), 0, noOpCallback()));
@@ -645,6 +708,15 @@
     }
 
     @Test
+    public void testPostSwitchResponse_noHalSupported() {
+        // Cannot use mUserHalService because it's already set with supported properties
+        UserHalService myHalService = new UserHalService(mVehicleHal);
+
+        assertThrows(IllegalStateException.class,
+                () -> myHalService.postSwitchResponse(new SwitchUserRequest()));
+    }
+
+    @Test
     public void testPostSwitchResponse_noUsersInfo() {
         SwitchUserRequest request = createUserSwitchRequest(mUser10, null);
         request.requestId = 42;
@@ -686,6 +758,15 @@
     }
 
     @Test
+    public void testRemoveUser_noHalSupported() {
+        // Cannot use mUserHalService because it's already set with supported properties
+        UserHalService myHalService = new UserHalService(mVehicleHal);
+
+        assertThrows(IllegalStateException.class,
+                () -> myHalService.removeUser(new RemoveUserRequest()));
+    }
+
+    @Test
     public void testRemoveUser_nullRequest() {
         RemoveUserRequest request = null;
 
@@ -735,6 +816,15 @@
     }
 
     @Test
+    public void testLegacyUserSwitch_noHalSupported() {
+        // Cannot use mUserHalService because it's already set with supported properties
+        UserHalService myHalService = new UserHalService(mVehicleHal);
+
+        assertThrows(IllegalStateException.class,
+                () -> myHalService.legacyUserSwitch(new SwitchUserRequest()));
+    }
+
+    @Test
     public void testLegacyUserSwitch_noUsersInfo() {
         SwitchUserRequest request = new SwitchUserRequest();
         request.messageType = SwitchUserMessageType.ANDROID_SWITCH;
@@ -762,6 +852,15 @@
     }
 
     @Test
+    public void testCreateUser_noHalSupported() {
+        // Cannot use mUserHalService because it's already set with supported properties
+        UserHalService myHalService = new UserHalService(mVehicleHal);
+
+        assertThrows(IllegalStateException.class,
+                () -> myHalService.createUser(new CreateUserRequest(), TIMEOUT_MS, noOpCallback()));
+    }
+
+    @Test
     public void testCreateUser_noRequest() {
         assertThrows(NullPointerException.class, () -> mUserHalService
                 .createUser(null, TIMEOUT_MS, noOpCallback()));
@@ -842,7 +941,7 @@
     }
 
     @Test
-    public void testUserCreate_success() throws Exception {
+    public void testCreateUser_success() throws Exception {
         VehiclePropValue propResponse =
                 UserHalHelper.createPropRequest(CREATE_USER, REQUEST_ID_PLACE_HOLDER);
         propResponse.value.int32Values.add(CreateUserStatus.SUCCESS);
@@ -870,7 +969,7 @@
     }
 
     @Test
-    public void testUserCreate_failure() throws Exception {
+    public void testCreateUser_failure() throws Exception {
         VehiclePropValue propResponse =
                 UserHalHelper.createPropRequest(CREATE_USER, REQUEST_ID_PLACE_HOLDER);
         propResponse.value.int32Values.add(CreateUserStatus.FAILURE);
@@ -937,6 +1036,15 @@
     }
 
     @Test
+    public void testGetUserAssociation_noHalSupported() {
+        // Cannot use mUserHalService because it's already set with supported properties
+        UserHalService myHalService = new UserHalService(mVehicleHal);
+
+        assertThrows(IllegalStateException.class,
+                () -> myHalService.getUserAssociation(new UserIdentificationGetRequest()));
+    }
+
+    @Test
     public void testGetUserAssociation_nullRequest() {
         assertThrows(NullPointerException.class, () -> mUserHalService.getUserAssociation(null));
     }
@@ -1046,6 +1154,15 @@
     }
 
     @Test
+    public void testSetUserAssociation_noHalSupported() {
+        // Cannot use mUserHalService because it's already set with supported properties
+        UserHalService myHalService = new UserHalService(mVehicleHal);
+
+        assertThrows(IllegalStateException.class, () -> myHalService.setUserAssociation(TIMEOUT_MS,
+                new UserIdentificationSetRequest(), noOpCallback()));
+    }
+
+    @Test
     public void testSetUserAssociation_invalidTimeout() {
         UserIdentificationSetRequest request = new UserIdentificationSetRequest();
         assertThrows(IllegalArgumentException.class, () ->
@@ -1518,4 +1635,4 @@
                     + mExtraCalls);
         }
     }
-}
+}
\ No newline at end of file
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 1003957..3d38ac4 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
@@ -369,6 +369,22 @@
         assertThat(result.getErrorMessage()).isEqualTo("D'OH!");
     }
 
+    @Test
+    public void testIsUserHalUserAssociation() throws Exception {
+        when(mService.isUserHalUserAssociationSupported()).thenReturn(false).thenReturn(true);
+
+        assertThat(mMgr.isUserHalUserAssociationSupported()).isFalse();
+        assertThat(mMgr.isUserHalUserAssociationSupported()).isTrue();
+    }
+
+    @Test
+    public void testIsUserHalUserAssociation_remoteException() throws Exception {
+        doThrow(new RemoteException("D'OH!")).when(mService).isUserHalUserAssociationSupported();
+        mockHandleRemoteExceptionFromCarServiceWithDefaultValue(mCar);
+
+        assertThat(mMgr.isUserHalUserAssociationSupported()).isFalse();
+    }
+
     private void expectServiceSwitchUserSucceeds(@UserIdInt int userId,
             @UserSwitchResult.Status int status, @Nullable String errorMessage)
             throws RemoteException {
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 79d3282..ee5c311 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
@@ -219,6 +219,8 @@
         doReturn(mMockedDrawable).when(mMockedDrawable).mutate();
         doReturn(1).when(mMockedDrawable).getIntrinsicWidth();
         doReturn(1).when(mMockedDrawable).getIntrinsicHeight();
+        mockUserHalSupported(true);
+        mockUserHalUserAssociationSupported(true);
         doReturn(Optional.of(mAsyncCallTimeoutMs)).when(() -> CarProperties.user_hal_timeout());
         mCarUserService =
                 new CarUserService(
@@ -245,6 +247,31 @@
     }
 
     @Test
+    public void testOnUserLifecycleEvent_legacyUserSwitch_halCalled() throws Exception {
+        // Arrange
+        mockExistingUsers();
+
+        // Act
+        sendUserSwitchingEvent(mAdminUser.id, mRegularUser.id);
+
+        // Verify
+        verify(mUserHal).legacyUserSwitch(any());
+    }
+
+    @Test
+    public void testOnUserLifecycleEvent_legacyUserSwitch_halnotSupported() throws Exception {
+        // Arrange
+        mockExistingUsers();
+        mockUserHalSupported(false);
+
+        // Act
+        sendUserSwitchingEvent(mAdminUser.id, mRegularUser.id);
+
+        // Verify
+        verify(mUserHal, never()).legacyUserSwitch(any());
+    }
+
+    @Test
     public void testOnUserLifecycleEvent_notifyListener() throws Exception {
         // Arrange
         mCarUserService.addUserLifecycleListener(mUserLifecycleListener);
@@ -721,6 +748,19 @@
     }
 
     @Test
+    public void testRemoveUser_halNotSupported() throws Exception {
+        mockExistingUsersAndCurrentUser(mAdminUser);
+        int removeUserId = mRegularUser.id;
+        mockUserHalSupported(false);
+        when(mMockedUserManager.removeUser(removeUserId)).thenReturn(true);
+
+        UserRemovalResult result = mCarUserService.removeUser(removeUserId);
+
+        assertThat(result.getStatus()).isEqualTo(UserRemovalResult.STATUS_SUCCESSFUL);
+        verify(mUserHal, never()).removeUser(any());
+    }
+
+    @Test
     public void testRemoveUser_androidFailure() throws Exception {
         mockExistingUsersAndCurrentUser(mAdminUser);
         int targetUserId = mRegularUser.id;
@@ -756,6 +796,36 @@
     }
 
     @Test
+    public void testSwitchUser_halNotSupported_success() throws Exception {
+        mockExistingUsersAndCurrentUser(mAdminUser);
+        mockUserHalSupported(false);
+        mockAmSwitchUser(mRegularUser, true);
+
+        mCarUserService.switchUser(mRegularUser.id, mAsyncCallTimeoutMs, mUserSwitchFuture);
+
+        assertThat(getUserSwitchResult().getStatus())
+                .isEqualTo(UserSwitchResult.STATUS_SUCCESSFUL);
+        verify(mUserHal, never()).switchUser(any(), anyInt(), any());
+
+        // update current user due to successful user switch
+        mockCurrentUser(mRegularUser);
+        sendUserUnlockedEvent(mRegularUser.id);
+        assertNoPostSwitch();
+    }
+
+    @Test
+    public void testSwitchUser_halNotSupported_failure() throws Exception {
+        mockExistingUsersAndCurrentUser(mAdminUser);
+        mockUserHalSupported(false);
+
+        mCarUserService.switchUser(mRegularUser.id, mAsyncCallTimeoutMs, mUserSwitchFuture);
+
+        assertThat(getUserSwitchResult().getStatus())
+                .isEqualTo(UserSwitchResult.STATUS_ANDROID_FAILURE);
+        verify(mUserHal, never()).switchUser(any(), anyInt(), any());
+    }
+
+    @Test
     public void testSwitchUser_HalSuccessAndroidSuccess() throws Exception {
         mockExistingUsersAndCurrentUser(mAdminUser);
         int requestId = 42;
@@ -1206,6 +1276,22 @@
     }
 
     @Test
+    public void testCreateUser_halNotSupported_success() throws Exception {
+        mockUserHalSupported(false);
+        mockExistingUsersAndCurrentUser(mAdminUser);
+        int userId = mGuestUser.id;
+        mockUmCreateUser(mMockedUserManager, "dude", UserManager.USER_TYPE_FULL_GUEST,
+                UserInfo.FLAG_EPHEMERAL, userId);
+
+        mCarUserService.createUser("dude", UserManager.USER_TYPE_FULL_GUEST,
+                UserInfo.FLAG_EPHEMERAL, mAsyncCallTimeoutMs, mUserCreationFuture);
+
+        UserCreationResult result = getUserCreationResult();
+        assertThat(result.getStatus()).isEqualTo(UserCreationResult.STATUS_SUCCESSFUL);
+        verify(mUserHal, never()).createUser(any(), anyInt(), any());
+    }
+
+    @Test
     public void testCreateUser_success() throws Exception {
         mockExistingUsersAndCurrentUser(mAdminUser);
         int userId = mGuestUser.id;
@@ -1362,6 +1448,17 @@
         assertUserName(resultData, newUserName);
     }
 
+    @Test
+    public void testGetUserInfo_halNotSupported() throws Exception {
+        mockExistingUsersAndCurrentUser(mAdminUser);
+        mockUserHalSupported(false);
+
+        mCarUserService.getInitialUserInfo(mGetUserInfoRequestType, mAsyncCallTimeoutMs, mReceiver);
+
+        verify(mUserHal, never()).getInitialUserInfo(anyInt(), anyInt(), any(), any());
+        assertThat(mReceiver.getResultCode()).isEqualTo(HalCallback.STATUS_HAL_NOT_SUPPORTED);
+    }
+
     /**
      * Tests the {@code getUserInfo()} that's used by other services.
      */
@@ -1383,6 +1480,17 @@
     }
 
     @Test
+    public void testGetInitialUserInfo_halNotSupported_callback() throws Exception {
+        int requestType = 42;
+        mockUserHalSupported(false);
+        HalCallback<InitialUserInfoResponse> callback = (s, r) -> { };
+
+        mCarUserService.getInitialUserInfo(requestType, callback);
+
+        verify(mUserHal, never()).getInitialUserInfo(anyInt(), anyInt(), any(), any());
+    }
+
+    @Test
     public void testGetInitialUserInfo_invalidPermission() throws Exception {
         mockManageUsersPermission(android.Manifest.permission.MANAGE_USERS, false);
         assertThrows(SecurityException.class,
@@ -1440,8 +1548,6 @@
     public void testGetUserIdentificationAssociation_service_returnNull() throws Exception {
         mockCurrentUserForBinderCalls();
 
-        // Not mocking service call, so it will return null
-
         UserIdentificationAssociationResponse response = mCarUserService
                 .getUserIdentificationAssociation(new int[] { 108 });
 
@@ -1451,6 +1557,19 @@
     }
 
     @Test
+    public void testGetUserIdentificationAssociation_halNotSupported() throws Exception {
+        mockUserHalUserAssociationSupported(false);
+
+        UserIdentificationAssociationResponse response = mCarUserService
+                .getUserIdentificationAssociation(new int[] { });
+
+        assertThat(response.isSuccess()).isFalse();
+        assertThat(response.getValues()).isNull();
+        assertThat(response.getErrorMessage()).isEqualTo(CarUserService.VEHICLE_HAL_NOT_SUPPORTED);
+        verify(mUserHal, never()).getUserAssociation(any());
+    }
+
+    @Test
     public void testGetUserIdentificationAssociation_ok() throws Exception {
         UserInfo currentUser = mockCurrentUserForBinderCalls();
 
@@ -1517,6 +1636,22 @@
     }
 
     @Test
+    public void testSetUserIdentificationAssociation_halNotSupported() throws Exception {
+        int[] types = new int[] { 1, 2, 3 };
+        int[] values = new int[] { 10, 20, 30 };
+        mockUserHalUserAssociationSupported(false);
+
+        mCarUserService.setUserIdentificationAssociation(mAsyncCallTimeoutMs, types, values,
+                mUserAssociationRespFuture);
+        UserIdentificationAssociationResponse response = getUserAssociationRespResult();
+
+        assertThat(response.isSuccess()).isFalse();
+        assertThat(response.getValues()).isNull();
+        assertThat(response.getErrorMessage()).isEqualTo(CarUserService.VEHICLE_HAL_NOT_SUPPORTED);
+        verify(mUserHal, never()).setUserAssociation(anyInt(), any(), any());
+    }
+
+    @Test
     public void testSetUserIdentificationAssociation_halFailedWithErrorMessage() throws Exception {
         mockCurrentUserForBinderCalls();
         mockHalSetUserIdentificationAssociationFailure("D'OH!");
@@ -1823,6 +1958,15 @@
                 anyInt(), anyInt(), eq(true)));
     }
 
+    private void mockUserHalSupported(boolean result) {
+        when(mUserHal.isSupported()).thenReturn(result);
+    }
+
+    private void mockUserHalUserAssociationSupported(boolean result) {
+        when(mUserHal.isUserAssociationSupported()).thenReturn(result);
+    }
+
+
     /**
      * Asserts a {@link UsersInfo} that was created based on {@link #mockCurrentUsers(UserInfo)}.
      */
diff --git a/user/car-user-lib/src/android/car/userlib/HalCallback.java b/user/car-user-lib/src/android/car/userlib/HalCallback.java
index 55528a3..784d01e 100644
--- a/user/car-user-lib/src/android/car/userlib/HalCallback.java
+++ b/user/car-user-lib/src/android/car/userlib/HalCallback.java
@@ -35,6 +35,7 @@
     int STATUS_HAL_RESPONSE_TIMEOUT = 3;
     int STATUS_WRONG_HAL_RESPONSE = 4;
     int STATUS_CONCURRENT_OPERATION = 5;
+    int STATUS_HAL_NOT_SUPPORTED = 6;
 
     /** @hide */
     @IntDef(prefix = { "STATUS_" }, value = {
@@ -42,7 +43,8 @@
             STATUS_HAL_SET_TIMEOUT,
             STATUS_HAL_RESPONSE_TIMEOUT,
             STATUS_WRONG_HAL_RESPONSE,
-            STATUS_CONCURRENT_OPERATION
+            STATUS_CONCURRENT_OPERATION,
+            STATUS_HAL_NOT_SUPPORTED
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface HalCallbackStatus{}