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.builtin.util.Slogf;
25 import android.car.diagnostic.CarDiagnosticEvent;
26 import android.car.diagnostic.CarDiagnosticManager;
27 import android.car.hardware.CarSensorManager;
28 import android.hardware.automotive.vehicle.DiagnosticFloatSensorIndex;
29 import android.hardware.automotive.vehicle.DiagnosticIntegerSensorIndex;
30 import android.hardware.automotive.vehicle.VehicleProperty;
31 import android.hardware.automotive.vehicle.VehiclePropertyChangeMode;
32 import android.os.ServiceSpecificException;
33 import android.util.SparseArray;
34 
35 import com.android.car.CarLog;
36 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
37 import com.android.internal.annotations.GuardedBy;
38 
39 import java.io.PrintWriter;
40 import java.lang.reflect.Field;
41 import java.lang.reflect.Modifier;
42 import java.util.Arrays;
43 import java.util.BitSet;
44 import java.util.Collection;
45 import java.util.LinkedList;
46 import java.util.List;
47 import java.util.concurrent.CopyOnWriteArraySet;
48 
49 /**
50  * Diagnostic HAL service supporting gathering diagnostic info from VHAL and translating it into
51  * higher-level semantic information
52  */
53 public class DiagnosticHalService extends HalServiceBase {
54     static final int OBD2_SELECTIVE_FRAME_CLEAR = 1;
55     static final boolean DEBUG = false;
56 
57     private static final int[] SUPPORTED_PROPERTIES = new int[]{
58             VehicleProperty.OBD2_LIVE_FRAME,
59             VehicleProperty.OBD2_FREEZE_FRAME,
60             VehicleProperty.OBD2_FREEZE_FRAME_INFO,
61             VehicleProperty.OBD2_FREEZE_FRAME_CLEAR
62     };
63 
64     private final Object mLock = new Object();
65     private final VehicleHal mVehicleHal;
66     private final HalPropValueBuilder mPropValueBuilder;
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<HalPropConfig> mVehiclePropertyToConfig = new SparseArray<>();
118 
119     @GuardedBy("mLock")
120     protected final SparseArray<HalPropConfig> mSensorTypeToConfig = new SparseArray<>();
121 
DiagnosticHalService(VehicleHal hal)122     public DiagnosticHalService(VehicleHal hal) {
123         mVehicleHal = hal;
124         mPropValueBuilder = mVehicleHal.getHalPropValueBuilder();
125     }
126 
127     @Override
getAllSupportedProperties()128     public int[] getAllSupportedProperties() {
129         return SUPPORTED_PROPERTIES;
130     }
131 
132     @Override
takeProperties(Collection<HalPropConfig> properties)133     public void takeProperties(Collection<HalPropConfig> properties) {
134         if (DEBUG) {
135             Slogf.d(CarLog.TAG_DIAGNOSTIC, "takeSupportedProperties");
136         }
137         for (HalPropConfig vp : properties) {
138             int sensorType = getTokenForProperty(vp);
139             if (sensorType == NOT_SUPPORTED_PROPERTY) {
140                 if (DEBUG) {
141                     Slogf.d(CarLog.TAG_DIAGNOSTIC, new StringBuilder()
142                                 .append("0x")
143                                 .append(toHexString(vp.getPropId()))
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      *
158      * This token will be stored in {@link DiagnosticHalService#mSensorTypeToConfig} to allow
159      * callers to go from unique sensor identifiers to HalPropConfig objects.
160      *
161      * <p>Possible returned tokens are:
162      * <ul>
163      *   <li>{@link HalServiceBase.NOT_SUPPORTED_PROPERTY}
164      *   <li>{@link CarDiagnosticManager.FRAME_TYPE_LIVE}
165      *   <li>{@link CarDiagnosticManager.FRAME_TYPE_FREEZE}
166      *
167      * @param propConfig The property config
168      * @return A unique token.
169      */
getTokenForProperty(HalPropConfig propConfig)170     protected int getTokenForProperty(HalPropConfig propConfig) {
171         int propId = propConfig.getPropId();
172         synchronized (mLock) {
173             switch (propId) {
174                 case VehicleProperty.OBD2_LIVE_FRAME:
175                     mDiagnosticCapabilities.setSupported(propId);
176                     mVehiclePropertyToConfig.put(propId, propConfig);
177                     Slogf.i(CarLog.TAG_DIAGNOSTIC, "configArray for OBD2_LIVE_FRAME is "
178                             + Arrays.toString(propConfig.getConfigArray()));
179                     return CarDiagnosticManager.FRAME_TYPE_LIVE;
180                 case VehicleProperty.OBD2_FREEZE_FRAME:
181                     mDiagnosticCapabilities.setSupported(propId);
182                     mVehiclePropertyToConfig.put(propId, propConfig);
183                     Slogf.i(CarLog.TAG_DIAGNOSTIC, "configArray for OBD2_FREEZE_FRAME is "
184                             + Arrays.toString(propConfig.getConfigArray()));
185                     return CarDiagnosticManager.FRAME_TYPE_FREEZE;
186                 case VehicleProperty.OBD2_FREEZE_FRAME_INFO:
187                     mDiagnosticCapabilities.setSupported(propId);
188                     // We should not directly expose this to the client. This property is used
189                     // only by {@link DiagnosticHalService} internally. Caller should instead use
190                     // function like {@link DiagnosticHalService#getFreezeFrameTimestamps} to access
191                     // frame info.
192                     return NOT_SUPPORTED_PROPERTY;
193                 case VehicleProperty.OBD2_FREEZE_FRAME_CLEAR:
194                     mDiagnosticCapabilities.setSupported(propId);
195                     int[] configArray = propConfig.getConfigArray();
196                     Slogf.i(CarLog.TAG_DIAGNOSTIC, "configArray for OBD2_FREEZE_FRAME_CLEAR is "
197                             + Arrays.toString(configArray));
198                     if (configArray.length < 1) {
199                         Slogf.e(CarLog.TAG_DIAGNOSTIC, "property 0x%x does not specify whether it "
200                                 + "supports selective clearing of freeze frames. assuming it does "
201                                 + "not.", propId);
202                     } else {
203                         if (configArray[0] == 1) {
204                             mDiagnosticCapabilities.setSupported(OBD2_SELECTIVE_FRAME_CLEAR);
205                         }
206                     }
207                     // We should not directly expose this to the client.
208                     return NOT_SUPPORTED_PROPERTY;
209                 default:
210                     return NOT_SUPPORTED_PROPERTY;
211             }
212         }
213     }
214 
215     @Override
init()216     public void init() {
217         if (DEBUG) {
218             Slogf.d(CarLog.TAG_DIAGNOSTIC, "init()");
219         }
220         synchronized (mLock) {
221             mIsReady = true;
222         }
223     }
224 
225     @Override
release()226     public void release() {
227         synchronized (mLock) {
228             mDiagnosticCapabilities.clear();
229             mIsReady = false;
230         }
231     }
232 
233     /**
234      * Returns the status of Diagnostic HAL.
235      * @return true if Diagnostic HAL is ready after init call.
236      */
isReady()237     public boolean isReady() {
238         synchronized (mLock) {
239             return mIsReady;
240         }
241     }
242 
243     /**
244      * Returns an array of diagnostic property Ids implemented by this vehicle.
245      *
246      * @return Array of diagnostic property Ids implemented by this vehicle. Empty array if
247      * no property available.
248      */
getSupportedDiagnosticProperties()249     public int[] getSupportedDiagnosticProperties() {
250         int[] supportedDiagnosticProperties;
251         synchronized (mLock) {
252             supportedDiagnosticProperties = new int[mSensorTypeToConfig.size()];
253             for (int i = 0; i < supportedDiagnosticProperties.length; i++) {
254                 supportedDiagnosticProperties[i] = mSensorTypeToConfig.keyAt(i);
255             }
256         }
257         return supportedDiagnosticProperties;
258     }
259 
260     /**
261      * Start to request diagnostic information.
262      *
263      * <p>The supported sensorTypes are one of:
264      * <ul>
265      *   <li>{@link CarDiagnosticManager.FRAME_TYPE_LIVE}
266      *   <li>{@link CarDiagnosticManager.FRAME_TYPE_FREEZE}
267      *
268      * <p>The supported rate are one of:
269      * <ul>
270      *   <li>{@link CarSensorManager.SENSOR_RATE_ONCHANGE}
271      *   <li>{@link CarSensorManager.SENSOR_RATE_NORMAL}
272      *   <li>{@link CarSensorManager.SENSOR_RATE_FASTEST}
273      *   <li>{@link CarSensorManager.SENSOR_RATE_FAST}
274      *   <li>{@link CarSensorManager.SENSOR_RATE_UI}
275      *
276      * @param sensorType One of the supported sensor types.
277      * @param rate One of the supported rate.
278      *
279      * @return true if request successfully. otherwise return false
280      */
requestDiagnosticStart(int sensorType, int rate)281     public boolean requestDiagnosticStart(int sensorType, int rate) {
282         HalPropConfig propConfig;
283         synchronized (mLock) {
284             propConfig = mSensorTypeToConfig.get(sensorType);
285         }
286         if (propConfig == null) {
287             Slogf.e(CarLog.TAG_DIAGNOSTIC, new StringBuilder()
288                     .append("Unsupported sensor type: 0x")
289                     .append(toHexString(sensorType))
290                     .toString());
291             return false;
292         }
293         int propId = propConfig.getPropId();
294         if (DEBUG) {
295             Slogf.d(CarLog.TAG_DIAGNOSTIC, new StringBuilder()
296                     .append("requestDiagnosticStart, propertyId: 0x")
297                     .append(toHexString(propId))
298                     .append(", rate: ")
299                     .append(rate)
300                     .toString());
301         }
302         mVehicleHal.subscribePropertySafe(this, propId,
303                 fixSamplingRateForProperty(propConfig, rate));
304         return true;
305     }
306 
307     /**
308      * Stop requesting diagnostic information.
309      * @param sensorType
310      */
requestDiagnosticStop(int sensorType)311     public void requestDiagnosticStop(int sensorType) {
312         HalPropConfig propConfig;
313         synchronized (mLock) {
314             propConfig = mSensorTypeToConfig.get(sensorType);
315         }
316         if (propConfig == null) {
317             Slogf.e(CarLog.TAG_DIAGNOSTIC, new StringBuilder()
318                     .append("Unsupported sensor type: 0x")
319                     .append(toHexString(sensorType))
320                     .toString());
321             return;
322         }
323         int propId = propConfig.getPropId();
324         if (DEBUG) {
325             Slogf.d(CarLog.TAG_DIAGNOSTIC, new StringBuilder()
326                     .append("requestDiagnosticStop, propertyId: 0x")
327                     .append(toHexString(propId))
328                     .toString());
329         }
330         mVehicleHal.unsubscribePropertySafe(this, propId);
331 
332     }
333 
334     /**
335      * Query current diagnostic value
336      * @param sensorType
337      * @return The property value.
338      */
339     @Nullable
getCurrentDiagnosticValue(int sensorType)340     public HalPropValue getCurrentDiagnosticValue(int sensorType) {
341         HalPropConfig propConfig;
342         synchronized (mLock) {
343             propConfig = mSensorTypeToConfig.get(sensorType);
344         }
345         if (propConfig == null) {
346             Slogf.e(CarLog.TAG_DIAGNOSTIC, new StringBuilder()
347                     .append("property not available, sensor type: 0x")
348                     .append(toHexString(sensorType))
349                     .toString());
350             return null;
351         }
352         int propId = propConfig.getPropId();
353         try {
354             return mVehicleHal.get(propId);
355         } catch (ServiceSpecificException e) {
356             Slogf.e(CarLog.TAG_DIAGNOSTIC, "property not ready 0x" + toHexString(propId),
357                     e);
358             return null;
359         } catch (IllegalArgumentException e) {
360             Slogf.e(CarLog.TAG_DIAGNOSTIC, "illegal argument trying to read property: 0x"
361                     + toHexString(propId), e);
362             return null;
363         }
364 
365     }
366 
getPropConfig(int halPropId)367     private HalPropConfig getPropConfig(int halPropId) {
368         HalPropConfig config;
369         synchronized (mLock) {
370             config = mVehiclePropertyToConfig.get(halPropId, null);
371         }
372         return config;
373     }
374 
getPropConfigArray(int halPropId)375     private int[] getPropConfigArray(int halPropId) {
376         HalPropConfig propConfig = getPropConfig(halPropId);
377         return propConfig.getConfigArray();
378     }
379 
getLastIndex(Class<?> clazz)380     private static int getLastIndex(Class<?> clazz) {
381         int lastIndex = 0;
382         for (Field field : clazz.getDeclaredFields()) {
383             int modifiers = field.getModifiers();
384             try {
385                 if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)
386                         && Modifier.isPublic(modifiers) && field.getType().equals(int.class)) {
387                     int value = field.getInt(/* object= */ null);
388                     if (value > lastIndex) {
389                         lastIndex = value;
390                     }
391                 }
392             } catch (IllegalAccessException ignored) {
393                 // Ignore the exception.
394             }
395         }
396         return lastIndex;
397     }
398 
getNumIntegerSensors(int halPropId)399     private int getNumIntegerSensors(int halPropId) {
400         int count = getLastIndex(DiagnosticIntegerSensorIndex.class) + 1;
401         int[] configArray = getPropConfigArray(halPropId);
402         if (configArray.length < 2) {
403             Slogf.e(CarLog.TAG_DIAGNOSTIC, "property 0x%x does not specify the number of "
404                     + "vendor-specific properties. Assuming 0.", halPropId);
405         } else {
406             count += configArray[0];
407         }
408         return count;
409     }
410 
getNumFloatSensors(int halPropId)411     private int getNumFloatSensors(int halPropId) {
412         int count = getLastIndex(DiagnosticFloatSensorIndex.class) + 1;
413         int[] configArray = getPropConfigArray(halPropId);
414         if (configArray.length < 2) {
415             Slogf.e(CarLog.TAG_DIAGNOSTIC, "property 0x%x does not specify the number of "
416                     + "vendor-specific properties. Assuming 0.", halPropId);
417         } else {
418             count += configArray[1];
419         }
420         return count;
421     }
422 
createCarDiagnosticEvent(HalPropValue value)423     private CarDiagnosticEvent createCarDiagnosticEvent(HalPropValue value) {
424         if (value == null) {
425             return null;
426         }
427         int propId = value.getPropId();
428         final boolean isFreezeFrame = propId == VehicleProperty.OBD2_FREEZE_FRAME;
429         CarDiagnosticEvent.Builder builder =
430                 (isFreezeFrame
431                                 ? CarDiagnosticEvent.Builder.newFreezeFrameBuilder()
432                                 : CarDiagnosticEvent.Builder.newLiveFrameBuilder())
433                         .atTimestamp(value.getTimestamp());
434 
435         BitSet bitset = BitSet.valueOf(value.getByteArray());
436 
437         int numIntegerProperties = getNumIntegerSensors(propId);
438         int numFloatProperties = getNumFloatSensors(propId);
439 
440         for (int i = 0; i < numIntegerProperties; ++i) {
441             if (bitset.get(i)) {
442                 builder.withIntValue(i, value.getInt32Value(i));
443             }
444         }
445 
446         for (int i = 0; i < numFloatProperties; ++i) {
447             if (bitset.get(numIntegerProperties + i)) {
448                 builder.withFloatValue(i, value.getFloatValue(i));
449             }
450         }
451 
452         builder.withDtc(value.getStringValue());
453 
454         return builder.build();
455     }
456 
457     /** Listener for monitoring diagnostic event. */
458     public interface DiagnosticListener {
459         /**
460          * Diagnostic events are available.
461          *
462          * @param events
463          */
onDiagnosticEvents(List<CarDiagnosticEvent> events)464         void onDiagnosticEvents(List<CarDiagnosticEvent> events);
465     }
466 
467     // Should be used only inside handleHalEvents method.
468     private final LinkedList<CarDiagnosticEvent> mEventsToDispatch = new LinkedList<>();
469 
470     @Override
onHalEvents(List<HalPropValue> values)471     public void onHalEvents(List<HalPropValue> values) {
472         for (HalPropValue value : values) {
473             CarDiagnosticEvent event = createCarDiagnosticEvent(value);
474             if (event != null) {
475                 mEventsToDispatch.add(event);
476             }
477         }
478 
479         DiagnosticListener listener = null;
480         synchronized (mLock) {
481             listener = mDiagnosticListener;
482         }
483         if (listener != null) {
484             listener.onDiagnosticEvents(mEventsToDispatch);
485         }
486         mEventsToDispatch.clear();
487     }
488 
489     /**
490      * Set DiagnosticListener.
491      * @param listener
492      */
setDiagnosticListener(DiagnosticListener listener)493     public void setDiagnosticListener(DiagnosticListener listener) {
494         synchronized (mLock) {
495             mDiagnosticListener = listener;
496         }
497     }
498 
getDiagnosticListener()499     public DiagnosticListener getDiagnosticListener() {
500         synchronized (mLock) {
501             return mDiagnosticListener;
502         }
503     }
504 
505     @Override
506     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(PrintWriter writer)507     public void dump(PrintWriter writer) {
508         writer.println("*Diagnostic HAL*");
509     }
510 
fixSamplingRateForProperty(HalPropConfig prop, int carSensorManagerRate)511     protected float fixSamplingRateForProperty(HalPropConfig prop, int carSensorManagerRate) {
512         switch (prop.getChangeMode()) {
513             case VehiclePropertyChangeMode.ON_CHANGE:
514                 return 0;
515         }
516         float rate = 1.0f;
517         switch (carSensorManagerRate) {
518             case CarSensorManager.SENSOR_RATE_FASTEST:
519             case CarSensorManager.SENSOR_RATE_FAST:
520                 rate = 10f;
521                 break;
522             case CarSensorManager.SENSOR_RATE_UI:
523                 rate = 5f;
524                 break;
525             default: // fall back to default.
526                 break;
527         }
528         if (rate > prop.getMaxSampleRate()) {
529             rate = prop.getMaxSampleRate();
530         }
531         if (rate < prop.getMinSampleRate()) {
532             rate = prop.getMinSampleRate();
533         }
534         return rate;
535     }
536 
getDiagnosticCapabilities()537     public DiagnosticCapabilities getDiagnosticCapabilities() {
538         synchronized (mLock) {
539             return mDiagnosticCapabilities;
540         }
541     }
542 
543     /**
544      * Returns the {@link CarDiagnosticEvent} for the current Vehicle HAL's live frame.
545      */
546     @Nullable
getCurrentLiveFrame()547     public CarDiagnosticEvent getCurrentLiveFrame() {
548         try {
549             HalPropValue value = mVehicleHal.get(VehicleProperty.OBD2_LIVE_FRAME);
550             return createCarDiagnosticEvent(value);
551         } catch (ServiceSpecificException e) {
552             Slogf.e(CarLog.TAG_DIAGNOSTIC, "Failed to read OBD2_LIVE_FRAME.", e);
553             return null;
554         } catch (IllegalArgumentException e) {
555             Slogf.e(CarLog.TAG_DIAGNOSTIC, "illegal argument trying to read OBD2_LIVE_FRAME", e);
556             return null;
557         }
558     }
559 
560     /**
561      * Returns all timestamps for the Vehicle HAL's Freeze Frame data.
562      */
563     @Nullable
getFreezeFrameTimestamps()564     public long[] getFreezeFrameTimestamps() {
565         try {
566             HalPropValue value = mVehicleHal.get(VehicleProperty.OBD2_FREEZE_FRAME_INFO);
567             long[] timestamps = new long[value.getInt64ValuesSize()];
568             for (int i = 0; i < timestamps.length; ++i) {
569                 timestamps[i] = value.getInt64Value(i);
570             }
571             return timestamps;
572         } catch (ServiceSpecificException e) {
573             Slogf.e(CarLog.TAG_DIAGNOSTIC, "Failed to read OBD2_FREEZE_FRAME_INFO.", e);
574             return null;
575         } catch (IllegalArgumentException e) {
576             Slogf.e(CarLog.TAG_DIAGNOSTIC, "illegal argument trying to read OBD2_FREEZE_FRAME_INFO",
577                     e);
578             return null;
579         }
580     }
581 
582     /**
583      * Returns the {@link CarDiagnosticEvent} representing a Freeze Frame data for the timestamp
584      * passed as parameter.
585      */
586     @Nullable
getFreezeFrame(long timestamp)587     public CarDiagnosticEvent getFreezeFrame(long timestamp) {
588         HalPropValue getValue = mPropValueBuilder.build(
589                 VehicleProperty.OBD2_FREEZE_FRAME, /*areaId=*/0, /*value=*/timestamp);
590         try {
591             HalPropValue value = mVehicleHal.get(getValue);
592             return createCarDiagnosticEvent(value);
593         } catch (ServiceSpecificException e) {
594             Slogf.e(CarLog.TAG_DIAGNOSTIC, "Failed to read OBD2_FREEZE_FRAME.", e);
595             return null;
596         } catch (IllegalArgumentException e) {
597             Slogf.e(CarLog.TAG_DIAGNOSTIC, "illegal argument trying to read OBD2_FREEZE_FRAME", e);
598             return null;
599         }
600     }
601 
602     /**
603      * Clears all Vehicle HAL's Freeze Frame data for the timestamps passed as parameter.
604      */
clearFreezeFrames(long... timestamps)605     public void clearFreezeFrames(long... timestamps) {
606         HalPropValue value = mPropValueBuilder.build(
607                 VehicleProperty.OBD2_FREEZE_FRAME_CLEAR, /*areaId=*/0, /*values=*/timestamps);
608         try {
609             mVehicleHal.set(value);
610         } catch (ServiceSpecificException e) {
611             Slogf.e(CarLog.TAG_DIAGNOSTIC, "Failed to write OBD2_FREEZE_FRAME_CLEAR.", e);
612         } catch (IllegalArgumentException e) {
613             Slogf.e(CarLog.TAG_DIAGNOSTIC, "illegal argument trying to write "
614                     + "OBD2_FREEZE_FRAME_CLEAR", e);
615         }
616     }
617 }
618