1 /*
2  * Copyright (C) 2015 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.CarServiceUtils.toByteArray;
20 import static com.android.car.CarServiceUtils.toFloatArray;
21 import static com.android.car.CarServiceUtils.toIntArray;
22 
23 import static java.lang.Integer.toHexString;
24 
25 import android.annotation.CheckResult;
26 import android.content.Context;
27 import android.hardware.automotive.vehicle.V2_0.IVehicle;
28 import android.hardware.automotive.vehicle.V2_0.IVehicleCallback;
29 import android.hardware.automotive.vehicle.V2_0.SubscribeFlags;
30 import android.hardware.automotive.vehicle.V2_0.SubscribeOptions;
31 import android.hardware.automotive.vehicle.V2_0.VehicleAreaConfig;
32 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
33 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
34 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
35 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyAccess;
36 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyChangeMode;
37 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyType;
38 import android.os.HandlerThread;
39 import android.os.RemoteException;
40 import android.os.SystemClock;
41 import android.util.ArraySet;
42 import android.util.Log;
43 import android.util.SparseArray;
44 
45 import com.android.car.CarLog;
46 import com.android.internal.annotations.VisibleForTesting;
47 
48 import com.google.android.collect.Lists;
49 
50 import java.io.PrintWriter;
51 import java.lang.ref.WeakReference;
52 import java.util.ArrayList;
53 import java.util.Arrays;
54 import java.util.Collection;
55 import java.util.HashMap;
56 import java.util.HashSet;
57 import java.util.List;
58 import java.util.Set;
59 
60 /**
61  * Abstraction for vehicle HAL. This class handles interface with native HAL and do basic parsing
62  * of received data (type check). Then each event is sent to corresponding {@link HalServiceBase}
63  * implementation. It is responsibility of {@link HalServiceBase} to convert data to corresponding
64  * Car*Service for Car*Manager API.
65  */
66 public class VehicleHal extends IVehicleCallback.Stub {
67 
68     private static final boolean DBG = false;
69 
70     private static final int NO_AREA = -1;
71 
72     private final HandlerThread mHandlerThread;
73     private final PowerHalService mPowerHal;
74     private final PropertyHalService mPropertyHal;
75     private final InputHalService mInputHal;
76     private final VmsHalService mVmsHal;
77     private DiagnosticHalService mDiagnosticHal = null;
78 
79     /** Might be re-assigned if Vehicle HAL is reconnected. */
80     private volatile HalClient mHalClient;
81 
82     /** Stores handler for each HAL property. Property events are sent to handler. */
83     private final SparseArray<HalServiceBase> mPropertyHandlers = new SparseArray<>();
84     /** This is for iterating all HalServices with fixed order. */
85     private final ArrayList<HalServiceBase> mAllServices = new ArrayList<>();
86     private final HashMap<Integer, SubscribeOptions> mSubscribedProperties = new HashMap<>();
87     private final HashMap<Integer, VehiclePropConfig> mAllProperties = new HashMap<>();
88     private final HashMap<Integer, VehiclePropertyEventInfo> mEventLog = new HashMap<>();
89 
90     // Used by injectVHALEvent for testing purposes.  Delimiter for an array of data
91     private static final String DATA_DELIMITER = ",";
92 
VehicleHal(Context context, IVehicle vehicle)93     public VehicleHal(Context context, IVehicle vehicle) {
94         mHandlerThread = new HandlerThread("VEHICLE-HAL");
95         mHandlerThread.start();
96         // passing this should be safe as long as it is just kept and not used in constructor
97         mPowerHal = new PowerHalService(this);
98         mPropertyHal = new PropertyHalService(this);
99         mInputHal = new InputHalService(this);
100         mVmsHal = new VmsHalService(context, this);
101         mDiagnosticHal = new DiagnosticHalService(this);
102         mAllServices.addAll(Arrays.asList(mPowerHal,
103                 mInputHal,
104                 mPropertyHal,
105                 mDiagnosticHal,
106                 mVmsHal));
107 
108         mHalClient = new HalClient(vehicle, mHandlerThread.getLooper(), this /*IVehicleCallback*/);
109     }
110 
111     /** Placeholder version only for testing */
112     @VisibleForTesting
VehicleHal(PowerHalService powerHal, DiagnosticHalService diagnosticHal, HalClient halClient, PropertyHalService propertyHal)113     public VehicleHal(PowerHalService powerHal, DiagnosticHalService diagnosticHal,
114             HalClient halClient, PropertyHalService propertyHal) {
115         mHandlerThread = null;
116         mPowerHal = powerHal;
117         mPropertyHal = propertyHal;
118         mDiagnosticHal = diagnosticHal;
119         mInputHal = null;
120         mVmsHal = null;
121         mHalClient = halClient;
122         mDiagnosticHal = diagnosticHal;
123     }
124 
vehicleHalReconnected(IVehicle vehicle)125     public void vehicleHalReconnected(IVehicle vehicle) {
126         synchronized (this) {
127             mHalClient = new HalClient(vehicle, mHandlerThread.getLooper(),
128                     this /*IVehicleCallback*/);
129 
130             SubscribeOptions[] options = mSubscribedProperties.values()
131                     .toArray(new SubscribeOptions[0]);
132 
133             try {
134                 mHalClient.subscribe(options);
135             } catch (RemoteException e) {
136                 throw new RuntimeException("Failed to subscribe: " + Arrays.asList(options), e);
137             }
138         }
139     }
140 
init()141     public void init() {
142         Set<VehiclePropConfig> properties;
143         try {
144             properties = new HashSet<>(mHalClient.getAllPropConfigs());
145         } catch (RemoteException e) {
146             throw new RuntimeException("Unable to retrieve vehicle property configuration", e);
147         }
148 
149         synchronized (this) {
150             // Create map of all properties
151             for (VehiclePropConfig p : properties) {
152                 mAllProperties.put(p.prop, p);
153             }
154         }
155 
156         for (HalServiceBase service: mAllServices) {
157             Collection<VehiclePropConfig> taken = service.takeSupportedProperties(properties);
158             if (taken == null) {
159                 continue;
160             }
161             if (DBG) {
162                 Log.i(CarLog.TAG_HAL, "HalService " + service + " take properties " + taken.size());
163             }
164             synchronized (this) {
165                 for (VehiclePropConfig p: taken) {
166                     mPropertyHandlers.append(p.prop, service);
167                 }
168             }
169             properties.removeAll(taken);
170             service.init();
171         }
172     }
173 
release()174     public void release() {
175         // release in reverse order from init
176         for (int i = mAllServices.size() - 1; i >= 0; i--) {
177             mAllServices.get(i).release();
178         }
179         synchronized (this) {
180             for (int p : mSubscribedProperties.keySet()) {
181                 try {
182                     mHalClient.unsubscribe(p);
183                 } catch (RemoteException e) {
184                     //  Ignore exceptions on shutdown path.
185                     Log.w(CarLog.TAG_HAL, "Failed to unsubscribe", e);
186                 }
187             }
188             mSubscribedProperties.clear();
189             mAllProperties.clear();
190         }
191         // keep the looper thread as should be kept for the whole life cycle.
192     }
193 
getDiagnosticHal()194     public DiagnosticHalService getDiagnosticHal() { return mDiagnosticHal; }
195 
getPowerHal()196     public PowerHalService getPowerHal() {
197         return mPowerHal;
198     }
199 
getPropertyHal()200     public PropertyHalService getPropertyHal() {
201         return mPropertyHal;
202     }
203 
getInputHal()204     public InputHalService getInputHal() {
205         return mInputHal;
206     }
207 
getVmsHal()208     public VmsHalService getVmsHal() { return mVmsHal; }
209 
assertServiceOwnerLocked(HalServiceBase service, int property)210     private void assertServiceOwnerLocked(HalServiceBase service, int property) {
211         if (service != mPropertyHandlers.get(property)) {
212             throw new IllegalArgumentException("Property 0x" + toHexString(property)
213                     + " is not owned by service: " + service);
214         }
215     }
216 
217     /**
218      * Subscribes given properties with sampling rate defaults to 0 and no special flags provided.
219      *
220      * @see #subscribeProperty(HalServiceBase, int, float, int)
221      */
subscribeProperty(HalServiceBase service, int property)222     public void subscribeProperty(HalServiceBase service, int property)
223             throws IllegalArgumentException {
224         subscribeProperty(service, property, 0f, SubscribeFlags.EVENTS_FROM_CAR);
225     }
226 
227     /**
228      * Subscribes given properties with default subscribe flag.
229      *
230      * @see #subscribeProperty(HalServiceBase, int, float, int)
231      */
subscribeProperty(HalServiceBase service, int property, float sampleRateHz)232     public void subscribeProperty(HalServiceBase service, int property, float sampleRateHz)
233             throws IllegalArgumentException {
234         subscribeProperty(service, property, sampleRateHz, SubscribeFlags.EVENTS_FROM_CAR);
235     }
236 
237     /**
238      * Subscribe given property. Only Hal service owning the property can subscribe it.
239      *
240      * @param service HalService that owns this property
241      * @param property property id (VehicleProperty)
242      * @param samplingRateHz sampling rate in Hz for continuous properties
243      * @param flags flags from {@link android.hardware.automotive.vehicle.V2_0.SubscribeFlags}
244      * @throws IllegalArgumentException thrown if property is not supported by VHAL
245      */
subscribeProperty(HalServiceBase service, int property, float samplingRateHz, int flags)246     public void subscribeProperty(HalServiceBase service, int property,
247             float samplingRateHz, int flags) throws IllegalArgumentException {
248         if (DBG) {
249             Log.i(CarLog.TAG_HAL, "subscribeProperty, service:" + service
250                     + ", property: 0x" + toHexString(property));
251         }
252         VehiclePropConfig config;
253         synchronized (this) {
254             config = mAllProperties.get(property);
255         }
256 
257         if (config == null) {
258             throw new IllegalArgumentException("subscribe error: config is null for property 0x" +
259                     toHexString(property));
260         } else if (isPropertySubscribable(config)) {
261             SubscribeOptions opts = new SubscribeOptions();
262             opts.propId = property;
263             opts.sampleRate = samplingRateHz;
264             opts.flags = flags;
265             synchronized (this) {
266                 assertServiceOwnerLocked(service, property);
267                 mSubscribedProperties.put(property, opts);
268             }
269             try {
270                 mHalClient.subscribe(opts);
271             } catch (RemoteException e) {
272                 Log.e(CarLog.TAG_HAL, "Failed to subscribe to property: 0x" + property, e);
273             }
274         } else {
275             Log.e(CarLog.TAG_HAL, "Cannot subscribe to property: " + property);
276         }
277     }
278 
unsubscribeProperty(HalServiceBase service, int property)279     public void unsubscribeProperty(HalServiceBase service, int property) {
280         if (DBG) {
281             Log.i(CarLog.TAG_HAL, "unsubscribeProperty, service:" + service
282                     + ", property: 0x" + toHexString(property));
283         }
284         VehiclePropConfig config;
285         synchronized (this) {
286             config = mAllProperties.get(property);
287         }
288 
289         if (config == null) {
290             Log.e(CarLog.TAG_HAL, "unsubscribeProperty: property " + property + " does not exist");
291         } else if (isPropertySubscribable(config)) {
292             synchronized (this) {
293                 assertServiceOwnerLocked(service, property);
294                 mSubscribedProperties.remove(property);
295             }
296             try {
297                 mHalClient.unsubscribe(property);
298             } catch (RemoteException e) {
299                 Log.e(CarLog.TAG_SERVICE, "Failed to unsubscribe from property: 0x"
300                         + toHexString(property), e);
301             }
302         } else {
303             Log.e(CarLog.TAG_HAL, "Cannot unsubscribe property: " + property);
304         }
305     }
306 
isPropertySupported(int propertyId)307     public boolean isPropertySupported(int propertyId) {
308         return mAllProperties.containsKey(propertyId);
309     }
310 
getAllPropConfigs()311     public Collection<VehiclePropConfig> getAllPropConfigs() {
312         return mAllProperties.values();
313     }
314 
get(int propertyId)315     public VehiclePropValue get(int propertyId) throws PropertyTimeoutException {
316         return get(propertyId, NO_AREA);
317     }
318 
get(int propertyId, int areaId)319     public VehiclePropValue get(int propertyId, int areaId) throws PropertyTimeoutException {
320         if (DBG) {
321             Log.i(CarLog.TAG_HAL, "get, property: 0x" + toHexString(propertyId)
322                     + ", areaId: 0x" + toHexString(areaId));
323         }
324         VehiclePropValue propValue = new VehiclePropValue();
325         propValue.prop = propertyId;
326         propValue.areaId = areaId;
327         return mHalClient.getValue(propValue);
328     }
329 
get(Class clazz, int propertyId)330     public <T> T get(Class clazz, int propertyId) throws PropertyTimeoutException {
331         return get(clazz, createPropValue(propertyId, NO_AREA));
332     }
333 
get(Class clazz, int propertyId, int areaId)334     public <T> T get(Class clazz, int propertyId, int areaId) throws PropertyTimeoutException {
335         return get(clazz, createPropValue(propertyId, areaId));
336     }
337 
338     @SuppressWarnings("unchecked")
get(Class clazz, VehiclePropValue requestedPropValue)339     public <T> T get(Class clazz, VehiclePropValue requestedPropValue)
340             throws PropertyTimeoutException {
341         VehiclePropValue propValue;
342         propValue = mHalClient.getValue(requestedPropValue);
343 
344         if (clazz == Integer.class || clazz == int.class) {
345             return (T) propValue.value.int32Values.get(0);
346         } else if (clazz == Boolean.class || clazz == boolean.class) {
347             return (T) Boolean.valueOf(propValue.value.int32Values.get(0) == 1);
348         } else if (clazz == Float.class || clazz == float.class) {
349             return (T) propValue.value.floatValues.get(0);
350         } else if (clazz == Integer[].class) {
351             Integer[] intArray = new Integer[propValue.value.int32Values.size()];
352             return (T) propValue.value.int32Values.toArray(intArray);
353         } else if (clazz == Float[].class) {
354             Float[] floatArray = new Float[propValue.value.floatValues.size()];
355             return (T) propValue.value.floatValues.toArray(floatArray);
356         } else if (clazz == int[].class) {
357             return (T) toIntArray(propValue.value.int32Values);
358         } else if (clazz == float[].class) {
359             return (T) toFloatArray(propValue.value.floatValues);
360         } else if (clazz == byte[].class) {
361             return (T) toByteArray(propValue.value.bytes);
362         } else if (clazz == String.class) {
363             return (T) propValue.value.stringValue;
364         } else {
365             throw new IllegalArgumentException("Unexpected type: " + clazz);
366         }
367     }
368 
get(VehiclePropValue requestedPropValue)369     public VehiclePropValue get(VehiclePropValue requestedPropValue)
370             throws PropertyTimeoutException {
371         return mHalClient.getValue(requestedPropValue);
372     }
373 
374     /**
375      *
376      * @param propId Property ID to return the current sample rate for.
377      *
378      * @return float Returns the current sample rate of the specified propId, or -1 if the
379      *                  property is not currently subscribed.
380      */
getSampleRate(int propId)381     public float getSampleRate(int propId) {
382         SubscribeOptions opts = mSubscribedProperties.get(propId);
383         if (opts == null) {
384             // No sample rate for this property
385             return -1;
386         } else {
387             return opts.sampleRate;
388         }
389     }
390 
set(VehiclePropValue propValue)391     protected void set(VehiclePropValue propValue) throws PropertyTimeoutException {
392         mHalClient.setValue(propValue);
393     }
394 
395     @CheckResult
set(int propId)396     VehiclePropValueSetter set(int propId) {
397         return new VehiclePropValueSetter(mHalClient, propId, NO_AREA);
398     }
399 
400     @CheckResult
set(int propId, int areaId)401     VehiclePropValueSetter set(int propId, int areaId) {
402         return new VehiclePropValueSetter(mHalClient, propId, areaId);
403     }
404 
isPropertySubscribable(VehiclePropConfig config)405     static boolean isPropertySubscribable(VehiclePropConfig config) {
406         if ((config.access & VehiclePropertyAccess.READ) == 0 ||
407                 (config.changeMode == VehiclePropertyChangeMode.STATIC)) {
408             return false;
409         }
410         return true;
411     }
412 
dumpProperties(PrintWriter writer, Collection<VehiclePropConfig> configs)413     static void dumpProperties(PrintWriter writer, Collection<VehiclePropConfig> configs) {
414         for (VehiclePropConfig config : configs) {
415             writer.println(String.format("property 0x%x", config.prop));
416         }
417     }
418 
419     private final ArraySet<HalServiceBase> mServicesToDispatch = new ArraySet<>();
420 
421     @Override
onPropertyEvent(ArrayList<VehiclePropValue> propValues)422     public void onPropertyEvent(ArrayList<VehiclePropValue> propValues) {
423         synchronized (this) {
424             for (VehiclePropValue v : propValues) {
425                 HalServiceBase service = mPropertyHandlers.get(v.prop);
426                 if(service == null) {
427                     Log.e(CarLog.TAG_HAL, "HalService not found for prop: 0x"
428                         + toHexString(v.prop));
429                     continue;
430                 }
431                 service.getDispatchList().add(v);
432                 mServicesToDispatch.add(service);
433                 VehiclePropertyEventInfo info = mEventLog.get(v.prop);
434                 if (info == null) {
435                     info = new VehiclePropertyEventInfo(v);
436                     mEventLog.put(v.prop, info);
437                 } else {
438                     info.addNewEvent(v);
439                 }
440             }
441         }
442         for (HalServiceBase s : mServicesToDispatch) {
443             s.handleHalEvents(s.getDispatchList());
444             s.getDispatchList().clear();
445         }
446         mServicesToDispatch.clear();
447     }
448 
449     @Override
onPropertySet(VehiclePropValue value)450     public void onPropertySet(VehiclePropValue value) {
451         // No need to handle on-property-set events in HAL service yet.
452     }
453 
454     @Override
onPropertySetError(int errorCode, int propId, int areaId)455     public void onPropertySetError(int errorCode, int propId, int areaId) {
456         Log.e(CarLog.TAG_HAL, String.format("onPropertySetError, errorCode: %d, prop: 0x%x, "
457                 + "area: 0x%x", errorCode, propId, areaId));
458         if (propId != VehicleProperty.INVALID) {
459             HalServiceBase service = mPropertyHandlers.get(propId);
460             if (service != null) {
461                 service.handlePropertySetError(propId, areaId);
462             }
463         }
464     }
465 
dump(PrintWriter writer)466     public void dump(PrintWriter writer) {
467         writer.println("**dump HAL services**");
468         for (HalServiceBase service: mAllServices) {
469             service.dump(writer);
470         }
471         // Dump all VHAL property configure.
472         dumpPropertyConfigs(writer, "");
473         writer.println(String.format("**All Events, now ns:%d**",
474                 SystemClock.elapsedRealtimeNanos()));
475         for (VehiclePropertyEventInfo info : mEventLog.values()) {
476             writer.println(String.format("event count:%d, lastEvent:%s",
477                     info.eventCount, dumpVehiclePropValue(info.lastEvent)));
478         }
479 
480         writer.println("**Property handlers**");
481         for (int i = 0; i < mPropertyHandlers.size(); i++) {
482             int propId = mPropertyHandlers.keyAt(i);
483             HalServiceBase service = mPropertyHandlers.valueAt(i);
484             writer.println(String.format("Prop: 0x%08X, service: %s", propId, service));
485         }
486     }
487 
488     /**
489      * Dumps vehicle property values.
490      * @param writer
491      * @param propId property id, dump all properties' value if it is empty string.
492      * @param areaId areaId of the property, dump the property for all areaIds in the config
493      * if it is empty string.
494      */
dumpPropertyValueByCommend(PrintWriter writer, String propId, String areaId)495     public void dumpPropertyValueByCommend(PrintWriter writer, String propId, String areaId) {
496         if (propId.equals("")) {
497             writer.println("**All property values**");
498             for (VehiclePropConfig config : mAllProperties.values()) {
499                 dumpPropertyValueByConfig(writer, config);
500             }
501         } else if (areaId.equals("")) {
502             VehiclePropConfig config = mAllProperties.get(Integer.parseInt(propId, 16));
503             dumpPropertyValueByConfig(writer, config);
504         } else {
505             int id = Integer.parseInt(propId, 16);
506             int area = Integer.parseInt(areaId);
507             try {
508                 VehiclePropValue value = get(id, area);
509                 writer.println(dumpVehiclePropValue(value));
510             } catch (Exception e) {
511                 writer.println("Can not get property value for propertyId: 0x"
512                         + propId + ", areaId: " + area);
513             }
514         }
515     }
516 
dumpPropertyValueByConfig(PrintWriter writer, VehiclePropConfig config)517     private void dumpPropertyValueByConfig(PrintWriter writer, VehiclePropConfig config) {
518         if (config.areaConfigs.isEmpty()) {
519             try {
520                 VehiclePropValue value = get(config.prop);
521                 writer.println(dumpVehiclePropValue(value));
522             } catch (Exception e) {
523                 writer.println("Can not get property value for propertyId: 0x"
524                         + toHexString(config.prop) + ", areaId: 0");
525             }
526         } else {
527             for (VehicleAreaConfig areaConfig : config.areaConfigs) {
528                 int area = areaConfig.areaId;
529                 try {
530                     VehiclePropValue value = get(config.prop, area);
531                     writer.println(dumpVehiclePropValue(value));
532                 } catch (Exception e) {
533                     writer.println("Can not get property value for propertyId: 0x"
534                             + toHexString(config.prop) + ", areaId: " + area);
535                 }
536             }
537         }
538     }
539 
540     /**
541      * Dump VHAL property configs.
542      *
543      * @param writer
544      * @param propId Property ID in Hex. If propid is empty string, dump all properties.
545      */
dumpPropertyConfigs(PrintWriter writer, String propId)546     public void dumpPropertyConfigs(PrintWriter writer, String propId) {
547         List<VehiclePropConfig> configList;
548         synchronized (this) {
549             configList = new ArrayList<>(mAllProperties.values());
550         }
551 
552         if (propId.equals("")) {
553             writer.println("**All properties**");
554             for (VehiclePropConfig config : configList) {
555                 writer.println(dumpPropertyConfigsHelp(config));
556             }
557             return;
558         }
559         for (VehiclePropConfig config : configList) {
560             if (toHexString(config.prop).equals(propId)) {
561                 writer.println(dumpPropertyConfigsHelp(config));
562                 return;
563             }
564         }
565 
566     }
567 
568     /** Use VehiclePropertyConfig to construct string for dumping */
dumpPropertyConfigsHelp(VehiclePropConfig config)569     private static String dumpPropertyConfigsHelp(VehiclePropConfig config) {
570         StringBuilder builder = new StringBuilder()
571                 .append("Property:0x").append(toHexString(config.prop))
572                 .append(",Property name:").append(VehicleProperty.toString(config.prop))
573                 .append(",access:0x").append(toHexString(config.access))
574                 .append(",changeMode:0x").append(toHexString(config.changeMode))
575                 .append(",config:0x").append(Arrays.toString(config.configArray.toArray()))
576                 .append(",fs min:").append(config.minSampleRate)
577                 .append(",fs max:").append(config.maxSampleRate);
578         for (VehicleAreaConfig area : config.areaConfigs) {
579             builder.append(",areaId :").append(toHexString(area.areaId))
580                     .append(",f min:").append(area.minFloatValue)
581                     .append(",f max:").append(area.maxFloatValue)
582                     .append(",i min:").append(area.minInt32Value)
583                     .append(",i max:").append(area.maxInt32Value)
584                     .append(",i64 min:").append(area.minInt64Value)
585                     .append(",i64 max:").append(area.maxInt64Value);
586         }
587         return builder.toString();
588     }
589     /**
590      * Inject a VHAL event
591      *
592      * @param property the Vehicle property Id as defined in the HAL
593      * @param zone     Zone that this event services
594      * @param value    Data value of the event
595      */
injectVhalEvent(String property, String zone, String value)596     public void injectVhalEvent(String property, String zone, String value)
597             throws NumberFormatException {
598         if (value == null || zone == null || property == null) {
599             return;
600         }
601         int propId = Integer.decode(property);
602         int zoneId = Integer.decode(zone);
603         VehiclePropValue v = createPropValue(propId, zoneId);
604         int propertyType = propId & VehiclePropertyType.MASK;
605         // Values can be comma separated list
606         List<String> dataList = new ArrayList<>(Arrays.asList(value.split(DATA_DELIMITER)));
607         switch (propertyType) {
608             case VehiclePropertyType.BOOLEAN:
609                 boolean boolValue = Boolean.valueOf(value);
610                 v.value.int32Values.add(boolValue ? 1 : 0);
611                 break;
612             case VehiclePropertyType.INT32:
613             case VehiclePropertyType.INT32_VEC:
614                 for (String s : dataList) {
615                     v.value.int32Values.add(Integer.decode(s));
616                 }
617                 break;
618             case VehiclePropertyType.FLOAT:
619             case VehiclePropertyType.FLOAT_VEC:
620                 for (String s : dataList) {
621                     v.value.floatValues.add(Float.parseFloat(s));
622                 }
623                 break;
624             default:
625                 Log.e(CarLog.TAG_HAL, "Property type unsupported:" + propertyType);
626                 return;
627         }
628         v.timestamp = SystemClock.elapsedRealtimeNanos();
629         onPropertyEvent(Lists.newArrayList(v));
630     }
631 
632     /**
633      * Inject an error event.
634      *
635      * @param property the Vehicle property Id as defined in the HAL
636      * @param zone Zone for the event to inject
637      * @param errorCode Error code return from HAL
638      */
injectOnPropertySetError(String property, String zone, String errorCode)639     public void injectOnPropertySetError(String property, String zone, String errorCode) {
640         if (zone == null || property == null || errorCode == null) {
641             return;
642         }
643         int propId = Integer.decode(property);
644         int zoneId = Integer.decode(zone);
645         int errorId = Integer.decode(errorCode);
646         onPropertySetError(errorId, propId, zoneId);
647     }
648 
649     private static class VehiclePropertyEventInfo {
650         private int eventCount;
651         private VehiclePropValue lastEvent;
652 
VehiclePropertyEventInfo(VehiclePropValue event)653         private VehiclePropertyEventInfo(VehiclePropValue event) {
654             eventCount = 1;
655             lastEvent = event;
656         }
657 
addNewEvent(VehiclePropValue event)658         private void addNewEvent(VehiclePropValue event) {
659             eventCount++;
660             lastEvent = event;
661         }
662     }
663 
664     final class VehiclePropValueSetter {
665         final WeakReference<HalClient> mClient;
666         final VehiclePropValue mPropValue;
667 
VehiclePropValueSetter(HalClient client, int propId, int areaId)668         private VehiclePropValueSetter(HalClient client, int propId, int areaId) {
669             mClient = new WeakReference<>(client);
670             mPropValue = new VehiclePropValue();
671             mPropValue.prop = propId;
672             mPropValue.areaId = areaId;
673         }
674 
to(boolean value)675         void to(boolean value) throws PropertyTimeoutException {
676             to(value ? 1 : 0);
677         }
678 
to(int value)679         void to(int value) throws PropertyTimeoutException {
680             mPropValue.value.int32Values.add(value);
681             submit();
682         }
683 
to(int[] values)684         void to(int[] values) throws PropertyTimeoutException {
685             for (int value : values) {
686                 mPropValue.value.int32Values.add(value);
687             }
688             submit();
689         }
690 
to(Collection<Integer> values)691         void to(Collection<Integer> values) throws PropertyTimeoutException {
692             mPropValue.value.int32Values.addAll(values);
693             submit();
694         }
695 
submit()696         void submit() throws PropertyTimeoutException {
697             HalClient client =  mClient.get();
698             if (client != null) {
699                 if (DBG) {
700                     Log.i(CarLog.TAG_HAL, "set, property: 0x" + toHexString(mPropValue.prop)
701                             + ", areaId: 0x" + toHexString(mPropValue.areaId));
702                 }
703                 client.setValue(mPropValue);
704             }
705         }
706     }
707 
dumpVehiclePropValue(VehiclePropValue value)708     private static String dumpVehiclePropValue(VehiclePropValue value) {
709         final int MAX_BYTE_SIZE = 20;
710 
711         StringBuilder sb = new StringBuilder()
712                 .append("Property:0x").append(toHexString(value.prop))
713                 .append(",status: ").append(value.status)
714                 .append(",timestamp:").append(value.timestamp)
715                 .append(",zone:0x").append(toHexString(value.areaId))
716                 .append(",floatValues: ").append(Arrays.toString(value.value.floatValues.toArray()))
717                 .append(",int32Values: ").append(Arrays.toString(value.value.int32Values.toArray()))
718                 .append(",int64Values: ")
719                 .append(Arrays.toString(value.value.int64Values.toArray()));
720 
721         if (value.value.bytes.size() > MAX_BYTE_SIZE) {
722             Object[] bytes = Arrays.copyOf(value.value.bytes.toArray(), MAX_BYTE_SIZE);
723             sb.append(",bytes: ").append(Arrays.toString(bytes));
724         } else {
725             sb.append(",bytes: ").append(Arrays.toString(value.value.bytes.toArray()));
726         }
727         sb.append(",string: ").append(value.value.stringValue);
728 
729         return sb.toString();
730     }
731 
createPropValue(int propId, int areaId)732     private static VehiclePropValue createPropValue(int propId, int areaId) {
733         VehiclePropValue propValue = new VehiclePropValue();
734         propValue.prop = propId;
735         propValue.areaId = areaId;
736         return propValue;
737     }
738 }
739