Add CarGenericManager and refactor HVAC to use it

Change-Id: I0af862f8da2259069a5be0d17efb1f82a208fa76
diff --git a/service/src/com/android/car/CarHvacService.java b/service/src/com/android/car/CarHvacService.java
index ab09f60..173e307 100644
--- a/service/src/com/android/car/CarHvacService.java
+++ b/service/src/com/android/car/CarHvacService.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2016 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.
@@ -17,182 +17,13 @@
 package com.android.car;
 
 import android.car.Car;
-import android.car.hardware.hvac.CarHvacEvent;
-import android.car.hardware.CarPropertyConfig;
-import android.car.hardware.CarPropertyValue;
-import android.car.hardware.hvac.ICarHvac;
-import android.car.hardware.hvac.ICarHvacEventListener;
 import android.content.Context;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.car.hal.HvacHalService;
+import com.android.car.CarLog;
 import com.android.car.hal.VehicleHal;
 
-import java.io.PrintWriter;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class CarHvacService extends ICarHvac.Stub
-        implements CarServiceBase, HvacHalService.HvacHalListener {
-    public static final boolean DBG = true;
-    public static final String  TAG = CarLog.TAG_HVAC + ".CarHvacService";
-
-    private HvacHalService mHvacHal;
-    private final Map<IBinder, ICarHvacEventListener> mListenersMap = new HashMap<>();
-    private final Map<IBinder, HvacDeathRecipient> mDeathRecipientMap = new HashMap<>();
-    private final Context mContext;
-
+public class CarHvacService extends CarPropertyServiceBase {
     public CarHvacService(Context context) {
-        mHvacHal = VehicleHal.getInstance().getHvacHal();
-        mContext = context;
-    }
-
-    class HvacDeathRecipient implements IBinder.DeathRecipient {
-        private static final String TAG = CarHvacService.TAG + ".HvacDeathRecipient";
-        private IBinder mListenerBinder;
-
-        HvacDeathRecipient(IBinder listenerBinder) {
-            mListenerBinder = listenerBinder;
-        }
-
-        /**
-         * Client died. Remove the listener from HAL service and unregister if this is the last
-         * client.
-         */
-        @Override
-        public void binderDied() {
-            if (DBG) {
-                Log.d(TAG, "binderDied " + mListenerBinder);
-            }
-            CarHvacService.this.unregisterListenerLocked(mListenerBinder);
-        }
-
-        void release() {
-            mListenerBinder.unlinkToDeath(this, 0);
-        }
-    }
-
-    @Override
-    public synchronized void init() {
-    }
-
-    @Override
-    public synchronized void release() {
-        for (HvacDeathRecipient recipient : mDeathRecipientMap.values()) {
-            recipient.release();
-        }
-        mDeathRecipientMap.clear();
-        mListenersMap.clear();
-    }
-
-    @Override
-    public void dump(PrintWriter writer) {
-        // TODO
-    }
-
-    @Override
-    public synchronized void registerListener(ICarHvacEventListener listener) {
-        if (DBG) {
-            Log.d(TAG, "registerListener");
-        }
-        ICarImpl.assertHvacPermission(mContext);
-        if (listener == null) {
-            Log.e(TAG, "registerListener: Listener is null.");
-            throw new IllegalArgumentException("listener cannot be null.");
-        }
-
-        IBinder listenerBinder = listener.asBinder();
-        if (mListenersMap.containsKey(listenerBinder)) {
-            // Already registered, nothing to do.
-            return;
-        }
-
-        HvacDeathRecipient deathRecipient = new HvacDeathRecipient(listenerBinder);
-        try {
-            listenerBinder.linkToDeath(deathRecipient, 0);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to link death for recipient. " + e);
-            throw new IllegalStateException(Car.CAR_NOT_CONNECTED_EXCEPTION_MSG);
-        }
-        mDeathRecipientMap.put(listenerBinder, deathRecipient);
-
-        if (mListenersMap.isEmpty()) {
-            mHvacHal.setListener(this);
-        }
-
-        mListenersMap.put(listenerBinder, listener);
-    }
-
-    @Override
-    public synchronized void unregisterListener(ICarHvacEventListener listener) {
-        if (DBG) {
-            Log.d(TAG, "unregisterListener");
-        }
-        ICarImpl.assertHvacPermission(mContext);
-        if (listener == null) {
-            Log.e(TAG, "unregisterListener: Listener is null.");
-            throw new IllegalArgumentException("Listener is null");
-        }
-
-        IBinder listenerBinder = listener.asBinder();
-        if (!mListenersMap.containsKey(listenerBinder)) {
-            Log.e(TAG, "unregisterListener: Listener was not previously registered.");
-        }
-        unregisterListenerLocked(listenerBinder);
-    }
-
-    // Removes the listenerBinder from the current state.
-    // The function assumes that the binder will exist both in listeners and death recipients list.
-    private void unregisterListenerLocked(IBinder listenerBinder) {
-        Object status = mListenersMap.remove(listenerBinder);
-
-        if (status != null) {
-            mDeathRecipientMap.get(listenerBinder).release();
-            mDeathRecipientMap.remove(listenerBinder);
-        }
-
-        if (mListenersMap.isEmpty()) {
-            mHvacHal.setListener(null);
-        }
-    }
-
-    @Override
-    public synchronized List<CarPropertyConfig> getHvacProperties() {
-        ICarImpl.assertHvacPermission(mContext);
-        return mHvacHal.getHvacProperties();
-    }
-
-    @Override
-    public synchronized CarPropertyValue getProperty(int prop, int zone) {
-        ICarImpl.assertHvacPermission(mContext);
-        return mHvacHal.getHvacProperty(prop, zone);
-    }
-
-    @Override
-    public synchronized void setProperty(CarPropertyValue prop) {
-        ICarImpl.assertHvacPermission(mContext);
-        mHvacHal.setHvacProperty(prop);
-    }
-
-    // Implement HvacHalListener interface
-    @Override
-    public synchronized void onPropertyChange(CarHvacEvent event) {
-        for (ICarHvacEventListener l : mListenersMap.values()) {
-            try {
-                l.onEvent(event);
-            } catch (RemoteException ex) {
-                // If we could not send a record, its likely the connection snapped. Let the binder
-                // death handle the situation.
-                Log.e(TAG, "onEvent calling failed: " + ex);
-            }
-        }
-    }
-
-    @Override
-    public synchronized void onError(int zone, int property) {
-        // TODO:
+        super(context, VehicleHal.getInstance().getHvacHal(), Car.PERMISSION_CAR_HVAC, true,
+                CarLog.TAG_HVAC);
     }
 }
diff --git a/service/src/com/android/car/CarLog.java b/service/src/com/android/car/CarLog.java
index f818f6a..6b2a2e4 100644
--- a/service/src/com/android/car/CarLog.java
+++ b/service/src/com/android/car/CarLog.java
@@ -31,6 +31,7 @@
     public static final String TAG_PACKAGE = "CAR.PACKAGE";
     public static final String TAG_POWER = "CAR.POWER";
     public static final String TAG_PROJECTION = "CAR.PROJECTION";
+    public static final String TAG_PROPERTY = "CAR.PROPERTY";
     public static final String TAG_RADIO = "CAR.RADIO";
     public static final String TAG_SENSOR = "CAR.SENSOR";
     public static final String TAG_SERVICE = "CAR.SERVICE";
diff --git a/service/src/com/android/car/CarPropertyServiceBase.java b/service/src/com/android/car/CarPropertyServiceBase.java
new file mode 100644
index 0000000..4c2028a
--- /dev/null
+++ b/service/src/com/android/car/CarPropertyServiceBase.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car;
+
+import android.car.Car;
+import android.car.hardware.CarPropertyConfig;
+import android.car.hardware.CarPropertyValue;
+import android.car.hardware.property.CarPropertyEvent;
+import android.car.hardware.property.ICarProperty;
+import android.car.hardware.property.ICarPropertyEventListener;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.car.hal.PropertyHalServiceBase;
+
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class implements the binder interface for ICarProperty.aidl to make it easier to create
+ * multiple managers that deal with Vehicle Properties.  To create a new service, simply extend
+ * this class and call the super() constructor with the appropriate arguments for the new service.
+ * CarHvacService.java shows the basic usage.
+ */
+public class CarPropertyServiceBase extends ICarProperty.Stub
+        implements CarServiceBase, PropertyHalServiceBase.PropertyHalListener {
+    private final Context mContext;
+    private final boolean mDbg;
+    private final Map<IBinder, PropertyDeathRecipient> mDeathRecipientMap = new HashMap<>();
+    private final PropertyHalServiceBase mHal;
+    private final Map<IBinder, ICarPropertyEventListener> mListenersMap = new HashMap<>();
+    private final String mPermission;
+    private final String mTag;
+
+    public CarPropertyServiceBase(Context context, PropertyHalServiceBase hal, String permission,
+            boolean dbg, String tag) {
+        mContext = context;
+        mHal = hal;
+        mPermission = permission;
+        mDbg = dbg;
+        mTag = tag + ".service";
+    }
+
+    class PropertyDeathRecipient implements IBinder.DeathRecipient {
+        private IBinder mListenerBinder;
+
+        PropertyDeathRecipient(IBinder listenerBinder) {
+            mListenerBinder = listenerBinder;
+        }
+
+        /**
+         * Client died. Remove the listener from HAL service and unregister if this is the last
+         * client.
+         */
+        @Override
+        public void binderDied() {
+            if (mDbg) {
+                Log.d(mTag, "binderDied " + mListenerBinder);
+            }
+            CarPropertyServiceBase.this.unregisterListenerLocked(mListenerBinder);
+        }
+
+        void release() {
+            mListenerBinder.unlinkToDeath(this, 0);
+        }
+    }
+
+    @Override
+    public synchronized void init() {
+    }
+
+    @Override
+    public synchronized void release() {
+        for (PropertyDeathRecipient recipient : mDeathRecipientMap.values()) {
+            recipient.release();
+        }
+        mDeathRecipientMap.clear();
+        mListenersMap.clear();
+    }
+
+    @Override
+    public void dump(PrintWriter writer) {
+        // TODO
+    }
+
+    @Override
+    public synchronized void registerListener(ICarPropertyEventListener listener) {
+        if (mDbg) {
+            Log.d(mTag, "registerListener");
+        }
+        ICarImpl.assertPermission(mContext, mPermission);
+        if (listener == null) {
+            Log.e(mTag, "registerListener: Listener is null.");
+            throw new IllegalArgumentException("listener cannot be null.");
+        }
+
+        IBinder listenerBinder = listener.asBinder();
+        if (mListenersMap.containsKey(listenerBinder)) {
+            // Already registered, nothing to do.
+            return;
+        }
+
+        PropertyDeathRecipient deathRecipient = new PropertyDeathRecipient(listenerBinder);
+        try {
+            listenerBinder.linkToDeath(deathRecipient, 0);
+        } catch (RemoteException e) {
+            Log.e(mTag, "Failed to link death for recipient. " + e);
+            throw new IllegalStateException(Car.CAR_NOT_CONNECTED_EXCEPTION_MSG);
+        }
+        mDeathRecipientMap.put(listenerBinder, deathRecipient);
+
+        if (mListenersMap.isEmpty()) {
+            mHal.setListener(this);
+        }
+
+        mListenersMap.put(listenerBinder, listener);
+    }
+
+    @Override
+    public synchronized void unregisterListener(ICarPropertyEventListener listener) {
+        if (mDbg) {
+            Log.d(mTag, "unregisterListener");
+        }
+        ICarImpl.assertPermission(mContext, mPermission);
+        if (listener == null) {
+            Log.e(mTag, "unregisterListener: Listener is null.");
+            throw new IllegalArgumentException("Listener is null");
+        }
+
+        IBinder listenerBinder = listener.asBinder();
+        if (!mListenersMap.containsKey(listenerBinder)) {
+            Log.e(mTag, "unregisterListener: Listener was not previously registered.");
+        }
+        unregisterListenerLocked(listenerBinder);
+    }
+
+    // Removes the listenerBinder from the current state.
+    // The function assumes that binder will exist both in listeners and death recipients list.
+    private void unregisterListenerLocked(IBinder listenerBinder) {
+        boolean found = mListenersMap.remove(listenerBinder) != null;
+
+        if (found) {
+            mDeathRecipientMap.get(listenerBinder).release();
+            mDeathRecipientMap.remove(listenerBinder);
+        }
+
+        if (mListenersMap.isEmpty()) {
+            mHal.setListener(null);
+        }
+    }
+
+    @Override
+    public synchronized List<CarPropertyConfig> getPropertyList() {
+        ICarImpl.assertPermission(mContext, mPermission);
+        return mHal.getPropertyList();
+    }
+
+    @Override
+    public synchronized CarPropertyValue getProperty(int prop, int zone) {
+        ICarImpl.assertPermission(mContext, mPermission);
+        return mHal.getProperty(prop, zone);
+    }
+
+    @Override
+    public synchronized void setProperty(CarPropertyValue prop) {
+        ICarImpl.assertPermission(mContext, mPermission);
+        mHal.setProperty(prop);
+    }
+
+    // Implement PropertyHalListener interface
+    @Override
+    public synchronized void onPropertyChange(CarPropertyEvent event) {
+        for (ICarPropertyEventListener l : mListenersMap.values()) {
+            try {
+                l.onEvent(event);
+            } 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);
+            }
+        }
+    }
+
+    @Override
+    public synchronized void onError(int zone, int property) {
+        // TODO:
+    }
+}
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index 7d34f2b..05e8f59 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -245,49 +245,32 @@
     }
 
     public static void assertVehicleHalMockPermission(Context context) {
-        if (context.checkCallingOrSelfPermission(Car.PERMISSION_MOCK_VEHICLE_HAL)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("requires CAR_MOCK_VEHICLE_HAL permission");
-        }
+        assertPermission(context, Car.PERMISSION_MOCK_VEHICLE_HAL);
     }
 
     public static void assertCameraPermission(Context context) {
-        if (context.checkCallingOrSelfPermission(Car.PERMISSION_CAR_CAMERA)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException(
-                    "requires " + Car.PERMISSION_CAR_CAMERA);
-        }
+        assertPermission(context, Car.PERMISSION_CAR_CAMERA);
     }
 
     public static void assertNavigationManagerPermission(Context context) {
-        if (context.checkCallingOrSelfPermission(Car.PERMISSION_CAR_NAVIGATION_MANAGER)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException(
-                    "requires " + Car.PERMISSION_CAR_NAVIGATION_MANAGER);
-        }
+        assertPermission(context, Car.PERMISSION_CAR_NAVIGATION_MANAGER);
     }
 
     public static void assertHvacPermission(Context context) {
-        if (context.checkCallingOrSelfPermission(Car.PERMISSION_CAR_HVAC)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException(
-                    "requires " + Car.PERMISSION_CAR_HVAC);
-        }
+        assertPermission(context, Car.PERMISSION_CAR_HVAC);
     }
 
     private static void assertRadioPermission(Context context) {
-        if (context.checkCallingOrSelfPermission(Car.PERMISSION_CAR_RADIO)
-            != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException(
-                "requires permission " + Car.PERMISSION_CAR_RADIO);
-        }
+        assertPermission(context, Car.PERMISSION_CAR_RADIO);
     }
 
     public static void assertProjectionPermission(Context context) {
-        if (context.checkCallingOrSelfPermission(Car.PERMISSION_CAR_PROJECTION)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException(
-                    "requires " + Car.PERMISSION_CAR_PROJECTION);
+        assertPermission(context, Car.PERMISSION_CAR_PROJECTION);
+    }
+
+    public static void assertPermission(Context context, String permission) {
+        if (context.checkCallingOrSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("requires " + permission);
         }
     }
 
diff --git a/service/src/com/android/car/hal/HvacHalService.java b/service/src/com/android/car/hal/HvacHalService.java
index 98a15d1..1341506 100644
--- a/service/src/com/android/car/hal/HvacHalService.java
+++ b/service/src/com/android/car/hal/HvacHalService.java
@@ -15,193 +15,21 @@
  */
 package com.android.car.hal;
 
-import static com.android.car.hal.CarPropertyUtils.toCarPropertyValue;
-import static com.android.car.hal.CarPropertyUtils.toVehiclePropValue;
-import static java.lang.Integer.toHexString;
-
-import android.car.hardware.CarPropertyConfig;
-import android.car.hardware.CarPropertyValue;
-import android.car.hardware.hvac.CarHvacEvent;
 import android.car.hardware.hvac.CarHvacManager.HvacPropertyId;
-import android.os.ServiceSpecificException;
-import android.util.Log;
-import android.util.SparseIntArray;
 
-import com.android.car.CarLog;
-import com.android.car.vehiclenetwork.VehicleNetwork;
 import com.android.car.vehiclenetwork.VehicleNetworkConsts;
-import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropConfig;
-import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropValue;
 
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-
-public class HvacHalService extends HalServiceBase {
-    private static final boolean   DBG = true;
-    private static final String    TAG = CarLog.TAG_HVAC + ".HvacHalService";
-    private HvacHalListener        mListener;
-    private final VehicleHal       mVehicleHal;
-
-    private final HashMap<Integer, CarPropertyConfig<?>> mProps = new HashMap<>();
-    private final SparseIntArray mHalPropToValueType = new SparseIntArray();
-
-    public interface HvacHalListener {
-        void onPropertyChange(CarHvacEvent event);
-        void onError(int zone, int property);
-    }
+public class HvacHalService extends PropertyHalServiceBase {
+    private static final boolean DBG = true;
+    private static final String TAG = "HvacHalService";
 
     public HvacHalService(VehicleHal vehicleHal) {
-        mVehicleHal = vehicleHal;
-        if (DBG) {
-            Log.d(TAG, "started HvacHalService!");
-        }
-    }
-
-    public void setListener(HvacHalListener listener) {
-        synchronized (this) {
-            mListener = listener;
-        }
-    }
-
-    public List<CarPropertyConfig> getHvacProperties() {
-        List<CarPropertyConfig> propList;
-        synchronized (mProps) {
-            propList = new ArrayList<>(mProps.values());
-        }
-        return propList;
-    }
-
-    public CarPropertyValue getHvacProperty(int hvacPropertyId, int areaId) {
-        int halProp = hvacToHalPropId(hvacPropertyId);
-
-        VehiclePropValue value = null;
-        try {
-            VehiclePropValue valueRequest = VehiclePropValue.newBuilder()
-                    .setProp(halProp)
-                    .setZone(areaId)
-                    .setValueType(mHalPropToValueType.get(halProp))
-                    .build();
-
-            value = mVehicleHal.getVehicleNetwork().getProperty(valueRequest);
-        } catch (ServiceSpecificException e) {
-            Log.e(CarLog.TAG_HVAC, "get, property not ready 0x" + toHexString(halProp), e);
-        }
-
-        return value == null ? null : toCarPropertyValue(value, hvacPropertyId);
-    }
-
-    public void setHvacProperty(CarPropertyValue prop) {
-        VehiclePropValue halProp = toVehiclePropValue(prop, hvacToHalPropId(prop.getPropertyId()));
-        try {
-            mVehicleHal.getVehicleNetwork().setProperty(halProp);
-        } catch (ServiceSpecificException e) {
-            Log.e(CarLog.TAG_HVAC, "set, property not ready 0x" + toHexString(halProp.getProp()), e);
-            throw new RuntimeException(e);
-        }
-    }
-
-    @Override
-    public void init() {
-        if (DBG) {
-            Log.d(TAG, "init()");
-        }
-        synchronized (mProps) {
-            // Subscribe to each of the HVAC properties
-            for (Integer prop : mProps.keySet()) {
-                mVehicleHal.subscribeProperty(this, prop, 0);
-            }
-        }
-    }
-
-    @Override
-    public void release() {
-        if (DBG) {
-            Log.d(TAG, "release()");
-        }
-        synchronized (mProps) {
-            for (Integer prop : mProps.keySet()) {
-                mVehicleHal.unsubscribeProperty(this, prop);
-            }
-
-            // Clear the property list
-            mProps.clear();
-        }
-        mListener = null;
-    }
-
-    @Override
-    public synchronized List<VehiclePropConfig> takeSupportedProperties(
-            List<VehiclePropConfig> allProperties) {
-        List<VehiclePropConfig> taken = new LinkedList<>();
-
-        for (VehiclePropConfig p : allProperties) {
-            int hvacPropId;
-            try {
-                hvacPropId = halToHvacPropId(p.getProp());
-            } catch (IllegalArgumentException e) {
-                continue;
-            }
-            CarPropertyConfig hvacConfig = CarPropertyUtils.toCarPropertyConfig(p, hvacPropId);
-
-            taken.add(p);
-            mProps.put(p.getProp(), hvacConfig);
-            mHalPropToValueType.put(p.getProp(), p.getValueType());
-
-            if (DBG) {
-                Log.d(TAG, "takeSupportedProperties:  " + toHexString(p.getProp()));
-            }
-        }
-        return taken;
-    }
-
-    @Override
-    public void handleHalEvents(List<VehiclePropValue> values) {
-        HvacHalListener listener;
-        synchronized (this) {
-            listener = mListener;
-        }
-        if (listener != null) {
-            dispatchEventToListener(listener, values);
-        }
-    }
-
-    private void dispatchEventToListener(HvacHalListener listener, List<VehiclePropValue> values) {
-        for (VehiclePropValue v : values) {
-            int prop = v.getProp();
-
-            int hvacPropId;
-            try {
-                hvacPropId = halToHvacPropId(prop);
-            } catch (IllegalArgumentException ex) {
-                Log.e(TAG, "Property is not supported: 0x" + toHexString(prop), ex);
-                continue;
-            }
-
-            CarHvacEvent event;
-            CarPropertyValue<?> hvacProperty = toCarPropertyValue(v, hvacPropId);
-            event = new CarHvacEvent(CarHvacEvent.HVAC_EVENT_PROPERTY_CHANGE, hvacProperty);
-
-            listener.onPropertyChange(event);
-            if (DBG) {
-                Log.d(TAG, "dispatchEventToListener event: " + event);
-            }
-        }
-    }
-
-    @Override
-    public void dump(PrintWriter writer) {
-        writer.println("*HVAC HAL*");
-        writer.println("  Properties available:");
-        for (CarPropertyConfig prop : mProps.values()) {
-            writer.println("    " + prop.toString());
-        }
+        super(vehicleHal, TAG, DBG);
     }
 
     // Convert the HVAC public API property ID to HAL property ID
-    private static int hvacToHalPropId(int hvacPropId) {
+    @Override
+    protected int managerToHalPropId(int hvacPropId) {
         switch (hvacPropId) {
             case HvacPropertyId.ZONED_FAN_SPEED_SETPOINT:
                 return VehicleNetworkConsts.VEHICLE_PROPERTY_HVAC_FAN_SPEED;
@@ -228,12 +56,13 @@
             case HvacPropertyId.ZONED_MAX_DEFROST_ON:
                 return VehicleNetworkConsts.VEHICLE_PROPERTY_HVAC_MAX_DEFROST_ON;
             default:
-                throw new IllegalArgumentException("hvacPropId " + hvacPropId + " is not supported");
+                throw new IllegalArgumentException("hvacPropId " + hvacPropId + " not supported");
         }
     }
 
     // Convert he HAL specific property ID to HVAC public API
-    private static int halToHvacPropId(int halPropId) {
+    @Override
+    protected int halToManagerPropId(int halPropId) {
         switch (halPropId) {
             case VehicleNetworkConsts.VEHICLE_PROPERTY_HVAC_FAN_SPEED:
                 return HvacPropertyId.ZONED_FAN_SPEED_SETPOINT;
@@ -260,7 +89,7 @@
             case VehicleNetworkConsts.VEHICLE_PROPERTY_HVAC_MAX_DEFROST_ON:
                 return HvacPropertyId.ZONED_MAX_DEFROST_ON;
             default:
-                throw new IllegalArgumentException("halPropId " + halPropId + " is not supported");
+                throw new IllegalArgumentException("halPropId " + halPropId + " not supported");
         }
     }
 }
diff --git a/service/src/com/android/car/hal/PropertyHalServiceBase.java b/service/src/com/android/car/hal/PropertyHalServiceBase.java
new file mode 100644
index 0000000..bc93e56
--- /dev/null
+++ b/service/src/com/android/car/hal/PropertyHalServiceBase.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2016 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.hal;
+
+import static com.android.car.hal.CarPropertyUtils.toCarPropertyValue;
+import static com.android.car.hal.CarPropertyUtils.toVehiclePropValue;
+import static java.lang.Integer.toHexString;
+
+import android.car.hardware.CarPropertyConfig;
+import android.car.hardware.CarPropertyValue;
+import android.car.hardware.property.CarPropertyEvent;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+import android.util.SparseIntArray;
+
+import com.android.car.CarLog;
+import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropConfig;
+import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropValue;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Common interface for HAL services that send Vehicle Properties back and forth via ICarProperty.
+ * Services that communicate by passing vehicle properties back and forth via ICarProperty should
+ * extend this class.
+ */
+public abstract class PropertyHalServiceBase extends HalServiceBase {
+    private final boolean mDbg;
+    private final SparseIntArray mHalPropToValueType = new SparseIntArray();
+    private PropertyHalListener mListener;
+    private final ConcurrentHashMap<Integer, CarPropertyConfig<?>> mProps =
+            new ConcurrentHashMap<>();
+    private final String mTag;
+    private final VehicleHal mVehicleHal;
+
+    public interface PropertyHalListener {
+        void onPropertyChange(CarPropertyEvent event);
+        void onError(int zone, int property);
+    }
+
+    public PropertyHalServiceBase(VehicleHal vehicleHal, String tag, boolean dbg) {
+        mVehicleHal = vehicleHal;
+        mTag = "PropertyHalServiceBase." + tag;
+        mDbg = dbg;
+
+        if (mDbg) {
+            Log.d(mTag, "started PropertyHalServiceBase!");
+        }
+    }
+
+    public void setListener(PropertyHalListener listener) {
+        synchronized (this) {
+            mListener = listener;
+        }
+    }
+
+    public List<CarPropertyConfig> getPropertyList() {
+        return new ArrayList<>(mProps.values());
+    }
+
+    public CarPropertyValue getProperty(int mgrPropId, int areaId) {
+        int halPropId = managerToHalPropId(mgrPropId);
+
+        VehiclePropValue value = null;
+        try {
+            VehiclePropValue valueRequest = VehiclePropValue.newBuilder()
+                    .setProp(halPropId)
+                    .setZone(areaId)
+                    .setValueType(mHalPropToValueType.get(halPropId))
+                    .build();
+
+            value = mVehicleHal.getVehicleNetwork().getProperty(valueRequest);
+        } catch (ServiceSpecificException e) {
+            Log.e(CarLog.TAG_PROPERTY, "get, property not ready 0x" + toHexString(halPropId), e);
+        }
+
+        return value == null ? null : toCarPropertyValue(value, mgrPropId);
+    }
+
+    public void setProperty(CarPropertyValue prop) {
+        int halPropId = managerToHalPropId(prop.getPropertyId());
+        VehiclePropValue halProp = toVehiclePropValue(prop, halPropId);
+        try {
+            mVehicleHal.getVehicleNetwork().setProperty(halProp);
+        } catch (ServiceSpecificException e) {
+            Log.e(CarLog.TAG_PROPERTY, "set, property not ready 0x" + toHexString(halPropId), e);
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void init() {
+        if (mDbg) {
+            Log.d(mTag, "init()");
+        }
+        // Subscribe to each of the properties
+        for (Integer prop : mProps.keySet()) {
+            mVehicleHal.subscribeProperty(this, prop, 0);
+        }
+    }
+
+    @Override
+    public void release() {
+        if (mDbg) {
+            Log.d(mTag, "release()");
+        }
+
+        for (Integer prop : mProps.keySet()) {
+            mVehicleHal.unsubscribeProperty(this, prop);
+        }
+
+        // Clear the property list
+        mProps.clear();
+
+        synchronized (this) {
+            mListener = null;
+        }
+    }
+
+    @Override
+    public List<VehiclePropConfig> takeSupportedProperties(
+            List<VehiclePropConfig> allProperties) {
+        List<VehiclePropConfig> taken = new LinkedList<>();
+
+        for (VehiclePropConfig p : allProperties) {
+            int mgrPropId;
+            int halPropId;
+
+            try {
+                // See if the property is handled by this HAL
+                mgrPropId = halToManagerPropId(p.getProp());
+                halPropId = managerToHalPropId(mgrPropId);
+                if (halPropId != p.getProp()) {
+                    throw new IllegalArgumentException("propId " + p.getProp() + " becomes " +
+                            halPropId);
+                }
+            } catch (IllegalArgumentException e) {
+                continue;
+            }
+            CarPropertyConfig config = CarPropertyUtils.toCarPropertyConfig(p, mgrPropId);
+
+            taken.add(p);
+            mProps.put(p.getProp(), config);
+            mHalPropToValueType.put(p.getProp(), p.getValueType());
+
+            if (mDbg) {
+                Log.d(mTag, "takeSupportedProperties:  " + toHexString(p.getProp()));
+            }
+        }
+        return taken;
+    }
+
+    @Override
+    public void handleHalEvents(List<VehiclePropValue> values) {
+        PropertyHalListener listener;
+        synchronized (this) {
+            listener = mListener;
+        }
+        if (listener != null) {
+            for (VehiclePropValue v : values) {
+                int prop = v.getProp();
+                int mgrPropId;
+
+                try {
+                    mgrPropId = halToManagerPropId(prop);
+                } catch (IllegalArgumentException ex) {
+                    Log.e(mTag, "Property is not supported: 0x" + toHexString(prop), ex);
+                    continue;
+                }
+
+                CarPropertyEvent event;
+                CarPropertyValue<?> propVal = toCarPropertyValue(v, mgrPropId);
+                event = new CarPropertyEvent(CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE,
+                        propVal);
+
+                listener.onPropertyChange(event);
+                if (mDbg) {
+                    Log.d(mTag, "handleHalEvents event: " + event);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void dump(PrintWriter writer) {
+        writer.println(mTag);
+        writer.println("  Properties available:");
+        for (CarPropertyConfig prop : mProps.values()) {
+            writer.println("    " + prop.toString());
+        }
+    }
+
+    /**
+     * Convert manager property ID to Vehicle HAL property ID
+     */
+    abstract protected int managerToHalPropId(int managerPropId);
+
+    /**
+     * Convert Vehicle HAL property ID to manager property ID
+     */
+    abstract protected int halToManagerPropId(int halPropId);
+}