Propagate VHAL errors to managers that support it

Fix: b/32068464

Test: functional tests provided
Change-Id: I7199ce61815a94671c1b155711cebca0897d2608
diff --git a/service/src/com/android/car/CarPropertyServiceBase.java b/service/src/com/android/car/CarPropertyServiceBase.java
index 7864e6a..02ab7c9 100644
--- a/service/src/com/android/car/CarPropertyServiceBase.java
+++ b/service/src/com/android/car/CarPropertyServiceBase.java
@@ -192,12 +192,19 @@
         mHal.setProperty(prop);
     }
 
+    private ICarPropertyEventListener[] getListeners() {
+        synchronized (mLock) {
+            int size = mListenersMap.values().size();
+            return mListenersMap.values().toArray(new ICarPropertyEventListener[size]);
+        }
+    }
+
     // Implement PropertyHalListener interface
     @Override
     public void onPropertyChange(CarPropertyEvent event) {
-        for (ICarPropertyEventListener l : mListenersMap.values()) {
+        for (ICarPropertyEventListener listener : getListeners()) {
             try {
-                l.onEvent(event);
+                listener.onEvent(event);
             } catch (RemoteException ex) {
                 // If we could not send a record, its likely the connection snapped. Let the binder
                 // death handle the situation.
@@ -207,7 +214,20 @@
     }
 
     @Override
-    public void onError(int zone, int property) {
-        // TODO bug:32068464
+    public void onPropertySetError(int property, int area) {
+        for (ICarPropertyEventListener listener : getListeners()) {
+            try {
+                listener.onEvent(createErrorEvent(property, area));
+            } catch (RemoteException ex) {
+                // If we could not send a record, its likely the connection snapped. Let the binder
+                // death handle the situation.
+                Log.e(mTag, "onEvent calling failed: " + ex);
+            }
+        }
+    }
+
+    private static CarPropertyEvent createErrorEvent(int property, int area) {
+        return new CarPropertyEvent(CarPropertyEvent.PROPERTY_EVENT_ERROR,
+                new CarPropertyValue<>(property, area, null));
     }
 }
diff --git a/service/src/com/android/car/hal/HalClient.java b/service/src/com/android/car/hal/HalClient.java
index d0c9954..cf1ca0f 100644
--- a/service/src/com/android/car/hal/HalClient.java
+++ b/service/src/com/android/car/hal/HalClient.java
@@ -27,7 +27,6 @@
 import android.hardware.vehicle.V2_0.VehiclePropConfig;
 import android.hardware.vehicle.V2_0.VehiclePropValue;
 import android.os.Handler;
-import android.os.IHwBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.util.Log;
diff --git a/service/src/com/android/car/hal/HalServiceBase.java b/service/src/com/android/car/hal/HalServiceBase.java
index 30115ae..364f834 100644
--- a/service/src/com/android/car/hal/HalServiceBase.java
+++ b/service/src/com/android/car/hal/HalServiceBase.java
@@ -62,5 +62,7 @@
 
     public abstract void handleHalEvents(List<VehiclePropValue> values);
 
+    public void handlePropertySetError(int property, int area) {}
+
     public abstract void dump(PrintWriter writer);
 }
diff --git a/service/src/com/android/car/hal/PropertyHalServiceBase.java b/service/src/com/android/car/hal/PropertyHalServiceBase.java
index 28dd4c6..cd75599 100644
--- a/service/src/com/android/car/hal/PropertyHalServiceBase.java
+++ b/service/src/com/android/car/hal/PropertyHalServiceBase.java
@@ -58,7 +58,7 @@
 
     public interface PropertyHalListener {
         void onPropertyChange(CarPropertyEvent event);
-        void onError(int zone, int property);
+        void onPropertySetError(int property, int area);
     }
 
     protected PropertyHalServiceBase(VehicleHal vehicleHal, String tag, boolean dbg) {
@@ -172,7 +172,7 @@
     @Override
     public void handleHalEvents(List<VehiclePropValue> values) {
         PropertyHalListener listener;
-        synchronized (this) {
+        synchronized (mLock) {
             listener = mListener;
         }
         if (listener != null) {
@@ -199,6 +199,17 @@
     }
 
     @Override
+    public void handlePropertySetError(int property, int area) {
+        PropertyHalListener listener;
+        synchronized (mLock) {
+            listener = mListener;
+        }
+        if (listener != null) {
+            listener.onPropertySetError(property, area);
+        }
+    }
+
+    @Override
     public void dump(PrintWriter writer) {
         writer.println(mTag);
         writer.println("  Properties available:");
diff --git a/service/src/com/android/car/hal/VehicleHal.java b/service/src/com/android/car/hal/VehicleHal.java
index 182583d..7e3dea4 100644
--- a/service/src/com/android/car/hal/VehicleHal.java
+++ b/service/src/com/android/car/hal/VehicleHal.java
@@ -27,6 +27,7 @@
 import android.hardware.vehicle.V2_0.VehicleAreaConfig;
 import android.hardware.vehicle.V2_0.VehiclePropConfig;
 import android.hardware.vehicle.V2_0.VehiclePropValue;
+import android.hardware.vehicle.V2_0.VehicleProperty;
 import android.hardware.vehicle.V2_0.VehiclePropertyAccess;
 import android.hardware.vehicle.V2_0.VehiclePropertyChangeMode;
 import android.os.HandlerThread;
@@ -389,7 +390,12 @@
     public void onPropertySetError(int errorCode, int propId, int areaId) {
         Log.e(CarLog.TAG_HAL, String.format("onPropertySetError, errorCode: %d, prop: 0x%x, "
                 + "area: 0x%x", errorCode, propId, areaId));
-        // TODO propagate per property error to HAL services and handle global error, bug:32068464
+        if (propId != VehicleProperty.INVALID) {
+            HalServiceBase service = mPropertyHandlers.get(propId);
+            if (service != null) {
+                service.handlePropertySetError(propId, areaId);
+            }
+        }
     }
 
     public void dump(PrintWriter writer) {
@@ -403,7 +409,6 @@
             configList = new ArrayList<>(mAllProperties.values());
         }
 
-
         writer.println("**All properties**");
         for (VehiclePropConfig config : configList) {
             StringBuilder builder = new StringBuilder()
diff --git a/tests/carservice_test/AndroidManifest.xml b/tests/carservice_test/AndroidManifest.xml
index d7c7bdf..bf48e88 100644
--- a/tests/carservice_test/AndroidManifest.xml
+++ b/tests/carservice_test/AndroidManifest.xml
@@ -15,7 +15,7 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.car.carservicetest"
+        package="com.android.car.test"
         android:sharedUserId="android.uid.system" >
 
     <uses-permission android:name="android.Manifest.permission.MODIFY_AUDIO_ROUTING" />
@@ -24,7 +24,7 @@
     <uses-permission android:name="android.car.permission.CONTROL_APP_BLOCKING" />
     <uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME" />
     <instrumentation android:name="android.test.InstrumentationTestRunner"
-            android:targetPackage="com.android.car.carservicetest"
+            android:targetPackage="com.android.car.test"
             android:label="Tests for Car APIs"/>
 
     <application android:label="CarServiceTest">
diff --git a/tests/carservice_test/src/com/android/car/test/CarCabinManagerTest.java b/tests/carservice_test/src/com/android/car/test/CarCabinManagerTest.java
index 7024250..a369436 100644
--- a/tests/carservice_test/src/com/android/car/test/CarCabinManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/test/CarCabinManagerTest.java
@@ -20,6 +20,7 @@
 import android.car.hardware.CarPropertyValue;
 import android.car.hardware.cabin.CarCabinManager;
 import android.car.hardware.cabin.CarCabinManager.CarCabinEventCallback;
+import android.car.hardware.cabin.CarCabinManager.PropertyId;
 import android.hardware.vehicle.V2_0.VehicleAreaDoor;
 import android.hardware.vehicle.V2_0.VehicleAreaWindow;
 import android.hardware.vehicle.V2_0.VehiclePropValue;
@@ -27,11 +28,13 @@
 import android.os.SystemClock;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
+import android.util.MutableInt;
 
 import com.android.car.vehiclehal.VehiclePropValueBuilder;
 import com.android.car.vehiclehal.test.MockedVehicleHal.VehicleHalPropertyHandler;
 
 import java.util.HashMap;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 
@@ -93,6 +96,36 @@
         assertEquals(25, windowPos);
     }
 
+    public void testError() throws Exception {
+        final int PROP = VehicleProperty.DOOR_LOCK;
+        final int AREA = VehicleAreaWindow.ROW_1_LEFT;
+        final int ERR_CODE = 42;
+
+        CountDownLatch errorLatch = new CountDownLatch(1);
+        MutableInt propertyIdReceived = new MutableInt(0);
+        MutableInt areaIdReceived = new MutableInt(0);
+
+        mCarCabinManager.registerCallback(new CarCabinEventCallback() {
+            @Override
+            public void onChangeEvent(CarPropertyValue value) {
+
+            }
+
+            @Override
+            public void onErrorEvent(@PropertyId int propertyId, int area) {
+                propertyIdReceived.value = propertyId;
+                areaIdReceived.value = area;
+                errorLatch.countDown();
+            }
+        });
+
+        getMockedVehicleHal().injectError(ERR_CODE, PROP, AREA);
+        assertTrue(errorLatch.await(DEFAULT_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertEquals(PROP, propertyIdReceived.value);
+        assertEquals(AREA, areaIdReceived.value);
+    }
+
+
     // Test an event
     public void testEvent() throws Exception {
         mCarCabinManager.registerCallback(new EventListener());
diff --git a/tests/carservice_test/src/com/android/car/test/CarHvacManagerTest.java b/tests/carservice_test/src/com/android/car/test/CarHvacManagerTest.java
index 8510e4c..26225ef 100644
--- a/tests/carservice_test/src/com/android/car/test/CarHvacManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/test/CarHvacManagerTest.java
@@ -20,6 +20,7 @@
 import android.car.hardware.CarPropertyValue;
 import android.car.hardware.hvac.CarHvacManager;
 import android.car.hardware.hvac.CarHvacManager.CarHvacEventCallback;
+import android.car.hardware.hvac.CarHvacManager.PropertyId;
 import android.hardware.vehicle.V2_0.VehicleAreaWindow;
 import android.hardware.vehicle.V2_0.VehicleAreaZone;
 import android.hardware.vehicle.V2_0.VehiclePropValue;
@@ -29,11 +30,13 @@
 import android.os.SystemClock;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
+import android.util.MutableInt;
 
 import com.android.car.vehiclehal.VehiclePropValueBuilder;
 import com.android.car.vehiclehal.test.MockedVehicleHal.VehicleHalPropertyHandler;
 
 import java.util.HashMap;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 
@@ -117,6 +120,35 @@
         assertEquals(65.5, temp, 0);
     }
 
+    public void testError() throws Exception {
+        final int PROP = VehicleProperty.HVAC_DEFROSTER;
+        final int AREA = VehicleAreaWindow.FRONT_WINDSHIELD;
+        final int ERR_CODE = 42;
+
+        CountDownLatch errorLatch = new CountDownLatch(1);
+        MutableInt propertyIdReceived = new MutableInt(0);
+        MutableInt areaIdReceived = new MutableInt(0);
+
+        mCarHvacManager.registerCallback(new CarHvacEventCallback() {
+            @Override
+            public void onChangeEvent(CarPropertyValue value) {
+
+            }
+
+            @Override
+            public void onErrorEvent(@PropertyId int propertyId, int area) {
+                propertyIdReceived.value = propertyId;
+                areaIdReceived.value = area;
+                errorLatch.countDown();
+            }
+        });
+
+        getMockedVehicleHal().injectError(ERR_CODE, PROP, AREA);
+        assertTrue(errorLatch.await(DEFAULT_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertEquals(PROP, propertyIdReceived.value);
+        assertEquals(AREA, areaIdReceived.value);
+    }
+
     // Test an event
     public void testEvent() throws Exception {
         mCarHvacManager.registerCallback(new EventListener());
diff --git a/vehicle-hal-support-lib/src/com/android/car/vehiclehal/test/MockedVehicleHal.java b/vehicle-hal-support-lib/src/com/android/car/vehiclehal/test/MockedVehicleHal.java
index 2d73a10..0382208 100644
--- a/vehicle-hal-support-lib/src/com/android/car/vehiclehal/test/MockedVehicleHal.java
+++ b/vehicle-hal-support-lib/src/com/android/car/vehiclehal/test/MockedVehicleHal.java
@@ -28,7 +28,6 @@
 import android.hardware.vehicle.V2_0.VehiclePropConfig;
 import android.hardware.vehicle.V2_0.VehiclePropValue;
 import android.hardware.vehicle.V2_0.VehiclePropertyAccess;
-import android.os.IHwBinder;
 
 import com.google.android.collect.Lists;
 
@@ -83,6 +82,15 @@
         }
     }
 
+    public synchronized void injectError(int errorCode, int propertyId, int areaId) {
+        List<IVehicleCallback> callbacks = mSubscribers.get(propertyId);
+        assertNotNull("Injecting error failed for property: " + propertyId + ". No listeners found",
+                callbacks);
+        for (IVehicleCallback callback : callbacks) {
+            callback.onPropertySetError(errorCode, propertyId, areaId);
+        }
+    }
+
     @Override
     public synchronized ArrayList<VehiclePropConfig> getAllPropConfigs() {
         return new ArrayList<>(mConfigs.values());