Dump VMS HAL client stats obtained by reading a vendor property.

Bug: 139153868
Test: Added new unit tests to VmsHalServiceTest
Test: atest AndroidCarApiTest CarServiceTest CarServiceUnitTest
Change-Id: I97fcd730c7433ea6753719207d967e119bcc6a4f
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index 95383dc..370fe5d 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -78,6 +78,8 @@
     </string-array>
     <!-- Default home activity -->
     <string name="defaultHomeActivity" translatable="false"><!--com.your.package/com.your.package.Activity--></string>
+    <!-- The vendor-defined HAL property used to collect VMS client metrics. Disabled by default.-->
+    <integer name="vmsHalClientMetricsProperty">0</integer>
     <!--  The com.android.car.vms.VmsClientManager will bind to this list of clients running as system user -->
     <string-array translatable="false" name="vmsPublisherSystemClients">
     </string-array>
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index 5c170d9..8527208 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -118,7 +118,7 @@
             CanBusErrorNotifier errorNotifier, String vehicleInterfaceName) {
         mContext = serviceContext;
         mSystemInterface = systemInterface;
-        mHal = new VehicleHal(vehicle);
+        mHal = new VehicleHal(serviceContext, vehicle);
         mVehicleInterfaceName = vehicleInterfaceName;
         mUserManagerHelper = new CarUserManagerHelper(serviceContext);
         final Resources res = mContext.getResources();
@@ -473,6 +473,8 @@
         } else if ("--metrics".equals(args[0])) {
             writer.println("*Dump car service metrics*");
             dumpAllServices(writer, true);
+        } else if ("--vms-hal".equals(args[0])) {
+            mHal.getVmsHal().dumpMetrics(fd);
         } else if (Build.IS_USERDEBUG || Build.IS_ENG) {
             execShellCmd(args, writer);
         } else {
diff --git a/service/src/com/android/car/hal/VehicleHal.java b/service/src/com/android/car/hal/VehicleHal.java
index 374ae7b..d85a357 100644
--- a/service/src/com/android/car/hal/VehicleHal.java
+++ b/service/src/com/android/car/hal/VehicleHal.java
@@ -23,6 +23,7 @@
 import static java.lang.Integer.toHexString;
 
 import android.annotation.CheckResult;
+import android.content.Context;
 import android.hardware.automotive.vehicle.V2_0.IVehicle;
 import android.hardware.automotive.vehicle.V2_0.IVehicleCallback;
 import android.hardware.automotive.vehicle.V2_0.SubscribeFlags;
@@ -89,14 +90,14 @@
     // Used by injectVHALEvent for testing purposes.  Delimiter for an array of data
     private static final String DATA_DELIMITER = ",";
 
-    public VehicleHal(IVehicle vehicle) {
+    public VehicleHal(Context context, IVehicle vehicle) {
         mHandlerThread = new HandlerThread("VEHICLE-HAL");
         mHandlerThread.start();
         // passing this should be safe as long as it is just kept and not used in constructor
         mPowerHal = new PowerHalService(this);
         mPropertyHal = new PropertyHalService(this);
         mInputHal = new InputHalService(this);
-        mVmsHal = new VmsHalService(this);
+        mVmsHal = new VmsHalService(context, this);
         mDiagnosticHal = new DiagnosticHalService(this);
         mAllServices.addAll(Arrays.asList(mPowerHal,
                 mInputHal,
diff --git a/service/src/com/android/car/hal/VmsHalService.java b/service/src/com/android/car/hal/VmsHalService.java
index fcf717f..40982c8 100644
--- a/service/src/com/android/car/hal/VmsHalService.java
+++ b/service/src/com/android/car/hal/VmsHalService.java
@@ -31,9 +31,11 @@
 import android.car.vms.VmsLayersOffering;
 import android.car.vms.VmsOperationRecorder;
 import android.car.vms.VmsSubscriptionState;
+import android.content.Context;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropertyGroup;
 import android.hardware.automotive.vehicle.V2_0.VmsBaseMessageIntegerValuesIndex;
 import android.hardware.automotive.vehicle.V2_0.VmsMessageType;
 import android.hardware.automotive.vehicle.V2_0.VmsMessageWithLayerAndPublisherIdIntegerValuesIndex;
@@ -55,6 +57,9 @@
 import com.android.car.CarLog;
 import com.android.car.vms.VmsClientManager;
 
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -81,6 +86,7 @@
     private final VehicleHal mVehicleHal;
     private final int mCoreId;
     private final MessageQueue mMessageQueue;
+    private final int mClientMetricsProperty;
     private volatile boolean mIsSupported = false;
 
     private VmsClientManager mClientManager;
@@ -192,15 +198,33 @@
     /**
      * Constructor used by {@link VehicleHal}
      */
-    VmsHalService(VehicleHal vehicleHal) {
-        this(vehicleHal, SystemClock::uptimeMillis);
+    VmsHalService(Context context, VehicleHal vehicleHal) {
+        this(context, vehicleHal, SystemClock::uptimeMillis);
     }
 
     @VisibleForTesting
-    VmsHalService(VehicleHal vehicleHal, Supplier<Long> getCoreId) {
+    VmsHalService(Context context, VehicleHal vehicleHal, Supplier<Long> getCoreId) {
         mVehicleHal = vehicleHal;
         mCoreId = (int) (getCoreId.get() % Integer.MAX_VALUE);
         mMessageQueue = new MessageQueue();
+        mClientMetricsProperty = getClientMetricsProperty(context);
+    }
+
+    private static int getClientMetricsProperty(Context context) {
+        int propId = context.getResources().getInteger(
+                com.android.car.R.integer.vmsHalClientMetricsProperty);
+        if (propId == 0) {
+            Log.i(TAG, "Metrics collection disabled");
+            return 0;
+        }
+        if ((propId & VehiclePropertyGroup.MASK) != VehiclePropertyGroup.VENDOR) {
+            Log.w(TAG, String.format("Metrics collection disabled, non-vendor property: 0x%x",
+                    propId));
+            return 0;
+        }
+
+        Log.i(TAG, String.format("Metrics collection property: 0x%x", propId));
+        return propId;
     }
 
     /**
@@ -289,6 +313,37 @@
     }
 
     /**
+     * Dumps HAL client metrics obtained by reading the VMS HAL property.
+     *
+     * @param fd Dumpsys file descriptor to write client metrics to.
+     */
+    public void dumpMetrics(FileDescriptor fd) {
+        if (mClientMetricsProperty == 0) {
+            Log.w(TAG, "Metrics collection is disabled");
+            return;
+        }
+
+        VehiclePropValue vehicleProp = null;
+        try {
+            vehicleProp = mVehicleHal.get(mClientMetricsProperty);
+        } catch (PropertyTimeoutException e) {
+            Log.e(TAG, "Timeout while reading metrics from client");
+        }
+        if (vehicleProp == null) {
+            if (DBG) Log.d(TAG, "Metrics unavailable");
+            return;
+        }
+
+        FileOutputStream fout = new FileOutputStream(fd);
+        try {
+            fout.write(toByteArray(vehicleProp.value.bytes));
+            fout.flush();
+        } catch (IOException e) {
+            Log.e(TAG, "Error writing metrics to output stream");
+        }
+    }
+
+    /**
      * Consumes/produces HAL messages.
      *
      * The format of these messages is defined in: