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;