Post-O. Sketch out CarDiagnosticManager API surface
Provide an implementation of all layers required to plumb CarDiagnosticManager through:
* CarDiagnosticEvent
* DiagnosticHalService
* CarDiagnosticService
* CarDiagnosticManager
If FutureFeatures are enabled, this is integrated end-to-end enough to run trivial tests of the API
Test: build with TARGET_USES_CAR_FUTURE_FEATURES=true then at a shell runtest -x packages/services/Car/tests/android_car_api_test/ -c android.car.apitest.CarDiagnosticManagerTest
Change-Id: I0f2aafd039d26fec15182dd7029cf8c7995ce85b
diff --git a/car-lib/api/system-current.txt b/car-lib/api/system-current.txt
index 1833163..f4e0a95 100644
--- a/car-lib/api/system-current.txt
+++ b/car-lib/api/system-current.txt
@@ -14,6 +14,7 @@
field public static final java.lang.String CABIN_SERVICE = "cabin";
field public static final java.lang.String CAMERA_SERVICE = "camera";
field public static final int CONNECTION_TYPE_EMBEDDED = 5; // 0x5
+ field public static final java.lang.String DIAGNOSTIC_SERVICE = "diagnostic";
field public static final java.lang.String HVAC_SERVICE = "hvac";
field public static final java.lang.String INFO_SERVICE = "info";
field public static final java.lang.String PACKAGE_SERVICE = "package";
diff --git a/car-lib/src/android/car/Car.java b/car-lib/src/android/car/Car.java
index 7648ab9..4c458bf 100644
--- a/car-lib/src/android/car/Car.java
+++ b/car-lib/src/android/car/Car.java
@@ -21,6 +21,7 @@
import android.annotation.SystemApi;
import android.car.annotation.FutureFeature;
import android.car.content.pm.CarPackageManager;
+import android.car.hardware.CarDiagnosticManager;
import android.car.hardware.CarSensorManager;
import android.car.hardware.CarVendorExtensionManager;
import android.car.hardware.cabin.CarCabinManager;
@@ -93,6 +94,12 @@
* @hide
*/
@SystemApi
+ public static final String DIAGNOSTIC_SERVICE = "diagnostic";
+
+ /**
+ * @hide
+ */
+ @SystemApi
public static final String CAMERA_SERVICE = "camera";
/**
@@ -561,6 +568,10 @@
case CABIN_SERVICE:
manager = new CarCabinManager(binder, mContext, mEventHandler);
break;
+ case DIAGNOSTIC_SERVICE:
+ //TODO(egranata): only enable this if FeatureConfiguration is turned on
+ manager = new CarDiagnosticManager(binder, mContext, mEventHandler);
+ break;
case CAMERA_SERVICE:
manager = new CarCameraManager(binder, mContext);
break;
diff --git a/car-lib/src/android/car/hardware/CarDiagnosticEvent.aidl b/car-lib/src/android/car/hardware/CarDiagnosticEvent.aidl
new file mode 100644
index 0000000..73b184e
--- /dev/null
+++ b/car-lib/src/android/car/hardware/CarDiagnosticEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017 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 android.car.hardware;
+
+parcelable CarDiagnosticEvent;
diff --git a/car-lib/src/android/car/hardware/CarDiagnosticEvent.java b/car-lib/src/android/car/hardware/CarDiagnosticEvent.java
new file mode 100644
index 0000000..c8c8ddb
--- /dev/null
+++ b/car-lib/src/android/car/hardware/CarDiagnosticEvent.java
@@ -0,0 +1,480 @@
+/*
+ * Copyright (C) 2017 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 android.car.hardware;
+
+import android.annotation.IntDef;
+import android.car.annotation.FutureFeature;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * A CarDiagnosticEvent object corresponds to a single diagnostic event frame coming from the car.
+ * @hide
+ */
+@FutureFeature
+public class CarDiagnosticEvent implements Parcelable {
+ /**
+ * This class contains the indices of the standard OBD2 sensors with an integer value
+ */
+ public static final class Obd2IntegerSensorIndex {
+ private Obd2IntegerSensorIndex() {}
+
+ public static final int FUEL_SYSTEM_STATUS = 0;
+ public static final int MALFUNCTION_INDICATOR_LIGHT_ON = 1;
+ public static final int IGNITION_MONITORS_SUPPORTED = 2;
+ public static final int IGNITION_SPECIFIC_MONITORS = 3;
+ public static final int INTAKE_AIR_TEMPERATURE = 4;
+ public static final int COMMANDED_SECONDARY_AIR_STATUS = 5;
+ public static final int NUM_OXYGEN_SENSORS_PRESENT = 6;
+ public static final int RUNTIME_SINCE_ENGINE_START = 7;
+ public static final int DISTANCE_TRAVELED_WITH_MALFUNCTION_INDICATOR_LIGHT_ON = 8;
+ public static final int WARMUPS_SINCE_CODES_CLEARED = 9;
+ public static final int DISTANCE_TRAVELED_SINCE_CODES_CLEARED = 10;
+ public static final int ABSOLUTE_BAROMETRIC_PRESSURE = 11;
+ public static final int CONTROL_MODULE_VOLTAGE = 12;
+ public static final int AMBIENT_AIR_TEMPERATURE = 13;
+ public static final int TIME_WITH_MALFUNCTION_LIGHT_ON = 14;
+ public static final int TIME_SINCE_TROUBLE_CODES_CLEARED = 15;
+ public static final int MAX_FUEL_AIR_EQUIVALENCE_RATIO = 16;
+ public static final int MAX_OXYGEN_SENSOR_VOLTAGE = 17;
+ public static final int MAX_OXYGEN_SENSOR_CURRENT = 18;
+ public static final int MAX_INTAKE_MANIFOLD_ABSOLUTE_PRESSURE = 19;
+ public static final int MAX_AIR_FLOW_RATE_FROM_MASS_AIR_FLOW_SENSOR = 20;
+ public static final int FUEL_TYPE = 21;
+ public static final int FUEL_RAIL_ABSOLUTE_PRESSURE = 22;
+ public static final int ENGINE_OIL_TEMPERATURE = 23;
+ public static final int DRIVER_DEMAND_PERCENT_TORQUE = 24;
+ public static final int ENGINE_ACTUAL_PERCENT_TORQUE = 25;
+ public static final int ENGINE_REFERENCE_PERCENT_TORQUE = 26;
+ public static final int ENGINE_PERCENT_TORQUE_DATA_IDLE = 27;
+ public static final int ENGINE_PERCENT_TORQUE_DATA_POINT1 = 28;
+ public static final int ENGINE_PERCENT_TORQUE_DATA_POINT2 = 29;
+ public static final int ENGINE_PERCENT_TORQUE_DATA_POINT3 = 30;
+ public static final int ENGINE_PERCENT_TORQUE_DATA_POINT4 = 31;
+ public static final int LAST_SYSTEM = ENGINE_PERCENT_TORQUE_DATA_POINT4;
+ public static final int VENDOR_START = LAST_SYSTEM + 1;
+ }
+
+ /**
+ * This class contains the indices of the standard OBD2 sensors with a floating-point value
+ */
+ public static final class Obd2FloatSensorIndex {
+
+ private Obd2FloatSensorIndex() {}
+
+ public static final int CALCULATED_ENGINE_LOAD = 0;
+ public static final int ENGINE_COOLANT_TEMPERATURE = 1;
+ public static final int SHORT_TERM_FUEL_TRIM_BANK1 = 2;
+ public static final int LONG_TERM_FUEL_TRIM_BANK1 = 3;
+ public static final int SHORT_TERM_FUEL_TRIM_BANK2 = 4;
+ public static final int LONG_TERM_FUEL_TRIM_BANK2 = 5;
+ public static final int FUEL_PRESSURE = 6;
+ public static final int INTAKE_MANIFOLD_ABSOLUTE_PRESSURE = 7;
+ public static final int ENGINE_RPM = 8;
+ public static final int VEHICLE_SPEED = 9;
+ public static final int TIMING_ADVANCE = 10;
+ public static final int MAF_AIR_FLOW_RATE = 11;
+ public static final int THROTTLE_POSITION = 12;
+ public static final int OXYGEN_SENSOR1_VOLTAGE = 13;
+ public static final int OXYGEN_SENSOR1_SHORT_TERM_FUEL_TRIM = 14;
+ public static final int OXYGEN_SENSOR1_FUEL_AIR_EQUIVALENCE_RATIO = 15;
+ public static final int OXYGEN_SENSOR2_VOLTAGE = 16;
+ public static final int OXYGEN_SENSOR2_SHORT_TERM_FUEL_TRIM = 17;
+ public static final int OXYGEN_SENSOR2_FUEL_AIR_EQUIVALENCE_RATIO = 18;
+ public static final int OXYGEN_SENSOR3_VOLTAGE = 19;
+ public static final int OXYGEN_SENSOR3_SHORT_TERM_FUEL_TRIM = 20;
+ public static final int OXYGEN_SENSOR3_FUEL_AIR_EQUIVALENCE_RATIO = 21;
+ public static final int OXYGEN_SENSOR4_VOLTAGE = 22;
+ public static final int OXYGEN_SENSOR4_SHORT_TERM_FUEL_TRIM = 23;
+ public static final int OXYGEN_SENSOR4_FUEL_AIR_EQUIVALENCE_RATIO = 24;
+ public static final int OXYGEN_SENSOR5_VOLTAGE = 25;
+ public static final int OXYGEN_SENSOR5_SHORT_TERM_FUEL_TRIM = 26;
+ public static final int OXYGEN_SENSOR5_FUEL_AIR_EQUIVALENCE_RATIO = 27;
+ public static final int OXYGEN_SENSOR6_VOLTAGE = 28;
+ public static final int OXYGEN_SENSOR6_SHORT_TERM_FUEL_TRIM = 29;
+ public static final int OXYGEN_SENSOR6_FUEL_AIR_EQUIVALENCE_RATIO = 30;
+ public static final int OXYGEN_SENSOR7_VOLTAGE = 31;
+ public static final int OXYGEN_SENSOR7_SHORT_TERM_FUEL_TRIM = 32;
+ public static final int OXYGEN_SENSOR7_FUEL_AIR_EQUIVALENCE_RATIO = 33;
+ public static final int OXYGEN_SENSOR8_VOLTAGE = 34;
+ public static final int OXYGEN_SENSOR8_SHORT_TERM_FUEL_TRIM = 35;
+ public static final int OXYGEN_SENSOR8_FUEL_AIR_EQUIVALENCE_RATIO = 36;
+ public static final int FUEL_RAIL_PRESSURE = 37;
+ public static final int FUEL_RAIL_GAUGE_PRESSURE = 38;
+ public static final int COMMANDED_EXHAUST_GAS_RECIRCULATION = 39;
+ public static final int EXHAUST_GAS_RECIRCULATION_ERROR = 40;
+ public static final int COMMANDED_EVAPORATIVE_PURGE = 41;
+ public static final int FUEL_TANK_LEVEL_INPUT = 42;
+ public static final int EVAPORATION_SYSTEM_VAPOR_PRESSURE = 43;
+ public static final int CATALYST_TEMPERATURE_BANK1_SENSOR1 = 44;
+ public static final int CATALYST_TEMPERATURE_BANK2_SENSOR1 = 45;
+ public static final int CATALYST_TEMPERATURE_BANK1_SENSOR2 = 46;
+ public static final int CATALYST_TEMPERATURE_BANK2_SENSOR2 = 47;
+ public static final int ABSOLUTE_LOAD_VALUE = 48;
+ public static final int FUEL_AIR_COMMANDED_EQUIVALENCE_RATIO = 49;
+ public static final int RELATIVE_THROTTLE_POSITION = 50;
+ public static final int ABSOLUTE_THROTTLE_POSITION_B = 51;
+ public static final int ABSOLUTE_THROTTLE_POSITION_C = 52;
+ public static final int ACCELERATOR_PEDAL_POSITION_D = 53;
+ public static final int ACCELERATOR_PEDAL_POSITION_E = 54;
+ public static final int ACCELERATOR_PEDAL_POSITION_F = 55;
+ public static final int COMMANDED_THROTTLE_ACTUATOR = 56;
+ public static final int ETHANOL_FUEL_PERCENTAGE = 57;
+ public static final int ABSOLUTE_EVAPORATION_SYSTEM_VAPOR_PRESSURE = 58;
+ public static final int SHORT_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK1 = 59;
+ public static final int SHORT_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK2 = 60;
+ public static final int SHORT_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK3 = 61;
+ public static final int SHORT_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK4 = 62;
+ public static final int LONG_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK1 = 63;
+ public static final int LONG_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK2 = 64;
+ public static final int LONG_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK3 = 65;
+ public static final int LONG_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK4 = 66;
+ public static final int RELATIVE_ACCELERATOR_PEDAL_POSITION = 67;
+ public static final int HYBRID_BATTERY_PACK_REMAINING_LIFE = 68;
+ public static final int FUEL_INJECTION_TIMING = 69;
+ public static final int ENGINE_FUEL_RATE = 70;
+ public static final int LAST_SYSTEM = ENGINE_FUEL_RATE;
+ public static final int VENDOR_START = LAST_SYSTEM + 1;
+ }
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ Obd2IntegerSensorIndex.FUEL_SYSTEM_STATUS,
+ Obd2IntegerSensorIndex.MALFUNCTION_INDICATOR_LIGHT_ON,
+ Obd2IntegerSensorIndex.IGNITION_MONITORS_SUPPORTED,
+ Obd2IntegerSensorIndex.IGNITION_SPECIFIC_MONITORS,
+ Obd2IntegerSensorIndex.INTAKE_AIR_TEMPERATURE,
+ Obd2IntegerSensorIndex.COMMANDED_SECONDARY_AIR_STATUS,
+ Obd2IntegerSensorIndex.NUM_OXYGEN_SENSORS_PRESENT,
+ Obd2IntegerSensorIndex.RUNTIME_SINCE_ENGINE_START,
+ Obd2IntegerSensorIndex.DISTANCE_TRAVELED_WITH_MALFUNCTION_INDICATOR_LIGHT_ON,
+ Obd2IntegerSensorIndex.WARMUPS_SINCE_CODES_CLEARED,
+ Obd2IntegerSensorIndex.DISTANCE_TRAVELED_SINCE_CODES_CLEARED,
+ Obd2IntegerSensorIndex.ABSOLUTE_BAROMETRIC_PRESSURE,
+ Obd2IntegerSensorIndex.CONTROL_MODULE_VOLTAGE,
+ Obd2IntegerSensorIndex.AMBIENT_AIR_TEMPERATURE,
+ Obd2IntegerSensorIndex.TIME_WITH_MALFUNCTION_LIGHT_ON,
+ Obd2IntegerSensorIndex.TIME_SINCE_TROUBLE_CODES_CLEARED,
+ Obd2IntegerSensorIndex.MAX_FUEL_AIR_EQUIVALENCE_RATIO,
+ Obd2IntegerSensorIndex.MAX_OXYGEN_SENSOR_VOLTAGE,
+ Obd2IntegerSensorIndex.MAX_OXYGEN_SENSOR_CURRENT,
+ Obd2IntegerSensorIndex.MAX_INTAKE_MANIFOLD_ABSOLUTE_PRESSURE,
+ Obd2IntegerSensorIndex.MAX_AIR_FLOW_RATE_FROM_MASS_AIR_FLOW_SENSOR,
+ Obd2IntegerSensorIndex.FUEL_TYPE,
+ Obd2IntegerSensorIndex.FUEL_RAIL_ABSOLUTE_PRESSURE,
+ Obd2IntegerSensorIndex.ENGINE_OIL_TEMPERATURE,
+ Obd2IntegerSensorIndex.DRIVER_DEMAND_PERCENT_TORQUE,
+ Obd2IntegerSensorIndex.ENGINE_ACTUAL_PERCENT_TORQUE,
+ Obd2IntegerSensorIndex.ENGINE_REFERENCE_PERCENT_TORQUE,
+ Obd2IntegerSensorIndex.ENGINE_PERCENT_TORQUE_DATA_IDLE,
+ Obd2IntegerSensorIndex.ENGINE_PERCENT_TORQUE_DATA_POINT1,
+ Obd2IntegerSensorIndex.ENGINE_PERCENT_TORQUE_DATA_POINT2,
+ Obd2IntegerSensorIndex.ENGINE_PERCENT_TORQUE_DATA_POINT3,
+ Obd2IntegerSensorIndex.ENGINE_PERCENT_TORQUE_DATA_POINT4,
+ Obd2IntegerSensorIndex.LAST_SYSTEM,
+ Obd2IntegerSensorIndex.VENDOR_START,
+ })
+ public @interface IntegerSensorIndex {}
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ Obd2FloatSensorIndex.CALCULATED_ENGINE_LOAD,
+ Obd2FloatSensorIndex.ENGINE_COOLANT_TEMPERATURE,
+ Obd2FloatSensorIndex.SHORT_TERM_FUEL_TRIM_BANK1,
+ Obd2FloatSensorIndex.LONG_TERM_FUEL_TRIM_BANK1,
+ Obd2FloatSensorIndex.SHORT_TERM_FUEL_TRIM_BANK2,
+ Obd2FloatSensorIndex.LONG_TERM_FUEL_TRIM_BANK2,
+ Obd2FloatSensorIndex.FUEL_PRESSURE,
+ Obd2FloatSensorIndex.INTAKE_MANIFOLD_ABSOLUTE_PRESSURE,
+ Obd2FloatSensorIndex.ENGINE_RPM,
+ Obd2FloatSensorIndex.VEHICLE_SPEED,
+ Obd2FloatSensorIndex.TIMING_ADVANCE,
+ Obd2FloatSensorIndex.MAF_AIR_FLOW_RATE,
+ Obd2FloatSensorIndex.THROTTLE_POSITION,
+ Obd2FloatSensorIndex.OXYGEN_SENSOR1_VOLTAGE,
+ Obd2FloatSensorIndex.OXYGEN_SENSOR1_SHORT_TERM_FUEL_TRIM,
+ Obd2FloatSensorIndex.OXYGEN_SENSOR1_FUEL_AIR_EQUIVALENCE_RATIO,
+ Obd2FloatSensorIndex.OXYGEN_SENSOR2_VOLTAGE,
+ Obd2FloatSensorIndex.OXYGEN_SENSOR2_SHORT_TERM_FUEL_TRIM,
+ Obd2FloatSensorIndex.OXYGEN_SENSOR2_FUEL_AIR_EQUIVALENCE_RATIO,
+ Obd2FloatSensorIndex.OXYGEN_SENSOR3_VOLTAGE,
+ Obd2FloatSensorIndex.OXYGEN_SENSOR3_SHORT_TERM_FUEL_TRIM,
+ Obd2FloatSensorIndex.OXYGEN_SENSOR3_FUEL_AIR_EQUIVALENCE_RATIO,
+ Obd2FloatSensorIndex.OXYGEN_SENSOR4_VOLTAGE,
+ Obd2FloatSensorIndex.OXYGEN_SENSOR4_SHORT_TERM_FUEL_TRIM,
+ Obd2FloatSensorIndex.OXYGEN_SENSOR4_FUEL_AIR_EQUIVALENCE_RATIO,
+ Obd2FloatSensorIndex.OXYGEN_SENSOR5_VOLTAGE,
+ Obd2FloatSensorIndex.OXYGEN_SENSOR5_SHORT_TERM_FUEL_TRIM,
+ Obd2FloatSensorIndex.OXYGEN_SENSOR5_FUEL_AIR_EQUIVALENCE_RATIO,
+ Obd2FloatSensorIndex.OXYGEN_SENSOR6_VOLTAGE,
+ Obd2FloatSensorIndex.OXYGEN_SENSOR6_SHORT_TERM_FUEL_TRIM,
+ Obd2FloatSensorIndex.OXYGEN_SENSOR6_FUEL_AIR_EQUIVALENCE_RATIO,
+ Obd2FloatSensorIndex.OXYGEN_SENSOR7_VOLTAGE,
+ Obd2FloatSensorIndex.OXYGEN_SENSOR7_SHORT_TERM_FUEL_TRIM,
+ Obd2FloatSensorIndex.OXYGEN_SENSOR7_FUEL_AIR_EQUIVALENCE_RATIO,
+ Obd2FloatSensorIndex.OXYGEN_SENSOR8_VOLTAGE,
+ Obd2FloatSensorIndex.OXYGEN_SENSOR8_SHORT_TERM_FUEL_TRIM,
+ Obd2FloatSensorIndex.OXYGEN_SENSOR8_FUEL_AIR_EQUIVALENCE_RATIO,
+ Obd2FloatSensorIndex.FUEL_RAIL_PRESSURE,
+ Obd2FloatSensorIndex.FUEL_RAIL_GAUGE_PRESSURE,
+ Obd2FloatSensorIndex.COMMANDED_EXHAUST_GAS_RECIRCULATION,
+ Obd2FloatSensorIndex.EXHAUST_GAS_RECIRCULATION_ERROR,
+ Obd2FloatSensorIndex.COMMANDED_EVAPORATIVE_PURGE,
+ Obd2FloatSensorIndex.FUEL_TANK_LEVEL_INPUT,
+ Obd2FloatSensorIndex.EVAPORATION_SYSTEM_VAPOR_PRESSURE,
+ Obd2FloatSensorIndex.CATALYST_TEMPERATURE_BANK1_SENSOR1,
+ Obd2FloatSensorIndex.CATALYST_TEMPERATURE_BANK2_SENSOR1,
+ Obd2FloatSensorIndex.CATALYST_TEMPERATURE_BANK1_SENSOR2,
+ Obd2FloatSensorIndex.CATALYST_TEMPERATURE_BANK2_SENSOR2,
+ Obd2FloatSensorIndex.ABSOLUTE_LOAD_VALUE,
+ Obd2FloatSensorIndex.FUEL_AIR_COMMANDED_EQUIVALENCE_RATIO,
+ Obd2FloatSensorIndex.RELATIVE_THROTTLE_POSITION,
+ Obd2FloatSensorIndex.ABSOLUTE_THROTTLE_POSITION_B,
+ Obd2FloatSensorIndex.ABSOLUTE_THROTTLE_POSITION_C,
+ Obd2FloatSensorIndex.ACCELERATOR_PEDAL_POSITION_D,
+ Obd2FloatSensorIndex.ACCELERATOR_PEDAL_POSITION_E,
+ Obd2FloatSensorIndex.ACCELERATOR_PEDAL_POSITION_F,
+ Obd2FloatSensorIndex.COMMANDED_THROTTLE_ACTUATOR,
+ Obd2FloatSensorIndex.ETHANOL_FUEL_PERCENTAGE,
+ Obd2FloatSensorIndex.ABSOLUTE_EVAPORATION_SYSTEM_VAPOR_PRESSURE,
+ Obd2FloatSensorIndex.SHORT_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK1,
+ Obd2FloatSensorIndex.SHORT_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK2,
+ Obd2FloatSensorIndex.SHORT_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK3,
+ Obd2FloatSensorIndex.SHORT_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK4,
+ Obd2FloatSensorIndex.LONG_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK1,
+ Obd2FloatSensorIndex.LONG_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK2,
+ Obd2FloatSensorIndex.LONG_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK3,
+ Obd2FloatSensorIndex.LONG_TERM_SECONDARY_OXYGEN_SENSOR_TRIM_BANK4,
+ Obd2FloatSensorIndex.RELATIVE_ACCELERATOR_PEDAL_POSITION,
+ Obd2FloatSensorIndex.HYBRID_BATTERY_PACK_REMAINING_LIFE,
+ Obd2FloatSensorIndex.FUEL_INJECTION_TIMING,
+ Obd2FloatSensorIndex.ENGINE_FUEL_RATE,
+ Obd2FloatSensorIndex.LAST_SYSTEM,
+ Obd2FloatSensorIndex.VENDOR_START,
+ })
+ public @interface FloatSensorIndex {}
+
+ /** Whether this frame represents a live or a freeze frame */
+ public final int frameKind;
+
+ /**
+ * When this data was acquired in car or received from car. It is elapsed real-time of data
+ * reception from car in nanoseconds since system boot.
+ */
+ public final long timestamp;
+
+ /**
+ * Sparse array that contains the mapping of OBD2 diagnostic properties to their values for
+ * integer valued properties
+ */
+ private final SparseIntArray intValues;
+
+ /**
+ * Sparse array that contains the mapping of OBD2 diagnostic properties to their values for
+ * float valued properties
+ */
+ private final SparseArray<Float> floatValues;
+
+ /** Diagnostic Troubleshooting Code (DTC) that was detected and caused this frame to be
+ * stored (if a freeze frame). Always empty for a live frame.
+ */
+ public final String DTC;
+
+ public CarDiagnosticEvent(Parcel in) {
+ frameKind = in.readInt();
+ timestamp = in.readLong();
+ int len = in.readInt();
+ floatValues = new SparseArray<>(len);
+ for (int i = 0; i < len; ++i) {
+ int key = in.readInt();
+ float value = in.readFloat();
+ floatValues.put(key, value);
+ }
+ len = in.readInt();
+ intValues = new SparseIntArray(len);
+ for (int i = 0; i < len; ++i) {
+ int key = in.readInt();
+ int value = in.readInt();
+ intValues.put(key, value);
+ }
+ DTC = in.readString();
+ // version 1 up to here
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(frameKind);
+ dest.writeLong(timestamp);
+ dest.writeInt(floatValues.size());
+ for (int i = 0; i < floatValues.size(); ++i) {
+ int key = floatValues.keyAt(i);
+ dest.writeInt(key);
+ dest.writeFloat(floatValues.get(key));
+ }
+ dest.writeInt(intValues.size());
+ for (int i = 0; i < intValues.size(); ++i) {
+ int key = intValues.keyAt(i);
+ dest.writeInt(key);
+ dest.writeInt(intValues.get(key));
+ }
+ dest.writeString(DTC);
+ }
+
+ public static final Parcelable.Creator<CarDiagnosticEvent> CREATOR =
+ new Parcelable.Creator<CarDiagnosticEvent>() {
+ public CarDiagnosticEvent createFromParcel(Parcel in) {
+ return new CarDiagnosticEvent(in);
+ }
+
+ public CarDiagnosticEvent[] newArray(int size) {
+ return new CarDiagnosticEvent[size];
+ }
+ };
+
+ private CarDiagnosticEvent(
+ int frameKind,
+ long timestamp,
+ SparseArray<Float> floatValues,
+ SparseIntArray intValues,
+ String dtc) {
+ this.frameKind = frameKind;
+ this.timestamp = timestamp;
+ this.floatValues = floatValues;
+ this.intValues = intValues;
+ this.DTC = dtc;
+ }
+
+ public static class Builder {
+ private int mKind = CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE;
+ private long mTimestamp = 0;
+ private SparseArray<Float> mFloatValues = new SparseArray<>();
+ private SparseIntArray mIntValues = new SparseIntArray();
+ private String mDTC = "";
+
+ private Builder(int kind) {
+ mKind = kind;
+ }
+
+ public static Builder liveFrame() {
+ return new Builder(CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE);
+ }
+
+ public static Builder freezeFrame() {
+ return new Builder(CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE);
+ }
+
+ public Builder atTimestamp(long timestamp) {
+ mTimestamp = timestamp;
+ return this;
+ }
+
+ public Builder withIntValue(int key, int value) {
+ mIntValues.put(key, value);
+ return this;
+ }
+
+ public Builder withFloatValue(int key, float value) {
+ mFloatValues.put(key, value);
+ return this;
+ }
+
+ public Builder withDTC(String DTC) {
+ mDTC = DTC;
+ return this;
+ }
+
+ public CarDiagnosticEvent build() {
+ return new CarDiagnosticEvent(
+ mKind, mTimestamp, mFloatValues, mIntValues, mDTC);
+ }
+ }
+
+ public boolean isLiveFrame() {
+ return CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE == frameKind;
+ }
+
+ public boolean isFreezeFrame() {
+ return CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE == frameKind;
+ }
+
+ public boolean isEmptyFrame() {
+ boolean empty = (0 == intValues.size());
+ empty &= (0 == floatValues.size());
+ if (isFreezeFrame()) empty &= DTC.isEmpty();
+ return empty;
+ }
+
+ /** @hide */
+ public CarDiagnosticEvent checkLiveFrame() {
+ if (!isLiveFrame()) throw new IllegalStateException("frame is not a live frame");
+ return this;
+ }
+
+ /** @hide */
+ public CarDiagnosticEvent checkFreezeFrame() {
+ if (!isFreezeFrame()) throw new IllegalStateException("frame is not a freeze frame");
+ return this;
+ }
+
+ /** @hide */
+ public boolean isEarlierThan(CarDiagnosticEvent otherEvent) {
+ otherEvent = Objects.requireNonNull(otherEvent);
+ return (timestamp < otherEvent.timestamp);
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "%s diagnostic frame {\n"
+ + "\ttimestamp: %d, "
+ + "DTC: %s\n"
+ + "\tintValues: %s\n"
+ + "\tfloatValues: %s\n}",
+ isLiveFrame() ? "live" : "freeze",
+ timestamp,
+ DTC,
+ intValues.toString(),
+ floatValues.toString());
+ }
+
+ public int getSystemIntegerSensor(@IntegerSensorIndex int index, int defaultValue) {
+ return intValues.get(index, defaultValue);
+ }
+
+ public float getSystemFloatSensor(@FloatSensorIndex int index, float defaultValue) {
+ return floatValues.get(index, defaultValue);
+ }
+
+ public int getVendorIntegerSensor(int index, int defaultValue) {
+ return intValues.get(index, defaultValue);
+ }
+
+ public float getVendorFloatSensor(int index, float defaultValue) {
+ return floatValues.get(index, defaultValue);
+ }
+}
diff --git a/car-lib/src/android/car/hardware/CarDiagnosticManager.java b/car-lib/src/android/car/hardware/CarDiagnosticManager.java
new file mode 100644
index 0000000..b85a491
--- /dev/null
+++ b/car-lib/src/android/car/hardware/CarDiagnosticManager.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2017 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 android.car.hardware;
+
+import android.annotation.SystemApi;
+import android.car.CarApiUtil;
+import android.car.CarManagerBase;
+import android.car.CarNotConnectedException;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Handler.Callback;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+
+/** API for monitoring car diagnostic data. */
+/** @hide */
+public final class CarDiagnosticManager implements CarManagerBase {
+ public static final int FRAME_TYPE_FLAG_LIVE = 0;
+ public static final int FRAME_TYPE_FLAG_FREEZE = 1;
+
+ private final ICarDiagnostic mService;
+
+ /** Handles call back into projected apps. */
+ private final Handler mHandler;
+ private final Callback mHandlerCallback = new Callback() {
+ @Override
+ public boolean handleMessage(Message msg) {
+ //TODO(egranata): dispatch diagnostic messages to listeners
+ return true;
+ }
+ };
+
+ public CarDiagnosticManager(IBinder service, Context context, Handler handler) {
+ mService = ICarDiagnostic.Stub.asInterface(service);
+ mHandler = new Handler(handler.getLooper(), mHandlerCallback);
+ }
+
+ @Override
+ public void onCarDisconnected() {}
+
+ /** Listener for diagnostic events. Callbacks are called in the Looper context. */
+ public interface OnDiagnosticEventListener {
+ /**
+ * Called when there is a diagnostic event from the car.
+ *
+ * @param carDiagnosticEvent
+ */
+ void onDiagnosticEvent(final CarDiagnosticManager manager,
+ final CarDiagnosticEvent carDiagnosticEvent);
+ }
+
+ // ICarDiagnostic forwards
+
+ /**
+ * Register a new diagnostic events listener, or update an existing registration.
+ * @param frameType
+ * @param rate
+ * @param listener
+ * @return true if registration successfully occurs
+ * @throws CarNotConnectedException
+ */
+ public boolean registerOrUpdateDiagnosticListener(int frameType, int rate,
+ ICarDiagnosticEventListener listener) throws CarNotConnectedException {
+ try {
+ return mService.registerOrUpdateDiagnosticListener(frameType, rate, listener);
+ } catch (IllegalStateException e) {
+ CarApiUtil.checkCarNotConnectedExceptionFromCarService(e);
+ } catch (RemoteException e) {
+ throw new CarNotConnectedException();
+ }
+ return false;
+ }
+
+ /**
+ * Retrieve the most-recently acquired live frame data from the car.
+ * @return
+ * @throws CarNotConnectedException
+ */
+ public CarDiagnosticEvent getLatestLiveFrame() throws CarNotConnectedException {
+ try {
+ return mService.getLatestLiveFrame();
+ } catch (IllegalStateException e) {
+ CarApiUtil.checkCarNotConnectedExceptionFromCarService(e);
+ } catch (RemoteException e) {
+ throw new CarNotConnectedException();
+ }
+ return null;
+ }
+
+ /**
+ * Return the list of the timestamps for which a freeze frame is currently stored.
+ * @return
+ * @throws CarNotConnectedException
+ */
+ public long[] getFreezeFrameTimestamps() throws CarNotConnectedException {
+ try {
+ return mService.getFreezeFrameTimestamps();
+ } catch (IllegalStateException e) {
+ CarApiUtil.checkCarNotConnectedExceptionFromCarService(e);
+ } catch (RemoteException e) {
+ throw new CarNotConnectedException();
+ }
+ return new long[]{};
+ }
+
+ /**
+ * Retrieve the freeze frame event data for a given timestamp, if available.
+ * @param timestamp
+ * @return
+ * @throws CarNotConnectedException
+ */
+ public CarDiagnosticEvent getFreezeFrame(long timestamp) throws CarNotConnectedException {
+ try {
+ return mService.getFreezeFrame(timestamp);
+ } catch (IllegalStateException e) {
+ CarApiUtil.checkCarNotConnectedExceptionFromCarService(e);
+ } catch (RemoteException e) {
+ throw new CarNotConnectedException();
+ }
+ return null;
+ }
+
+ /**
+ * Clear the freeze frame information from vehicle memory at the given timestamps.
+ * @param timestamps
+ * @return
+ * @throws CarNotConnectedException
+ */
+ public boolean clearFreezeFrames(long... timestamps) throws CarNotConnectedException {
+ try {
+ return mService.clearFreezeFrames(timestamps);
+ } catch (IllegalStateException e) {
+ CarApiUtil.checkCarNotConnectedExceptionFromCarService(e);
+ } catch (RemoteException e) {
+ throw new CarNotConnectedException();
+ }
+ return false;
+ }
+
+ /**
+ * Unregister a diagnostic events listener, such that the listener will stop receiveing events.
+ * This only removes the registration for one type of frame, such that if a listener is
+ * receiving events for multiple types of frames, it will keep receiving them for types other
+ * than the given frameType.
+ * @param frameType
+ * @param listener
+ * @throws CarNotConnectedException
+ */
+ public void unregisterDiagnosticListener(int frameType,
+ ICarDiagnosticEventListener listener) throws CarNotConnectedException {
+ try {
+ mService.unregisterDiagnosticListener(frameType, listener);
+ } catch (IllegalStateException e) {
+ CarApiUtil.checkCarNotConnectedExceptionFromCarService(e);
+ } catch (RemoteException e) {
+ throw new CarNotConnectedException();
+ }
+ }
+}
diff --git a/car-lib/src/android/car/hardware/ICarDiagnostic.aidl b/car-lib/src/android/car/hardware/ICarDiagnostic.aidl
new file mode 100644
index 0000000..44d7085
--- /dev/null
+++ b/car-lib/src/android/car/hardware/ICarDiagnostic.aidl
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 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 android.car.hardware;
+
+import android.car.hardware.CarDiagnosticEvent;
+import android.car.hardware.ICarDiagnosticEventListener;
+
+/** @hide */
+interface ICarDiagnostic {
+ /**
+ * Register a callback (or update registration) for diagnostic events.
+ */
+ boolean registerOrUpdateDiagnosticListener(int frameKind, int rate,
+ in ICarDiagnosticEventListener listener) = 1;
+
+ /**
+ * Get the value for the most recent live frame data available.
+ */
+ CarDiagnosticEvent getLatestLiveFrame() = 2;
+
+ /**
+ * Get the list of timestamps for which there exist a freeze frame stored.
+ */
+ long[] getFreezeFrameTimestamps() = 3;
+
+ /**
+ * Get the value for the freeze frame stored given a timestamp.
+ */
+ CarDiagnosticEvent getFreezeFrame(long timestamp) = 4;
+
+ /**
+ * Erase freeze frames given timestamps (or all, if no timestamps).
+ */
+ boolean clearFreezeFrames(in long[] timestamps) = 5;
+
+ /**
+ * Stop receiving diagnostic events for a given callback.
+ */
+ void unregisterDiagnosticListener(int frameKind,
+ in ICarDiagnosticEventListener callback) = 6;
+}
\ No newline at end of file
diff --git a/car-lib/src/android/car/hardware/ICarDiagnosticEventListener.aidl b/car-lib/src/android/car/hardware/ICarDiagnosticEventListener.aidl
new file mode 100644
index 0000000..3c9a189
--- /dev/null
+++ b/car-lib/src/android/car/hardware/ICarDiagnosticEventListener.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 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 android.car.hardware;
+
+import android.car.hardware.CarDiagnosticEvent;
+
+/**
+ * @hide
+ */
+oneway interface ICarDiagnosticEventListener {
+ void onDiagnosticEvents(in List<CarDiagnosticEvent> events) = 0;
+}
diff --git a/car-lib/src_feature_current/com/android/car/internal/FeatureConfiguration.java b/car-lib/src_feature_current/com/android/car/internal/FeatureConfiguration.java
index 55da870..aeb25a0 100644
--- a/car-lib/src_feature_current/com/android/car/internal/FeatureConfiguration.java
+++ b/car-lib/src_feature_current/com/android/car/internal/FeatureConfiguration.java
@@ -26,4 +26,5 @@
/** product configuration in CarInfoManager */
public static final boolean ENABLE_PRODUCT_CONFIGURATION_INFO = DEFAULT;
public static final boolean ENABLE_VEHICLE_MAP_SERVICE = DEFAULT;
+ public static final boolean ENABLE_DIAGNOSTIC = DEFAULT;
}
diff --git a/car-lib/src_feature_future/com/android/car/internal/FeatureConfiguration.java b/car-lib/src_feature_future/com/android/car/internal/FeatureConfiguration.java
index 66cff60..f7b5c9f 100644
--- a/car-lib/src_feature_future/com/android/car/internal/FeatureConfiguration.java
+++ b/car-lib/src_feature_future/com/android/car/internal/FeatureConfiguration.java
@@ -26,4 +26,5 @@
/** product configuration in CarInfoManager */
public static final boolean ENABLE_PRODUCT_CONFIGURATION_INFO = DEFAULT;
public static final boolean ENABLE_VEHICLE_MAP_SERVICE = DEFAULT;
+ public static final boolean ENABLE_DIAGNOSTIC = DEFAULT;
}
diff --git a/service/Android.mk b/service/Android.mk
index aedaeb9..4ba2dbd 100644
--- a/service/Android.mk
+++ b/service/Android.mk
@@ -44,6 +44,7 @@
LOCAL_JAVA_LIBRARIES += android.car
LOCAL_STATIC_JAVA_LIBRARIES += \
android.hardware.automotive.vehicle@2.0-java-static \
+ vehicle-hal-support-lib \
car-systemtest \
LOCAL_JNI_SHARED_LIBRARIES := libjni_car_service
@@ -63,6 +64,7 @@
LOCAL_JAVA_LIBRARIES += android.car
LOCAL_STATIC_JAVA_LIBRARIES += \
android.hardware.automotive.vehicle@2.0-java-static \
+ vehicle-hal-support-lib \
car-systemtest \
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/service/src/com/android/car/CarDiagnosticService.java b/service/src/com/android/car/CarDiagnosticService.java
new file mode 100644
index 0000000..3a02010
--- /dev/null
+++ b/service/src/com/android/car/CarDiagnosticService.java
@@ -0,0 +1,793 @@
+/*
+ * Copyright (C) 2017 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.annotation.FutureFeature;
+import android.car.hardware.CarDiagnosticEvent;
+import android.car.hardware.CarDiagnosticManager;
+import android.car.hardware.ICarDiagnostic;
+import android.car.hardware.ICarDiagnosticEventListener;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.ArrayMap;
+import android.util.Log;
+import com.android.car.Listeners.ClientWithRate;
+import com.android.car.hal.DiagnosticHalService;
+import com.android.internal.annotations.GuardedBy;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.ConcurrentModificationException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.locks.ReentrantLock;
+
+@FutureFeature
+/** @hide */
+public class CarDiagnosticService extends ICarDiagnostic.Stub
+ implements CarServiceBase, DiagnosticHalService.DiagnosticListener {
+ /** {@link #mDiagnosticLock} is not waited forever for handling disconnection */
+ private static final long MAX_DIAGNOSTIC_LOCK_WAIT_MS = 1000;
+
+ /** lock to access diagnostic structures */
+ private final ReentrantLock mDiagnosticLock = new ReentrantLock();
+ /** hold clients callback */
+ @GuardedBy("mDiagnosticLock")
+ private final LinkedList<DiagnosticClient> mClients = new LinkedList<>();
+
+ /** key: diagnostic type. */
+ @GuardedBy("mDiagnosticLock")
+ private final HashMap<Integer, Listeners<DiagnosticClient>> mDiagnosticListeners =
+ new HashMap<>();
+
+ /** the latest live frame data. */
+ @GuardedBy("mDiagnosticLock")
+ private final LiveFrameRecord mLiveFrameDiagnosticRecord = new LiveFrameRecord(mDiagnosticLock);
+
+ /** the latest freeze frame data (key: DTC) */
+ @GuardedBy("mDiagnosticLock")
+ private final FreezeFrameRecord mFreezeFrameDiagnosticRecords = new FreezeFrameRecord(
+ mDiagnosticLock);
+
+ private final DiagnosticHalService mDiagnosticHal;
+
+ private final Context mContext;
+
+ public CarDiagnosticService(Context context, DiagnosticHalService diagnosticHal) {
+ mContext = context;
+ mDiagnosticHal = diagnosticHal;
+ }
+
+ @Override
+ public void init() {
+ mDiagnosticLock.lock();
+ try {
+ mDiagnosticHal.setDiagnosticListener(this);
+ setInitialLiveFrame();
+ setInitialFreezeFrames();
+ } finally {
+ mDiagnosticLock.unlock();
+ }
+ }
+
+ private CarDiagnosticEvent setInitialLiveFrame() {
+ CarDiagnosticEvent liveFrame = setRecentmostLiveFrame(mDiagnosticHal.getCurrentLiveFrame());
+ return liveFrame;
+ }
+
+ private void setInitialFreezeFrames() {
+ for (long timestamp: mDiagnosticHal.getFreezeFrameTimestamps()) {
+ setRecentmostFreezeFrame(mDiagnosticHal.getFreezeFrame(timestamp));
+ }
+ }
+
+ private CarDiagnosticEvent setRecentmostLiveFrame(final CarDiagnosticEvent event) {
+ return mLiveFrameDiagnosticRecord.update(Objects.requireNonNull(event).checkLiveFrame());
+ }
+
+ private CarDiagnosticEvent setRecentmostFreezeFrame(final CarDiagnosticEvent event) {
+ return mFreezeFrameDiagnosticRecords.update(
+ Objects.requireNonNull(event).checkFreezeFrame());
+ }
+
+ @Override
+ public void release() {
+ mDiagnosticLock.lock();
+ try {
+ mDiagnosticListeners.forEach(
+ (Integer frameType, Listeners diagnosticListeners) ->
+ diagnosticListeners.release());
+ mDiagnosticListeners.clear();
+ mLiveFrameDiagnosticRecord.disableIfNeeded();
+ mFreezeFrameDiagnosticRecords.disableIfNeeded();
+ mClients.clear();
+ } finally {
+ mDiagnosticLock.unlock();
+ }
+ }
+
+ private void processDiagnosticData(List<CarDiagnosticEvent> events) {
+ ArrayMap<CarDiagnosticService.DiagnosticClient, List<CarDiagnosticEvent>> eventsByClient =
+ new ArrayMap<>();
+
+ Listeners<DiagnosticClient> listeners = null;
+
+ mDiagnosticLock.lock();
+ for (CarDiagnosticEvent event : events) {
+ if (event.isLiveFrame()) {
+ // record recent-most live frame information
+ setRecentmostLiveFrame(event);
+ listeners = mDiagnosticListeners.get(CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE);
+ } else if (event.isFreezeFrame()) {
+ setRecentmostFreezeFrame(event);
+ listeners = mDiagnosticListeners.get(CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE);
+ } else {
+ Log.w(
+ CarLog.TAG_DIAGNOSTIC,
+ String.format("received unknown diagnostic event: %s", event));
+ continue;
+ }
+
+ if (null != listeners) {
+ for (ClientWithRate<DiagnosticClient> clientWithRate : listeners.getClients()) {
+ DiagnosticClient client = clientWithRate.getClient();
+ List<CarDiagnosticEvent> clientEvents = eventsByClient.computeIfAbsent(client,
+ (DiagnosticClient diagnosticClient) -> new LinkedList<>());
+ clientEvents.add(event);
+ }
+ }
+ }
+ mDiagnosticLock.unlock();
+
+ for (ArrayMap.Entry<CarDiagnosticService.DiagnosticClient, List<CarDiagnosticEvent>> entry :
+ eventsByClient.entrySet()) {
+ CarDiagnosticService.DiagnosticClient client = entry.getKey();
+ List<CarDiagnosticEvent> clientEvents = entry.getValue();
+
+ client.dispatchDiagnosticUpdate(clientEvents);
+ }
+ }
+
+ /** Received diagnostic data from car. */
+ @Override
+ public void onDiagnosticEvents(List<CarDiagnosticEvent> events) {
+ processDiagnosticData(events);
+ }
+
+ private List<CarDiagnosticEvent> getCachedEventsLocked(int frameType) {
+ ArrayList<CarDiagnosticEvent> events = new ArrayList<>();
+ switch (frameType) {
+ case CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE:
+ mLiveFrameDiagnosticRecord.lock();
+ events.add(mLiveFrameDiagnosticRecord.getLastEvent());
+ mLiveFrameDiagnosticRecord.unlock();
+ break;
+ case CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE:
+ mFreezeFrameDiagnosticRecords.lock();
+ mFreezeFrameDiagnosticRecords.getEvents().forEach(events::add);
+ mFreezeFrameDiagnosticRecords.unlock();
+ break;
+ default: break;
+ }
+ return events;
+ }
+
+ private void assertPermission(int frameType) {
+ if (Binder.getCallingUid() != Process.myUid()) {
+ switch (getDiagnosticPermission(frameType)) {
+ case PackageManager.PERMISSION_GRANTED:
+ break;
+ default:
+ throw new SecurityException(
+ "client does not have permission:"
+ + getPermissionName(frameType)
+ + " pid:"
+ + Binder.getCallingPid()
+ + " uid:"
+ + Binder.getCallingUid());
+ }
+ }
+ }
+
+ @Override
+ public boolean registerOrUpdateDiagnosticListener(int frameType, int rate,
+ ICarDiagnosticEventListener listener) {
+ boolean shouldStartDiagnostics = false;
+ CarDiagnosticService.DiagnosticClient diagnosticClient = null;
+ Integer oldRate = null;
+ Listeners<DiagnosticClient> diagnosticListeners = null;
+ mDiagnosticLock.lock();
+ try {
+ assertPermission(frameType);
+ diagnosticClient = findDiagnosticClientLocked(listener);
+ Listeners.ClientWithRate<DiagnosticClient> diagnosticClientWithRate = null;
+ if (diagnosticClient == null) {
+ diagnosticClient = new DiagnosticClient(listener);
+ try {
+ listener.asBinder().linkToDeath(diagnosticClient, 0);
+ } catch (RemoteException e) {
+ Log.w(
+ CarLog.TAG_DIAGNOSTIC,
+ String.format(
+ "received RemoteException trying to register listener for %s",
+ frameType));
+ return false;
+ }
+ mClients.add(diagnosticClient);
+ }
+ // If we have a cached event for this diagnostic, send the event.
+ diagnosticClient.dispatchDiagnosticUpdate(getCachedEventsLocked(frameType));
+ diagnosticListeners = mDiagnosticListeners.get(frameType);
+ if (diagnosticListeners == null) {
+ diagnosticListeners = new Listeners<>(rate);
+ mDiagnosticListeners.put(frameType, diagnosticListeners);
+ shouldStartDiagnostics = true;
+ } else {
+ oldRate = diagnosticListeners.getRate();
+ diagnosticClientWithRate =
+ diagnosticListeners.findClientWithRate(diagnosticClient);
+ }
+ if (diagnosticClientWithRate == null) {
+ diagnosticClientWithRate =
+ new ClientWithRate<>(diagnosticClient, rate);
+ diagnosticListeners.addClientWithRate(diagnosticClientWithRate);
+ } else {
+ diagnosticClientWithRate.setRate(rate);
+ }
+ if (diagnosticListeners.getRate() > rate) {
+ diagnosticListeners.setRate(rate);
+ shouldStartDiagnostics = true;
+ }
+ diagnosticClient.addDiagnostic(frameType);
+ } finally {
+ mDiagnosticLock.unlock();
+ }
+ Log.i(
+ CarLog.TAG_DIAGNOSTIC,
+ String.format(
+ "shouldStartDiagnostics = %s for %s at rate %d",
+ shouldStartDiagnostics, frameType, rate));
+ // start diagnostic outside lock as it can take time.
+ if (shouldStartDiagnostics) {
+ if (!startDiagnostic(frameType, rate)) {
+ // failed. so remove from active diagnostic list.
+ Log.w(CarLog.TAG_DIAGNOSTIC, "startDiagnostic failed");
+ mDiagnosticLock.lock();
+ try {
+ diagnosticClient.removeDiagnostic(frameType);
+ if (oldRate != null) {
+ diagnosticListeners.setRate(oldRate);
+ } else {
+ mDiagnosticListeners.remove(frameType);
+ }
+ } finally {
+ mDiagnosticLock.unlock();
+ }
+ return false;
+ }
+ }
+ return true;
+ }
+
+ //TODO(egranata): handle permissions correctly
+ private int getDiagnosticPermission(int frameType) {
+ String permission = getPermissionName(frameType);
+ int result = PackageManager.PERMISSION_GRANTED;
+ if (permission != null) {
+ return mContext.checkCallingOrSelfPermission(permission);
+ }
+ // If no permission is required, return granted.
+ return result;
+ }
+
+ private String getPermissionName(int frameType) {
+ return null;
+ }
+
+ private boolean startDiagnostic(int frameType, int rate) {
+ Log.i(CarLog.TAG_DIAGNOSTIC, String.format("starting diagnostic %s at rate %d",
+ frameType, rate));
+ DiagnosticHalService diagnosticHal = getDiagnosticHal();
+ if (diagnosticHal != null) {
+ if (!diagnosticHal.isReady()) {
+ Log.w(CarLog.TAG_DIAGNOSTIC, "diagnosticHal not ready");
+ return false;
+ }
+ switch (frameType) {
+ case CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE:
+ if (mLiveFrameDiagnosticRecord.isEnabled()) {
+ return true;
+ }
+ if (diagnosticHal.requestSensorStart(CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE,
+ rate)) {
+ mLiveFrameDiagnosticRecord.enable();
+ return true;
+ }
+ break;
+ case CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE:
+ if (mFreezeFrameDiagnosticRecords.isEnabled()) {
+ return true;
+ }
+ if (diagnosticHal.requestSensorStart(CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE,
+ rate)) {
+ mFreezeFrameDiagnosticRecords.enable();
+ return true;
+ }
+ break;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void unregisterDiagnosticListener(
+ int frameType, ICarDiagnosticEventListener listener) {
+ boolean shouldStopDiagnostic = false;
+ boolean shouldRestartDiagnostic = false;
+ int newRate = 0;
+ mDiagnosticLock.lock();
+ try {
+ DiagnosticClient diagnosticClient = findDiagnosticClientLocked(listener);
+ if (diagnosticClient == null) {
+ Log.i(
+ CarLog.TAG_DIAGNOSTIC,
+ String.format(
+ "trying to unregister diagnostic client %s for %s which is not registered",
+ listener, frameType));
+ // never registered or already unregistered.
+ return;
+ }
+ diagnosticClient.removeDiagnostic(frameType);
+ if (diagnosticClient.getNumberOfActiveDiagnostic() == 0) {
+ diagnosticClient.release();
+ mClients.remove(diagnosticClient);
+ }
+ Listeners<DiagnosticClient> diagnosticListeners = mDiagnosticListeners.get(frameType);
+ if (diagnosticListeners == null) {
+ // diagnostic not active
+ return;
+ }
+ ClientWithRate<DiagnosticClient> clientWithRate =
+ diagnosticListeners.findClientWithRate(diagnosticClient);
+ if (clientWithRate == null) {
+ return;
+ }
+ diagnosticListeners.removeClientWithRate(clientWithRate);
+ if (diagnosticListeners.getNumberOfClients() == 0) {
+ shouldStopDiagnostic = true;
+ mDiagnosticListeners.remove(frameType);
+ } else if (diagnosticListeners.updateRate()) { // rate changed
+ newRate = diagnosticListeners.getRate();
+ shouldRestartDiagnostic = true;
+ }
+ } finally {
+ mDiagnosticLock.unlock();
+ }
+ Log.i(
+ CarLog.TAG_DIAGNOSTIC,
+ String.format(
+ "shouldStopDiagnostic = %s, shouldRestartDiagnostic = %s for type %s",
+ shouldStopDiagnostic, shouldRestartDiagnostic, frameType));
+ if (shouldStopDiagnostic) {
+ stopDiagnostic(frameType);
+ } else if (shouldRestartDiagnostic) {
+ startDiagnostic(frameType, newRate);
+ }
+ }
+
+ private void stopDiagnostic(int frameType) {
+ DiagnosticHalService diagnosticHal = getDiagnosticHal();
+ if (diagnosticHal == null || !diagnosticHal.isReady()) {
+ Log.w(CarLog.TAG_DIAGNOSTIC, "diagnosticHal not ready");
+ return;
+ }
+ switch (frameType) {
+ case CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE:
+ if (mLiveFrameDiagnosticRecord.disableIfNeeded())
+ diagnosticHal.requestSensorStop(CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE);
+ break;
+ case CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE:
+ if (mFreezeFrameDiagnosticRecords.disableIfNeeded())
+ diagnosticHal.requestSensorStop(CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE);
+ break;
+ }
+ }
+
+ private DiagnosticHalService getDiagnosticHal() {
+ return mDiagnosticHal;
+ }
+
+ // ICarDiagnostic implementations
+
+ @Override
+ public CarDiagnosticEvent getLatestLiveFrame() {
+ mLiveFrameDiagnosticRecord.lock();
+ CarDiagnosticEvent liveFrame = mLiveFrameDiagnosticRecord.getLastEvent();
+ mLiveFrameDiagnosticRecord.unlock();
+ return liveFrame;
+ }
+
+ @Override
+ public long[] getFreezeFrameTimestamps() {
+ mFreezeFrameDiagnosticRecords.lock();
+ long[] timestamps = mFreezeFrameDiagnosticRecords.getFreezeFrameTimestamps();
+ mFreezeFrameDiagnosticRecords.unlock();
+ return timestamps;
+ }
+
+ @Override
+ @Nullable
+ public CarDiagnosticEvent getFreezeFrame(long timestamp) {
+ mFreezeFrameDiagnosticRecords.lock();
+ CarDiagnosticEvent freezeFrame = mFreezeFrameDiagnosticRecords.getEvent(timestamp);
+ mFreezeFrameDiagnosticRecords.unlock();
+ return freezeFrame;
+ }
+
+ @Override
+ public boolean clearFreezeFrames(long... timestamps) {
+ mFreezeFrameDiagnosticRecords.lock();
+ mDiagnosticHal.clearFreezeFrames(timestamps);
+ mFreezeFrameDiagnosticRecords.clearEvents();
+ mFreezeFrameDiagnosticRecords.unlock();
+ return true;
+ }
+
+ /**
+ * Find DiagnosticClient from client list and return it. This should be called with mClients
+ * locked.
+ *
+ * @param listener
+ * @return null if not found.
+ */
+ private CarDiagnosticService.DiagnosticClient findDiagnosticClientLocked(
+ ICarDiagnosticEventListener listener) {
+ IBinder binder = listener.asBinder();
+ for (DiagnosticClient diagnosticClient : mClients) {
+ if (diagnosticClient.isHoldingListenerBinder(binder)) {
+ return diagnosticClient;
+ }
+ }
+ return null;
+ }
+
+ private void removeClient(DiagnosticClient diagnosticClient) {
+ mDiagnosticLock.lock();
+ try {
+ for (int diagnostic : diagnosticClient.getDiagnosticArray()) {
+ unregisterDiagnosticListener(
+ diagnostic, diagnosticClient.getICarDiagnosticEventListener());
+ }
+ mClients.remove(diagnosticClient);
+ } finally {
+ mDiagnosticLock.unlock();
+ }
+ }
+
+ private class DiagnosticDispatchHandler extends Handler {
+ private static final long DIAGNOSTIC_DISPATCH_MIN_INTERVAL_MS = 16; // over 60Hz
+
+ private static final int MSG_DIAGNOSTIC_DATA = 0;
+
+ private long mLastDiagnosticDispatchTime = -1;
+ private int mFreeListIndex = 0;
+ private final LinkedList<CarDiagnosticEvent>[] mDiagnosticDataList = new LinkedList[2];
+
+ private DiagnosticDispatchHandler(Looper looper) {
+ super(looper);
+ for (int i = 0; i < mDiagnosticDataList.length; i++) {
+ mDiagnosticDataList[i] = new LinkedList<CarDiagnosticEvent>();
+ }
+ }
+
+ private synchronized void handleDiagnosticEvents(List<CarDiagnosticEvent> data) {
+ LinkedList<CarDiagnosticEvent> list = mDiagnosticDataList[mFreeListIndex];
+ list.addAll(data);
+ requestDispatchLocked();
+ }
+
+ private synchronized void handleDiagnosticEvent(CarDiagnosticEvent event) {
+ LinkedList<CarDiagnosticEvent> list = mDiagnosticDataList[mFreeListIndex];
+ list.add(event);
+ requestDispatchLocked();
+ }
+
+ private void requestDispatchLocked() {
+ Message msg = obtainMessage(MSG_DIAGNOSTIC_DATA);
+ long now = SystemClock.uptimeMillis();
+ long delta = now - mLastDiagnosticDispatchTime;
+ if (delta > DIAGNOSTIC_DISPATCH_MIN_INTERVAL_MS) {
+ sendMessage(msg);
+ } else {
+ sendMessageDelayed(msg, DIAGNOSTIC_DISPATCH_MIN_INTERVAL_MS - delta);
+ }
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_DIAGNOSTIC_DATA:
+ doHandleDiagnosticData();
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void doHandleDiagnosticData() {
+ List<CarDiagnosticEvent> listToDispatch = null;
+ synchronized (this) {
+ mLastDiagnosticDispatchTime = SystemClock.uptimeMillis();
+ int nonFreeListIndex = mFreeListIndex ^ 0x1;
+ List<CarDiagnosticEvent> nonFreeList = mDiagnosticDataList[nonFreeListIndex];
+ List<CarDiagnosticEvent> freeList = mDiagnosticDataList[mFreeListIndex];
+ if (nonFreeList.size() > 0) {
+ // copy again, but this should not be normal case
+ nonFreeList.addAll(freeList);
+ listToDispatch = nonFreeList;
+ freeList.clear();
+ } else if (freeList.size() > 0) {
+ listToDispatch = freeList;
+ mFreeListIndex = nonFreeListIndex;
+ }
+ }
+ // leave this part outside lock so that time-taking dispatching can be done without
+ // blocking diagnostic event notification.
+ if (listToDispatch != null) {
+ processDiagnosticData(listToDispatch);
+ listToDispatch.clear();
+ }
+ }
+ }
+
+ /** internal instance for pending client request */
+ private class DiagnosticClient implements Listeners.IListener {
+ /** callback for diagnostic events */
+ private final ICarDiagnosticEventListener mListener;
+
+ private final Set<Integer> mActiveDiagnostics = new HashSet<>();
+
+ /** when false, it is already released */
+ private volatile boolean mActive = true;
+
+ DiagnosticClient(ICarDiagnosticEventListener listener) {
+ this.mListener = listener;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof CarDiagnosticService.DiagnosticClient
+ && mListener.asBinder()
+ == ((CarDiagnosticService.DiagnosticClient) o).mListener.asBinder()) {
+ return true;
+ }
+ return false;
+ }
+
+ boolean isHoldingListenerBinder(IBinder listenerBinder) {
+ return mListener.asBinder() == listenerBinder;
+ }
+
+ void addDiagnostic(int frameType) {
+ mActiveDiagnostics.add(frameType);
+ }
+
+ void removeDiagnostic(int frameType) {
+ mActiveDiagnostics.remove(frameType);
+ }
+
+ int getNumberOfActiveDiagnostic() {
+ return mActiveDiagnostics.size();
+ }
+
+ int[] getDiagnosticArray() {
+ return mActiveDiagnostics.stream().mapToInt(Integer::intValue).toArray();
+ }
+
+ ICarDiagnosticEventListener getICarDiagnosticEventListener() {
+ return mListener;
+ }
+
+ /** Client dead. should remove all diagnostic requests from client */
+ @Override
+ public void binderDied() {
+ mListener.asBinder().unlinkToDeath(this, 0);
+ removeClient(this);
+ }
+
+ void dispatchDiagnosticUpdate(List<CarDiagnosticEvent> events) {
+ if (events.size() == 0) {
+ return;
+ }
+ if (mActive) {
+ try {
+ mListener.onDiagnosticEvents(events);
+ } catch (RemoteException e) {
+ //ignore. crash will be handled by death handler
+ }
+ } else {
+ }
+ }
+
+ @Override
+ public void release() {
+ if (mActive) {
+ mListener.asBinder().unlinkToDeath(this, 0);
+ mActiveDiagnostics.clear();
+ mActive = false;
+ }
+ }
+ }
+
+ private static abstract class DiagnosticRecord {
+ private final ReentrantLock mLock;
+ protected boolean mEnabled = false;
+
+ DiagnosticRecord(ReentrantLock lock) {
+ mLock = lock;
+ }
+
+ void lock() {
+ mLock.lock();
+ }
+
+ void unlock() {
+ mLock.unlock();
+ }
+
+ boolean isEnabled() {
+ return mEnabled;
+ }
+
+ void enable() {
+ mEnabled = true;
+ }
+
+ abstract boolean disableIfNeeded();
+ abstract CarDiagnosticEvent update(CarDiagnosticEvent newEvent);
+ }
+
+ private static class LiveFrameRecord extends DiagnosticRecord {
+ /** Store the most recent live-frame. */
+ CarDiagnosticEvent mLastEvent = null;
+
+ LiveFrameRecord(ReentrantLock lock) {
+ super(lock);
+ }
+
+ @Override
+ boolean disableIfNeeded() {
+ if (!mEnabled) return false;
+ mEnabled = false;
+ mLastEvent = null;
+ return true;
+ }
+
+ @Override
+ CarDiagnosticEvent update(CarDiagnosticEvent newEvent) {
+ newEvent = Objects.requireNonNull(newEvent);
+ if((null == mLastEvent) || mLastEvent.isEarlierThan(newEvent))
+ mLastEvent = newEvent;
+ return mLastEvent;
+ }
+
+ CarDiagnosticEvent getLastEvent() {
+ return mLastEvent;
+ }
+ }
+
+ private static class FreezeFrameRecord extends DiagnosticRecord {
+ /** Store the timestamp --> freeze frame mapping. */
+ HashMap<Long, CarDiagnosticEvent> mEvents = new HashMap<>();
+
+ FreezeFrameRecord(ReentrantLock lock) {
+ super(lock);
+ }
+
+ @Override
+ boolean disableIfNeeded() {
+ if (!mEnabled) return false;
+ mEnabled = false;
+ clearEvents();
+ return true;
+ }
+
+ void clearEvents() {
+ mEvents.clear();
+ }
+
+ @Override
+ CarDiagnosticEvent update(CarDiagnosticEvent newEvent) {
+ mEvents.put(newEvent.timestamp, newEvent);
+ return newEvent;
+ }
+
+ long[] getFreezeFrameTimestamps() {
+ return mEvents.keySet().stream().mapToLong(Long::longValue).toArray();
+ }
+
+ CarDiagnosticEvent getEvent(long timestamp) {
+ return mEvents.get(timestamp);
+ }
+
+ Iterable<CarDiagnosticEvent> getEvents() {
+ return mEvents.values();
+ }
+ }
+
+ @Override
+ public void dump(PrintWriter writer) {
+ writer.println("*CarDiagnosticService*");
+ writer.println("**last events for diagnostics**");
+ if (null != mLiveFrameDiagnosticRecord.getLastEvent()) {
+ writer.println("last live frame event: ");
+ writer.println(mLiveFrameDiagnosticRecord.getLastEvent());
+ }
+ writer.println("freeze frame events: ");
+ mFreezeFrameDiagnosticRecords.getEvents().forEach(writer::println);
+ writer.println("**clients**");
+ try {
+ for (DiagnosticClient client : mClients) {
+ if (client != null) {
+ try {
+ writer.println(
+ "binder:"
+ + client.mListener
+ + " active diagnostics:"
+ + Arrays.toString(client.getDiagnosticArray()));
+ } catch (ConcurrentModificationException e) {
+ writer.println("concurrent modification happened");
+ }
+ } else {
+ writer.println("null client");
+ }
+ }
+ } catch (ConcurrentModificationException e) {
+ writer.println("concurrent modification happened");
+ }
+ writer.println("**diagnostic listeners**");
+ try {
+ for (int diagnostic : mDiagnosticListeners.keySet()) {
+ Listeners diagnosticListeners = mDiagnosticListeners.get(diagnostic);
+ if (diagnosticListeners != null) {
+ writer.println(
+ " Diagnostic:"
+ + diagnostic
+ + " num client:"
+ + diagnosticListeners.getNumberOfClients()
+ + " rate:"
+ + diagnosticListeners.getRate());
+ }
+ }
+ } catch (ConcurrentModificationException e) {
+ writer.println("concurrent modification happened");
+ }
+ }
+}
diff --git a/service/src/com/android/car/CarLog.java b/service/src/com/android/car/CarLog.java
index c290bbd..e594ec5 100644
--- a/service/src/com/android/car/CarLog.java
+++ b/service/src/com/android/car/CarLog.java
@@ -40,4 +40,5 @@
public static final String TAG_SERVICE = "CAR.SERVICE";
public static final String TAG_SYS = "CAR.SYS";
public static final String TAG_TEST = "CAR.TEST";
+ public static final String TAG_DIAGNOSTIC = "CAR.DIAGNOSTIC";
}
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index 2d58eb1..d98ca25 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -78,6 +78,8 @@
private final CarVendorExtensionService mCarVendorExtensionService;
private final CarBluetoothService mCarBluetoothService;
@FutureFeature
+ private CarDiagnosticService mCarDiagnosticService;
+ @FutureFeature
private VmsSubscriberService mVmsSubscriberService;
private final CarServiceBase[] mAllServices;
@@ -118,6 +120,10 @@
if (FeatureConfiguration.ENABLE_VEHICLE_MAP_SERVICE) {
mVmsSubscriberService = new VmsSubscriberService(serviceContext, mHal.getVmsHal());
}
+ if (FeatureConfiguration.ENABLE_DIAGNOSTIC) {
+ mCarDiagnosticService = new CarDiagnosticService(serviceContext,
+ mHal.getDiagnosticHal());
+ }
// Be careful with order. Service depending on other service should be inited later.
List<CarServiceBase> allServices = new ArrayList<>(Arrays.asList(
@@ -144,6 +150,9 @@
if (FeatureConfiguration.ENABLE_VEHICLE_MAP_SERVICE) {
allServices.add(mVmsSubscriberService);
}
+ if (FeatureConfiguration.ENABLE_DIAGNOSTIC) {
+ allServices.add(mCarDiagnosticService);
+ }
mAllServices = allServices.toArray(new CarServiceBase[0]);
}
@@ -188,6 +197,9 @@
case Car.CAMERA_SERVICE:
assertCameraPermission(mContext);
return mCarCameraService;
+ case Car.DIAGNOSTIC_SERVICE:
+ //TODO(egranata): handle permissions
+ return mCarDiagnosticService;
case Car.HVAC_SERVICE:
assertHvacPermission(mContext);
return mCarHvacService;
diff --git a/service/src/com/android/car/hal/DiagnosticHalService.java b/service/src/com/android/car/hal/DiagnosticHalService.java
new file mode 100644
index 0000000..1750b5c
--- /dev/null
+++ b/service/src/com/android/car/hal/DiagnosticHalService.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2017 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.annotation.Nullable;
+import android.car.annotation.FutureFeature;
+import android.car.hardware.CarDiagnosticEvent;
+import android.car.hardware.CarDiagnosticManager;
+import android.car.hardware.CarSensorManager;
+import android.hardware.automotive.vehicle.V2_0.Obd2FloatSensorIndex;
+import android.hardware.automotive.vehicle.V2_0.Obd2IntegerSensorIndex;
+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.VehiclePropertyChangeMode;
+import android.util.Log;
+import android.util.SparseArray;
+import com.android.car.CarLog;
+import com.android.car.CarServiceUtils;
+import com.android.car.vehiclehal.VehiclePropValueBuilder;
+import java.io.PrintWriter;
+import java.util.BitSet;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Diagnostic HAL service supporting gathering diagnostic info from VHAL and translating it into
+ * higher-level semantic information
+ */
+@FutureFeature
+public class DiagnosticHalService extends SensorHalServiceBase {
+ private DiagnosticListener mDiagnosticListener;
+ protected final SparseArray<VehiclePropConfig> mVehiclePropertyToConfig = new SparseArray<>();
+
+ public DiagnosticHalService(VehicleHal hal) {
+ super(hal);
+ }
+
+ @Override
+ protected int getTokenForProperty(VehiclePropConfig propConfig) {
+ switch (propConfig.prop) {
+ case VehicleProperty.OBD2_LIVE_FRAME:
+ mVehiclePropertyToConfig.put(propConfig.prop, propConfig);
+ Log.i(CarLog.TAG_DIAGNOSTIC, String.format("configArray for OBD2_LIVE_FRAME is %s",
+ propConfig.configArray));
+ return CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE;
+ case VehicleProperty.OBD2_FREEZE_FRAME:
+ mVehiclePropertyToConfig.put(propConfig.prop, propConfig);
+ Log.i(CarLog.TAG_DIAGNOSTIC, String.format("configArray for OBD2_FREEZE_FRAME is %s",
+ propConfig.configArray));
+ return CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE;
+ case VehicleProperty.OBD2_FREEZE_FRAME_INFO:
+ return propConfig.prop;
+ default:
+ return SENSOR_TYPE_INVALID;
+ }
+ }
+
+ private VehiclePropConfig getPropConfig(int halPropId) {
+ return mVehiclePropertyToConfig.get(halPropId, null);
+ }
+
+ private int getNumIntegerSensors(int halPropId) {
+ int count = Obd2IntegerSensorIndex.LAST_SYSTEM_INDEX + 1;
+ count = count + getPropConfig(halPropId).configArray.get(0);
+ return count;
+ }
+
+ private int getNumFloatSensors(int halPropId) {
+ int count = Obd2FloatSensorIndex.LAST_SYSTEM_INDEX + 1;
+ count = count + getPropConfig(halPropId).configArray.get(1);
+ return count;
+ }
+
+ private CarDiagnosticEvent createCarDiagnosticEvent(VehiclePropValue value) {
+ if (null == value)
+ return null;
+
+ final boolean isFreezeFrame = value.prop == VehicleProperty.OBD2_FREEZE_FRAME;
+
+ CarDiagnosticEvent.Builder builder =
+ (isFreezeFrame
+ ? CarDiagnosticEvent.Builder.freezeFrame()
+ : CarDiagnosticEvent.Builder.liveFrame())
+ .atTimestamp(value.timestamp);
+
+ BitSet bitset = BitSet.valueOf(CarServiceUtils.toByteArray(value.value.bytes));
+
+ int numIntegerProperties = getNumIntegerSensors(value.prop);
+ int numFloatProperties = getNumFloatSensors(value.prop);
+
+ for (int i = 0; i < numIntegerProperties; ++i) {
+ if (bitset.get(i)) {
+ builder.withIntValue(i, value.value.int32Values.get(i));
+ }
+ }
+
+ for (int i = 0; i < numFloatProperties; ++i) {
+ if (bitset.get(numIntegerProperties + i)) {
+ builder.withFloatValue(i, value.value.floatValues.get(i));
+ }
+ }
+
+ builder.withDTC(value.value.stringValue);
+
+ return builder.build();
+ }
+
+ /** Listener for monitoring diagnostic event. */
+ public interface DiagnosticListener {
+ /**
+ * Diagnostic events are available.
+ *
+ * @param events
+ */
+ void onDiagnosticEvents(List<CarDiagnosticEvent> events);
+ }
+
+ // Should be used only inside handleHalEvents method.
+ private final LinkedList<CarDiagnosticEvent> mEventsToDispatch = new LinkedList<>();
+
+ @Override
+ public void handleHalEvents(List<VehiclePropValue> values) {
+ for (VehiclePropValue value : values) {
+ CarDiagnosticEvent event = createCarDiagnosticEvent(value);
+ if (event != null) {
+ mEventsToDispatch.add(event);
+ }
+ }
+
+ DiagnosticListener listener = null;
+ synchronized (this) {
+ listener = mDiagnosticListener;
+ }
+ if (listener != null) {
+ listener.onDiagnosticEvents(mEventsToDispatch);
+ }
+ mEventsToDispatch.clear();
+ }
+
+ public synchronized void setDiagnosticListener(DiagnosticListener listener) {
+ mDiagnosticListener = listener;
+ }
+
+ public DiagnosticListener getDiagnosticListener() {
+ return mDiagnosticListener;
+ }
+
+ @Override
+ public void dump(PrintWriter writer) {
+ writer.println("*Diagnostic HAL*");
+ }
+
+ @Override
+ protected float fixSamplingRateForProperty(VehiclePropConfig prop, int carSensorManagerRate) {
+ //TODO(egranata): tweak this for diagnostics
+ switch (prop.changeMode) {
+ case VehiclePropertyChangeMode.ON_CHANGE:
+ case VehiclePropertyChangeMode.ON_SET:
+ return 0;
+ }
+ float rate = 1.0f;
+ switch (carSensorManagerRate) {
+ case CarSensorManager.SENSOR_RATE_FASTEST:
+ case CarSensorManager.SENSOR_RATE_FAST:
+ rate = 10f;
+ break;
+ case CarSensorManager.SENSOR_RATE_UI:
+ rate = 5f;
+ break;
+ default: // fall back to default.
+ break;
+ }
+ if (rate > prop.maxSampleRate) {
+ rate = prop.maxSampleRate;
+ }
+ if (rate < prop.minSampleRate) {
+ rate = prop.minSampleRate;
+ }
+ return rate;
+ }
+
+ @Nullable
+ public CarDiagnosticEvent getCurrentLiveFrame() {
+ try {
+ VehiclePropValue value = mHal.get(VehicleProperty.OBD2_LIVE_FRAME);
+ return createCarDiagnosticEvent(value);
+ } catch (PropertyTimeoutException e) {
+ Log.e(CarLog.TAG_DIAGNOSTIC, "timeout trying to read OBD2_LIVE_FRAME");
+ return null;
+ }
+ }
+
+ @Nullable
+ public long[] getFreezeFrameTimestamps() {
+ try {
+ VehiclePropValue value = mHal.get(VehicleProperty.OBD2_FREEZE_FRAME_INFO);
+ long[] timestamps = new long[value.value.int64Values.size()];
+ for (int i = 0; i < timestamps.length; ++i) {
+ timestamps[i] = value.value.int64Values.get(i);
+ }
+ return timestamps;
+ } catch (PropertyTimeoutException e) {
+ Log.e(CarLog.TAG_DIAGNOSTIC, "timeout trying to read OBD2_DTC_INFO");
+ return null;
+ }
+ }
+
+ @Nullable
+ public CarDiagnosticEvent getFreezeFrame(long timestamp) {
+ VehiclePropValueBuilder builder = VehiclePropValueBuilder.newBuilder(
+ VehicleProperty.OBD2_FREEZE_FRAME);
+ builder.setInt64Value(timestamp);
+ try {
+ VehiclePropValue value = mHal.get(builder.build());
+ return createCarDiagnosticEvent(value);
+ } catch (PropertyTimeoutException e) {
+ Log.e(CarLog.TAG_DIAGNOSTIC, "timeout trying to read OBD2_DTC_INFO");
+ return null;
+ }
+ }
+
+ public void clearFreezeFrames(long... timestamps) {
+ VehiclePropValueBuilder builder = VehiclePropValueBuilder.newBuilder(
+ VehicleProperty.OBD2_FREEZE_FRAME_CLEAR);
+ builder.setInt64Value(timestamps);
+ try {
+ mHal.set(builder.build());
+ } catch (PropertyTimeoutException e) {
+ Log.e(CarLog.TAG_DIAGNOSTIC, "timeout trying to write OBD2_DTC_CLEAR");
+ }
+ }
+}
diff --git a/service/src/com/android/car/hal/SensorHalServiceBase.java b/service/src/com/android/car/hal/SensorHalServiceBase.java
index 149b85a..92d2540 100644
--- a/service/src/com/android/car/hal/SensorHalServiceBase.java
+++ b/service/src/com/android/car/hal/SensorHalServiceBase.java
@@ -23,11 +23,8 @@
import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
-import android.hardware.automotive.vehicle.V2_0.VehiclePropertyAccess;
-import android.hardware.automotive.vehicle.V2_0.VehiclePropertyChangeMode;
import android.util.Log;
import android.util.SparseArray;
-import com.android.car.CarLog;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
@@ -40,12 +37,12 @@
* the {@link #requestSensorStart(int, int)} call.
*/
public abstract class SensorHalServiceBase extends HalServiceBase implements SensorBase {
- private final String TAG = "SensorHalServiceBase";
- private final VehicleHal mHal;
+ private static final String TAG = "SensorHalServiceBase";
private boolean mIsReady = false;
protected static final int SENSOR_TYPE_INVALID = NOT_SUPPORTED_PROPERTY;
+ protected final VehicleHal mHal;
protected final SparseArray<VehiclePropConfig> mSensorToPropConfig = new SparseArray<>();
public SensorHalServiceBase(VehicleHal hal) {
diff --git a/service/src/com/android/car/hal/VehicleHal.java b/service/src/com/android/car/hal/VehicleHal.java
index 1ca9914..bf7985c 100644
--- a/service/src/com/android/car/hal/VehicleHal.java
+++ b/service/src/com/android/car/hal/VehicleHal.java
@@ -78,13 +78,16 @@
@FutureFeature
private VmsHalService mVmsHal;
+ @FutureFeature
+ private DiagnosticHalService mDiagnosticHal = null;
+
/** Might be re-assigned if Vehicle HAL is reconnected. */
private volatile HalClient mHalClient;
/** Stores handler for each HAL property. Property events are sent to handler. */
private final SparseArray<HalServiceBase> mPropertyHandlers = new SparseArray<>();
/** This is for iterating all HalServices with fixed order. */
- private final HalServiceBase[] mAllServices;
+ private final ArrayList<HalServiceBase> mAllServices = new ArrayList<>();
private final HashMap<Integer, Float> mSubscribedProperties = new HashMap<>();
private final HashMap<Integer, VehiclePropConfig> mAllProperties = new HashMap<>();
private final HashMap<Integer, VehiclePropertyEventInfo> mEventLog = new HashMap<>();
@@ -105,21 +108,24 @@
if (FeatureConfiguration.ENABLE_VEHICLE_MAP_SERVICE) {
mVmsHal = new VmsHalService(this);
}
- List<HalServiceBase> allServices = new ArrayList<>(Arrays.asList(
- mPowerHal,
+ if(FeatureConfiguration.ENABLE_DIAGNOSTIC) {
+ mDiagnosticHal = new DiagnosticHalService(this);
+ }
+ mAllServices.addAll(Arrays.asList(mPowerHal,
+ mSensorHal,
+ mInfoHal,
mAudioHal,
mCabinHal,
- mHvacHal,
- mInfoHal,
- mSensorHal,
mRadioHal,
+ mHvacHal,
mInputHal,
- mVendorExtensionHal
- ));
+ mVendorExtensionHal));
if (FeatureConfiguration.ENABLE_VEHICLE_MAP_SERVICE) {
- allServices.add(mVmsHal);
+ mAllServices.add(mVmsHal);
}
- mAllServices = allServices.toArray(new HalServiceBase[0]);
+ if(FeatureConfiguration.ENABLE_DIAGNOSTIC) {
+ mAllServices.add(mDiagnosticHal);
+ }
mHalClient = new HalClient(vehicle, mHandlerThread.getLooper(), this /*IVehicleCallback*/);
}
@@ -127,8 +133,8 @@
/** Dummy version only for testing */
@VisibleForTesting
public VehicleHal(PowerHalService powerHal, SensorHalService sensorHal, InfoHalService infoHal,
- AudioHalService audioHal, CabinHalService cabinHal, RadioHalService radioHal,
- HvacHalService hvacHal, HalClient halClient) {
+ AudioHalService audioHal, CabinHalService cabinHal,
+ RadioHalService radioHal, HvacHalService hvacHal, HalClient halClient) {
mHandlerThread = null;
mPowerHal = powerHal;
mSensorHal = sensorHal;
@@ -139,14 +145,41 @@
mHvacHal = hvacHal;
mInputHal = null;
mVendorExtensionHal = null;
+
if (FeatureConfiguration.ENABLE_VEHICLE_MAP_SERVICE) {
// TODO(antoniocortes): do we need a test version of VmsHalService?
mVmsHal = null;
}
- mAllServices = null;
+ if(FeatureConfiguration.ENABLE_DIAGNOSTIC) {
+ mDiagnosticHal = null;
+ }
+
mHalClient = halClient;
}
+ /** Dummy version only for testing */
+ @VisibleForTesting
+ @FutureFeature
+ public VehicleHal(PowerHalService powerHal, SensorHalService sensorHal, InfoHalService infoHal,
+ AudioHalService audioHal, CabinHalService cabinHal, DiagnosticHalService diagnosticHal,
+ RadioHalService radioHal, HvacHalService hvacHal, HalClient halClient) {
+ mHandlerThread = null;
+ mPowerHal = powerHal;
+ mSensorHal = sensorHal;
+ mInfoHal = infoHal;
+ mAudioHal = audioHal;
+ mCabinHal = cabinHal;
+ mDiagnosticHal = diagnosticHal;
+ mRadioHal = radioHal;
+ mHvacHal = hvacHal;
+ mInputHal = null;
+ mVendorExtensionHal = null;
+ // TODO(antoniocortes): do we need a test version of VmsHalService?
+ mVmsHal = null;
+ mHalClient = halClient;
+ mDiagnosticHal = diagnosticHal;
+ }
+
public void vehicleHalReconnected(IVehicle vehicle) {
synchronized (this) {
mHalClient = new HalClient(vehicle, mHandlerThread.getLooper(),
@@ -199,8 +232,8 @@
public void release() {
// release in reverse order from init
- for (int i = mAllServices.length - 1; i >= 0; i--) {
- mAllServices[i].release();
+ for (int i = mAllServices.size() - 1; i >= 0; i--) {
+ mAllServices.get(i).release();
}
synchronized (this) {
for (int p : mSubscribedProperties.keySet()) {
@@ -233,6 +266,9 @@
return mCabinHal;
}
+ @FutureFeature
+ public DiagnosticHalService getDiagnosticHal() { return mDiagnosticHal; }
+
public RadioHalService getRadioHal() {
return mRadioHal;
}
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarDiagnosticManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarDiagnosticManagerTest.java
new file mode 100644
index 0000000..b19592c
--- /dev/null
+++ b/tests/android_car_api_test/src/android/car/apitest/CarDiagnosticManagerTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2017 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 android.car.apitest;
+
+import android.car.Car;
+import android.car.hardware.CarDiagnosticEvent;
+import android.car.hardware.CarDiagnosticManager;
+import android.content.ComponentName;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.Looper;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+@MediumTest
+public class CarDiagnosticManagerTest extends AndroidTestCase {
+ private static final long DEFAULT_WAIT_TIMEOUT_MS = 5000;
+
+ private final Semaphore mConnectionWait = new Semaphore(0);
+
+ private Car mCar;
+ private CarDiagnosticManager mCarDiagnosticManager;
+
+ private final ServiceConnection mConnectionListener =
+ new ServiceConnection() {
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ assertMainThread();
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ assertMainThread();
+ mConnectionWait.release();
+ }
+ };
+
+ private void assertMainThread() {
+ assertTrue(Looper.getMainLooper().isCurrentThread());
+ }
+
+ private void waitForConnection(long timeoutMs) throws InterruptedException {
+ mConnectionWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mCar = Car.createCar(getContext(), mConnectionListener);
+ mCar.connect();
+ waitForConnection(DEFAULT_WAIT_TIMEOUT_MS);
+ mCarDiagnosticManager = (CarDiagnosticManager) mCar.getCarManager(Car.DIAGNOSTIC_SERVICE);
+ assertNotNull(mCarDiagnosticManager);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ mCar.disconnect();
+ }
+
+ /**
+ * Test that we can read live frame data over the diagnostic manager
+ * @throws Exception
+ */
+ public void testLiveFrame() throws Exception {
+ CarDiagnosticEvent liveFrame = mCarDiagnosticManager.getLatestLiveFrame();
+ if (null != liveFrame) {
+ assertTrue(liveFrame.isLiveFrame());
+ assertFalse(liveFrame.isEmptyFrame());
+ }
+ }
+
+ /**
+ * Test that we can read well-formed freeze frame data over the diagnostic manager
+ * @throws Exception
+ */
+ public void testFreezeFrames() throws Exception {
+ long[] timestamps = mCarDiagnosticManager.getFreezeFrameTimestamps();
+ if (null != timestamps) {
+ for (long timestamp : timestamps) {
+ CarDiagnosticEvent freezeFrame = mCarDiagnosticManager.getFreezeFrame(timestamp);
+ assertNotNull(freezeFrame);
+ assertEquals(timestamp, freezeFrame.timestamp);
+ assertTrue(freezeFrame.isFreezeFrame());
+ assertFalse(freezeFrame.isEmptyFrame());
+ assertNotSame("", freezeFrame.DTC);
+ }
+ }
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/MockedPowerHalService.java b/tests/carservice_unit_test/src/com/android/car/MockedPowerHalService.java
index 02c23f2..fbb70d4 100644
--- a/tests/carservice_unit_test/src/com/android/car/MockedPowerHalService.java
+++ b/tests/carservice_unit_test/src/com/android/car/MockedPowerHalService.java
@@ -35,7 +35,7 @@
public MockedPowerHalService(boolean isPowerStateSupported, boolean isDeepSleepAllowed,
boolean isTimedWakeupAllowed) {
- super(new VehicleHal(null, null, null, null, null, null, null, null));
+ super(new VehicleHal(null, null, null, null, null, null, null, null, null));
mIsPowerStateSupported = isPowerStateSupported;
mIsDeepSleepAllowed = isDeepSleepAllowed;
mIsTimedWakeupAllowed = isTimedWakeupAllowed;