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