1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.car.diagnostic;
18
19import android.annotation.IntDef;
20import android.annotation.Nullable;
21import android.annotation.SystemApi;
22import android.os.Parcel;
23import android.os.Parcelable;
24import android.util.JsonWriter;
25import android.util.SparseArray;
26import android.util.SparseIntArray;
27import java.io.IOException;
28import java.lang.annotation.Retention;
29import java.lang.annotation.RetentionPolicy;
30import java.util.Objects;
31
32/**
33 * A CarDiagnosticEvent object corresponds to a single diagnostic event frame coming from the car.
34 *
35 * @hide
36 */
37@SystemApi
38public class CarDiagnosticEvent implements Parcelable {
39    /** Whether this frame represents a live or a freeze frame */
40    public final int frameType;
41
42    /**
43     * When this data was acquired in car or received from car. It is elapsed real-time of data
44     * reception from car in nanoseconds since system boot.
45     */
46    public final long timestamp;
47
48    /**
49     * Sparse array that contains the mapping of OBD2 diagnostic properties to their values for
50     * integer valued properties
51     */
52    private final SparseIntArray intValues;
53
54    /**
55     * Sparse array that contains the mapping of OBD2 diagnostic properties to their values for
56     * float valued properties
57     */
58    private final SparseArray<Float> floatValues;
59
60    /**
61     * Diagnostic Troubleshooting Code (DTC) that was detected and caused this frame to be stored
62     * (if a freeze frame). Always null for a live frame.
63     */
64    public final String dtc;
65
66    public CarDiagnosticEvent(Parcel in) {
67        frameType = in.readInt();
68        timestamp = in.readLong();
69        int len = in.readInt();
70        floatValues = new SparseArray<>(len);
71        for (int i = 0; i < len; ++i) {
72            int key = in.readInt();
73            float value = in.readFloat();
74            floatValues.put(key, value);
75        }
76        len = in.readInt();
77        intValues = new SparseIntArray(len);
78        for (int i = 0; i < len; ++i) {
79            int key = in.readInt();
80            int value = in.readInt();
81            intValues.put(key, value);
82        }
83        dtc = (String) in.readValue(String.class.getClassLoader());
84        // version 1 up to here
85    }
86
87    @Override
88    public int describeContents() {
89        return 0;
90    }
91
92    @Override
93    public void writeToParcel(Parcel dest, int flags) {
94        dest.writeInt(frameType);
95        dest.writeLong(timestamp);
96        dest.writeInt(floatValues.size());
97        for (int i = 0; i < floatValues.size(); ++i) {
98            int key = floatValues.keyAt(i);
99            dest.writeInt(key);
100            dest.writeFloat(floatValues.get(key));
101        }
102        dest.writeInt(intValues.size());
103        for (int i = 0; i < intValues.size(); ++i) {
104            int key = intValues.keyAt(i);
105            dest.writeInt(key);
106            dest.writeInt(intValues.get(key));
107        }
108        dest.writeValue(dtc);
109    }
110
111    /**
112     * Store the contents of this diagnostic event in a JsonWriter.
113     *
114     * The data is stored as a JSON object, with these fields:
115     *  type: either "live" or "freeze" depending on the type of frame;
116     *  timestamp: the timestamp at which this frame was generated;
117     *  intValues: an array of objects each of which has two elements:
118     *    id: the integer identifier of the sensor;
119     *    value: the integer value of the sensor;
120     *  floatValues: an array of objects each of which has two elements:
121     *    id: the integer identifier of the sensor;
122     *    value: the floating-point value of the sensor;
123     *  stringValue: the DTC for a freeze frame, omitted for a live frame
124     */
125    public void writeToJson(JsonWriter jsonWriter) throws IOException {
126        jsonWriter.beginObject();
127
128        jsonWriter.name("type");
129        switch (frameType) {
130            case CarDiagnosticManager.FRAME_TYPE_LIVE:
131                jsonWriter.value("live");
132                break;
133            case CarDiagnosticManager.FRAME_TYPE_FREEZE:
134                jsonWriter.value("freeze");
135                break;
136            default:
137                throw new IllegalStateException("unknown frameType " + frameType);
138        }
139
140        jsonWriter.name("timestamp").value(timestamp);
141
142        jsonWriter.name("intValues").beginArray();
143        for (int i = 0; i < intValues.size(); ++i) {
144            jsonWriter.beginObject();
145            jsonWriter.name("id").value(intValues.keyAt(i));
146            jsonWriter.name("value").value(intValues.valueAt(i));
147            jsonWriter.endObject();
148        }
149        jsonWriter.endArray();
150
151        jsonWriter.name("floatValues").beginArray();
152        for (int i = 0; i < floatValues.size(); ++i) {
153            jsonWriter.beginObject();
154            jsonWriter.name("id").value(floatValues.keyAt(i));
155            jsonWriter.name("value").value(floatValues.valueAt(i));
156            jsonWriter.endObject();
157        }
158        jsonWriter.endArray();
159
160        if (dtc != null) {
161            jsonWriter.name("stringValue").value(dtc);
162        }
163
164        jsonWriter.endObject();
165    }
166
167    public static final Parcelable.Creator<CarDiagnosticEvent> CREATOR =
168            new Parcelable.Creator<CarDiagnosticEvent>() {
169                public CarDiagnosticEvent createFromParcel(Parcel in) {
170                    return new CarDiagnosticEvent(in);
171                }
172
173                public CarDiagnosticEvent[] newArray(int size) {
174                    return new CarDiagnosticEvent[size];
175                }
176            };
177
178    private CarDiagnosticEvent(
179            int frameType,
180            long timestamp,
181            SparseArray<Float> floatValues,
182            SparseIntArray intValues,
183            String dtc) {
184        this.frameType = frameType;
185        this.timestamp = timestamp;
186        this.floatValues = floatValues;
187        this.intValues = intValues;
188        this.dtc = dtc;
189    }
190
191    /**
192     * This class can be used to incrementally construct a CarDiagnosticEvent.
193     * CarDiagnosticEvent instances are immutable once built.
194     */
195    public static class Builder {
196        private int mType = CarDiagnosticManager.FRAME_TYPE_LIVE;
197        private long mTimestamp = 0;
198        private SparseArray<Float> mFloatValues = new SparseArray<>();
199        private SparseIntArray mIntValues = new SparseIntArray();
200        private String mDtc = null;
201
202        private Builder(int type) {
203            mType = type;
204        }
205
206        /** Returns a new Builder for a live frame */
207        public static Builder newLiveFrameBuilder() {
208            return new Builder(CarDiagnosticManager.FRAME_TYPE_LIVE);
209        }
210
211        /** Returns a new Builder for a freeze frame */
212        public static Builder newFreezeFrameBuilder() {
213            return new Builder(CarDiagnosticManager.FRAME_TYPE_FREEZE);
214        }
215
216        /** Sets the timestamp for the frame being built */
217        public Builder atTimestamp(long timestamp) {
218            mTimestamp = timestamp;
219            return this;
220        }
221
222        /** Adds an integer-valued sensor to the frame being built */
223        public Builder withIntValue(int key, int value) {
224            mIntValues.put(key, value);
225            return this;
226        }
227
228        /** Adds a float-valued sensor to the frame being built */
229        public Builder withFloatValue(int key, float value) {
230            mFloatValues.put(key, value);
231            return this;
232        }
233
234        /** Sets the DTC for the frame being built */
235        public Builder withDtc(String dtc) {
236            mDtc = dtc;
237            return this;
238        }
239
240        /** Builds and returns the CarDiagnosticEvent */
241        public CarDiagnosticEvent build() {
242            return new CarDiagnosticEvent(mType, mTimestamp, mFloatValues, mIntValues, mDtc);
243        }
244    }
245
246    /**
247     * Returns a copy of this CarDiagnosticEvent with all vendor-specific sensors removed.
248     *
249     * @hide
250     */
251    public CarDiagnosticEvent withVendorSensorsRemoved() {
252        SparseIntArray newIntValues = intValues.clone();
253        SparseArray<Float> newFloatValues = floatValues.clone();
254        for (int i = 0; i < intValues.size(); ++i) {
255            int key = intValues.keyAt(i);
256            if (key >= android.car.diagnostic.IntegerSensorIndex.LAST_SYSTEM) {
257                newIntValues.delete(key);
258            }
259        }
260        for (int i = 0; i < floatValues.size(); ++i) {
261            int key = floatValues.keyAt(i);
262            if (key >= android.car.diagnostic.FloatSensorIndex.LAST_SYSTEM) {
263                newFloatValues.delete(key);
264            }
265        }
266        return new CarDiagnosticEvent(frameType, timestamp, newFloatValues, newIntValues, dtc);
267    }
268
269    /** Returns true if this object is a live frame, false otherwise */
270    public boolean isLiveFrame() {
271        return CarDiagnosticManager.FRAME_TYPE_LIVE == frameType;
272    }
273
274    /** Returns true if this object is a freeze frame, false otherwise */
275    public boolean isFreezeFrame() {
276        return CarDiagnosticManager.FRAME_TYPE_FREEZE == frameType;
277    }
278
279    /** @hide */
280    public boolean isEmptyFrame() {
281        boolean empty = (0 == intValues.size());
282        empty &= (0 == floatValues.size());
283        if (isFreezeFrame()) empty &= dtc.isEmpty();
284        return empty;
285    }
286
287    /** @hide */
288    public CarDiagnosticEvent checkLiveFrame() {
289        if (!isLiveFrame()) throw new IllegalStateException("frame is not a live frame");
290        return this;
291    }
292
293    /** @hide */
294    public CarDiagnosticEvent checkFreezeFrame() {
295        if (!isFreezeFrame()) throw new IllegalStateException("frame is not a freeze frame");
296        return this;
297    }
298
299    /** @hide */
300    public boolean isEarlierThan(CarDiagnosticEvent otherEvent) {
301        otherEvent = Objects.requireNonNull(otherEvent);
302        return (timestamp < otherEvent.timestamp);
303    }
304
305    @Override
306    public boolean equals(Object otherObject) {
307        if (this == otherObject) {
308            return true;
309        }
310        if (null == otherObject) {
311            return false;
312        }
313        if (!(otherObject instanceof CarDiagnosticEvent)) {
314            return false;
315        }
316        CarDiagnosticEvent otherEvent = (CarDiagnosticEvent)otherObject;
317        if (otherEvent.frameType != frameType)
318            return false;
319        if (otherEvent.timestamp != timestamp)
320            return false;
321        if (otherEvent.intValues.size() != intValues.size())
322            return false;
323        if (otherEvent.floatValues.size() != floatValues.size())
324            return false;
325        if (!Objects.equals(dtc, otherEvent.dtc))
326            return false;
327        for (int i = 0; i < intValues.size(); ++i) {
328            int key = intValues.keyAt(i);
329            int otherKey = otherEvent.intValues.keyAt(i);
330            if (key != otherKey) {
331                return false;
332            }
333            int value = intValues.valueAt(i);
334            int otherValue = otherEvent.intValues.valueAt(i);
335            if (value != otherValue) {
336                return false;
337            }
338        }
339        for (int i = 0; i < floatValues.size(); ++i) {
340            int key = floatValues.keyAt(i);
341            int otherKey = otherEvent.floatValues.keyAt(i);
342            if (key != otherKey) {
343                return false;
344            }
345            float value = floatValues.valueAt(i);
346            float otherValue = otherEvent.floatValues.valueAt(i);
347            if (value != otherValue) {
348                return false;
349            }
350        }
351        return true;
352    }
353
354    @Override
355    public int hashCode() {
356        Integer[] intKeys = new Integer[intValues.size()];
357        Integer[] floatKeys = new Integer[floatValues.size()];
358        Integer[] intValues = new Integer[intKeys.length];
359        Float[] floatValues = new Float[floatKeys.length];
360        for (int i = 0; i < intKeys.length; ++i) {
361            intKeys[i] = this.intValues.keyAt(i);
362            intValues[i] = this.intValues.valueAt(i);
363        }
364        for (int i = 0; i < floatKeys.length; ++i) {
365            floatKeys[i] = this.floatValues.keyAt(i);
366            floatValues[i] = this.floatValues.valueAt(i);
367        }
368        int intKeysHash = Objects.hash((Object[])intKeys);
369        int intValuesHash = Objects.hash((Object[])intValues);
370        int floatKeysHash = Objects.hash((Object[])floatKeys);
371        int floatValuesHash = Objects.hash((Object[])floatValues);
372        return Objects.hash(frameType,
373                timestamp,
374                dtc,
375                intKeysHash,
376                intValuesHash,
377                floatKeysHash,
378                floatValuesHash);
379    }
380
381    @Override
382    public String toString() {
383        return String.format(
384                "%s diagnostic frame {\n"
385                        + "\ttimestamp: %d, "
386                        + "DTC: %s\n"
387                        + "\tintValues: %s\n"
388                        + "\tfloatValues: %s\n}",
389                isLiveFrame() ? "live" : "freeze",
390                timestamp,
391                dtc,
392                intValues.toString(),
393                floatValues.toString());
394    }
395
396    /**
397     * Returns the value of the given integer sensor, if present in this frame.
398     * Returns defaultValue otherwise.
399     */
400    public int getSystemIntegerSensor(
401            @android.car.diagnostic.IntegerSensorIndex.SensorIndex int sensor, int defaultValue) {
402        return intValues.get(sensor, defaultValue);
403    }
404
405    /**
406     * Returns the value of the given float sensor, if present in this frame.
407     * Returns defaultValue otherwise.
408     */
409    public float getSystemFloatSensor(
410            @android.car.diagnostic.FloatSensorIndex.SensorIndex int sensor, float defaultValue) {
411        return floatValues.get(sensor, defaultValue);
412    }
413
414    /**
415     * Returns the value of the given integer sensor, if present in this frame.
416     * Returns defaultValue otherwise.
417     */
418    public int getVendorIntegerSensor(int sensor, int defaultValue) {
419        return intValues.get(sensor, defaultValue);
420    }
421
422    /**
423     * Returns the value of the given float sensor, if present in this frame.
424     * Returns defaultValue otherwise.
425     */
426    public float getVendorFloatSensor(int sensor, float defaultValue) {
427        return floatValues.get(sensor, defaultValue);
428    }
429
430    /**
431     * Returns the value of the given integer sensor, if present in this frame.
432     * Returns null otherwise.
433     */
434    public @Nullable Integer getSystemIntegerSensor(
435            @android.car.diagnostic.IntegerSensorIndex.SensorIndex int sensor) {
436        int index = intValues.indexOfKey(sensor);
437        if (index < 0) return null;
438        return intValues.valueAt(index);
439    }
440
441    /**
442     * Returns the value of the given float sensor, if present in this frame.
443     * Returns null otherwise.
444     */
445    public @Nullable Float getSystemFloatSensor(
446            @android.car.diagnostic.FloatSensorIndex.SensorIndex int sensor) {
447        int index = floatValues.indexOfKey(sensor);
448        if (index < 0) return null;
449        return floatValues.valueAt(index);
450    }
451
452    /**
453     * Returns the value of the given integer sensor, if present in this frame.
454     * Returns null otherwise.
455     */
456    public @Nullable Integer getVendorIntegerSensor(int sensor) {
457        int index = intValues.indexOfKey(sensor);
458        if (index < 0) return null;
459        return intValues.valueAt(index);
460    }
461
462    /**
463     * Returns the value of the given float sensor, if present in this frame.
464     * Returns null otherwise.
465     */
466    public @Nullable Float getVendorFloatSensor(int sensor) {
467        int index = floatValues.indexOfKey(sensor);
468        if (index < 0) return null;
469        return floatValues.valueAt(index);
470    }
471
472    /**
473     * Represents possible states of the fuel system; see {@link
474     * android.car.diagnostic.IntegerSensorIndex#FUEL_SYSTEM_STATUS}
475     */
476    public static final class FuelSystemStatus {
477        private FuelSystemStatus() {}
478
479        public static final int OPEN_INSUFFICIENT_ENGINE_TEMPERATURE = 1;
480        public static final int CLOSED_LOOP = 2;
481        public static final int OPEN_ENGINE_LOAD_OR_DECELERATION = 4;
482        public static final int OPEN_SYSTEM_FAILURE = 8;
483        public static final int CLOSED_LOOP_BUT_FEEDBACK_FAULT = 16;
484
485        @Retention(RetentionPolicy.SOURCE)
486        @IntDef({
487            OPEN_INSUFFICIENT_ENGINE_TEMPERATURE,
488            CLOSED_LOOP,
489            OPEN_ENGINE_LOAD_OR_DECELERATION,
490            OPEN_SYSTEM_FAILURE,
491            CLOSED_LOOP_BUT_FEEDBACK_FAULT
492        })
493        /** @hide */
494        public @interface Status {}
495    }
496
497    /**
498     * Represents possible states of the secondary air system; see {@link
499     * android.car.diagnostic.IntegerSensorIndex#COMMANDED_SECONDARY_AIR_STATUS}
500     */
501    public static final class SecondaryAirStatus {
502        private SecondaryAirStatus() {}
503
504        public static final int UPSTREAM = 1;
505        public static final int DOWNSTREAM_OF_CATALYCIC_CONVERTER = 2;
506        public static final int FROM_OUTSIDE_OR_OFF = 4;
507        public static final int PUMP_ON_FOR_DIAGNOSTICS = 8;
508
509        @Retention(RetentionPolicy.SOURCE)
510        @IntDef({
511            UPSTREAM,
512            DOWNSTREAM_OF_CATALYCIC_CONVERTER,
513            FROM_OUTSIDE_OR_OFF,
514            PUMP_ON_FOR_DIAGNOSTICS
515        })
516        /** @hide */
517        public @interface Status {}
518    }
519
520    /**
521     * Represents possible types of fuel; see {@link
522     * android.car.diagnostic.IntegerSensorIndex#FUEL_TYPE}
523     */
524    public static final class FuelType {
525        private FuelType() {}
526
527        public static final int NOT_AVAILABLE = 0;
528        public static final int GASOLINE = 1;
529        public static final int METHANOL = 2;
530        public static final int ETHANOL = 3;
531        public static final int DIESEL = 4;
532        public static final int LPG = 5;
533        public static final int CNG = 6;
534        public static final int PROPANE = 7;
535        public static final int ELECTRIC = 8;
536        public static final int BIFUEL_RUNNING_GASOLINE = 9;
537        public static final int BIFUEL_RUNNING_METHANOL = 10;
538        public static final int BIFUEL_RUNNING_ETHANOL = 11;
539        public static final int BIFUEL_RUNNING_LPG = 12;
540        public static final int BIFUEL_RUNNING_CNG = 13;
541        public static final int BIFUEL_RUNNING_PROPANE = 14;
542        public static final int BIFUEL_RUNNING_ELECTRIC = 15;
543        public static final int BIFUEL_RUNNING_ELECTRIC_AND_COMBUSTION = 16;
544        public static final int HYBRID_GASOLINE = 17;
545        public static final int HYBRID_ETHANOL = 18;
546        public static final int HYBRID_DIESEL = 19;
547        public static final int HYBRID_ELECTRIC = 20;
548        public static final int HYBRID_RUNNING_ELECTRIC_AND_COMBUSTION = 21;
549        public static final int HYBRID_REGENERATIVE = 22;
550        public static final int BIFUEL_RUNNING_DIESEL = 23;
551
552        @Retention(RetentionPolicy.SOURCE)
553        @IntDef({
554            NOT_AVAILABLE,
555            GASOLINE,
556            METHANOL,
557            ETHANOL,
558            DIESEL,
559            LPG,
560            CNG,
561            PROPANE,
562            ELECTRIC,
563            BIFUEL_RUNNING_GASOLINE,
564            BIFUEL_RUNNING_METHANOL,
565            BIFUEL_RUNNING_ETHANOL,
566            BIFUEL_RUNNING_LPG,
567            BIFUEL_RUNNING_CNG,
568            BIFUEL_RUNNING_PROPANE,
569            BIFUEL_RUNNING_ELECTRIC,
570            BIFUEL_RUNNING_ELECTRIC_AND_COMBUSTION,
571            HYBRID_GASOLINE,
572            HYBRID_ETHANOL,
573            HYBRID_DIESEL,
574            HYBRID_ELECTRIC,
575            HYBRID_RUNNING_ELECTRIC_AND_COMBUSTION,
576            HYBRID_REGENERATIVE,
577            BIFUEL_RUNNING_DIESEL
578        })
579        /** @hide */
580        public @interface Type {}
581    }
582
583    /**
584     * Represents the state of an ignition monitor on a vehicle.
585     */
586    public static final class IgnitionMonitor {
587        public final boolean available;
588        public final boolean incomplete;
589
590        IgnitionMonitor(boolean available, boolean incomplete) {
591            this.available = available;
592            this.incomplete = incomplete;
593        }
594
595        /** @hide */
596        public static final class Decoder {
597            private final int mAvailableBitmask;
598            private final int mIncompleteBitmask;
599
600            Decoder(int availableBitmask, int incompleteBitmask) {
601                mAvailableBitmask = availableBitmask;
602                mIncompleteBitmask = incompleteBitmask;
603            }
604
605            public IgnitionMonitor fromValue(int value) {
606                boolean available = (0 != (value & mAvailableBitmask));
607                boolean incomplete = (0 != (value & mIncompleteBitmask));
608
609                return new IgnitionMonitor(available, incomplete);
610            }
611        }
612    }
613
614    /**
615     * Contains information about ignition monitors common to all vehicle types.
616     */
617    public static class CommonIgnitionMonitors {
618        public final IgnitionMonitor components;
619        public final IgnitionMonitor fuelSystem;
620        public final IgnitionMonitor misfire;
621
622        /** @hide */
623        public static final int COMPONENTS_AVAILABLE = 0x1 << 0;
624        /** @hide */
625        public static final int COMPONENTS_INCOMPLETE = 0x1 << 1;
626
627        /** @hide */
628        public static final int FUEL_SYSTEM_AVAILABLE = 0x1 << 2;
629        /** @hide */
630        public static final int FUEL_SYSTEM_INCOMPLETE = 0x1 << 3;
631
632        /** @hide */
633        public static final int MISFIRE_AVAILABLE = 0x1 << 4;
634        /** @hide */
635        public static final int MISFIRE_INCOMPLETE = 0x1 << 5;
636
637        static final IgnitionMonitor.Decoder COMPONENTS_DECODER =
638                new IgnitionMonitor.Decoder(COMPONENTS_AVAILABLE, COMPONENTS_INCOMPLETE);
639
640        static final IgnitionMonitor.Decoder FUEL_SYSTEM_DECODER =
641                new IgnitionMonitor.Decoder(FUEL_SYSTEM_AVAILABLE, FUEL_SYSTEM_INCOMPLETE);
642
643        static final IgnitionMonitor.Decoder MISFIRE_DECODER =
644                new IgnitionMonitor.Decoder(MISFIRE_AVAILABLE, MISFIRE_INCOMPLETE);
645
646        CommonIgnitionMonitors(int bitmask) {
647            components = COMPONENTS_DECODER.fromValue(bitmask);
648            fuelSystem = FUEL_SYSTEM_DECODER.fromValue(bitmask);
649            misfire = MISFIRE_DECODER.fromValue(bitmask);
650        }
651
652        /**
653         * Returns data about ignition monitors specific to spark vehicles, if this
654         * object represents ignition monitors for a spark vehicle.
655         * Returns null otherwise.
656         */
657        public @Nullable SparkIgnitionMonitors asSparkIgnitionMonitors() {
658            if (this instanceof SparkIgnitionMonitors) return (SparkIgnitionMonitors) this;
659            return null;
660        }
661
662        /**
663         * Returns data about ignition monitors specific to compression vehicles, if this
664         * object represents ignition monitors for a compression vehicle.
665         * Returns null otherwise.
666         */
667        public @Nullable CompressionIgnitionMonitors asCompressionIgnitionMonitors() {
668            if (this instanceof CompressionIgnitionMonitors)
669                return (CompressionIgnitionMonitors) this;
670            return null;
671        }
672    }
673
674    /**
675     * Contains information about ignition monitors specific to spark vehicles.
676     */
677    public static final class SparkIgnitionMonitors extends CommonIgnitionMonitors {
678        public final IgnitionMonitor EGR;
679        public final IgnitionMonitor oxygenSensorHeater;
680        public final IgnitionMonitor oxygenSensor;
681        public final IgnitionMonitor ACRefrigerant;
682        public final IgnitionMonitor secondaryAirSystem;
683        public final IgnitionMonitor evaporativeSystem;
684        public final IgnitionMonitor heatedCatalyst;
685        public final IgnitionMonitor catalyst;
686
687        /** @hide */
688        public static final int EGR_AVAILABLE = 0x1 << 6;
689        /** @hide */
690        public static final int EGR_INCOMPLETE = 0x1 << 7;
691
692        /** @hide */
693        public static final int OXYGEN_SENSOR_HEATER_AVAILABLE = 0x1 << 8;
694        /** @hide */
695        public static final int OXYGEN_SENSOR_HEATER_INCOMPLETE = 0x1 << 9;
696
697        /** @hide */
698        public static final int OXYGEN_SENSOR_AVAILABLE = 0x1 << 10;
699        /** @hide */
700        public static final int OXYGEN_SENSOR_INCOMPLETE = 0x1 << 11;
701
702        /** @hide */
703        public static final int AC_REFRIGERANT_AVAILABLE = 0x1 << 12;
704        /** @hide */
705        public static final int AC_REFRIGERANT_INCOMPLETE = 0x1 << 13;
706
707        /** @hide */
708        public static final int SECONDARY_AIR_SYSTEM_AVAILABLE = 0x1 << 14;
709        /** @hide */
710        public static final int SECONDARY_AIR_SYSTEM_INCOMPLETE = 0x1 << 15;
711
712        /** @hide */
713        public static final int EVAPORATIVE_SYSTEM_AVAILABLE = 0x1 << 16;
714        /** @hide */
715        public static final int EVAPORATIVE_SYSTEM_INCOMPLETE = 0x1 << 17;
716
717        /** @hide */
718        public static final int HEATED_CATALYST_AVAILABLE = 0x1 << 18;
719        /** @hide */
720        public static final int HEATED_CATALYST_INCOMPLETE = 0x1 << 19;
721
722        /** @hide */
723        public static final int CATALYST_AVAILABLE = 0x1 << 20;
724        /** @hide */
725        public static final int CATALYST_INCOMPLETE = 0x1 << 21;
726
727        static final IgnitionMonitor.Decoder EGR_DECODER =
728                new IgnitionMonitor.Decoder(EGR_AVAILABLE, EGR_INCOMPLETE);
729
730        static final IgnitionMonitor.Decoder OXYGEN_SENSOR_HEATER_DECODER =
731                new IgnitionMonitor.Decoder(OXYGEN_SENSOR_HEATER_AVAILABLE,
732                        OXYGEN_SENSOR_HEATER_INCOMPLETE);
733
734        static final IgnitionMonitor.Decoder OXYGEN_SENSOR_DECODER =
735                new IgnitionMonitor.Decoder(OXYGEN_SENSOR_AVAILABLE, OXYGEN_SENSOR_INCOMPLETE);
736
737        static final IgnitionMonitor.Decoder AC_REFRIGERANT_DECODER =
738                new IgnitionMonitor.Decoder(AC_REFRIGERANT_AVAILABLE,
739                        AC_REFRIGERANT_INCOMPLETE);
740
741        static final IgnitionMonitor.Decoder SECONDARY_AIR_SYSTEM_DECODER =
742                new IgnitionMonitor.Decoder(SECONDARY_AIR_SYSTEM_AVAILABLE,
743                        SECONDARY_AIR_SYSTEM_INCOMPLETE);
744
745        static final IgnitionMonitor.Decoder EVAPORATIVE_SYSTEM_DECODER =
746                new IgnitionMonitor.Decoder(EVAPORATIVE_SYSTEM_AVAILABLE,
747                        EVAPORATIVE_SYSTEM_INCOMPLETE);
748
749        static final IgnitionMonitor.Decoder HEATED_CATALYST_DECODER =
750                new IgnitionMonitor.Decoder(HEATED_CATALYST_AVAILABLE,
751                        HEATED_CATALYST_INCOMPLETE);
752
753        static final IgnitionMonitor.Decoder CATALYST_DECODER =
754                new IgnitionMonitor.Decoder(CATALYST_AVAILABLE, CATALYST_INCOMPLETE);
755
756        SparkIgnitionMonitors(int bitmask) {
757            super(bitmask);
758            EGR = EGR_DECODER.fromValue(bitmask);
759            oxygenSensorHeater = OXYGEN_SENSOR_HEATER_DECODER.fromValue(bitmask);
760            oxygenSensor = OXYGEN_SENSOR_DECODER.fromValue(bitmask);
761            ACRefrigerant = AC_REFRIGERANT_DECODER.fromValue(bitmask);
762            secondaryAirSystem = SECONDARY_AIR_SYSTEM_DECODER.fromValue(bitmask);
763            evaporativeSystem = EVAPORATIVE_SYSTEM_DECODER.fromValue(bitmask);
764            heatedCatalyst = HEATED_CATALYST_DECODER.fromValue(bitmask);
765            catalyst = CATALYST_DECODER.fromValue(bitmask);
766        }
767    }
768
769    /**
770     * Contains information about ignition monitors specific to compression vehicles.
771     */
772    public static final class CompressionIgnitionMonitors extends CommonIgnitionMonitors {
773        public final IgnitionMonitor EGROrVVT;
774        public final IgnitionMonitor PMFilter;
775        public final IgnitionMonitor exhaustGasSensor;
776        public final IgnitionMonitor boostPressure;
777        public final IgnitionMonitor NOxSCR;
778        public final IgnitionMonitor NMHCCatalyst;
779
780        /** @hide */
781        public static final int EGR_OR_VVT_AVAILABLE = 0x1 << 6;
782        /** @hide */
783        public static final int EGR_OR_VVT_INCOMPLETE = 0x1 << 7;
784
785        /** @hide */
786        public static final int PM_FILTER_AVAILABLE = 0x1 << 8;
787        /** @hide */
788        public static final int PM_FILTER_INCOMPLETE = 0x1 << 9;
789
790        /** @hide */
791        public static final int EXHAUST_GAS_SENSOR_AVAILABLE = 0x1 << 10;
792        /** @hide */
793        public static final int EXHAUST_GAS_SENSOR_INCOMPLETE = 0x1 << 11;
794
795        /** @hide */
796        public static final int BOOST_PRESSURE_AVAILABLE = 0x1 << 12;
797        /** @hide */
798        public static final int BOOST_PRESSURE_INCOMPLETE = 0x1 << 13;
799
800        /** @hide */
801        public static final int NOx_SCR_AVAILABLE = 0x1 << 14;
802        /** @hide */
803        public static final int NOx_SCR_INCOMPLETE = 0x1 << 15;
804
805        /** @hide */
806        public static final int NMHC_CATALYST_AVAILABLE = 0x1 << 16;
807        /** @hide */
808        public static final int NMHC_CATALYST_INCOMPLETE = 0x1 << 17;
809
810        static final IgnitionMonitor.Decoder EGR_OR_VVT_DECODER =
811                new IgnitionMonitor.Decoder(EGR_OR_VVT_AVAILABLE, EGR_OR_VVT_INCOMPLETE);
812
813        static final IgnitionMonitor.Decoder PM_FILTER_DECODER =
814                new IgnitionMonitor.Decoder(PM_FILTER_AVAILABLE, PM_FILTER_INCOMPLETE);
815
816        static final IgnitionMonitor.Decoder EXHAUST_GAS_SENSOR_DECODER =
817                new IgnitionMonitor.Decoder(EXHAUST_GAS_SENSOR_AVAILABLE,
818                        EXHAUST_GAS_SENSOR_INCOMPLETE);
819
820        static final IgnitionMonitor.Decoder BOOST_PRESSURE_DECODER =
821                new IgnitionMonitor.Decoder(BOOST_PRESSURE_AVAILABLE,
822                        BOOST_PRESSURE_INCOMPLETE);
823
824        static final IgnitionMonitor.Decoder NOx_SCR_DECODER =
825                new IgnitionMonitor.Decoder(NOx_SCR_AVAILABLE, NOx_SCR_INCOMPLETE);
826
827        static final IgnitionMonitor.Decoder NMHC_CATALYST_DECODER =
828                new IgnitionMonitor.Decoder(NMHC_CATALYST_AVAILABLE, NMHC_CATALYST_INCOMPLETE);
829
830        CompressionIgnitionMonitors(int bitmask) {
831            super(bitmask);
832            EGROrVVT = EGR_OR_VVT_DECODER.fromValue(bitmask);
833            PMFilter = PM_FILTER_DECODER.fromValue(bitmask);
834            exhaustGasSensor = EXHAUST_GAS_SENSOR_DECODER.fromValue(bitmask);
835            boostPressure = BOOST_PRESSURE_DECODER.fromValue(bitmask);
836            NOxSCR = NOx_SCR_DECODER.fromValue(bitmask);
837            NMHCCatalyst = NMHC_CATALYST_DECODER.fromValue(bitmask);
838        }
839    }
840
841    /**
842     * Returns the state of the fuel system, if present in this frame.
843     * Returns null otherwise.
844     */
845    public @Nullable @FuelSystemStatus.Status Integer getFuelSystemStatus() {
846        return getSystemIntegerSensor(android.car.diagnostic.IntegerSensorIndex.FUEL_SYSTEM_STATUS);
847    }
848
849    /**
850     * Returns the state of the secondary air system, if present in this frame.
851     * Returns null otherwise.
852     */
853    public @Nullable @SecondaryAirStatus.Status Integer getSecondaryAirStatus() {
854        return getSystemIntegerSensor(android.car.diagnostic.IntegerSensorIndex.COMMANDED_SECONDARY_AIR_STATUS);
855    }
856
857    /**
858     * Returns data about the ignition monitors, if present in this frame.
859     * Returns null otherwise.
860     */
861    public @Nullable CommonIgnitionMonitors getIgnitionMonitors() {
862        Integer ignitionMonitorsType =
863                getSystemIntegerSensor(android.car.diagnostic.IntegerSensorIndex.IGNITION_MONITORS_SUPPORTED);
864        Integer ignitionMonitorsBitmask =
865                getSystemIntegerSensor(android.car.diagnostic.IntegerSensorIndex.IGNITION_SPECIFIC_MONITORS);
866        if (null == ignitionMonitorsType) return null;
867        if (null == ignitionMonitorsBitmask) return null;
868        switch (ignitionMonitorsType) {
869            case 0:
870                return new SparkIgnitionMonitors(ignitionMonitorsBitmask);
871            case 1:
872                return new CompressionIgnitionMonitors(ignitionMonitorsBitmask);
873            default:
874                return null;
875        }
876    }
877
878    /**
879     * Returns the fuel type, if present in this frame.
880     * Returns null otherwise.
881     */
882    public @Nullable @FuelType.Type Integer getFuelType() {
883        return getSystemIntegerSensor(android.car.diagnostic.IntegerSensorIndex.FUEL_TYPE);
884    }
885}
886