[1/N] Migrate VehicleStub to AIDL types.

This is part of CLs migrating car service from HIDL types to
AIDL types. This CL updates VehicleStub methods to expose HalPropValue
and AIDLtypes.

Test: atest CarServiceUnitTest:com.android.car.hal
Bug: 205774940
Change-Id: If8809392e5a87f2bab98ceb58d50f6d494e6ea25
diff --git a/service/src/com/android/car/AidlVehicleStub.java b/service/src/com/android/car/AidlVehicleStub.java
new file mode 100644
index 0000000..5cdec1b
--- /dev/null
+++ b/service/src/com/android/car/AidlVehicleStub.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2021 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.annotation.Nullable;
+import android.car.builtin.os.ServiceManagerHelper;
+import android.car.builtin.util.Slogf;
+import android.hardware.automotive.vehicle.IVehicle;
+import android.hardware.automotive.vehicle.SubscribeOptions;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+
+import com.android.car.hal.HalClientCallback;
+import com.android.car.hal.HalPropConfig;
+import com.android.car.hal.HalPropValue;
+import com.android.car.hal.HalPropValueBuilder;
+import com.android.internal.annotations.VisibleForTesting;
+
+final class AidlVehicleStub extends VehicleStub {
+
+    private static final String AIDL_VHAL_SERVICE =
+            "android.hardware.automotive.vehicle.IVehicle/default";
+
+    private final IVehicle mAidlVehicle;
+    private final HalPropValueBuilder mPropValueBuilder;
+
+    AidlVehicleStub() {
+        this(getAidlVehicle());
+    }
+
+    @VisibleForTesting
+    AidlVehicleStub(IVehicle aidlVehicle) {
+        mAidlVehicle = aidlVehicle;
+        mPropValueBuilder = new HalPropValueBuilder(/*isAidl=*/true);
+    }
+
+
+    /**
+     * Gets a HalPropValueBuilder that could be used to build a HalPropValue.
+     *
+     * @return a builder to build HalPropValue.
+     */
+    @Override
+    public HalPropValueBuilder getHalPropValueBuilder() {
+        return mPropValueBuilder;
+    }
+
+    /**
+     * Returns whether this vehicle stub is connecting to a valid vehicle HAL.
+     *
+     * @return Whether this vehicle stub is connecting to a valid vehicle HAL.
+     */
+    @Override
+    public boolean isValid() {
+        return mAidlVehicle != null;
+    }
+
+    /**
+     * Gets the interface descriptor for the connecting vehicle HAL.
+     *
+     * @return the interface descriptor.
+     * @throws IllegalStateException If unable to get the descriptor.
+     */
+    @Override
+    public String getInterfaceDescriptor() throws IllegalStateException {
+        try {
+            return mAidlVehicle.asBinder().getInterfaceDescriptor();
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Unable to get Vehicle HAL interface descriptor", e);
+        }
+    }
+
+    /**
+     * Register a death recipient that would be called when vehicle HAL died.
+     *
+     * @param recipient A death recipient.
+     * @throws IllegalStateException If unable to register the death recipient.
+     */
+    @Override
+    public void linkToDeath(IVehicleDeathRecipient recipient) throws IllegalStateException {
+        try {
+            mAidlVehicle.asBinder().linkToDeath(recipient, /*flag=*/ 0);
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Failed to linkToDeath Vehicle HAL");
+        }
+    }
+
+    /**
+     * Unlink a previously linked death recipient.
+     *
+     * @param recipient A previously linked death recipient.
+     */
+    @Override
+    public void unlinkToDeath(IVehicleDeathRecipient recipient) {
+        mAidlVehicle.asBinder().unlinkToDeath(recipient, /*flag=*/ 0);
+    }
+
+    /**
+     * Get all property configs.
+     *
+     * @return All the property configs.
+     * @throws RemoteException if the remote operation fails.
+     * @throws ServiceSpecificException if VHAL returns service specific error.
+     */
+    @Override
+    public HalPropConfig[] getAllPropConfigs()
+            throws RemoteException, ServiceSpecificException {
+        // TODO(b/205774940): Call AIDL APIs.
+        return null;
+    }
+
+    /**
+     * Subscribe to a property.
+     *
+     * @param callback The VehicleStubCallback that would be called for subscribe events.
+     * @param options The list of subscribe options.
+     * @throws RemoteException if the remote operation fails.
+     * @throws ServiceSpecificException if VHAL returns service specific error.
+     */
+    @Override
+    public void subscribe(VehicleStubCallback callback, SubscribeOptions[] options)
+            throws RemoteException, ServiceSpecificException {
+        // TODO(b/205774940): Call AIDL APIs.
+        return;
+    }
+
+    /**
+     * Unsubscribe to a property.
+     *
+     * @param callback The previously subscribed callback to unsubscribe.
+     * @param prop The ID for the property to unsubscribe.
+     * @throws RemoteException if the remote operation fails.
+     * @throws ServiceSpecificException if VHAL returns service specific error.
+     */
+    @Override
+    public void unsubscribe(VehicleStubCallback callback, int prop)
+            throws RemoteException, ServiceSpecificException {
+        // TODO(b/205774940): Call AIDL APIs.
+        return;
+    }
+
+    /**
+     * Get a new {@code VehicleStubCallback} that could be used to subscribe/unsubscribe.
+     *
+     * @param callback A callback that could be used to receive events.
+     * @return a {@code VehicleStubCallback} that could be passed to subscribe/unsubscribe.
+     */
+    @Override
+    public VehicleStubCallback newCallback(HalClientCallback callback) {
+        // TODO(b/205774940): Return AIDL callback.
+        return null;
+    }
+
+    /**
+     * Get a property.
+     *
+     * @param requestedPropValue The property to get.
+     * @return The vehicle property value.
+     * @throws RemoteException if the remote operation fails.
+     * @throws ServiceSpecificException if VHAL returns service specific error.
+     */
+    @Override
+    @Nullable
+    public HalPropValue get(HalPropValue requestedPropValue)
+            throws RemoteException, ServiceSpecificException {
+        // TODO(b/205774940): Call AIDL APIs.
+        return null;
+    }
+
+    /**
+     * Set a property.
+     *
+     * @param propValue The property to set.
+     * @throws RemoteException if the remote operation fails.
+     * @throws ServiceSpecificException if VHAL returns service specific error.
+     */
+    @Override
+    public void set(HalPropValue propValue) throws RemoteException, ServiceSpecificException {
+        // TODO(b/205774940): Call AIDL APIs.
+        return;
+    }
+
+    @Nullable
+    private static IVehicle getAidlVehicle() {
+        try {
+            return IVehicle.Stub.asInterface(
+                    ServiceManagerHelper.waitForDeclaredService(AIDL_VHAL_SERVICE));
+        } catch (RuntimeException e) {
+            Slogf.w(CarLog.TAG_SERVICE, "Failed to get \"" + AIDL_VHAL_SERVICE + "\" service", e);
+        }
+        return null;
+    }
+}
diff --git a/service/src/com/android/car/CarServiceImpl.java b/service/src/com/android/car/CarServiceImpl.java
index dec3b99..fe77f17 100644
--- a/service/src/com/android/car/CarServiceImpl.java
+++ b/service/src/com/android/car/CarServiceImpl.java
@@ -55,7 +55,7 @@
         initTiming.traceBegin("CarService.onCreate");
 
         initTiming.traceBegin("getVehicle");
