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 
17 package com.android.car.hal;
18 
19 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
20 
21 import static java.lang.Integer.toHexString;
22 
23 import android.annotation.Nullable;
24 import android.car.diagnostic.CarDiagnosticEvent;
25 import android.car.diagnostic.CarDiagnosticManager;
26 import android.car.hardware.CarSensorManager;
27 import android.hardware.automotive.vehicle.V2_0.DiagnosticFloatSensorIndex;
28 import android.hardware.automotive.vehicle.V2_0.DiagnosticIntegerSensorIndex;
29 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
30 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
31 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
32 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyChangeMode;
33 import android.os.ServiceSpecificException;
34 import android.util.Slog;
35 import android.util.SparseArray;
36 
37 import com.android.car.CarLog;
38 import com.android.car.CarServiceUtils;
39 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
40 import com.android.car.vehiclehal.VehiclePropValueBuilder;
41 import com.android.internal.annotations.GuardedBy;
42 
43 import java.io.PrintWriter;
44 import java.util.BitSet;
45 import java.util.Collection;
46 import java.util.LinkedList;
47 import java.util.List;
48 import java.util.concurrent.CopyOnWriteArraySet;
49 
50 /**
51  * Diagnostic HAL service supporting gathering diagnostic info from VHAL and translating it into
52  * higher-level semantic information
53  */
54 public class DiagnosticHalService extends HalServiceBase {
55     static final int OBD2_SELECTIVE_FRAME_CLEAR = 1;
56     static final boolean DEBUG = false;
57 
58     private static final int[] SUPPORTED_PROPERTIES = new int[]{
59             VehicleProperty.OBD2_LIVE_FRAME,
60             VehicleProperty.OBD2_FREEZE_FRAME,
61             VehicleProperty.OBD2_FREEZE_FRAME_INFO,
62             VehicleProperty.OBD2_FREEZE_FRAME_CLEAR
63     };
64 
65     private final Object mLock = new Object();
66     private final VehicleHal mVehicleHal;
67 
68     @GuardedBy("mLock")
69     private boolean mIsReady = false;
70 
71     /**
72      * Nested class used as a place holder for vehicle HAL's diagnosed properties.
73      */
74     public static final class DiagnosticCapabilities {
75         private final CopyOnWriteArraySet<Integer> mProperties = new CopyOnWriteArraySet<>();
76 
setSupported(int propertyId)77         void setSupported(int propertyId) {
78             mProperties.add(propertyId);
79         }
80 
isSupported(int propertyId)81         boolean isSupported(int propertyId) {
82             return mProperties.contains(propertyId);
83         }
84 
isLiveFrameSupported()85         public boolean isLiveFrameSupported() {
86             return isSupported(VehicleProperty.OBD2_LIVE_FRAME);
87         }
88 
isFreezeFrameSupported()89         public boolean isFreezeFrameSupported() {
90             return isSupported(VehicleProperty.OBD2_FREEZE_FRAME);
91         }
92 
isFreezeFrameInfoSupported()93         public boolean isFreezeFrameInfoSupported() {
94             return isSupported(VehicleProperty.OBD2_FREEZE_FRAME_INFO);
95         }
96 
isFreezeFrameClearSupported()97         public boolean isFreezeFrameClearSupported() {
98             return isSupported(VehicleProperty.OBD2_FREEZE_FRAME_CLEAR);
99         }
100 
isSelectiveClearFreezeFramesSupported()101         public boolean isSelectiveClearFreezeFramesSupported() {
102             return isSupported(OBD2_SELECTIVE_FRAME_CLEAR);
103         }
104 
clear()105         void clear() {
106             mProperties.clear();
107         }
108     }
109 
110     @GuardedBy("mLock")
111     private final DiagnosticCapabilities mDiagnosticCapabilities = new DiagnosticCapabilities();
112 
113     @GuardedBy("mLock")
114     private DiagnosticListener mDiagnosticListener;
115 
116     @GuardedBy("mLock")
117     protected final SparseArray<VehiclePropConfig> mVehiclePropertyToConfig = new SparseArray<>();
118 
119     @GuardedBy("mLock")
120     protected final SparseArray<VehiclePropConfig> mSensorTypeToConfig = new SparseArray<>();
121 
DiagnosticHalService(VehicleHal hal)122     public DiagnosticHalService(VehicleHal hal) {
123         mVehicleHal = hal;
124 
125     }
126 
127     @Override
getAllSupportedProperties()128     public int[] getAllSupportedProperties() {
129         return SUPPORTED_PROPERTIES;
130     }
131 
132     @Override
takeProperties(Collection<VehiclePropConfig> properties)133     public void takeProperties(Collection<VehiclePropConfig> properties) {
134         if (DEBUG) {
135             Slog.d(CarLog.TAG_DIAGNOSTIC, "takeSupportedProperties");
136         }
137         for (VehiclePropConfig vp : properties) {
138             int sensorType = getTokenForProperty(vp);
139             if (sensorType == NOT_SUPPORTED_PROPERTY) {
140                 if (DEBUG) {
141                     Slog.d(CarLog.TAG_DIAGNOSTIC, new StringBuilder()
142                                 .append("0x")
143                                 .append(toHexString(vp.prop))
144                                 .append(" ignored")
145                                 .toString());
146                 }
147             } else {
148                 synchronized (mLock) {
149                     mSensorTypeToConfig.append(sensorType, vp);
150                 }
151             }
152         }
153     }
154 
155     /**
156      * Returns a unique token to be used to map this property to a higher-level sensor
157      * This token will be stored in {@link DiagnosticHalService#mSensorTypeToConfig} to allow
158      * callers to go from unique sensor identifiers to VehiclePropConfig objects
159      * @param propConfig
160      * @return SENSOR_TYPE_INVALID or a locally unique token
161      */
getTokenForProperty(VehiclePropConfig propConfig)162     protected int getTokenForProperty(VehiclePropConfig propConfig) {
163         switch (propConfig.prop) {
164             case VehicleProperty.OBD2_LIVE_FRAME:
165                 mDiagnosticCapabilities.setSupported(propConfig.prop);
166                 mVehiclePropertyToConfig.put(propConfig.prop, propConfig);
167                 Slog.i(CarLog.TAG_DIAGNOSTIC, "configArray for OBD2_LIVE_FRAME is "
168                         + propConfig.configArray);
169                 return CarDiagnosticManager.FRAME_TYPE_LIVE;
170             case VehicleProperty.OBD2_FREEZE_FRAME:
171                 mDiagnosticCapabilities.setSupported(propConfig.prop);
172                 mVehiclePropertyToConfig.put(propConfig.prop, propConfig);
173                 Slog.i(CarLog.TAG_DIAGNOSTIC, "configArray for OBD2_FREEZE_FRAME is "
174                         + propConfig.configArray);
175                 return CarDiagnosticManager.FRAME_TYPE_FREEZE;
176             case VehicleProperty.OBD2_FREEZE_FRAME_INFO:
177                 mDiagnosticCapabilities.setSupported(propConfig.prop);
178                 return propConfig.prop;
179             case VehicleProperty.OBD2_FREEZE_FRAME_CLEAR:
180                 mDiagnosticCapabilities.setSupported(propConfig.prop);
181                 Slog.i(CarLog.TAG_DIAGNOSTIC, "configArray for OBD2_FREEZE_FRAME_CLEAR is "
182                         + propConfig.configArray);
183                 if (propConfig.configArray.size() < 1) {
184                     Slog.e(CarLog.TAG_DIAGNOSTIC, String.format(
185                             "property 0x%x does not specify whether it supports selective "
186                                     + "clearing of freeze frames. assuming it does not.",
187                             propConfig.prop));
188                 } else {
189                     if (propConfig.configArray.get(0) == 1) {
190                         mDiagnosticCapabilities.setSupported(OBD2_SELECTIVE_FRAME_CLEAR);
191                     }
192                 }
193                 return propConfig.prop;
194             default:
195                 return NOT_SUPPORTED_PROPERTY;
196         }
197     }
198 
199     @Override
init()200     public void init() {
201         if (DEBUG) {
202             Slog.d(CarLog.TAG_DIAGNOSTIC, "init()");
203         }
204         synchronized (mLock) {
205             mIsReady = true;
206         }
207     }
208 
209     @Override
release()210     public void release() {
211         synchronized (mLock) {
212             mDiagnosticCapabilities.clear();
213             mIsReady = false;
214         }
215     }
216 
217     /**
218      * Returns the status of Diagnostic HAL.
219      * @return true if Diagnostic HAL is ready after init call.
220      */
isReady()221     public boolean isReady() {
222         return mIsReady;
223     }
224 
225     /**
226      * Returns an array of diagnostic property Ids implemented by this vehicle.
227      *
228      * @return Array of diagnostic property Ids implemented by this vehicle. Empty array if
229      * no property available.
230      */
getSupportedDiagnosticProperties()231     public int[] getSupportedDiagnosticProperties() {
232         int[] supportedDiagnosticProperties;
233         synchronized (mLock) {
234             supportedDiagnosticProperties = new int[mSensorTypeToConfig.size()];
235             for (int i = 0; i < supportedDiagnosticProperties.length; i++) {
236                 supportedDiagnosticProperties[i] = mSensorTypeToConfig.keyAt(i);
237             }
238         }
239         return supportedDiagnosticProperties;
240     }
241 
242     /**
243      * Start to request diagnostic information.
244      * @param sensorType
245      * @param rate
246      * @return true if request successfully. otherwise return false
247      */
requestDiagnosticStart(int sensorType, int rate)248     public boolean requestDiagnosticStart(int sensorType, int rate) {
249         VehiclePropConfig propConfig;
250         synchronized (mLock) {
251             propConfig = mSensorTypeToConfig.get(sensorType);
252         }
253         if (propConfig == null) {
254             Slog.e(CarLog.TAG_DIAGNOSTIC, new StringBuilder()
255                     .append("VehiclePropConfig not found, propertyId: 0x")
256                     .append(toHexString(propConfig.prop))
257                     .toString());
258             return false;
259         }
260         if (DEBUG) {
261             Slog.d(CarLog.TAG_DIAGNOSTIC, new StringBuilder()
262                     .append("requestDiagnosticStart, propertyId: 0x")
263                     .append(toHexString(propConfig.prop))
264                     .append(", rate: ")
265                     .append(rate)
266                     .toString());
267         }
268         mVehicleHal.subscribeProperty(this, propConfig.prop,
269                 fixSamplingRateForProperty(propConfig, rate));
270         return true;
271     }
272 
273     /**
274      * Stop requesting diagnostic information.
275      * @param sensorType
276      */
requestDiagnosticStop(int sensorType)277     public void requestDiagnosticStop(int sensorType) {
278         VehiclePropConfig propConfig;
279         synchronized (mLock) {
280             propConfig = mSensorTypeToConfig.get(sensorType);
281         }
282         if (propConfig == null) {
283             Slog.e(CarLog.TAG_DIAGNOSTIC, new StringBuilder()
284                     .append("VehiclePropConfig not found, propertyId: 0x")
285                     .append(toHexString(propConfig.prop))
286                     .toString());
287             return;
288         }
289         if (DEBUG) {
290             Slog.d(CarLog.TAG_DIAGNOSTIC, new StringBuilder()
291                     .append("requestDiagnosticStop, propertyId: 0x")
292                     .append(toHexString(propConfig.prop))
293                     .toString());
294         }
295         mVehicleHal.unsubscribeProperty(this, propConfig.prop);
296 
297     }
298 
299     /**
300      * Query current diagnostic value
301      * @param sensorType
302      * @return VehiclePropValue of the property
303      */
304     @Nullable
getCurrentDiagnosticValue(int sensorType)305     public VehiclePropValue getCurrentDiagnosticValue(int sensorType) {
306         VehiclePropConfig propConfig;
307         synchronized (mLock) {
308             propConfig = mSensorTypeToConfig.get(sensorType);
309         }
310         if (propConfig == null) {
311             Slog.e(CarLog.TAG_DIAGNOSTIC, new StringBuilder()
312                     .append("property not available 0x")
313                     .append(toHexString(propConfig.prop))
314                     .toString());
315             return null;
316         }
317         try {
318             return mVehicleHal.get(propConfig.prop);
319         } catch (ServiceSpecificException e) {
320             Slog.e(CarLog.TAG_DIAGNOSTIC,
321                     "property not ready 0x" + toHexString(propConfig.prop), e);
322             return null;
323         }
324 
325     }
326 
getPropConfig(int halPropId)327     private VehiclePropConfig getPropConfig(int halPropId) {
328         VehiclePropConfig config;
329         synchronized (mLock) {
330             config = mVehiclePropertyToConfig.get(halPropId, null);
331         }
332         return config;
333     }
334 
getPropConfigArray(int halPropId)335     private List<Integer> getPropConfigArray(int halPropId) {
336         VehiclePropConfig propConfig = getPropConfig(halPropId);
337         return propConfig.configArray;
338     }
339 
getNumIntegerSensors(int halPropId)340     private int getNumIntegerSensors(int halPropId) {
341         int count = DiagnosticIntegerSensorIndex.LAST_SYSTEM_INDEX + 1;
342         List<Integer> configArray = getPropConfigArray(halPropId);
343         if (configArray.size() < 2) {
344             Slog.e(CarLog.TAG_DIAGNOSTIC, String.format(
345                     "property 0x%x does not specify the number of vendor-specific properties."
346                             + "assuming 0.", halPropId));
347         } else {
348             count += configArray.get(0);
349         }
350         return count;
351     }
352 
getNumFloatSensors(int halPropId)353     private int getNumFloatSensors(int halPropId) {
354         int count = DiagnosticFloatSensorIndex.LAST_SYSTEM_INDEX + 1;
355         List<Integer> configArray = getPropConfigArray(halPropId);
356         if (configArray.size() < 2) {
357             Slog.e(CarLog.TAG_DIAGNOSTIC, String.format(
358                     "property 0x%x does not specify the number of vendor-specific properties."
359                             + "assuming 0.", halPropId));
360         } else {
361             count += configArray.get(1);
362         }
363         return count;
364     }
365 
createCarDiagnosticEvent(VehiclePropValue value)366     private CarDiagnosticEvent createCarDiagnosticEvent(VehiclePropValue value) {
367         if (value == null) return null;
368         final boolean isFreezeFrame = value.prop == VehicleProperty.OBD2_FREEZE_FRAME;
369         CarDiagnosticEvent.Builder builder =
370                 (isFreezeFrame
371                                 ? CarDiagnosticEvent.Builder.newFreezeFrameBuilder()
372                                 : CarDiagnosticEvent.Builder.newLiveFrameBuilder())
373                         .atTimestamp(value.timestamp);
374 
375         BitSet bitset = BitSet.valueOf(CarServiceUtils.toByteArray(value.value.bytes));
376 
377         int numIntegerProperties = getNumIntegerSensors(value.prop);
378         int numFloatProperties = getNumFloatSensors(value.prop);
379 
380         for (int i = 0; i < numIntegerProperties; ++i) {
381             if (bitset.get(i)) {
382                 builder.withIntValue(i, value.value.int32Values.get(i));
383             }
384         }
385 
386         for (int i = 0; i < numFloatProperties; ++i) {
387             if (bitset.get(numIntegerProperties + i)) {
388                 builder.withFloatValue(i, value.value.floatValues.get(i));
389             }
390         }
391 
392         builder.withDtc(value.value.stringValue);
393 
394         return builder.build();
395     }
396 
397     /** Listener for monitoring diagnostic event. */
398     public interface DiagnosticListener {
399         /**
400          * Diagnostic events are available.
401          *
402          * @param events
403          */
onDiagnosticEvents(List<CarDiagnosticEvent> events)404         void onDiagnosticEvents(List<CarDiagnosticEvent> events);
405     }
406 
407     // Should be used only inside handleHalEvents method.
408     private final LinkedList<CarDiagnosticEvent> mEventsToDispatch = new LinkedList<>();
409 
410     @Override
onHalEvents(List<VehiclePropValue> values)411     public void onHalEvents(List<VehiclePropValue> values) {
412         for (VehiclePropValue value : values) {
413             CarDiagnosticEvent event = createCarDiagnosticEvent(value);
414             if (event != null) {
415                 mEventsToDispatch.add(event);
416             }
417         }
418 
419         DiagnosticListener listener = null;
420         synchronized (mLock) {
421             listener = mDiagnosticListener;
422         }
423         if (listener != null) {
424             listener.onDiagnosticEvents(mEventsToDispatch);
425         }
426         mEventsToDispatch.clear();
427     }
428 
429     /**
430      * Set DiagnosticListener.
431      * @param listener
432      */
setDiagnosticListener(DiagnosticListener listener)433     public void setDiagnosticListener(DiagnosticListener listener) {
434         synchronized (mLock) {
435             mDiagnosticListener = listener;
436         }
437     }
438 
getDiagnosticListener()439     public DiagnosticListener getDiagnosticListener() {
440         return mDiagnosticListener;
441     }
442 
443     @Override
444     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(PrintWriter writer)445     public void dump(PrintWriter writer) {
446         writer.println("*Diagnostic HAL*");
447     }
448 
fixSamplingRateForProperty(VehiclePropConfig prop, int carSensorManagerRate)449     protected float fixSamplingRateForProperty(VehiclePropConfig prop, int carSensorManagerRate) {
450         switch (prop.changeMode) {
451             case VehiclePropertyChangeMode.ON_CHANGE:
452                 return 0;
453         }
454         float rate = 1.0f;
455         switch (carSensorManagerRate) {
456             case CarSensorManager.SENSOR_RATE_FASTEST:
457             case CarSensorManager.SENSOR_RATE_FAST:
458                 rate = 10f;
459                 break;
460             case CarSensorManager.SENSOR_RATE_UI:
461                 rate = 5f;
462                 break;
463             default: // fall back to default.
464                 break;
465         }
466         if (rate > prop.maxSampleRate) {
467             rate = prop.maxSampleRate;
468         }
469         if (rate < prop.minSampleRate) {
470             rate = prop.minSampleRate;
471         }
472         return rate;
473     }
474 
getDiagnosticCapabilities()475     public DiagnosticCapabilities getDiagnosticCapabilities() {
476         return mDiagnosticCapabilities;
477     }
478 
479     /**
480      * Returns the {@link CarDiagnosticEvent} for the current Vehicle HAL's live frame.
481      */
482     @Nullable
getCurrentLiveFrame()483     public CarDiagnosticEvent getCurrentLiveFrame() {
484         try {
485             VehiclePropValue value = mVehicleHal.get(VehicleProperty.OBD2_LIVE_FRAME);
486             return createCarDiagnosticEvent(value);
487         } catch (ServiceSpecificException e) {
488             Slog.e(CarLog.TAG_DIAGNOSTIC, "Failed to read OBD2_LIVE_FRAME.", e);
489             return null;
490         } catch (IllegalArgumentException e) {
491             Slog.e(CarLog.TAG_DIAGNOSTIC, "illegal argument trying to read OBD2_LIVE_FRAME", e);
492             return null;
493         }
494     }
495 
496     /**
497      * Returns all timestamps for the Vehicle HAL's Freeze Frame data.
498      */
499     @Nullable
getFreezeFrameTimestamps()500     public long[] getFreezeFrameTimestamps() {
501         try {
502             VehiclePropValue value = mVehicleHal.get(VehicleProperty.OBD2_FREEZE_FRAME_INFO);
503             long[] timestamps = new long[value.value.int64Values.size()];
504             for (int i = 0; i < timestamps.length; ++i) {
505                 timestamps[i] = value.value.int64Values.get(i);
506             }
507             return timestamps;
508         } catch (ServiceSpecificException e) {
509             Slog.e(CarLog.TAG_DIAGNOSTIC, "Failed to read OBD2_FREEZE_FRAME_INFO.", e);
510             return null;
511         } catch (IllegalArgumentException e) {
512             Slog.e(CarLog.TAG_DIAGNOSTIC,
513                     "illegal argument trying to read OBD2_FREEZE_FRAME_INFO", e);
514             return null;
515         }
516     }
517 
518     /**
519      * Returns the {@link CarDiagnosticEvent} representing a Freeze Frame data for the timestamp
520      * passed as parameter.
521      */
522     @Nullable
getFreezeFrame(long timestamp)523     public CarDiagnosticEvent getFreezeFrame(long timestamp) {
524         VehiclePropValueBuilder builder = VehiclePropValueBuilder.newBuilder(
525                 VehicleProperty.OBD2_FREEZE_FRAME);
526         builder.setInt64Value(timestamp);
527         try {
528             VehiclePropValue value = mVehicleHal.get(builder.build());
529             return createCarDiagnosticEvent(value);
530         } catch (ServiceSpecificException e) {
531             Slog.e(CarLog.TAG_DIAGNOSTIC, "Failed to read OBD2_FREEZE_FRAME.", e);
532             return null;
533         } catch (IllegalArgumentException e) {
534             Slog.e(CarLog.TAG_DIAGNOSTIC,
535                     "illegal argument trying to read OBD2_FREEZE_FRAME", e);
536             return null;
537         }
538     }
539 
540     /**
541      * Clears all Vehicle HAL's Freeze Frame data for the timestamps passed as parameter.
542      */
clearFreezeFrames(long... timestamps)543     public void clearFreezeFrames(long... timestamps) {
544         VehiclePropValueBuilder builder = VehiclePropValueBuilder.newBuilder(
545                 VehicleProperty.OBD2_FREEZE_FRAME_CLEAR);
546         builder.setInt64Value(timestamps);
547         try {
548             mVehicleHal.set(builder.build());
549         } catch (ServiceSpecificException e) {
550             Slog.e(CarLog.TAG_DIAGNOSTIC, "Failed to write OBD2_FREEZE_FRAME_CLEAR.", e);
551         } catch (IllegalArgumentException e) {
552             Slog.e(CarLog.TAG_DIAGNOSTIC,
553                     "illegal argument trying to write OBD2_FREEZE_FRAME_CLEAR", e);
554         }
555     }
556 }
557