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 import static com.android.car.CarServiceUtils.toLongArray;
23 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
24 
25 import static java.lang.Integer.toHexString;
26 
27 import android.annotation.CheckResult;
28 import android.annotation.Nullable;
29 import android.car.VehiclePropertyIds;
30 import android.car.hardware.property.CarPropertyManager;
31 import android.content.Context;
32 import android.hardware.automotive.vehicle.V2_0.IVehicle;
33 import android.hardware.automotive.vehicle.V2_0.IVehicleCallback;
34 import android.hardware.automotive.vehicle.V2_0.SubscribeFlags;
35 import android.hardware.automotive.vehicle.V2_0.SubscribeOptions;
36 import android.hardware.automotive.vehicle.V2_0.VehicleAreaConfig;
37 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
38 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
39 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
40 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyAccess;
41 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyChangeMode;
42 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyType;
43 import android.os.Handler;
44 import android.os.HandlerThread;
45 import android.os.RemoteException;
46 import android.os.ServiceSpecificException;
47 import android.os.SystemClock;
48 import android.util.ArraySet;
49 import android.util.Slog;
50 import android.util.SparseArray;
51 
52 import com.android.car.CarLog;
53 import com.android.car.CarServiceUtils;
54 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
55 import com.android.internal.annotations.GuardedBy;
56 import com.android.internal.annotations.VisibleForTesting;
57 
58 import com.google.android.collect.Lists;
59 
60 import java.io.PrintWriter;
61 import java.lang.ref.WeakReference;
62 import java.util.ArrayList;
63 import java.util.Arrays;
64 import java.util.Collection;
65 import java.util.HashMap;
66 import java.util.List;
67 import java.util.Map;
68 import java.util.Timer;
69 import java.util.TimerTask;
70 import java.util.concurrent.TimeUnit;
71 import java.util.stream.Collectors;
72 
73 /**
74  * Abstraction for vehicle HAL. This class handles interface with native HAL and do basic parsing
75  * of received data (type check). Then each event is sent to corresponding {@link HalServiceBase}
76  * implementation. It is responsibility of {@link HalServiceBase} to convert data to corresponding
77  * Car*Service for Car*Manager API.
78  */
79 public class VehicleHal extends IVehicleCallback.Stub {
80 
81     private static final boolean DBG = false;
82 
83     /**
84      * Used in {@link VehicleHal#dumpVehiclePropValue} method when copying {@link VehiclePropValue}.
85      */
86     private static final int MAX_BYTE_SIZE = 20;
87 
88     public static final int NO_AREA = -1;
89     public static final float NO_SAMPLE_RATE = -1;
90 
91     private final HandlerThread mHandlerThread;
92     private final Handler mHandler;
93     private final PowerHalService mPowerHal;
94     private final PropertyHalService mPropertyHal;
95     private final InputHalService mInputHal;
96     private final VmsHalService mVmsHal;
97     private final UserHalService mUserHal;
98     private final DiagnosticHalService mDiagnosticHal;
99     private final ClusterHalService mClusterHalService;
100     private final EvsHalService mEvsHal;
101     private final TimeHalService mTimeHalService;
102 
103     private final Object mLock = new Object();
104 
105     /** Might be re-assigned if Vehicle HAL is reconnected. */
106     private volatile HalClient mHalClient;
107 
108     /** Stores handler for each HAL property. Property events are sent to handler. */
109     @GuardedBy("mLock")
110     private final SparseArray<HalServiceBase> mPropertyHandlers = new SparseArray<>();
111     /** This is for iterating all HalServices with fixed order. */
112     @GuardedBy("mLock")
113     private final ArrayList<HalServiceBase> mAllServices = new ArrayList<>();
114     @GuardedBy("mLock")
115     private final HashMap<Integer, SubscribeOptions> mSubscribedProperties = new HashMap<>();
116     @GuardedBy("mLock")
117     private final HashMap<Integer, VehiclePropConfig> mAllProperties = new HashMap<>();
118 
119     @GuardedBy("mLock")
120     private final HashMap<Integer, VehiclePropertyEventInfo> mEventLog = new HashMap<>();
121 
122     // Used by injectVHALEvent for testing purposes.  Delimiter for an array of data
123     private static final String DATA_DELIMITER = ",";
124 
125     /**
126      * Constructs a new {@link VehicleHal} object given the {@link Context} and {@link IVehicle}
127      * both passed as parameters.
128      */
VehicleHal(Context context, IVehicle vehicle)129     public VehicleHal(Context context, IVehicle vehicle) {
130         mHandlerThread = CarServiceUtils.getHandlerThread(
131                 VehicleHal.class.getSimpleName());
132         mHandler = new Handler(mHandlerThread.getLooper());
133         mPowerHal = new PowerHalService(this);
134         mPropertyHal = new PropertyHalService(this);
135         mInputHal = new InputHalService(this);
136         mVmsHal = new VmsHalService(context, this);
137         mUserHal = new UserHalService(this);
138         mDiagnosticHal = new DiagnosticHalService(this);
139         mClusterHalService = new ClusterHalService(this);
140         mEvsHal = new EvsHalService(this);
141         mTimeHalService = new TimeHalService(context, this);
142         //TODO(b/202396546): Dedupe this assignment with the other one in constructor below
143         mAllServices.addAll(Arrays.asList(mPowerHal,
144                 mInputHal,
145                 mDiagnosticHal,
146                 mVmsHal,
147                 mUserHal,
148                 mClusterHalService,
149                 mEvsHal,
150                 mTimeHalService,
151                 mPropertyHal)); // mPropertyHal should be the last.
152         mHalClient = new HalClient(vehicle, mHandlerThread.getLooper(),
153                 /* callback= */ this);
154     }
155 
156     /**
157      * Constructs a new {@link VehicleHal} object given the services and {@link HalClient} factory
158      * function passed as parameters. This method must be used by tests only.
159      */
160     @VisibleForTesting
VehicleHal(PowerHalService powerHal, PropertyHalService propertyHal, InputHalService inputHal, VmsHalService vmsHal, UserHalService userHal, DiagnosticHalService diagnosticHal, ClusterHalService clusterHalService, TimeHalService timeHalService, HalClient halClient, HandlerThread handlerThread)161     VehicleHal(PowerHalService powerHal,
162             PropertyHalService propertyHal,
163             InputHalService inputHal,
164             VmsHalService vmsHal,
165             UserHalService userHal,
166             DiagnosticHalService diagnosticHal,
167             ClusterHalService clusterHalService,
168             TimeHalService timeHalService,
169             HalClient halClient,
170             HandlerThread handlerThread) {
171         mHandlerThread = handlerThread;
172         mHandler = new Handler(mHandlerThread.getLooper());
173         mPowerHal = powerHal;
174         mPropertyHal = propertyHal;
175         mInputHal = inputHal;
176         mVmsHal = vmsHal;
177         mUserHal = userHal;
178         mDiagnosticHal = diagnosticHal;
179         mClusterHalService = clusterHalService;
180         mEvsHal = new EvsHalService(this);
181         mTimeHalService = timeHalService;
182         mAllServices.addAll(Arrays.asList(mPowerHal,
183                 mInputHal,
184                 mDiagnosticHal,
185                 mVmsHal,
186                 mUserHal,
187                 mEvsHal,
188                 mTimeHalService,
189                 mPropertyHal));
190         mHalClient = halClient;
191     }
192 
193     /** Called when connection to Vehicle HAL was restored. */
vehicleHalReconnected(IVehicle vehicle)194     public void vehicleHalReconnected(IVehicle vehicle) {
195         synchronized (mLock) {
196             mHalClient = new HalClient(vehicle, mHandlerThread.getLooper(),
197                     this /*IVehicleCallback*/);
198             SubscribeOptions[] options = mSubscribedProperties.values()
199                     .toArray(new SubscribeOptions[0]);
200             try {
201                 mHalClient.subscribe(options);
202             } catch (RemoteException e) {
203                 throw new RuntimeException("Failed to subscribe: " + Arrays.asList(options), e);
204             }
205         }
206     }
207 
fetchAllPropConfigs()208     private void fetchAllPropConfigs() {
209         synchronized (mLock) {
210             if (!mAllProperties.isEmpty()) { // already set
211                 Slog.i(CarLog.TAG_HAL, "fetchAllPropConfigs already fetched");
212                 return;
213             }
214         }
215         ArrayList<VehiclePropConfig> configs;
216         try {
217             configs = mHalClient.getAllPropConfigs();
218             if (configs == null || configs.size() == 0) {
219                 Slog.e(CarLog.TAG_HAL, "getAllPropConfigs returned empty configs");
220                 return;
221             }
222         } catch (RemoteException e) {
223             throw new RuntimeException("Unable to retrieve vehicle property configuration", e);
224         }
225 
226         synchronized (mLock) {
227             // Create map of all properties
228             for (VehiclePropConfig p : configs) {
229                 if (DBG) {
230                     Slog.i(CarLog.TAG_HAL, "Add config for prop:" + Integer.toHexString(p.prop)
231                             + " config:" + p);
232                 }
233                 mAllProperties.put(p.prop, p);
234             }
235         }
236     }
237 
238     /**
239      * Inits the vhal configurations.
240      *
241      * <p><Note that {@link #getIfAvailableOrFailForEarlyStage(int, int)}
242      * can be called before {@code init()}.
243      */
init()244     public void init() {
245         fetchAllPropConfigs();
246 
247         // PropertyHalService will take most properties, so make it big enough.
248         ArrayList<VehiclePropConfig> configsForService = new ArrayList<>(mAllServices.size());
249         for (int i = 0; i < mAllServices.size(); i++) {
250             HalServiceBase service = mAllServices.get(i);
251             int[] supportedProps =  service.getAllSupportedProperties();
252             configsForService.clear();
253             synchronized (mLock) {
254                 if (supportedProps.length == 0) {
255                     for (Integer propId : mAllProperties.keySet()) {
256                         if (service.isSupportedProperty(propId)) {
257                             VehiclePropConfig config = mAllProperties.get(propId);
258                             mPropertyHandlers.append(propId, service);
259                             configsForService.add(config);
260                         }
261                     }
262                 } else {
263                     for (int prop : supportedProps) {
264                         VehiclePropConfig config = mAllProperties.get(prop);
265                         if (config == null) {
266                             continue;
267                         }
268                         mPropertyHandlers.append(prop, service);
269                         configsForService.add(config);
270                     }
271                 }
272             }
273             service.takeProperties(configsForService);
274             service.init();
275         }
276     }
277 
278     /**
279      * Releases all connected services (power management service, input service, etc).
280      */
release()281     public void release() {
282         // release in reverse order from init
283         for (int i = mAllServices.size() - 1; i >= 0; i--) {
284             mAllServices.get(i).release();
285         }
286         synchronized (mLock) {
287             for (int p : mSubscribedProperties.keySet()) {
288                 try {
289                     mHalClient.unsubscribe(p);
290                 } catch (RemoteException e) {
291                     //  Ignore exceptions on shutdown path.
292                     Slog.w(CarLog.TAG_HAL, "Failed to unsubscribe", e);
293                 }
294             }
295             mSubscribedProperties.clear();
296             mAllProperties.clear();
297         }
298         // keep the looper thread as should be kept for the whole life cycle.
299     }
300 
getDiagnosticHal()301     public DiagnosticHalService getDiagnosticHal() {
302         return mDiagnosticHal;
303     }
304 
getPowerHal()305     public PowerHalService getPowerHal() {
306         return mPowerHal;
307     }
308 
getPropertyHal()309     public PropertyHalService getPropertyHal() {
310         return mPropertyHal;
311     }
312 
getInputHal()313     public InputHalService getInputHal() {
314         return mInputHal;
315     }
316 
getUserHal()317     public UserHalService getUserHal() {
318         return mUserHal;
319     }
320 
getVmsHal()321     public VmsHalService getVmsHal() {
322         return mVmsHal;
323     }
324 
getClusterHal()325     public ClusterHalService getClusterHal() {
326         return mClusterHalService;
327     }
328 
getEvsHal()329     public EvsHalService getEvsHal() {
330         return mEvsHal;
331     }
332 
getTimeHalService()333     public TimeHalService getTimeHalService() {
334         return mTimeHalService;
335     }
336 
assertServiceOwnerLocked(HalServiceBase service, int property)337     private void assertServiceOwnerLocked(HalServiceBase service, int property) {
338         if (service != mPropertyHandlers.get(property)) {
339             throw new IllegalArgumentException("Property 0x" + toHexString(property)
340                     + " is not owned by service: " + service);
341         }
342     }
343 
344     /**
345      * Subscribes given properties with sampling rate defaults to 0 and no special flags provided.
346      *
347      * @see #subscribeProperty(HalServiceBase, int, float, int)
348      */
subscribeProperty(HalServiceBase service, int property)349     public void subscribeProperty(HalServiceBase service, int property)
350             throws IllegalArgumentException {
351         subscribeProperty(service, property, /* samplingRateHz= */ 0f,
352                 SubscribeFlags.EVENTS_FROM_CAR);
353     }
354 
355     /**
356      * Subscribes given properties with default subscribe flag.
357      *
358      * @see #subscribeProperty(HalServiceBase, int, float, int)
359      */
subscribeProperty(HalServiceBase service, int property, float sampleRateHz)360     public void subscribeProperty(HalServiceBase service, int property, float sampleRateHz)
361             throws IllegalArgumentException {
362         subscribeProperty(service, property, sampleRateHz, SubscribeFlags.EVENTS_FROM_CAR);
363     }
364 
365     /**
366      * Subscribe given property. Only Hal service owning the property can subscribe it.
367      *
368      * @param service HalService that owns this property
369      * @param property property id (VehicleProperty)
370      * @param samplingRateHz sampling rate in Hz for continuous properties
371      * @param flags flags from {@link android.hardware.automotive.vehicle.V2_0.SubscribeFlags}
372      * @throws IllegalArgumentException thrown if property is not supported by VHAL
373      */
subscribeProperty(HalServiceBase service, int property, float samplingRateHz, int flags)374     public void subscribeProperty(HalServiceBase service, int property,
375             float samplingRateHz, int flags) throws IllegalArgumentException {
376         if (DBG) {
377             Slog.i(CarLog.TAG_HAL, "subscribeProperty, service:" + service
378                     + ", " + toCarPropertyLog(property));
379         }
380         VehiclePropConfig config;
381         synchronized (mLock) {
382             config = mAllProperties.get(property);
383         }
384 
385         if (config == null) {
386             throw new IllegalArgumentException("subscribe error: config is null for property 0x"
387                     + toHexString(property));
388         } else if (isPropertySubscribable(config)) {
389             SubscribeOptions opts = new SubscribeOptions();
390             opts.propId = property;
391             opts.sampleRate = samplingRateHz;
392             opts.flags = flags;
393             synchronized (mLock) {
394                 assertServiceOwnerLocked(service, property);
395                 mSubscribedProperties.put(property, opts);
396             }
397             try {
398                 mHalClient.subscribe(opts);
399             } catch (RemoteException e) {
400                 Slog.e(CarLog.TAG_HAL, "Failed to subscribe to " + toCarPropertyLog(property),
401                         e);
402             }
403         } else {
404             Slog.e(CarLog.TAG_HAL, "Cannot subscribe to " + toCarPropertyLog(property));
405         }
406     }
407 
408     /**
409      * Unsubscribes from receiving notifications for the property and HAL services passed
410      * as parameters.
411      */
unsubscribeProperty(HalServiceBase service, int property)412     public void unsubscribeProperty(HalServiceBase service, int property) {
413         if (DBG) {
414             Slog.i(CarLog.TAG_HAL, "unsubscribeProperty, service:" + service
415                     + ", " + toCarPropertyLog(property));
416         }
417         VehiclePropConfig config;
418         synchronized (mLock) {
419             config = mAllProperties.get(property);
420         }
421 
422         if (config == null) {
423             Slog.e(CarLog.TAG_HAL, "unsubscribeProperty " + toCarPropertyLog(property)
424                     + " does not exist");
425         } else if (isPropertySubscribable(config)) {
426             synchronized (mLock) {
427                 assertServiceOwnerLocked(service, property);
428                 mSubscribedProperties.remove(property);
429             }
430             try {
431                 mHalClient.unsubscribe(property);
432             } catch (RemoteException e) {
433                 Slog.e(CarLog.TAG_SERVICE, "Failed to unsubscribe: "
434                         + toCarPropertyLog(property), e);
435             }
436         } else {
437             Slog.e(CarLog.TAG_HAL, "Cannot unsubscribe " + toCarPropertyLog(property));
438         }
439     }
440 
441     /**
442      * Indicates if the property passed as parameter is supported.
443      */
isPropertySupported(int propertyId)444     public boolean isPropertySupported(int propertyId) {
445         synchronized (mLock) {
446             return mAllProperties.containsKey(propertyId);
447         }
448     }
449 
450     /**
451      * Gets given property with retries.
452      *
453      * <p>If getting the property fails after all retries, it will throw
454      * {@code IllegalStateException}. If the property does not exist, it will simply return
455      * {@code null}.
456      */
getIfAvailableOrFail(int propertyId, int numberOfRetries)457     public @Nullable VehiclePropValue getIfAvailableOrFail(int propertyId, int numberOfRetries) {
458         if (!isPropertySupported(propertyId)) {
459             return null;
460         }
461         VehiclePropValue value;
462         for (int i = 0; i < numberOfRetries; i++) {
463             try {
464                 return get(propertyId);
465             } catch (ServiceSpecificException e) {
466                 Slog.e(CarLog.TAG_HAL, "Cannot get " + toCarPropertyLog(propertyId), e);
467             }
468         }
469         throw new IllegalStateException("Cannot get property: 0x" + toHexString(propertyId)
470                 + " after " + numberOfRetries + " retries");
471     }
472 
473     /**
474      * This works similar to {@link #getIfAvailableOrFail(int, int)} except that this can be called
475      * before {@code init()} is called.
476      *
477      * <p>This call will check if requested vhal property is supported by querying directly to vhal
478      * and can have worse performance. Use this only for accessing vhal properties before
479      * {@code ICarImpl.init()} phase.
480      */
getIfAvailableOrFailForEarlyStage(int propertyId, int numberOfRetries)481     public @Nullable VehiclePropValue getIfAvailableOrFailForEarlyStage(int propertyId,
482             int numberOfRetries) {
483         fetchAllPropConfigs();
484         return getIfAvailableOrFail(propertyId, numberOfRetries);
485     }
486 
487     /**
488      * Returns the property's {@link VehiclePropValue} for the property id passed as parameter and
489      * not specified area.
490      */
get(int propertyId)491     public VehiclePropValue get(int propertyId) {
492         return get(propertyId, NO_AREA);
493     }
494 
495     /**
496      * Returns the property's {@link VehiclePropValue} for the property id and area id passed as
497      * parameters.
498      */
get(int propertyId, int areaId)499     public VehiclePropValue get(int propertyId, int areaId) {
500         if (DBG) {
501             Slog.i(CarLog.TAG_HAL, "get, " + toCarPropertyLog(propertyId)
502                     + toCarAreaLog(areaId));
503         }
504         return mHalClient.getValue(createPropValue(propertyId, areaId));
505     }
506 
507     /**
508      * Returns the property object value for the class and property id passed as parameter and
509      * no area specified.
510      */
get(Class clazz, int propertyId)511     public <T> T get(Class clazz, int propertyId) {
512         return get(clazz, createPropValue(propertyId, NO_AREA));
513     }
514 
515     /**
516      * Returns the property object value for the class, property id, and area id passed as
517      * parameter.
518      */
get(Class clazz, int propertyId, int areaId)519     public <T> T get(Class clazz, int propertyId, int areaId) {
520         return get(clazz, createPropValue(propertyId, areaId));
521     }
522 
523     /**
524      * Returns the property object value for the class and requested property value passed as
525      * parameter.
526      */
527     @SuppressWarnings("unchecked")
get(Class clazz, VehiclePropValue requestedPropValue)528     public <T> T get(Class clazz, VehiclePropValue requestedPropValue) {
529         VehiclePropValue propValue;
530         propValue = mHalClient.getValue(requestedPropValue);
531 
532         if (clazz == Long.class || clazz == long.class) {
533             return (T) propValue.value.int64Values.get(0);
534         } else if (clazz == Integer.class || clazz == int.class) {
535             return (T) propValue.value.int32Values.get(0);
536         } else if (clazz == Boolean.class || clazz == boolean.class) {
537             return (T) Boolean.valueOf(propValue.value.int32Values.get(0) == 1);
538         } else if (clazz == Float.class || clazz == float.class) {
539             return (T) propValue.value.floatValues.get(0);
540         } else if (clazz == Long[].class) {
541             Long[] longArray = new Long[propValue.value.int64Values.size()];
542             return (T) propValue.value.int32Values.toArray(longArray);
543         } else if (clazz == Integer[].class) {
544             Integer[] intArray = new Integer[propValue.value.int32Values.size()];
545             return (T) propValue.value.int32Values.toArray(intArray);
546         } else if (clazz == Float[].class) {
547             Float[] floatArray = new Float[propValue.value.floatValues.size()];
548             return (T) propValue.value.floatValues.toArray(floatArray);
549         } else if (clazz == long[].class) {
550             return (T) toLongArray(propValue.value.int64Values);
551         } else if (clazz == int[].class) {
552             return (T) toIntArray(propValue.value.int32Values);
553         } else if (clazz == float[].class) {
554             return (T) toFloatArray(propValue.value.floatValues);
555         } else if (clazz == byte[].class) {
556             return (T) toByteArray(propValue.value.bytes);
557         } else if (clazz == String.class) {
558             return (T) propValue.value.stringValue;
559         } else {
560             throw new IllegalArgumentException("Unexpected type: " + clazz);
561         }
562     }
563 
564     /**
565      * Returns the vehicle's {@link VehiclePropValue} for the requested property value passed
566      * as parameter.
567      */
get(VehiclePropValue requestedPropValue)568     public VehiclePropValue get(VehiclePropValue requestedPropValue) {
569         return mHalClient.getValue(requestedPropValue);
570     }
571 
572     /**
573      * Returns the sample rate for a subscribed property. Returns {@link VehicleHal#NO_SAMPLE_RATE}
574      * if the property id passed as parameter is not linked to any subscribed property.
575      */
getSampleRate(int propId)576     public float getSampleRate(int propId) {
577         SubscribeOptions opts = mSubscribedProperties.get(propId);
578         if (opts == null) {
579             // No sample rate for this property
580             return NO_SAMPLE_RATE;
581         } else {
582             return opts.sampleRate;
583         }
584     }
585 
set(VehiclePropValue propValue)586     protected void set(VehiclePropValue propValue) {
587         mHalClient.setValue(propValue);
588     }
589 
590     @CheckResult
set(int propId)591     VehiclePropValueSetter set(int propId) {
592         return new VehiclePropValueSetter(mHalClient, propId, NO_AREA);
593     }
594 
595     @CheckResult
set(int propId, int areaId)596     VehiclePropValueSetter set(int propId, int areaId) {
597         return new VehiclePropValueSetter(mHalClient, propId, areaId);
598     }
599 
isPropertySubscribable(VehiclePropConfig config)600     static boolean isPropertySubscribable(VehiclePropConfig config) {
601         if ((config.access & VehiclePropertyAccess.READ) == 0
602                 || (config.changeMode == VehiclePropertyChangeMode.STATIC)) {
603             return false;
604         }
605         return true;
606     }
607 
608     private final ArraySet<HalServiceBase> mServicesToDispatch = new ArraySet<>();
609 
610     // should be posted to the mHandlerThread
611     @Override
onPropertyEvent(ArrayList<VehiclePropValue> propValues)612     public void onPropertyEvent(ArrayList<VehiclePropValue> propValues) {
613         synchronized (mLock) {
614             for (VehiclePropValue v : propValues) {
615                 HalServiceBase service = mPropertyHandlers.get(v.prop);
616                 if (service == null) {
617                     Slog.e(CarLog.TAG_HAL, "HalService not found for prop: 0x"
618                             + toHexString(v.prop));
619                     continue;
620                 }
621                 service.getDispatchList().add(v);
622                 mServicesToDispatch.add(service);
623                 VehiclePropertyEventInfo info = mEventLog.get(v.prop);
624                 if (info == null) {
625                     info = new VehiclePropertyEventInfo(v);
626                     mEventLog.put(v.prop, info);
627                 } else {
628                     info.addNewEvent(v);
629                 }
630             }
631         }
632         for (HalServiceBase s : mServicesToDispatch) {
633             s.onHalEvents(s.getDispatchList());
634             s.getDispatchList().clear();
635         }
636         mServicesToDispatch.clear();
637     }
638 
639     @Override
onPropertySet(VehiclePropValue value)640     public void onPropertySet(VehiclePropValue value) {
641         // No need to handle on-property-set events in HAL service yet.
642     }
643 
644     // should be posted to the mHandlerThread
645     @Override
onPropertySetError(@arPropertyManager.CarSetPropertyErrorCode int errorCode, int propId, int areaId)646     public void onPropertySetError(@CarPropertyManager.CarSetPropertyErrorCode int errorCode,
647             int propId, int areaId) {
648         Slog.e(CarLog.TAG_HAL, String.format("onPropertySetError, errorCode: %d, prop: 0x%x, "
649                 + "area: 0x%x", errorCode, propId, areaId));
650         if (propId != VehicleProperty.INVALID) {
651             HalServiceBase service = mPropertyHandlers.get(propId);
652             if (service != null) {
653                 service.onPropertySetError(propId, areaId, errorCode);
654             }
655         }
656     }
657 
658     /**
659      * Dumps HAL service info using the print writer passed as parameter.
660      */
661     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(PrintWriter writer)662     public void dump(PrintWriter writer) {
663         writer.println("**dump HAL services**");
664         for (HalServiceBase service: mAllServices) {
665             service.dump(writer);
666         }
667         // Dump all VHAL property configure.
668         dumpPropertyConfigs(writer, -1);
669         writer.printf("**All Events, now ns:%d**\n",
670                 SystemClock.elapsedRealtimeNanos());
671         for (VehiclePropertyEventInfo info : mEventLog.values()) {
672             writer.printf("event count:%d, lastEvent: ", info.mEventCount);
673             dumpVehiclePropValue(writer, info.mLastEvent);
674         }
675 
676         writer.println("**Property handlers**");
677         for (int i = 0; i < mPropertyHandlers.size(); i++) {
678             int propId = mPropertyHandlers.keyAt(i);
679             HalServiceBase service = mPropertyHandlers.valueAt(i);
680             writer.printf("Property Id: %d // 0x%x name: %s, service: %s\n", propId, propId,
681                     VehiclePropertyIds.toString(propId), service);
682         }
683     }
684 
685     /**
686      * Dumps the list of HALs.
687      */
dumpListHals(PrintWriter writer)688     public void dumpListHals(PrintWriter writer) {
689         for (HalServiceBase service: mAllServices) {
690             writer.println(service.getClass().getName());
691         }
692     }
693 
694     /**
695      * Dumps the given HALs.
696      */
dumpSpecificHals(PrintWriter writer, String... halNames)697     public void dumpSpecificHals(PrintWriter writer, String... halNames) {
698         Map<String, HalServiceBase> byName = mAllServices.stream()
699                 .collect(Collectors.toMap(s -> s.getClass().getSimpleName(), s -> s));
700         for (String halName : halNames) {
701             HalServiceBase service = byName.get(halName);
702             if (service == null) {
703                 writer.printf("No HAL named %s. Valid options are: %s\n", halName, byName.keySet());
704                 continue;
705             }
706             service.dump(writer);
707         }
708     }
709 
710     /**
711      * Dumps vehicle property values.
712      * @param writer
713      * @param propId property id, dump all properties' value if it is empty string.
714      * @param areaId areaId of the property, dump the property for all areaIds in the config
715      * if it is empty string.
716      */
dumpPropertyValueByCommend(PrintWriter writer, int propId, int areaId)717     public void dumpPropertyValueByCommend(PrintWriter writer, int propId, int areaId) {
718         if (propId == -1) {
719             writer.println("**All property values**");
720             for (VehiclePropConfig config : mAllProperties.values()) {
721                 dumpPropertyValueByConfig(writer, config);
722             }
723         } else if (areaId == -1) {
724             VehiclePropConfig config = mAllProperties.get(propId);
725             if (config == null) {
726                 writer.print("Property ");
727                 dumpPropHelper(writer, propId);
728                 writer.print(" not supported by HAL\n");
729                 return;
730             }
731             dumpPropertyValueByConfig(writer, config);
732         } else {
733             try {
734                 VehiclePropValue value = get(propId, areaId);
735                 dumpVehiclePropValue(writer, value);
736             } catch (RuntimeException e) {
737                 writer.printf("Can not get property value for property: %d // 0x%x "
738                                 + "in areaId: %d // 0x%x.\n", propId, propId, areaId, areaId);
739             }
740         }
741     }
742 
dumpPropHelper(PrintWriter pw, int propId)743     private static void dumpPropHelper(PrintWriter pw, int propId) {
744         pw.printf("Id: %d // 0x%x, name: %s ", propId, propId, VehiclePropertyIds.toString(propId));
745     }
746 
dumpPropertyValueByConfig(PrintWriter writer, VehiclePropConfig config)747     private void dumpPropertyValueByConfig(PrintWriter writer, VehiclePropConfig config) {
748         if (config.areaConfigs.isEmpty()) {
749             try {
750                 VehiclePropValue value = get(config.prop);
751                 dumpVehiclePropValue(writer, value);
752             } catch (RuntimeException e) {
753                 writer.printf("Can not get property value for property: %d // 0x%x,"
754                                 + " areaId: 0 \n", config.prop, config.prop);
755             }
756         } else {
757             for (VehicleAreaConfig areaConfig : config.areaConfigs) {
758                 int area = areaConfig.areaId;
759                 try {
760                     VehiclePropValue value = get(config.prop, area);
761                     dumpVehiclePropValue(writer, value);
762                 } catch (RuntimeException e) {
763                     writer.printf("Can not get property value for property: %d // 0x%x "
764                             + "in areaId: %d // 0x%x\n", config.prop, config.prop , area, area);
765                 }
766             }
767         }
768     }
769 
770     /**
771      * Dump VHAL property configs.
772      *
773      * @param writer
774      * @param propId Property ID. If propid is empty string, dump all properties.
775      */
dumpPropertyConfigs(PrintWriter writer, int propId)776     public void dumpPropertyConfigs(PrintWriter writer, int propId) {
777         List<VehiclePropConfig> configList;
778         synchronized (mLock) {
779             configList = new ArrayList<>(mAllProperties.values());
780         }
781 
782         if (propId == -1) {
783             writer.println("**All properties**");
784             for (VehiclePropConfig config : configList) {
785                 dumpPropertyConfigsHelp(writer, config);
786             }
787             return;
788         }
789         for (VehiclePropConfig config : configList) {
790             if (config.prop == propId) {
791                 dumpPropertyConfigsHelp(writer, config);
792                 return;
793             }
794         }
795 
796     }
797 
798     /** Dumps VehiclePropertyConfigs */
dumpPropertyConfigsHelp(PrintWriter writer, VehiclePropConfig config)799     private static void dumpPropertyConfigsHelp(PrintWriter writer, VehiclePropConfig config) {
800         writer.printf("Property:0x%x, Property name:%s, access:0x%x, changeMode:0x%x, "
801                         + "config:%s, fs min:%f, fs max:%f\n",
802                 config.prop, VehiclePropertyIds.toString(config.prop), config.access,
803                 config.changeMode, Arrays.toString(config.configArray.toArray()),
804                 config.minSampleRate, config.maxSampleRate);
805         for (VehicleAreaConfig area : config.areaConfigs) {
806             writer.printf("\tareaId:0x%x, f min:%f, f max:%f, i min:%d, i max:%d,"
807                     + " i64 min:%d, i64 max:%d\n",
808                     area.areaId, area.minFloatValue, area.maxFloatValue, area.minInt32Value,
809                     area.maxInt32Value, area.minInt64Value, area.maxInt64Value);
810         }
811     }
812 
813     /**
814      * Inject a VHAL event
815      *
816      * @param property the Vehicle property Id as defined in the HAL
817      * @param zone     Zone that this event services
818      * @param value    Data value of the event
819      * @param delayTime Add a certain duration to event timestamp
820      */
injectVhalEvent(int property, int zone, String value, int delayTime)821     public void injectVhalEvent(int property, int zone, String value, int delayTime)
822             throws NumberFormatException {
823 
824         VehiclePropValue v = createPropValueForInjecting(property, zone,
825                 Arrays.asList(value.split(DATA_DELIMITER)));
826         if (v == null) {
827             return;
828         }
829         // update timestamp
830         v.timestamp = SystemClock.elapsedRealtimeNanos() + TimeUnit.SECONDS.toNanos(delayTime);
831         mHandler.post(() -> onPropertyEvent(Lists.newArrayList(v)));
832     }
833 
834     /**
835      * Injects continuous VHAL events.
836      *
837      * @param property the Vehicle property Id as defined in the HAL
838      * @param zone Zone that this event services
839      * @param value Data value of the event
840      * @param sampleRate Sample Rate for events in Hz
841      * @param timeDurationInSec The duration for injecting events in seconds
842      */
injectContinuousVhalEvent(int property, int zone, String value, float sampleRate, long timeDurationInSec)843     public void injectContinuousVhalEvent(int property, int zone, String value,
844             float sampleRate, long timeDurationInSec) {
845 
846         VehiclePropValue v = createPropValueForInjecting(property, zone,
847                 new ArrayList<>(Arrays.asList(value.split(DATA_DELIMITER))));
848         if (v == null) {
849             return;
850         }
851         // rate in Hz
852         if (sampleRate <= 0) {
853             Slog.e(CarLog.TAG_HAL, "Inject events at an invalid sample rate: " + sampleRate);
854             return;
855         }
856         long period = (long) (1000 / sampleRate);
857         long stopTime = timeDurationInSec * 1000 + SystemClock.elapsedRealtime();
858         Timer timer = new Timer();
859         timer.schedule(new TimerTask() {
860             @Override
861             public void run() {
862                 if (stopTime < SystemClock.elapsedRealtime()) {
863                     timer.cancel();
864                     timer.purge();
865                 } else {
866                     // Avoid the fake events be covered by real Event
867                     v.timestamp = SystemClock.elapsedRealtimeNanos()
868                             + TimeUnit.SECONDS.toNanos(timeDurationInSec);
869                     mHandler.post(() -> onPropertyEvent(Lists.newArrayList(v)));
870                 }
871             }
872         }, /* delay= */0, period);
873     }
874 
875     // Returns null if the property type is unsupported.
876     @Nullable
createPropValueForInjecting(int propId, int zoneId, List<String> dataList)877     private static VehiclePropValue createPropValueForInjecting(int propId, int zoneId,
878             List<String> dataList) {
879         VehiclePropValue v = createPropValue(propId, zoneId);
880         int propertyType = propId & VehiclePropertyType.MASK;
881         // Values can be comma separated list
882         switch (propertyType) {
883             case VehiclePropertyType.BOOLEAN:
884                 boolean boolValue = Boolean.parseBoolean(dataList.get(0));
885                 v.value.int32Values.add(boolValue ? 1 : 0);
886                 break;
887             case VehiclePropertyType.INT32:
888             case VehiclePropertyType.INT32_VEC:
889                 for (String s : dataList) {
890                     v.value.int32Values.add(Integer.decode(s));
891                 }
892                 break;
893             case VehiclePropertyType.FLOAT:
894             case VehiclePropertyType.FLOAT_VEC:
895                 for (String s : dataList) {
896                     v.value.floatValues.add(Float.parseFloat(s));
897                 }
898                 break;
899             default:
900                 Slog.e(CarLog.TAG_HAL, "Property type unsupported:" + propertyType);
901                 return null;
902         }
903         return v;
904     }
905 
906     private static class VehiclePropertyEventInfo {
907         private int mEventCount;
908         private VehiclePropValue mLastEvent;
909 
VehiclePropertyEventInfo(VehiclePropValue event)910         private VehiclePropertyEventInfo(VehiclePropValue event) {
911             mEventCount = 1;
912             mLastEvent = event;
913         }
914 
addNewEvent(VehiclePropValue event)915         private void addNewEvent(VehiclePropValue event) {
916             mEventCount++;
917             mLastEvent = event;
918         }
919     }
920 
921     final class VehiclePropValueSetter {
922         final WeakReference<HalClient> mClient;
923         final VehiclePropValue mPropValue;
924 
VehiclePropValueSetter(HalClient client, int propId, int areaId)925         private VehiclePropValueSetter(HalClient client, int propId, int areaId) {
926             mClient = new WeakReference<>(client);
927             mPropValue = new VehiclePropValue();
928             mPropValue.prop = propId;
929             mPropValue.areaId = areaId;
930         }
931 
to(boolean value)932         void to(boolean value) {
933             to(value ? 1 : 0);
934         }
935 
to(int value)936         void to(int value) {
937             mPropValue.value.int32Values.add(value);
938             submit();
939         }
940 
to(int[] values)941         void to(int[] values) {
942             for (int value : values) {
943                 mPropValue.value.int32Values.add(value);
944             }
945             submit();
946         }
947 
to(Collection<Integer> values)948         void to(Collection<Integer> values) {
949             mPropValue.value.int32Values.addAll(values);
950             submit();
951         }
952 
submit()953         void submit() {
954             HalClient client =  mClient.get();
955             if (client != null) {
956                 if (DBG) {
957                     Slog.i(CarLog.TAG_HAL, "set, " + toCarPropertyLog(mPropValue.prop)
958                             + toCarAreaLog(mPropValue.areaId));
959                 }
960                 client.setValue(mPropValue);
961             }
962         }
963     }
964 
dumpVehiclePropValue(PrintWriter writer, VehiclePropValue value)965     private static void dumpVehiclePropValue(PrintWriter writer, VehiclePropValue value) {
966         String bytesString = "";
967         if (value.value.bytes.size() > MAX_BYTE_SIZE) {
968             Object[] bytes = Arrays.copyOf(value.value.bytes.toArray(), MAX_BYTE_SIZE);
969             bytesString = Arrays.toString(bytes);
970         } else {
971             bytesString = Arrays.toString(value.value.bytes.toArray());
972         }
973 
974         writer.printf("Property:0x%x, status: %d, timestamp: %d, zone: 0x%x, "
975                 + "floatValues: %s, int32Values: %s, int64Values: %s, bytes: %s, string: %s\n",
976                 value.prop, value.status, value.timestamp, value.areaId,
977                 Arrays.toString(value.value.floatValues.toArray()),
978                 Arrays.toString(value.value.int32Values.toArray()),
979                 Arrays.toString(value.value.int64Values.toArray()),
980                 bytesString, value.value.stringValue);
981     }
982 
createPropValue(int propId, int areaId)983     private static VehiclePropValue createPropValue(int propId, int areaId) {
984         VehiclePropValue propValue = new VehiclePropValue();
985         propValue.prop = propId;
986         propValue.areaId = areaId;
987         return propValue;
988     }
989 
toCarPropertyLog(int propId)990     private static String toCarPropertyLog(int propId) {
991         return String.format("property Id: %d // 0x%x, property name: %s ", propId, propId,
992                 VehiclePropertyIds.toString(propId));
993     }
994 
toCarAreaLog(int areaId)995     private static String toCarAreaLog(int areaId) {
996         return String.format("areaId: %d // 0x%x", areaId, areaId);
997     }
998 }
999