-        mVehicle = new VehicleStub();
+        mVehicle = VehicleStub.newVehicleStub();
         initTiming.traceEnd(); // "getVehicle"
 
         EventLogHelper.writeCarServiceCreate(/* hasVhal= */ mVehicle.isValid());
diff --git a/service/src/com/android/car/HidlVehicleStub.java b/service/src/com/android/car/HidlVehicleStub.java
new file mode 100644
index 0000000..88db8bb
--- /dev/null
+++ b/service/src/com/android/car/HidlVehicleStub.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car;
+
+import static com.android.car.CarServiceUtils.subscribeOptionsToHidl;
+
+import android.annotation.Nullable;
+import android.car.builtin.util.Slogf;
+import android.hardware.automotive.vehicle.SubscribeOptions;
+import android.hardware.automotive.vehicle.V2_0.IVehicle;
+import android.hardware.automotive.vehicle.V2_0.IVehicleCallback;
+import android.hardware.automotive.vehicle.V2_0.StatusCode;
+import android.hardware.automotive.vehicle.VehiclePropError;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.os.SystemProperties;
+
+import com.android.car.hal.HalClientCallback;
+import com.android.car.hal.HalPropConfig;
+import com.android.car.hal.HalPropValue;
+import com.android.car.hal.HalPropValueBuilder;
+import com.android.car.hal.HidlHalPropConfig;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.NoSuchElementException;
+
+final class HidlVehicleStub extends VehicleStub {
+
+    private final IVehicle mHidlVehicle;
+    private final HalPropValueBuilder mPropValueBuilder;
+
+    HidlVehicleStub() {
+        this(getHidlVehicle());
+    }
+
+    @VisibleForTesting
+    HidlVehicleStub(IVehicle hidlVehicle) {
+        mHidlVehicle = hidlVehicle;
+        mPropValueBuilder = new HalPropValueBuilder(/*isAidl=*/false);
+    }
+
+    /**
+     * Gets a HalPropValueBuilder that could be used to build a HalPropValue.
+     *
+     * @return a builder to build HalPropValue.
+     */
+    @Override
+    public HalPropValueBuilder getHalPropValueBuilder() {
+        return mPropValueBuilder;
+    }
+
+    /**
+     * Returns whether this vehicle stub is connecting to a valid vehicle HAL.
+     *
+     * @return Whether this vehicle stub is connecting to a valid vehicle HAL.
+     */
+    @Override
+    public boolean isValid() {
+        return mHidlVehicle != null;
+    }
+
+    /**
+     * Gets the interface descriptor for the connecting vehicle HAL.
+     *
+     * @return the interface descriptor.
+     * @throws IllegalStateException If unable to get the descriptor.
+     */
+    @Override
+    public String getInterfaceDescriptor() throws IllegalStateException {
+        try {
+            return mHidlVehicle.interfaceDescriptor();
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Unable to get Vehicle HAL interface descriptor", e);
+        }
+    }
+
+    /**
+     * Register a death recipient that would be called when vehicle HAL died.
+     *
+     * @param recipient A death recipient.
+     * @throws IllegalStateException If unable to register the death recipient.
+     */
+    @Override
+    public void linkToDeath(IVehicleDeathRecipient recipient) throws IllegalStateException {
+        try {
+            mHidlVehicle.linkToDeath(recipient, /*flag=*/ 0);
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Failed to linkToDeath Vehicle HAL");
+        }
+    }
+
+    /**
+     * Unlink a previously linked death recipient.
+     *
+     * @param recipient A previously linked death recipient.
+     */
+    @Override
+    public void unlinkToDeath(IVehicleDeathRecipient recipient) {
+        try {
+            mHidlVehicle.unlinkToDeath(recipient);
+        } catch (RemoteException e) {
+            // Ignore errors on shutdown path.
+        }
+    }
+
+    /**
+     * Get all property configs.
+     *
+     * @return All the property configs.
+     */
+    @Override
+    public HalPropConfig[] getAllPropConfigs() throws RemoteException {
+        ArrayList<android.hardware.automotive.vehicle.V2_0.VehiclePropConfig> hidlConfigs =
+                mHidlVehicle.getAllPropConfigs();
+        int configSize = hidlConfigs.size();
+        HalPropConfig[] configs = new HalPropConfig[configSize];
+        for (int i = 0; i < configSize; i++) {
+            configs[i] = new HidlHalPropConfig(hidlConfigs.get(i));
+        }
+        return configs;
+    }
+
+    /**
+     * Subscribe to a property.
+     *
+     * @param callback The VehicleStubCallback that would be called for subscribe events.
+     * @param options The list of subscribe options.
+     * @throws RemoteException if the subscription fails.
+     */
+    @Override
+    public void subscribe(VehicleStubCallback callback, SubscribeOptions[] options)
+            throws RemoteException {
+        ArrayList<android.hardware.automotive.vehicle.V2_0.SubscribeOptions> hidlOptions =
+                new ArrayList<android.hardware.automotive.vehicle.V2_0.SubscribeOptions>();
+        for (SubscribeOptions option : options) {
+            hidlOptions.add(subscribeOptionsToHidl(option));
+        }
+        mHidlVehicle.subscribe(callback.getHidlCallback(), hidlOptions);
+    }
+
+    /**
+     * Unsubscribe to a property.
+     *
+     * @param callback The previously subscribed callback to unsubscribe.
+     * @param prop The ID for the property to unsubscribe.
+     * @throws RemoteException if the unsubscription fails.
+     */
+    @Override
+    public void unsubscribe(VehicleStubCallback callback, int prop) throws RemoteException {
+        mHidlVehicle.unsubscribe((IVehicleCallback.Stub)
+                callback, prop);
+    }
+
+    /**
+     * Get a new {@code VehicleStubCallback} that could be used to subscribe/unsubscribe.
+     *
+     * @param callback A callback that could be used to receive events.
+     * @return a {@code VehicleStubCallback} that could be passed to subscribe/unsubscribe.
+     */
+    @Override
+    public VehicleStubCallback newCallback(HalClientCallback callback) {
+        return new HidlVehicleCallback(callback, mPropValueBuilder);
+    }
+
+    private static class GetValueResult {
+        public int status;
+        public android.hardware.automotive.vehicle.V2_0.VehiclePropValue value;
+    }
+
+    /**
+     * Get a property.
+     *
+     * @param requestedPropValue The property to get.
+     * @return The vehicle property value.
+     * @throws RemoteException if the remote operation fails.
+     * @throws ServiceSpecificException if VHAL returns service specific error.
+     */
+    @Override
+    @Nullable
+    public HalPropValue get(HalPropValue requestedPropValue)
+            throws RemoteException, ServiceSpecificException {
+        android.hardware.automotive.vehicle.V2_0.VehiclePropValue hidlPropValue =
+                (android.hardware.automotive.vehicle.V2_0.VehiclePropValue) requestedPropValue
+                        .toVehiclePropValue();
+        GetValueResult result = new GetValueResult();
+        mHidlVehicle.get(
+                hidlPropValue,
+                (s, p) -> {
+                    result.status = s;
+                    result.value = p;
+                });
+
+        if (result.status != android.hardware.automotive.vehicle.V2_0.StatusCode.OK) {
+            throw new ServiceSpecificException(
+                    result.status,
+                    "failed to get value for property: " + Integer.toString(hidlPropValue.prop));
+        }
+
+        if (result.value == null) {
+            return null;
+        }
+
+        return getHalPropValueBuilder().build(result.value);
+    }
+
+    /**
+     * Set a property.
+     *
+     * @param propValue The property to set.
+     * @throws RemoteException if the remote operation fails.
+     * @throws ServiceSpecificException if VHAL returns service specific error.
+     */
+    @Override
+    public void set(HalPropValue propValue) throws RemoteException {
+        android.hardware.automotive.vehicle.V2_0.VehiclePropValue hidlPropValue =
+                (android.hardware.automotive.vehicle.V2_0.VehiclePropValue) propValue
+                        .toVehiclePropValue();
+        int status = mHidlVehicle.set(hidlPropValue);
+        if (status != StatusCode.OK) {
+            throw new ServiceSpecificException(status, "failed to set value for property: "
+                    + Integer.toString(hidlPropValue.prop));
+        }
+    }
+
+    @Nullable
+    private static IVehicle getHidlVehicle() {
+        String instanceName = SystemProperties.get("ro.vehicle.hal", "default");
+
+        try {
+            return IVehicle.getService(instanceName);
+        } catch (RemoteException e) {
+            Slogf.e(CarLog.TAG_SERVICE, "Failed to get IVehicle/" + instanceName + " service", e);
+        } catch (NoSuchElementException e) {
+            Slogf.e(CarLog.TAG_SERVICE, "IVehicle/" + instanceName + " service not registered yet");
+        }
+        return null;
+    }
+
+    private static class HidlVehicleCallback
+            extends IVehicleCallback.Stub
+            implements VehicleStubCallback {
+        private final HalClientCallback mCallback;
+        private final HalPropValueBuilder mBuilder;
+
+        HidlVehicleCallback(HalClientCallback callback, HalPropValueBuilder builder) {
+            mCallback = callback;
+            mBuilder = builder;
+        }
+
+        @Override
+        public android.hardware.automotive.vehicle.IVehicleCallback getAidlCallback() {
+            throw new UnsupportedOperationException(
+                    "getAidlCallback should never be called on a HidlVehicleCallback");
+        }
+
+        public android.hardware.automotive.vehicle.V2_0.IVehicleCallback.Stub getHidlCallback() {
+            return this;
+        }
+
+        @Override
+        public void onPropertyEvent(
+                ArrayList<android.hardware.automotive.vehicle.V2_0.VehiclePropValue> propValues) {
+            ArrayList<HalPropValue> values = new ArrayList<>();
+            for (android.hardware.automotive.vehicle.V2_0.VehiclePropValue value : propValues) {
+                values.add(mBuilder.build(value));
+            }
+            mCallback.onPropertyEvent(values);
+        }
+
+        @Override
+        public void onPropertySet(
+                android.hardware.automotive.vehicle.V2_0.VehiclePropValue propValue) {
+            // Deprecated, do nothing.
+        }
+
+        @Override
+        public void onPropertySetError(int errorCode, int propId, int areaId) {
+            VehiclePropError error = new VehiclePropError();
+            error.propId = propId;
+            error.areaId = areaId;
+            error.errorCode = errorCode;
+            ArrayList<VehiclePropError> errors = new ArrayList<VehiclePropError>();
+            errors.add(error);
+            mCallback.onPropertySetError(errors);
+        }
+    }
+}
diff --git a/service/src/com/android/car/VehicleStub.java b/service/src/com/android/car/VehicleStub.java
index fa05845..4218220 100644
--- a/service/src/com/android/car/VehicleStub.java
+++ b/service/src/com/android/car/VehicleStub.java
@@ -17,59 +17,66 @@
 package com.android.car;
 
 import android.annotation.Nullable;
-import android.car.builtin.os.ServiceManagerHelper;
 import android.car.builtin.util.Slogf;
-import android.hardware.automotive.vehicle.IVehicle;
+import android.hardware.automotive.vehicle.SubscribeOptions;
 import android.os.RemoteException;
-import android.os.SystemProperties;
+import android.os.ServiceSpecificException;
 
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.util.ArrayList;
-import java.util.NoSuchElementException;
+import com.android.car.hal.HalClientCallback;
+import com.android.car.hal.HalPropConfig;
+import com.android.car.hal.HalPropValue;
+import com.android.car.hal.HalPropValueBuilder;
 
 /**
  * VehicleStub represents an IVehicle service interface in either AIDL or legacy HIDL version. It
  * exposes common interface so that the client does not need to care about which version the
  * underlying IVehicle service is in.
  */
-public class VehicleStub {
-    private static final String AIDL_VHAL_SERVICE = "android.hardware.automotive.vehicle";
+public abstract class VehicleStub {
+    /** VehicleStubCallback is either an AIDL or a HIDL callback. */
+    public interface VehicleStubCallback {
+        /**
+         *  Get the callback interface for AIDL backend.
+         */
+        android.hardware.automotive.vehicle.IVehicleCallback getAidlCallback();
+        /**
+         * Get the callback interface for HIDL backend.
+         */
+        android.hardware.automotive.vehicle.V2_0.IVehicleCallback.Stub getHidlCallback();
+    }
 
-    private final IVehicle mAidlVehicle;
-    private final android.hardware.automotive.vehicle.V2_0.IVehicle mHidlVehicle;
-
-    public VehicleStub() {
-        mAidlVehicle = getAidlVehicle();
-        if (mAidlVehicle != null) {
-            mHidlVehicle = null;
-            return;
+    /**
+     * Create a new VehicleStub to connect to Vehicle HAL.
+     *
+     * Create a new VehicleStub to connect to Vehicle HAL according to which backend (AIDL or HIDL)
+     * is available. Caller must call isValid to check the returned {@code VehicleStub} before using
+     * it.
+     *
+     * @return a vehicle stub to connect to Vehicle HAL.
+     */
+    public static VehicleStub newVehicleStub() {
+        VehicleStub stub = new AidlVehicleStub();
+        if (stub.isValid()) {
+            return stub;
         }
 
         Slogf.w(CarLog.TAG_SERVICE, "No AIDL vehicle HAL found, fall back to HIDL version");
-        mHidlVehicle = getHidlVehicle();
+        return new HidlVehicleStub();
     }
 
-    @VisibleForTesting
-    public VehicleStub(IVehicle aidlVehicle) {
-        mAidlVehicle = aidlVehicle;
-        mHidlVehicle = null;
-    }
-
-    @VisibleForTesting
-    public VehicleStub(android.hardware.automotive.vehicle.V2_0.IVehicle hidlVehicle) {
-        mHidlVehicle = hidlVehicle;
-        mAidlVehicle = null;
-    }
+    /**
+     * Gets a HalPropValueBuilder that could be used to build a HalPropValue.
+     *
+     * @return a builder to build HalPropValue.
+     */
+    public abstract HalPropValueBuilder getHalPropValueBuilder();
 
     /**
      * Returns whether this vehicle stub is connecting to a valid vehicle HAL.
      *
      * @return Whether this vehicle stub is connecting to a valid vehicle HAL.
      */
-    public boolean isValid() {
-        return mAidlVehicle != null || mHidlVehicle != null;
-    }
+    public abstract boolean isValid();
 
     /**
      * Gets the interface descriptor for the connecting vehicle HAL.
@@ -77,16 +84,7 @@
      * @return the interface descriptor.
      * @throws IllegalStateException If unable to get the descriptor.
      */
-    public String getInterfaceDescriptor() throws IllegalStateException {
-        try {
-            if (mAidlVehicle != null) {
-                return mAidlVehicle.asBinder().getInterfaceDescriptor();
-            }
-            return mHidlVehicle.interfaceDescriptor();
-        } catch (RemoteException e) {
-            throw new IllegalStateException("Unable to get Vehicle HAL interface descriptor", e);
-        }
-    }
+    public abstract String getInterfaceDescriptor() throws IllegalStateException;
 
     /**
      * Register a death recipient that would be called when vehicle HAL died.
@@ -94,141 +92,74 @@
      * @param recipient A death recipient.
      * @throws IllegalStateException If unable to register the death recipient.
      */
-    public void linkToDeath(IVehicleDeathRecipient recipient) throws IllegalStateException {
-        try {
-            if (mAidlVehicle != null) {
-                mAidlVehicle.asBinder().linkToDeath(recipient, /*flag=*/ 0);
-                return;
-            }
-            mHidlVehicle.linkToDeath(recipient, /*flag=*/ 0);
-        } catch (RemoteException e) {
-            throw new IllegalStateException("Failed to linkToDeath Vehicle HAL");
-        }
-    }
+    public abstract void linkToDeath(IVehicleDeathRecipient recipient) throws IllegalStateException;
 
     /**
      * Unlink a previously linked death recipient.
      *
      * @param recipient A previously linked death recipient.
      */
-    public void unlinkToDeath(IVehicleDeathRecipient recipient) {
-        if (mAidlVehicle != null) {
-            mAidlVehicle.asBinder().unlinkToDeath(recipient, /*flag=*/ 0);
-            return;
-        }
-
-        try {
-            mHidlVehicle.unlinkToDeath(recipient);
-        } catch (RemoteException e) {
-            // Ignore errors on shutdown path.
-        }
-    }
+    public abstract void unlinkToDeath(IVehicleDeathRecipient recipient);
 
     /**
      * Get all property configs.
      *
      * @return All the property configs.
+     * @throws RemoteException if the remote operation fails.
+     * @throws ServiceSpecificException if VHAL returns service specific error.
      */
-    public ArrayList<android.hardware.automotive.vehicle.V2_0.VehiclePropConfig> getAllPropConfigs()
-            throws RemoteException {
-        if (mAidlVehicle != null) {
-            // TODO(b/205774940): Call AIDL APIs.
-            return null;
-        }
-        return mHidlVehicle.getAllPropConfigs();
-    }
+    public abstract HalPropConfig[] getAllPropConfigs()
+            throws RemoteException, ServiceSpecificException;
 
     /**
      * Subscribe to a property.
      *
-     * @param callback The IVehicleCallback that would be called for subscribe events.
+     * @param callback The VehicleStubCallback that would be called for subscribe events.
      * @param options The list of subscribe options.
-     * @throws RemoteException if the subscription fails.
+     * @throws RemoteException if the remote operation fails.
+     * @throws ServiceSpecificException if VHAL returns service specific error.
      */
-    public void subscribe(
-            android.hardware.automotive.vehicle.V2_0.IVehicleCallback callback,
-            ArrayList<android.hardware.automotive.vehicle.V2_0.SubscribeOptions> options)
-            throws RemoteException {
-        if (mAidlVehicle != null) {
-            // TODO(b/205774940): Call AIDL APIs.
-            return;
-        }
-        mHidlVehicle.subscribe(callback, options);
-    }
+    public abstract void subscribe(VehicleStubCallback callback,
+            SubscribeOptions[] options) throws RemoteException, ServiceSpecificException;
 
     /**
      * Unsubscribe to a property.
      *
      * @param callback The previously subscribed callback to unsubscribe.
      * @param prop The ID for the property to unsubscribe.
-     * @throws RemoteException if the unsubscription fails.
+     * @throws RemoteException if the remote operation fails.
+     * @throws ServiceSpecificException if VHAL returns service specific error.
      */
-    public void unsubscribe(
-            android.hardware.automotive.vehicle.V2_0.IVehicleCallback callback, int prop)
-            throws RemoteException {
-        if (mAidlVehicle != null) {
-            // TODO(b/205774940): Call AIDL APIs.
-            return;
-        }
-        mHidlVehicle.unsubscribe(callback, prop);
-    }
+    public abstract void unsubscribe(VehicleStubCallback callback, int prop)
+            throws RemoteException, ServiceSpecificException;
+
+    /**
+     * Get a new {@code VehicleStubCallback} that could be used to subscribe/unsubscribe.
+     *
+     * @param callback A callback that could be used to receive events.
+     * @return a {@code VehicleStubCallback} that could be passed to subscribe/unsubscribe.
+     */
+    public abstract VehicleStubCallback newCallback(HalClientCallback callback);
 
     /**
      * Get a property.
      *
      * @param requestedPropValue The property to get.
-     * @param callback The callback to be called for the result.
-     * @throws RemoteException if the operation fails.
+     * @return The vehicle property value.
+     * @throws RemoteException if the remote operation fails.
+     * @throws ServiceSpecificException if VHAL returns service specific error.
      */
-    public void get(
-            android.hardware.automotive.vehicle.V2_0.VehiclePropValue requestedPropValue,
-            android.hardware.automotive.vehicle.V2_0.IVehicle.getCallback callback)
-            throws RemoteException {
-        if (mAidlVehicle != null) {
-            // TODO(b/205774940): Call AIDL APIs.
-            return;
-        }
-        mHidlVehicle.get(requestedPropValue, callback);
-    }
+    @Nullable
+    public abstract HalPropValue get(HalPropValue requestedPropValue)
+            throws RemoteException, ServiceSpecificException;
 
     /**
      * Set a property.
      *
      * @param propValue The property to set.
-     * @return The status code.
-     * @throws RemoteException if the operation fails.
+     * @throws RemoteException if the remote operation fails.
+     * @throws ServiceSpecificException if VHAL returns service specific error.
      */
-    public int set(android.hardware.automotive.vehicle.V2_0.VehiclePropValue propValue)
-            throws RemoteException {
-        if (mAidlVehicle != null) {
-            // TODO(b/205774940): Call AIDL APIs.
-            return 0;
-        }
-        return mHidlVehicle.set(propValue);
-    }
-
-    @Nullable
-    private static android.hardware.automotive.vehicle.V2_0.IVehicle getHidlVehicle() {
-        String instanceName = SystemProperties.get("ro.vehicle.hal", "default");
-
-        try {
-            return android.hardware.automotive.vehicle.V2_0.IVehicle.getService(instanceName);
-        } catch (RemoteException e) {
-            Slogf.e(CarLog.TAG_SERVICE, "Failed to get IVehicle/" + instanceName + " service", e);
-        } catch (NoSuchElementException e) {
-            Slogf.e(CarLog.TAG_SERVICE, "IVehicle/" + instanceName + " service not registered yet");
-        }
-        return null;
-    }
-
-    @Nullable
-    private IVehicle getAidlVehicle() {
-        try {
-            return IVehicle.Stub.asInterface(
-                    ServiceManagerHelper.waitForDeclaredService(AIDL_VHAL_SERVICE));
-        } catch (RuntimeException e) {
-            Slogf.w(CarLog.TAG_SERVICE, "Failed to get \"" + AIDL_VHAL_SERVICE + "\" service", e);
-        }
-        return null;
-    }
+    public abstract void set(HalPropValue propValue)
+            throws RemoteException, ServiceSpecificException;
 }
diff --git a/service/src/com/android/car/hal/HalClientCallback.java b/service/src/com/android/car/hal/HalClientCallback.java
new file mode 100644
index 0000000..6950a4f
--- /dev/null
+++ b/service/src/com/android/car/hal/HalClientCallback.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 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 android.hardware.automotive.vehicle.VehiclePropError;
+
+import java.util.ArrayList;
+
+/**
+ * HalClientCallback is the callback functions that HalClient supports.
+ */
+public interface HalClientCallback {
+    /**
+     * Called when new property events happen.
+     */
+    void onPropertyEvent(ArrayList<HalPropValue> values);
+
+    /**
+     * Called when property set errors happen.
+     */
+    void onPropertySetError(ArrayList<VehiclePropError> errors);
+}
diff --git a/tests/carservice_test/src/com/android/car/MockedCarTestBase.java b/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
index ecf02f3..60912fe 100644
--- a/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
+++ b/tests/carservice_test/src/com/android/car/MockedCarTestBase.java
@@ -234,7 +234,7 @@
         // This should be done here as feature property is accessed inside the constructor.
         initMockedHal();
 
-        mMockedVehicleStub = new VehicleStub(mMockedVehicleHal);
+        mMockedVehicleStub = new HidlVehicleStub(mMockedVehicleHal);
 
         ICarImpl carImpl = new ICarImpl(mMockedCarTestContext, /*builtinContext=*/null,
                 mMockedVehicleStub, mFakeSystemInterface, /*vehicleInterfaceName=*/"MockedCar",
diff --git a/tests/carservice_unit_test/src/com/android/car/VehicleStubTest.java b/tests/carservice_unit_test/src/com/android/car/VehicleStubTest.java
new file mode 100644
index 0000000..1671e74
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/VehicleStubTest.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.automotive.vehicle.IVehicle;
+import android.hardware.automotive.vehicle.StatusCode;
+import android.hardware.automotive.vehicle.SubscribeOptions;
+import android.hardware.automotive.vehicle.VehiclePropError;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+
+import com.android.car.hal.HalClientCallback;
+import com.android.car.hal.HalPropConfig;
+import com.android.car.hal.HalPropValue;
+import com.android.car.hal.HalPropValueBuilder;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+@RunWith(MockitoJUnitRunner.class)
+public class VehicleStubTest {
+
+    private static final int TEST_PROP = 1;
+    private static final int TEST_ACCESS = 2;
+    private static final float TEST_SAMPLE_RATE = 3.0f;
+    private static final int TEST_VALUE = 3;
+    private static final int TEST_AREA = 4;
+    private static final int TEST_STATUS = 5;
+
+    @Mock
+    private IVehicle mAidlVehicle;
+    @Mock
+    private IBinder mAidlBinder;
+    @Mock
+    private android.hardware.automotive.vehicle.V2_0.IVehicle mHidlVehicle;
+
+    private VehicleStub mAidlVehicleStub;
+    private VehicleStub mHidlVehicleStub;
+
+    @Before
+    public void setUp() {
+        when(mAidlVehicle.asBinder()).thenReturn(mAidlBinder);
+
+        mAidlVehicleStub = new AidlVehicleStub(mAidlVehicle);
+        mHidlVehicleStub = new HidlVehicleStub(mHidlVehicle);
+
+        assertThat(mAidlVehicleStub.isValid()).isTrue();
+        assertThat(mHidlVehicleStub.isValid()).isTrue();
+    }
+
+    @Test
+    public void testGetInterfaceDescriptorHidl() throws Exception {
+        mHidlVehicleStub.getInterfaceDescriptor();
+
+        verify(mHidlVehicle).interfaceDescriptor();
+    }
+
+    @Test
+    public void testGetInterfaceDescriptorAidl() throws Exception {
+        mAidlVehicleStub.getInterfaceDescriptor();
+
+        verify(mAidlBinder).getInterfaceDescriptor();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testGetInterfaceDescriptorRemoteException() throws Exception {
+        when(mAidlBinder.getInterfaceDescriptor()).thenThrow(new RemoteException());
+
+        mAidlVehicleStub.getInterfaceDescriptor();
+    }
+
+    @Test
+    public void testLinkToDeathHidl() throws Exception {
+        IVehicleDeathRecipient recipient = mock(IVehicleDeathRecipient.class);
+
+        mHidlVehicleStub.linkToDeath(recipient);
+
+        verify(mHidlVehicle).linkToDeath(recipient, 0);
+    }
+
+    @Test
+    public void testLinkToDeathAidl() throws Exception {
+        IVehicleDeathRecipient recipient = mock(IVehicleDeathRecipient.class);
+
+        mAidlVehicleStub.linkToDeath(recipient);
+
+        verify(mAidlBinder).linkToDeath(recipient, 0);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testLinkToDeathRemoteException() throws Exception {
+        IVehicleDeathRecipient recipient = mock(IVehicleDeathRecipient.class);
+        doThrow(new RemoteException()).when(mAidlBinder).linkToDeath(recipient, 0);
+
+        mAidlVehicleStub.linkToDeath(recipient);
+    }
+
+    @Test
+    public void testUnlinkToDeathHidl() throws Exception {
+        IVehicleDeathRecipient recipient = mock(IVehicleDeathRecipient.class);
+
+        mHidlVehicleStub.unlinkToDeath(recipient);
+
+        verify(mHidlVehicle).unlinkToDeath(recipient);
+    }
+
+    @Test
+    public void testUnlinkToDeathAidl() throws Exception {
+        IVehicleDeathRecipient recipient = mock(IVehicleDeathRecipient.class);
+
+        mAidlVehicleStub.unlinkToDeath(recipient);
+
+        verify(mAidlBinder).unlinkToDeath(recipient, 0);
+    }
+
+    @Test
+    public void testUnlinkToDeathRemoteException() throws Exception {
+        IVehicleDeathRecipient recipient = mock(IVehicleDeathRecipient.class);
+        doThrow(new RemoteException()).when(mHidlVehicle).unlinkToDeath(recipient);
+
+        mHidlVehicleStub.unlinkToDeath(recipient);
+    }
+
+    @Test
+    public void testGetAllPropConfigsHidl() throws Exception {
+        ArrayList<android.hardware.automotive.vehicle.V2_0.VehiclePropConfig> hidlConfigs = new
+                ArrayList<android.hardware.automotive.vehicle.V2_0.VehiclePropConfig>();
+        android.hardware.automotive.vehicle.V2_0.VehiclePropConfig hidlConfig =
+                new android.hardware.automotive.vehicle.V2_0.VehiclePropConfig();
+        hidlConfig.prop = TEST_PROP;
+        hidlConfig.access = TEST_ACCESS;
+        hidlConfigs.add(hidlConfig);
+
+        when(mHidlVehicle.getAllPropConfigs()).thenReturn(hidlConfigs);
+
+        HalPropConfig[] configs = mHidlVehicleStub.getAllPropConfigs();
+
+        assertThat(configs.length).isEqualTo(1);
+        assertThat(configs[0].getPropId()).isEqualTo(TEST_PROP);
+        assertThat(configs[0].getAccess()).isEqualTo(TEST_ACCESS);
+    }
+
+    @Test
+    public void testSubscribeHidl() throws Exception {
+        SubscribeOptions aidlOptions = new SubscribeOptions();
+        aidlOptions.propId = TEST_PROP;
+        aidlOptions.sampleRate = TEST_SAMPLE_RATE;
+        android.hardware.automotive.vehicle.V2_0.SubscribeOptions hidlOptions =
+                new android.hardware.automotive.vehicle.V2_0.SubscribeOptions();
+        hidlOptions.propId = TEST_PROP;
+        hidlOptions.sampleRate = TEST_SAMPLE_RATE;
+        hidlOptions.flags = android.hardware.automotive.vehicle.V2_0.SubscribeFlags.EVENTS_FROM_CAR;
+
+        HalClientCallback callback = mock(HalClientCallback.class);
+        VehicleStub.VehicleStubCallback stubCallback = mHidlVehicleStub.newCallback(callback);
+
+        mHidlVehicleStub.subscribe(stubCallback, new SubscribeOptions[]{aidlOptions});
+
+        verify(mHidlVehicle).subscribe(
+                stubCallback.getHidlCallback(),
+                new ArrayList<android.hardware.automotive.vehicle.V2_0.SubscribeOptions>(
+                        Arrays.asList(hidlOptions)));
+    }
+
+    @Test
+    public void testUnsubscribeHidl() throws Exception {
+        HalClientCallback callback = mock(HalClientCallback.class);
+        VehicleStub.VehicleStubCallback stubCallback = mHidlVehicleStub.newCallback(callback);
+
+        mHidlVehicleStub.unsubscribe(stubCallback, TEST_PROP);
+
+        verify(mHidlVehicle).unsubscribe(stubCallback.getHidlCallback(), TEST_PROP);
+    }
+
+    @Test
+    public void testGetHidl() throws Exception {
+        doAnswer(new Answer() {
+            public Object answer(InvocationOnMock invocation) {
+                Object[] args = invocation.getArguments();
+                android.hardware.automotive.vehicle.V2_0.VehiclePropValue propValue =
+                        (android.hardware.automotive.vehicle.V2_0.VehiclePropValue) args[0];
+                assertThat(propValue.prop).isEqualTo(TEST_PROP);
+                android.hardware.automotive.vehicle.V2_0.IVehicle.getCallback callback =
+                        (android.hardware.automotive.vehicle.V2_0.IVehicle.getCallback) args[1];
+                callback.onValues(StatusCode.OK, propValue);
+                return null;
+            }
+        }).when(mHidlVehicle).get(any(), any());
+
+        HalPropValueBuilder builder = new HalPropValueBuilder(/*isAidl=*/false);
+        HalPropValue value = builder.build(TEST_PROP, 0, TEST_VALUE);
+
+        HalPropValue gotValue = mHidlVehicleStub.get(value);
+
+        assertThat(gotValue).isEqualTo(value);
+    }
+
+    @Test(expected = ServiceSpecificException.class)
+    public void testGetHidlError() throws Exception {
+        doAnswer(new Answer() {
+            public Object answer(InvocationOnMock invocation) {
+                Object[] args = invocation.getArguments();
+                android.hardware.automotive.vehicle.V2_0.VehiclePropValue propValue =
+                        (android.hardware.automotive.vehicle.V2_0.VehiclePropValue) args[0];
+                assertThat(propValue.prop).isEqualTo(TEST_PROP);
+                android.hardware.automotive.vehicle.V2_0.IVehicle.getCallback callback =
+                        (android.hardware.automotive.vehicle.V2_0.IVehicle.getCallback) args[1];
+                callback.onValues(StatusCode.INVALID_ARG, propValue);
+                return null;
+            }
+        }).when(mHidlVehicle).get(any(), any());
+
+        HalPropValueBuilder builder = new HalPropValueBuilder(/*isAidl=*/false);
+        HalPropValue value = builder.build(TEST_PROP, 0, TEST_VALUE);
+
+        mHidlVehicleStub.get(value);
+    }
+
+    @Test
+    public void testSetHidl() throws Exception {
+        HalPropValueBuilder builder = new HalPropValueBuilder(/*isAidl=*/false);
+        HalPropValue value = builder.build(TEST_PROP, 0, TEST_VALUE);
+        android.hardware.automotive.vehicle.V2_0.VehiclePropValue propValue =
+                new android.hardware.automotive.vehicle.V2_0.VehiclePropValue();
+        propValue.prop = TEST_PROP;
+        propValue.value.int32Values.add(TEST_VALUE);
+
+        when(mHidlVehicle.set(propValue)).thenReturn(StatusCode.OK);
+
+        mHidlVehicleStub.set(value);
+    }
+
+    @Test
+    public void testSetHidlError() throws Exception {
+        HalPropValueBuilder builder = new HalPropValueBuilder(/*isAidl=*/false);
+        HalPropValue value = builder.build(TEST_PROP, 0, TEST_VALUE);
+        android.hardware.automotive.vehicle.V2_0.VehiclePropValue propValue =
+                new android.hardware.automotive.vehicle.V2_0.VehiclePropValue();
+        propValue.prop = TEST_PROP;
+        propValue.value.int32Values.add(TEST_VALUE);
+
+        when(mHidlVehicle.set(propValue)).thenReturn(StatusCode.INVALID_ARG);
+
+        ServiceSpecificException exception = assertThrows(ServiceSpecificException.class, () -> {
+            mHidlVehicleStub.set(value);
+        });
+        assertThat(exception.errorCode).isEqualTo(StatusCode.INVALID_ARG);
+    }
+
+    @Test
+    public void testHidlVehicleCallbackOnPropertyEvent() throws Exception {
+        HalClientCallback callback = mock(HalClientCallback.class);
+        android.hardware.automotive.vehicle.V2_0.IVehicleCallback.Stub hidlCallback =
+                mHidlVehicleStub.newCallback(callback).getHidlCallback();
+        android.hardware.automotive.vehicle.V2_0.VehiclePropValue propValue =
+                new android.hardware.automotive.vehicle.V2_0.VehiclePropValue();
+        propValue.prop = TEST_PROP;
+        propValue.value.int32Values.add(TEST_VALUE);
+        HalPropValueBuilder builder = new HalPropValueBuilder(/*isAidl=*/false);
+        HalPropValue halPropValue = builder.build(TEST_PROP, 0, TEST_VALUE);
+
+        hidlCallback.onPropertyEvent(
+                new ArrayList<android.hardware.automotive.vehicle.V2_0.VehiclePropValue>(
+                        Arrays.asList(propValue)));
+
+        verify(callback).onPropertyEvent(new ArrayList<HalPropValue>(Arrays.asList(halPropValue)));
+    }
+
+    @Test
+    public void testHidlVehicleCallbackOnPropertySetError() throws Exception {
+        HalClientCallback callback = mock(HalClientCallback.class);
+        android.hardware.automotive.vehicle.V2_0.IVehicleCallback.Stub hidlCallback =
+                mHidlVehicleStub.newCallback(callback).getHidlCallback();
+        VehiclePropError error = new VehiclePropError();
+        error.propId = TEST_PROP;
+        error.areaId = TEST_AREA;
+        error.errorCode = TEST_STATUS;
+
+        hidlCallback.onPropertySetError(TEST_STATUS, TEST_PROP, TEST_AREA);
+
+        verify(callback).onPropertySetError(new ArrayList<VehiclePropError>(Arrays.asList(error)));
+    }
+}