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 android.os.SystemClock.uptimeMillis;
20 
21 import static com.android.car.hal.property.HalPropertyDebugUtils.toAccessString;
22 import static com.android.car.hal.property.HalPropertyDebugUtils.toAreaIdString;
23 import static com.android.car.hal.property.HalPropertyDebugUtils.toAreaTypeString;
24 import static com.android.car.hal.property.HalPropertyDebugUtils.toChangeModeString;
25 import static com.android.car.hal.property.HalPropertyDebugUtils.toGroupString;
26 import static com.android.car.hal.property.HalPropertyDebugUtils.toPropertyIdString;
27 import static com.android.car.hal.property.HalPropertyDebugUtils.toValueTypeString;
28 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
29 
30 import android.annotation.CheckResult;
31 import android.annotation.Nullable;
32 import android.car.VehiclePropertyIds;
33 import android.car.builtin.os.TraceHelper;
34 import android.car.builtin.util.Slogf;
35 import android.car.feature.FeatureFlags;
36 import android.car.feature.FeatureFlagsImpl;
37 import android.content.Context;
38 import android.hardware.automotive.vehicle.StatusCode;
39 import android.hardware.automotive.vehicle.SubscribeOptions;
40 import android.hardware.automotive.vehicle.VehiclePropError;
41 import android.hardware.automotive.vehicle.VehicleProperty;
42 import android.hardware.automotive.vehicle.VehiclePropertyAccess;
43 import android.hardware.automotive.vehicle.VehiclePropertyChangeMode;
44 import android.hardware.automotive.vehicle.VehiclePropertyStatus;
45 import android.hardware.automotive.vehicle.VehiclePropertyType;
46 import android.os.Handler;
47 import android.os.HandlerThread;
48 import android.os.ParcelFileDescriptor;
49 import android.os.RemoteException;
50 import android.os.ServiceSpecificException;
51 import android.os.SystemClock;
52 import android.os.Trace;
53 import android.util.ArrayMap;
54 import android.util.ArraySet;
55 import android.util.Log;
56 import android.util.SparseArray;
57 
58 import com.android.car.CarLog;
59 import com.android.car.CarServiceUtils;
60 import com.android.car.CarSystemService;
61 import com.android.car.VehicleStub;
62 import com.android.car.VehicleStub.SubscriptionClient;
63 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
64 import com.android.car.internal.util.IndentingPrintWriter;
65 import com.android.car.internal.util.Lists;
66 import com.android.car.internal.util.PairSparseArray;
67 import com.android.internal.annotations.GuardedBy;
68 import com.android.internal.annotations.VisibleForTesting;
69 
70 import java.io.PrintWriter;
71 import java.util.ArrayList;
72 import java.util.Arrays;
73 import java.util.Collection;
74 import java.util.List;
75 import java.util.Map;
76 import java.util.Objects;
77 import java.util.Timer;
78 import java.util.TimerTask;
79 import java.util.concurrent.TimeUnit;
80 
81 /**
82  * Abstraction for vehicle HAL. This class handles interface with native HAL and does basic parsing
83  * of received data (type check). Then each event is sent to corresponding {@link HalServiceBase}
84  * implementation. It is the responsibility of {@link HalServiceBase} to convert data to
85  * corresponding Car*Service for Car*Manager API.
86  */
87 public class VehicleHal implements VehicleHalCallback, CarSystemService {
88     private static final boolean DBG = Slogf.isLoggable(CarLog.TAG_HAL, Log.DEBUG);;
89     private static final long TRACE_TAG = TraceHelper.TRACE_TAG_CAR_SERVICE;
90 
91     private static final int GLOBAL_AREA_ID = 0;
92 
93     /**
94      * If call to vehicle HAL returns StatusCode.TRY_AGAIN, we will retry to invoke that method
95      * again for this amount of milliseconds.
96      */
97     private static final int MAX_DURATION_FOR_RETRIABLE_RESULT_MS = 2000;
98 
99     private static final int SLEEP_BETWEEN_RETRIABLE_INVOKES_MS = 100;
100     private static final float PRECISION_THRESHOLD = 0.001f;
101 
102     private final HandlerThread mHandlerThread;
103     private final Handler mHandler;
104     private final SubscriptionClient mSubscriptionClient;
105 
106     private final PowerHalService mPowerHal;
107     private final PropertyHalService mPropertyHal;
108     private final InputHalService mInputHal;
109     private final VmsHalService mVmsHal;
110     private final UserHalService mUserHal;
111     private final DiagnosticHalService mDiagnosticHal;
112     private final ClusterHalService mClusterHalService;
113     private final EvsHalService mEvsHal;
114     private final TimeHalService mTimeHalService;
115     private final HalPropValueBuilder mPropValueBuilder;
116     private final VehicleStub mVehicleStub;
117 
118     private final Object mLock = new Object();
119 
120     private FeatureFlags mFeatureFlags = new FeatureFlagsImpl();
121 
122     // Only changed for test.
123     private int mMaxDurationForRetryMs = MAX_DURATION_FOR_RETRIABLE_RESULT_MS;
124     // Only changed for test.
125     private int mSleepBetweenRetryMs = SLEEP_BETWEEN_RETRIABLE_INVOKES_MS;
126 
127     /** Stores handler for each HAL property. Property events are sent to handler. */
128     @GuardedBy("mLock")
129     private final SparseArray<HalServiceBase> mPropertyHandlers = new SparseArray<>();
130     /** This is for iterating all HalServices with fixed order. */
131     @GuardedBy("mLock")
132     private final List<HalServiceBase> mAllServices;
133     @GuardedBy("mLock")
134     private PairSparseArray<RateInfo> mRateInfoByPropIdAreaId = new PairSparseArray<>();
135     @GuardedBy("mLock")
136     private final SparseArray<HalPropConfig> mAllProperties = new SparseArray<>();
137     @GuardedBy("mLock")
138     private final PairSparseArray<Integer> mAccessByPropIdAreaId = new PairSparseArray<Integer>();
139 
140     @GuardedBy("mLock")
141     private final SparseArray<VehiclePropertyEventInfo> mEventLog = new SparseArray<>();
142 
143     // Used by injectVHALEvent for testing purposes.  Delimiter for an array of data
144     private static final String DATA_DELIMITER = ",";
145 
146     /** A structure to store update rate in hz and whether to enable VUR. */
147     private static final class RateInfo {
148         public float updateRateHz;
149         public boolean enableVariableUpdateRate;
150         public float resolution;
151 
RateInfo(float updateRateHz, boolean enableVariableUpdateRate, float resolution)152         RateInfo(float updateRateHz, boolean enableVariableUpdateRate, float resolution) {
153             this.updateRateHz = updateRateHz;
154             this.enableVariableUpdateRate = enableVariableUpdateRate;
155             this.resolution = resolution;
156         }
157     }
158 
159     /* package */ static final class HalSubscribeOptions {
160         private final int mHalPropId;
161         private final int[] mAreaIds;
162         private final float mUpdateRateHz;
163         private final boolean mEnableVariableUpdateRate;
164         private final float mResolution;
165 
HalSubscribeOptions(int halPropId, int[] areaIds, float updateRateHz)166         HalSubscribeOptions(int halPropId, int[] areaIds, float updateRateHz) {
167             this(halPropId, areaIds, updateRateHz, /* enableVariableUpdateRate= */ false,
168                     /* resolution= */ 0.0f);
169         }
170 
HalSubscribeOptions(int halPropId, int[] areaIds, float updateRateHz, boolean enableVariableUpdateRate)171         HalSubscribeOptions(int halPropId, int[] areaIds, float updateRateHz,
172                 boolean enableVariableUpdateRate) {
173             this(halPropId, areaIds, updateRateHz, enableVariableUpdateRate,
174                     /* resolution= */ 0.0f);
175         }
176 
HalSubscribeOptions(int halPropId, int[] areaIds, float updateRateHz, boolean enableVariableUpdateRate, float resolution)177         HalSubscribeOptions(int halPropId, int[] areaIds, float updateRateHz,
178                             boolean enableVariableUpdateRate, float resolution) {
179             mHalPropId = halPropId;
180             mAreaIds = areaIds;
181             mUpdateRateHz = updateRateHz;
182             mEnableVariableUpdateRate = enableVariableUpdateRate;
183             mResolution = resolution;
184         }
185 
getHalPropId()186         int getHalPropId() {
187             return mHalPropId;
188         }
189 
getAreaId()190         int[] getAreaId() {
191             return mAreaIds;
192         }
193 
getUpdateRateHz()194         float getUpdateRateHz() {
195             return mUpdateRateHz;
196         }
197 
isVariableUpdateRateEnabled()198         boolean isVariableUpdateRateEnabled() {
199             return mEnableVariableUpdateRate;
200         }
getResolution()201         float getResolution() {
202             return mResolution;
203         }
204 
205         @Override
equals(Object other)206         public boolean equals(Object other) {
207             if (other == this) {
208                 return true;
209             }
210 
211             if (!(other instanceof VehicleHal.HalSubscribeOptions)) {
212                 return false;
213             }
214 
215             VehicleHal.HalSubscribeOptions o = (VehicleHal.HalSubscribeOptions) other;
216 
217             return mHalPropId == o.getHalPropId() && mUpdateRateHz == o.getUpdateRateHz()
218                     && Arrays.equals(mAreaIds, o.getAreaId())
219                     && mEnableVariableUpdateRate == o.isVariableUpdateRateEnabled()
220                     && mResolution == o.getResolution();
221         }
222 
223         @Override
toString()224         public String toString() {
225             return "HalSubscribeOptions{"
226                     + "PropertyId: " + mHalPropId
227                     + ", AreaId: " + Arrays.toString(mAreaIds)
228                     + ", UpdateRateHz: " + mUpdateRateHz
229                     + ", enableVariableUpdateRate: " + mEnableVariableUpdateRate
230                     + ", Resolution: " + mResolution
231                     + "}";
232         }
233 
234         @Override
hashCode()235         public int hashCode() {
236             return Objects.hash(mHalPropId, Arrays.hashCode(mAreaIds), mUpdateRateHz,
237                     mEnableVariableUpdateRate, mResolution);
238         }
239     }
240 
241     /**
242      * Constructs a new {@link VehicleHal} object given the {@link Context} and {@link IVehicle}
243      * both passed as parameters.
244      */
VehicleHal(Context context, VehicleStub vehicle)245     public VehicleHal(Context context, VehicleStub vehicle) {
246         this(context, /* powerHal= */ null, /* propertyHal= */ null,
247                 /* inputHal= */ null, /* vmsHal= */ null, /* userHal= */ null,
248                 /* diagnosticHal= */ null, /* clusterHalService= */ null,
249                 /* timeHalService= */ null,
250                 CarServiceUtils.getHandlerThread(VehicleHal.class.getSimpleName()), vehicle);
251     }
252 
253     /**
254      * Constructs a new {@link VehicleHal} object given the services passed as parameters.
255      * This method must be used by tests only.
256      */
257     @VisibleForTesting
VehicleHal(Context context, PowerHalService powerHal, PropertyHalService propertyHal, InputHalService inputHal, VmsHalService vmsHal, UserHalService userHal, DiagnosticHalService diagnosticHal, ClusterHalService clusterHalService, TimeHalService timeHalService, HandlerThread handlerThread, VehicleStub vehicle)258     VehicleHal(Context context,
259             PowerHalService powerHal,
260             PropertyHalService propertyHal,
261             InputHalService inputHal,
262             VmsHalService vmsHal,
263             UserHalService userHal,
264             DiagnosticHalService diagnosticHal,
265             ClusterHalService clusterHalService,
266             TimeHalService timeHalService,
267             HandlerThread handlerThread,
268             VehicleStub vehicle) {
269         // Must be initialized before HalService so that HalService could use this.
270         mPropValueBuilder = vehicle.getHalPropValueBuilder();
271         mHandlerThread = handlerThread;
272         mHandler = new Handler(mHandlerThread.getLooper());
273         mPowerHal = powerHal != null ? powerHal : new PowerHalService(context, mFeatureFlags, this);
274         mPropertyHal = propertyHal != null ? propertyHal : new PropertyHalService(this);
275         mInputHal = inputHal != null ? inputHal : new InputHalService(this);
276         mVmsHal = vmsHal != null ? vmsHal : new VmsHalService(context, this);
277         mUserHal = userHal != null ? userHal :  new UserHalService(this);
278         mDiagnosticHal = diagnosticHal != null ? diagnosticHal : new DiagnosticHalService(this);
279         mClusterHalService = clusterHalService != null
280                 ? clusterHalService : new ClusterHalService(context, this);
281         mEvsHal = new EvsHalService(this);
282         mTimeHalService = timeHalService != null
283                 ? timeHalService : new TimeHalService(context, this);
284         mAllServices = List.of(
285                 mPowerHal,
286                 mInputHal,
287                 mDiagnosticHal,
288                 mVmsHal,
289                 mUserHal,
290                 mClusterHalService,
291                 mEvsHal,
292                 mTimeHalService,
293                 // mPropertyHal must be the last so that on init/release it can be used for all
294                 // other HAL services properties.
295                 mPropertyHal);
296         mVehicleStub = vehicle;
297         mSubscriptionClient = vehicle.newSubscriptionClient(this);
298     }
299 
300     /** Sets fake feature flag for unit testing. */
301     @VisibleForTesting
setFeatureFlags(FeatureFlags fakeFeatureFlags)302     public void setFeatureFlags(FeatureFlags fakeFeatureFlags) {
303         mFeatureFlags = fakeFeatureFlags;
304     }
305 
306     @VisibleForTesting
setMaxDurationForRetryMs(int maxDurationForRetryMs)307     void setMaxDurationForRetryMs(int maxDurationForRetryMs) {
308         mMaxDurationForRetryMs = maxDurationForRetryMs;
309     }
310 
311     @VisibleForTesting
setSleepBetweenRetryMs(int sleepBetweenRetryMs)312     void setSleepBetweenRetryMs(int sleepBetweenRetryMs) {
313         mSleepBetweenRetryMs = sleepBetweenRetryMs;
314     }
315 
316     @VisibleForTesting
fetchAllPropConfigs()317     void fetchAllPropConfigs() {
318         synchronized (mLock) {
319             if (mAllProperties.size() != 0) { // already set
320                 Slogf.i(CarLog.TAG_HAL, "fetchAllPropConfigs already fetched");
321                 return;
322             }
323         }
324         HalPropConfig[] configs;
325         try {
326             configs = getAllPropConfigs();
327             if (configs == null || configs.length == 0) {
328                 Slogf.e(CarLog.TAG_HAL, "getAllPropConfigs returned empty configs");
329                 return;
330             }
331         } catch (RemoteException | ServiceSpecificException e) {
332             throw new RuntimeException("Unable to retrieve vehicle property configuration", e);
333         }
334 
335         synchronized (mLock) {
336             // Create map of all properties
337             for (HalPropConfig p : configs) {
338                 if (DBG) {
339                     Slogf.d(CarLog.TAG_HAL, "Add config for prop: 0x%x config: %s", p.getPropId(),
340                             p.toString());
341                 }
342                 mAllProperties.put(p.getPropId(), p);
343                 if (p.getAreaConfigs().length == 0) {
344                     mAccessByPropIdAreaId.put(p.getPropId(), /* areaId */ 0, p.getAccess());
345                 } else {
346                     for (HalAreaConfig areaConfig : p.getAreaConfigs()) {
347                         mAccessByPropIdAreaId.put(p.getPropId(), areaConfig.getAreaId(),
348                                 areaConfig.getAccess());
349                     }
350                 }
351             }
352         }
353     }
354 
handleOnPropertyEvent(List<HalPropValue> propValues)355     private void handleOnPropertyEvent(List<HalPropValue> propValues) {
356         synchronized (mLock) {
357             for (int i = 0; i < propValues.size(); i++) {
358                 HalPropValue v = propValues.get(i);
359                 int propId = v.getPropId();
360                 HalServiceBase service = mPropertyHandlers.get(propId);
361                 if (service == null) {
362                     Slogf.e(CarLog.TAG_HAL, "handleOnPropertyEvent: HalService not found for %s",
363                             v);
364                     continue;
365                 }
366                 service.getDispatchList().add(v);
367                 mServicesToDispatch.add(service);
368                 VehiclePropertyEventInfo info = mEventLog.get(propId);
369                 if (info == null) {
370                     info = new VehiclePropertyEventInfo(v);
371                     mEventLog.put(propId, info);
372                 } else {
373                     info.addNewEvent(v);
374                 }
375             }
376         }
377         for (HalServiceBase s : mServicesToDispatch) {
378             s.onHalEvents(s.getDispatchList());
379             s.getDispatchList().clear();
380         }
381         mServicesToDispatch.clear();
382     }
383 
handleOnPropertySetError(List<VehiclePropError> errors)384     private void handleOnPropertySetError(List<VehiclePropError> errors) {
385         SparseArray<ArrayList<VehiclePropError>> errorsByPropId =
386                 new SparseArray<ArrayList<VehiclePropError>>();
387         for (int i = 0; i < errors.size(); i++) {
388             VehiclePropError error = errors.get(i);
389             int errorCode = error.errorCode;
390             int propId = error.propId;
391             int areaId = error.areaId;
392             Slogf.w(CarLog.TAG_HAL, "onPropertySetError, errorCode: %d, prop: 0x%x, area: 0x%x",
393                     errorCode, propId, areaId);
394             if (propId == VehicleProperty.INVALID) {
395                 continue;
396             }
397 
398             ArrayList<VehiclePropError> propErrors;
399             if (errorsByPropId.get(propId) == null) {
400                 propErrors = new ArrayList<VehiclePropError>();
401                 errorsByPropId.put(propId, propErrors);
402             } else {
403                 propErrors = errorsByPropId.get(propId);
404             }
405             propErrors.add(error);
406         }
407 
408         for (int i = 0; i < errorsByPropId.size(); i++) {
409             int propId = errorsByPropId.keyAt(i);
410             HalServiceBase service;
411             synchronized (mLock) {
412                 service = mPropertyHandlers.get(propId);
413             }
414             if (service == null) {
415                 Slogf.e(CarLog.TAG_HAL,
416                         "handleOnPropertySetError: HalService not found for prop: 0x%x", propId);
417                 continue;
418             }
419 
420             ArrayList<VehiclePropError> propErrors = errorsByPropId.get(propId);
421             service.onPropertySetError(propErrors);
422         }
423     }
424 
errorMessage(String action, HalPropValue propValue, String errorMsg)425     private static String errorMessage(String action, HalPropValue propValue, String errorMsg) {
426         return String.format("Failed to %s value for: %s, error: %s", action,
427                 propValue, errorMsg);
428     }
429 
getValueWithRetry(HalPropValue value)430     private HalPropValue getValueWithRetry(HalPropValue value) {
431         return getValueWithRetry(value, /* maxRetries= */ 0);
432     }
433 
getValueWithRetry(HalPropValue value, int maxRetries)434     private HalPropValue getValueWithRetry(HalPropValue value, int maxRetries) {
435         HalPropValue result;
436         Trace.traceBegin(TRACE_TAG, "VehicleStub#getValueWithRetry");
437         try {
438             result = invokeRetriable((requestValue) -> {
439                 Trace.traceBegin(TRACE_TAG, "VehicleStub#get");
440                 try {
441                     return mVehicleStub.get(requestValue);
442                 } finally {
443                     Trace.traceEnd(TRACE_TAG);
444                 }
445             }, "get", value, mMaxDurationForRetryMs, mSleepBetweenRetryMs, maxRetries);
446         } finally {
447             Trace.traceEnd(TRACE_TAG);
448         }
449 
450         if (result == null) {
451             // If VHAL returns null result, but the status is OKAY. We treat that as NOT_AVAILABLE.
452             throw new ServiceSpecificException(StatusCode.NOT_AVAILABLE,
453                     errorMessage("get", value, "VHAL returns null for property value"));
454         }
455         return result;
456     }
457 
setValueWithRetry(HalPropValue value)458     private void setValueWithRetry(HalPropValue value)  {
459         invokeRetriable((requestValue) -> {
460             Trace.traceBegin(TRACE_TAG, "VehicleStub#set");
461             mVehicleStub.set(requestValue);
462             Trace.traceEnd(TRACE_TAG);
463             return null;
464         }, "set", value, mMaxDurationForRetryMs, mSleepBetweenRetryMs, /* maxRetries= */ 0);
465     }
466 
467     /**
468      * Inits the vhal configurations.
469      */
470     @Override
init()471     public void init() {
472         // nothing to init as everything was done on priorityInit
473     }
474 
475     /**
476      * PriorityInit for the vhal configurations.
477      */
priorityInit()478     public void priorityInit() {
479         fetchAllPropConfigs();
480 
481         // PropertyHalService will take most properties, so make it big enough.
482         ArrayMap<HalServiceBase, ArrayList<HalPropConfig>> configsForAllServices;
483         synchronized (mLock) {
484             configsForAllServices = new ArrayMap<>(mAllServices.size());
485             for (int i = 0; i < mAllServices.size(); i++) {
486                 ArrayList<HalPropConfig> configsForService = new ArrayList();
487                 HalServiceBase service = mAllServices.get(i);
488                 configsForAllServices.put(service, configsForService);
489                 int[] supportedProps = service.getAllSupportedProperties();
490                 if (supportedProps.length == 0) {
491                     for (int j = 0; j < mAllProperties.size(); j++) {
492                         Integer propId = mAllProperties.keyAt(j);
493                         if (service.isSupportedProperty(propId)) {
494                             HalPropConfig config = mAllProperties.get(propId);
495                             mPropertyHandlers.append(propId, service);
496                             configsForService.add(config);
497                         }
498                     }
499                 } else {
500                     for (int prop : supportedProps) {
501                         HalPropConfig config = mAllProperties.get(prop);
502                         if (config == null) {
503                             continue;
504                         }
505                         mPropertyHandlers.append(prop, service);
506                         configsForService.add(config);
507                     }
508                 }
509             }
510         }
511 
512         for (Map.Entry<HalServiceBase, ArrayList<HalPropConfig>> entry
513                 : configsForAllServices.entrySet()) {
514             HalServiceBase service = entry.getKey();
515             ArrayList<HalPropConfig> configsForService = entry.getValue();
516             service.takeProperties(configsForService);
517             service.init();
518         }
519     }
520 
521     /**
522      * Releases all connected services (power management service, input service, etc).
523      */
524     @Override
release()525     public void release() {
526         ArraySet<Integer> subscribedProperties = new ArraySet<>();
527         synchronized (mLock) {
528             // release in reverse order from init
529             for (int i = mAllServices.size() - 1; i >= 0; i--) {
530                 mAllServices.get(i).release();
531             }
532             for (int i = 0; i < mRateInfoByPropIdAreaId.size(); i++) {
533                 int propertyId = mRateInfoByPropIdAreaId.keyPairAt(i)[0];
534                 subscribedProperties.add(propertyId);
535             }
536             mRateInfoByPropIdAreaId.clear();
537             mAllProperties.clear();
538             mAccessByPropIdAreaId.clear();
539         }
540         for (int i = 0; i < subscribedProperties.size(); i++) {
541             try {
542                 mSubscriptionClient.unsubscribe(subscribedProperties.valueAt(i));
543             } catch (RemoteException | ServiceSpecificException e) {
544                 //  Ignore exceptions on shutdown path.
545                 Slogf.w(CarLog.TAG_HAL, "Failed to unsubscribe", e);
546             }
547         }
548         // keep the looper thread as should be kept for the whole life cycle.
549     }
550 
getDiagnosticHal()551     public DiagnosticHalService getDiagnosticHal() {
552         return mDiagnosticHal;
553     }
554 
getPowerHal()555     public PowerHalService getPowerHal() {
556         return mPowerHal;
557     }
558 
getPropertyHal()559     public PropertyHalService getPropertyHal() {
560         return mPropertyHal;
561     }
562 
getInputHal()563     public InputHalService getInputHal() {
564         return mInputHal;
565     }
566 
getUserHal()567     public UserHalService getUserHal() {
568         return mUserHal;
569     }
570 
getVmsHal()571     public VmsHalService getVmsHal() {
572         return mVmsHal;
573     }
574 
getClusterHal()575     public ClusterHalService getClusterHal() {
576         return mClusterHalService;
577     }
578 
getEvsHal()579     public EvsHalService getEvsHal() {
580         return mEvsHal;
581     }
582 
getTimeHalService()583     public TimeHalService getTimeHalService() {
584         return mTimeHalService;
585     }
586 
getHalPropValueBuilder()587     public HalPropValueBuilder getHalPropValueBuilder() {
588         return mPropValueBuilder;
589     }
590 
591     @GuardedBy("mLock")
assertServiceOwnerLocked(HalServiceBase service, int property)592     private void assertServiceOwnerLocked(HalServiceBase service, int property) {
593         if (service != mPropertyHandlers.get(property)) {
594             throw new IllegalArgumentException(String.format(
595                     "Property 0x%x  is not owned by service: %s", property, service));
596         }
597     }
598 
599     /**
600      * Subscribes given properties with sampling rate defaults to 0 and no special flags provided.
601      *
602      * @throws IllegalArgumentException thrown if property is not supported by VHAL
603      * @throws ServiceSpecificException if VHAL returns error or lost connection with VHAL.
604      * @see #subscribeProperty(HalServiceBase, int, float)
605      */
subscribeProperty(HalServiceBase service, int property)606     public void subscribeProperty(HalServiceBase service, int property)
607             throws IllegalArgumentException, ServiceSpecificException {
608         subscribeProperty(service, property, /* samplingRateHz= */ 0f);
609     }
610 
611     /**
612      * Similar to {@link #subscribeProperty(HalServiceBase, int)} except that all exceptions
613      * are caught and are logged.
614      */
subscribePropertySafe(HalServiceBase service, int property)615     public void subscribePropertySafe(HalServiceBase service, int property) {
616         try {
617             subscribeProperty(service, property);
618         } catch (IllegalArgumentException | ServiceSpecificException e) {
619             Slogf.w(CarLog.TAG_HAL, "Failed to subscribe for property: "
620                     + VehiclePropertyIds.toString(property), e);
621         }
622     }
623 
624     /**
625      * Subscribe given property. Only Hal service owning the property can subscribe it.
626      *
627      * @param service HalService that owns this property
628      * @param property property id (VehicleProperty)
629      * @param samplingRateHz sampling rate in Hz for continuous properties
630      * @throws IllegalArgumentException thrown if property is not supported by VHAL
631      * @throws ServiceSpecificException if VHAL returns error or lost connection with VHAL.
632      */
subscribeProperty(HalServiceBase service, int property, float samplingRateHz)633     public void subscribeProperty(HalServiceBase service, int property, float samplingRateHz)
634             throws IllegalArgumentException, ServiceSpecificException {
635         HalSubscribeOptions options = new HalSubscribeOptions(property, new int[0], samplingRateHz);
636         subscribeProperty(service, List.of(options));
637     }
638 
639     /**
640      * Similar to {@link #subscribeProperty(HalServiceBase, int, float)} except that all exceptions
641      * are caught and converted to logs.
642      */
subscribePropertySafe(HalServiceBase service, int property, float sampleRateHz)643     public void subscribePropertySafe(HalServiceBase service, int property, float sampleRateHz) {
644         try {
645             subscribeProperty(service, property, sampleRateHz);
646         } catch (IllegalArgumentException | ServiceSpecificException e) {
647             Slogf.w(CarLog.TAG_HAL, e, "Failed to subscribe for property: %s, sample rate: %f hz",
648                     VehiclePropertyIds.toString(property), sampleRateHz);
649         }
650     }
651 
652     /**
653      * Subscribe given property. Only Hal service owning the property can subscribe it.
654      *
655      * @param service HalService that owns this property
656      * @param halSubscribeOptions Information needed to subscribe to VHAL
657      * @throws IllegalArgumentException thrown if property is not supported by VHAL
658      * @throws ServiceSpecificException if VHAL returns error or lost connection with VHAL.
659      */
subscribeProperty(HalServiceBase service, List<HalSubscribeOptions> halSubscribeOptions)660     public void subscribeProperty(HalServiceBase service, List<HalSubscribeOptions>
661             halSubscribeOptions) throws IllegalArgumentException, ServiceSpecificException {
662         synchronized (mLock) {
663             PairSparseArray<RateInfo> previousState = cloneState(mRateInfoByPropIdAreaId);
664             SubscribeOptions[] subscribeOptions = createVhalSubscribeOptionsLocked(
665                     service, halSubscribeOptions);
666             if (subscribeOptions.length == 0) {
667                 if (DBG) {
668                     Slogf.d(CarLog.TAG_HAL,
669                             "Ignore the subscribeProperty request, SubscribeOptions is length 0");
670                 }
671                 return;
672             }
673             try {
674                 mSubscriptionClient.subscribe(subscribeOptions);
675             } catch (RemoteException e) {
676                 mRateInfoByPropIdAreaId = previousState;
677                 Slogf.w(CarLog.TAG_HAL, "Failed to subscribe, connection to VHAL failed", e);
678                 // Convert RemoteException to ServiceSpecificException so that it could be passed
679                 // back to the client.
680                 throw new ServiceSpecificException(StatusCode.INTERNAL_ERROR,
681                         "Failed to subscribe, connection to VHAL failed, error: " + e);
682             } catch (ServiceSpecificException e) {
683                 mRateInfoByPropIdAreaId = previousState;
684                 Slogf.w(CarLog.TAG_HAL, "Failed to subscribe, received error from VHAL", e);
685                 throw e;
686             }
687         }
688     }
689 
690     /**
691      * Converts {@link HalSubscribeOptions} to {@link SubscribeOptions} which is the data structure
692      * used by VHAL.
693      */
694     @GuardedBy("mLock")
createVhalSubscribeOptionsLocked(HalServiceBase service, List<HalSubscribeOptions> halSubscribeOptions)695     private SubscribeOptions[] createVhalSubscribeOptionsLocked(HalServiceBase service,
696             List<HalSubscribeOptions> halSubscribeOptions) throws IllegalArgumentException {
697         if (DBG) {
698             Slogf.d(CarLog.TAG_HAL, "creating subscribeOptions from HalSubscribeOptions of size: "
699                     + halSubscribeOptions.size());
700         }
701         List<SubscribeOptions> subscribeOptionsList = new ArrayList<>();
702         for (int i = 0; i < halSubscribeOptions.size(); i++) {
703             HalSubscribeOptions halSubscribeOption = halSubscribeOptions.get(i);
704             int property = halSubscribeOption.getHalPropId();
705             int[] areaIds = halSubscribeOption.getAreaId();
706             float samplingRateHz = halSubscribeOption.getUpdateRateHz();
707             boolean enableVariableUpdateRate = halSubscribeOption.isVariableUpdateRateEnabled();
708             float resolution = halSubscribeOption.getResolution();
709 
710             HalPropConfig config;
711             config = mAllProperties.get(property);
712 
713             if (config == null) {
714                 throw new IllegalArgumentException("subscribe error: "
715                         + toPropertyIdString(property) + " is not supported");
716             }
717 
718             if (enableVariableUpdateRate) {
719                 if (config.getChangeMode() != VehiclePropertyChangeMode.CONTINUOUS) {
720                     // enableVur should be ignored if property is not continuous, but we set it to
721                     // false to be safe.
722                     enableVariableUpdateRate = false;
723                     Slogf.w(CarLog.TAG_HAL, "VUR is always off for non-continuous property: "
724                             + toPropertyIdString(property));
725                 }
726                 if (!mFeatureFlags.variableUpdateRate()) {
727                     enableVariableUpdateRate = false;
728                     Slogf.w(CarLog.TAG_HAL, "VUR feature is not enabled, VUR is always off");
729                 }
730             }
731 
732             if (resolution != 0.0f) {
733                 if (config.getChangeMode() != VehiclePropertyChangeMode.CONTINUOUS) {
734                     // resolution should be ignored if property is not continuous, but we set it to
735                     // 0 to be safe.
736                     resolution = 0.0f;
737                     Slogf.w(CarLog.TAG_HAL, "resolution is always 0 for non-continuous property: "
738                             + toPropertyIdString(property));
739                 }
740                 if (!mFeatureFlags.subscriptionWithResolution()) {
741                     resolution = 0.0f;
742                     Slogf.w(CarLog.TAG_HAL,
743                             "Resolution feature is not enabled, resolution is always 0");
744                 }
745             }
746 
747             if (isStaticProperty(config)) {
748                 Slogf.w(CarLog.TAG_HAL, "Ignore subscribing to static property: "
749                         + toPropertyIdString(property));
750                 continue;
751             }
752 
753             if (areaIds.length == 0) {
754                 if (!isPropertySubscribable(config)) {
755                     throw new IllegalArgumentException("Property: " + toPropertyIdString(property)
756                             + " is not subscribable");
757                 }
758                 areaIds = getAllAreaIdsFromPropertyId(config);
759             } else {
760                 for (int j = 0; j < areaIds.length; j++) {
761                     Integer access = mAccessByPropIdAreaId.get(config.getPropId(), areaIds[j]);
762                     if (access == null) {
763                         throw new IllegalArgumentException(
764                                 "Cannot subscribe to " + toPropertyIdString(property)
765                                 + " at areaId " + toAreaIdString(property, areaIds[j])
766                                 + " the property does not have the requested areaId");
767                     }
768                     if (!isPropIdAreaIdReadable(config, access.intValue())) {
769                         throw new IllegalArgumentException(
770                                 "Cannot subscribe to " + toPropertyIdString(property)
771                                 + " at areaId " + toAreaIdString(property, areaIds[j])
772                                 + " the property's access mode does not contain READ");
773                     }
774                 }
775             }
776             SubscribeOptions opts = new SubscribeOptions();
777             opts.propId = property;
778             opts.sampleRate = samplingRateHz;
779             opts.enableVariableUpdateRate = enableVariableUpdateRate;
780             opts.resolution = resolution;
781             RateInfo rateInfo = new RateInfo(samplingRateHz, enableVariableUpdateRate, resolution);
782             int[] filteredAreaIds = filterAreaIdsWithSameRateInfo(property, areaIds, rateInfo);
783             opts.areaIds = filteredAreaIds;
784             if (opts.areaIds.length == 0) {
785                 if (DBG) {
786                     Slogf.d(CarLog.TAG_HAL, "property: " + VehiclePropertyIds.toString(property)
787                             + " is already subscribed at rate: " + samplingRateHz + " hz");
788                 }
789                 continue;
790             }
791             assertServiceOwnerLocked(service, property);
792             for (int j = 0; j < filteredAreaIds.length; j++) {
793                 if (DBG) {
794                     Slogf.d(CarLog.TAG_HAL, "Update subscription rate for propertyId:"
795                                     + " %s, areaId: %d, SampleRateHz: %f, enableVur: %b,"
796                                     + " resolution: %f",
797                             VehiclePropertyIds.toString(opts.propId), filteredAreaIds[j],
798                             samplingRateHz, enableVariableUpdateRate, resolution);
799                 }
800                 mRateInfoByPropIdAreaId.put(property, filteredAreaIds[j], rateInfo);
801             }
802             subscribeOptionsList.add(opts);
803         }
804         return subscribeOptionsList.toArray(new SubscribeOptions[0]);
805     }
806 
filterAreaIdsWithSameRateInfo(int property, int[] areaIds, RateInfo rateInfo)807     private int[] filterAreaIdsWithSameRateInfo(int property, int[] areaIds, RateInfo rateInfo) {
808         List<Integer> areaIdList = new ArrayList<>();
809         synchronized (mLock) {
810             for (int i = 0; i < areaIds.length; i++) {
811                 RateInfo savedRateInfo = mRateInfoByPropIdAreaId.get(property, areaIds[i]);
812 
813                 // Strict equality (==) is used here for comparing resolutions. This approach does
814                 // not introduce a margin of error through PRECISION_THRESHOLD, and thus can allow
815                 // clients to request the highest possible resolution without being limited by a
816                 // predefined threshold. This approach is assumed to be feasible under the
817                 // hypothesis that the floating point representation of numbers is consistent
818                 // across the system. That is, if two clients specify a resolution of 0.01f,
819                 // their internal representations will match, enabling an exact comparison despite
820                 // floating point inaccuracies. If this is inaccurate, we must introduce a margin
821                 // of error (ideally 1e-7 as floats can reliably represent up to 7 significant
822                 // figures, but can be higher if necessary), and update the documentation in {@link
823                 // android.car.hardware.property.Subscription.Builder#setResolution(float)}
824                 // appropriately.
825                 if (savedRateInfo != null
826                         && (Math.abs(savedRateInfo.updateRateHz - rateInfo.updateRateHz)
827                                 < PRECISION_THRESHOLD)
828                         && (savedRateInfo.enableVariableUpdateRate
829                                 == rateInfo.enableVariableUpdateRate)
830                         && savedRateInfo.resolution == rateInfo.resolution) {
831                     if (DBG) {
832                         Slogf.d(CarLog.TAG_HAL, "Property: %s is already subscribed at rate: %f hz"
833                                 + ", enableVur: %b, resolution: %f",
834                                 toPropertyIdString(property), rateInfo.updateRateHz,
835                                 rateInfo.enableVariableUpdateRate, rateInfo.resolution);
836                     }
837                     continue;
838                 }
839                 areaIdList.add(areaIds[i]);
840             }
841         }
842         return CarServiceUtils.toIntArray(areaIdList);
843     }
844 
getAllAreaIdsFromPropertyId(HalPropConfig config)845     private int[] getAllAreaIdsFromPropertyId(HalPropConfig config) {
846         HalAreaConfig[] allAreaConfigs = config.getAreaConfigs();
847         if (allAreaConfigs.length == 0) {
848             return new int[]{/* areaId= */ 0};
849         }
850         int[] areaId = new int[allAreaConfigs.length];
851         for (int i = 0; i < allAreaConfigs.length; i++) {
852             areaId[i] = allAreaConfigs[i].getAreaId();
853         }
854         return areaId;
855     }
856 
857     /**
858      * Like {@link unsubscribeProperty} except that exceptions are logged.
859      */
unsubscribePropertySafe(HalServiceBase service, int property)860     public void unsubscribePropertySafe(HalServiceBase service, int property) {
861         try {
862             unsubscribeProperty(service, property);
863         } catch (ServiceSpecificException e) {
864             Slogf.w(CarLog.TAG_SERVICE, "Failed to unsubscribe: "
865                     + toPropertyIdString(property), e);
866         }
867     }
868 
869     /**
870      * Unsubscribes from receiving notifications for the property and HAL services passed
871      * as parameters.
872      */
unsubscribeProperty(HalServiceBase service, int property)873     public void unsubscribeProperty(HalServiceBase service, int property)
874             throws ServiceSpecificException {
875         if (DBG) {
876             Slogf.d(CarLog.TAG_HAL, "unsubscribeProperty, service:" + service
877                     + ", " + toPropertyIdString(property));
878         }
879         synchronized (mLock) {
880             HalPropConfig config = mAllProperties.get(property);
881             if (config == null) {
882                 Slogf.w(CarLog.TAG_HAL, "unsubscribeProperty " + toPropertyIdString(property)
883                         + " does not exist");
884                 return;
885             }
886             if (isStaticProperty(config)) {
887                 Slogf.w(CarLog.TAG_HAL, "Unsubscribe to a static property: "
888                         + toPropertyIdString(property) + ", do nothing");
889                 return;
890             }
891             assertServiceOwnerLocked(service, property);
892             HalAreaConfig[] halAreaConfigs = config.getAreaConfigs();
893             boolean isSubscribed = false;
894             PairSparseArray<RateInfo> previousState = cloneState(mRateInfoByPropIdAreaId);
895             if (halAreaConfigs.length == 0) {
896                 int index = mRateInfoByPropIdAreaId.indexOfKeyPair(property, 0);
897                 if (hasReadAccess(config.getAccess()) && index >= 0) {
898                     mRateInfoByPropIdAreaId.removeAt(index);
899                     isSubscribed = true;
900                 }
901             } else {
902                 for (int i = 0; i < halAreaConfigs.length; i++) {
903                     if (!isPropIdAreaIdReadable(config, halAreaConfigs[i].getAccess())) {
904                         Slogf.w(CarLog.TAG_HAL,
905                                 "Cannot unsubscribe to " + toPropertyIdString(property)
906                                 + " at areaId " + toAreaIdString(property,
907                                 halAreaConfigs[i].getAreaId())
908                                 + " the property's access mode does not contain READ");
909                         continue;
910                     }
911                     int index = mRateInfoByPropIdAreaId.indexOfKeyPair(property,
912                             halAreaConfigs[i].getAreaId());
913                     if (index >= 0) {
914                         mRateInfoByPropIdAreaId.removeAt(index);
915                         isSubscribed = true;
916                     }
917                 }
918             }
919             if (!isSubscribed) {
920                 if (DBG) {
921                     Slogf.d(CarLog.TAG_HAL, "Property " + toPropertyIdString(property)
922                             + " was not subscribed, do nothing");
923                 }
924                 return;
925             }
926             try {
927                 mSubscriptionClient.unsubscribe(property);
928             } catch (RemoteException e) {
929                 mRateInfoByPropIdAreaId = previousState;
930                 Slogf.w(CarLog.TAG_HAL, "Failed to unsubscribe, connection to VHAL failed", e);
931                 throw new ServiceSpecificException(StatusCode.INTERNAL_ERROR,
932                         "Failed to unsubscribe, connection to VHAL failed, error: " + e);
933             } catch (ServiceSpecificException e) {
934                 mRateInfoByPropIdAreaId = previousState;
935                 Slogf.w(CarLog.TAG_HAL, "Failed to unsubscribe, received error from VHAL", e);
936                 throw e;
937             }
938         }
939     }
940 
941     /**
942      * Indicates if the property passed as parameter is supported.
943      */
isPropertySupported(int propertyId)944     public boolean isPropertySupported(int propertyId) {
945         synchronized (mLock) {
946             return mAllProperties.contains(propertyId);
947         }
948     }
949 
950     /**
951      * Gets given property with retries.
952      *
953      * <p>If getting the property fails after all retries, it will throw
954      * {@code IllegalStateException}. If the property is not supported, it will simply return
955      * {@code null}.
956      */
957     @Nullable
getIfSupportedOrFail(int propertyId, int maxRetries)958     public HalPropValue getIfSupportedOrFail(int propertyId, int maxRetries) {
959         if (!isPropertySupported(propertyId)) {
960             return null;
961         }
962         try {
963             return getValueWithRetry(mPropValueBuilder.build(propertyId, GLOBAL_AREA_ID),
964                     maxRetries);
965         } catch (Exception e) {
966             throw new IllegalStateException(e);
967         }
968     }
969 
970     /**
971      * This works similar to {@link #getIfSupportedOrFail(int, int)} except that this can be called
972      * before {@code init()} is called.
973      *
974      * <p>This call will check if requested vhal property is supported by querying directly to vhal
975      * and can have worse performance. Use this only for accessing vhal properties before
976      * {@code ICarImpl.init()} phase.
977      */
978     @Nullable
getIfSupportedOrFailForEarlyStage(int propertyId, int maxRetries)979     public HalPropValue getIfSupportedOrFailForEarlyStage(int propertyId, int maxRetries) {
980         fetchAllPropConfigs();
981         return getIfSupportedOrFail(propertyId, maxRetries);
982     }
983 
984     /**
985      * Returns the property's {@link HalPropValue} for the property id passed as parameter and
986      * not specified area.
987      *
988      * @throws IllegalArgumentException if argument is invalid
989      * @throws ServiceSpecificException if VHAL returns error
990      */
get(int propertyId)991     public HalPropValue get(int propertyId)
992             throws IllegalArgumentException, ServiceSpecificException {
993         return get(propertyId, GLOBAL_AREA_ID);
994     }
995 
996     /**
997      * Returns the property's {@link HalPropValue} for the property id and area id passed as
998      * parameters.
999      *
1000      * @throws IllegalArgumentException if argument is invalid
1001      * @throws ServiceSpecificException if VHAL returns error
1002      */
get(int propertyId, int areaId)1003     public HalPropValue get(int propertyId, int areaId)
1004             throws IllegalArgumentException, ServiceSpecificException {
1005         if (DBG) {
1006             Slogf.d(CarLog.TAG_HAL, "get, " + toPropertyIdString(propertyId)
1007                     + toAreaIdString(propertyId, areaId));
1008         }
1009         return getValueWithRetry(mPropValueBuilder.build(propertyId, areaId));
1010     }
1011 
1012     /**
1013      * Returns the property object value for the class and property id passed as parameter and
1014      * no area specified.
1015      *
1016      * @throws IllegalArgumentException if argument is invalid
1017      * @throws ServiceSpecificException if VHAL returns error
1018      */
get(Class clazz, int propertyId)1019     public <T> T get(Class clazz, int propertyId)
1020             throws IllegalArgumentException, ServiceSpecificException {
1021         return get(clazz, propertyId, GLOBAL_AREA_ID);
1022     }
1023 
1024     /**
1025      * Returns the property object value for the class, property id, and area id passed as
1026      * parameter.
1027      *
1028      * @throws IllegalArgumentException if argument is invalid
1029      * @throws ServiceSpecificException if VHAL returns error
1030      */
get(Class clazz, int propertyId, int areaId)1031     public <T> T get(Class clazz, int propertyId, int areaId)
1032             throws IllegalArgumentException, ServiceSpecificException {
1033         return get(clazz, mPropValueBuilder.build(propertyId, areaId));
1034     }
1035 
1036     /**
1037      * Returns the property object value for the class and requested property value passed as
1038      * parameter.
1039      *
1040      * @throws IllegalArgumentException if argument is invalid
1041      * @throws ServiceSpecificException if VHAL returns error
1042      */
1043     @SuppressWarnings("unchecked")
get(Class clazz, HalPropValue requestedPropValue)1044     public <T> T get(Class clazz, HalPropValue requestedPropValue)
1045             throws IllegalArgumentException, ServiceSpecificException {
1046         HalPropValue propValue;
1047         propValue = getValueWithRetry(requestedPropValue);
1048 
1049         if (clazz == Long.class || clazz == long.class) {
1050             Long value = propValue.getInt64Value(0);
1051             return (T) value;
1052         } else if (clazz == Integer.class || clazz == int.class) {
1053             Integer value = propValue.getInt32Value(0);
1054             return (T) value;
1055         } else if (clazz == Boolean.class || clazz == boolean.class) {
1056             Boolean value = Boolean.valueOf(propValue.getInt32Value(0) == 1);
1057             return (T) value;
1058         } else if (clazz == Float.class || clazz == float.class) {
1059             Float value = propValue.getFloatValue(0);
1060             return (T) value;
1061         } else if (clazz == Long[].class) {
1062             int size = propValue.getInt64ValuesSize();
1063             Long[] longArray = new Long[size];
1064             for (int i = 0; i < size; i++) {
1065                 longArray[i] = propValue.getInt64Value(i);
1066             }
1067             return (T) longArray;
1068         } else if (clazz == Integer[].class) {
1069             int size = propValue.getInt32ValuesSize();
1070             Integer[] intArray = new Integer[size];
1071             for (int i = 0; i < size; i++) {
1072                 intArray[i] = propValue.getInt32Value(i);
1073             }
1074             return (T) intArray;
1075         } else if (clazz == Float[].class) {
1076             int size = propValue.getFloatValuesSize();
1077             Float[] floatArray = new Float[size];
1078             for (int i = 0; i < size; i++) {
1079                 floatArray[i] = propValue.getFloatValue(i);
1080             }
1081             return (T) floatArray;
1082         } else if (clazz == long[].class) {
1083             int size = propValue.getInt64ValuesSize();
1084             long[] longArray = new long[size];
1085             for (int i = 0; i < size; i++) {
1086                 longArray[i] = propValue.getInt64Value(i);
1087             }
1088             return (T) longArray;
1089         } else if (clazz == int[].class) {
1090             int size = propValue.getInt32ValuesSize();
1091             int[] intArray = new int[size];
1092             for (int i = 0; i < size; i++) {
1093                 intArray[i] = propValue.getInt32Value(i);
1094             }
1095             return (T) intArray;
1096         } else if (clazz == float[].class) {
1097             int size = propValue.getFloatValuesSize();
1098             float[] floatArray = new float[size];
1099             for (int i = 0; i < size; i++) {
1100                 floatArray[i] = propValue.getFloatValue(i);
1101             }
1102             return (T) floatArray;
1103         } else if (clazz == byte[].class) {
1104             return (T) propValue.getByteArray();
1105         } else if (clazz == String.class) {
1106             return (T) propValue.getStringValue();
1107         } else {
1108             throw new IllegalArgumentException("Unexpected type: " + clazz);
1109         }
1110     }
1111 
1112     /**
1113      * Returns the vehicle's {@link HalPropValue} for the requested property value passed
1114      * as parameter.
1115      *
1116      * @throws IllegalArgumentException if argument is invalid
1117      * @throws ServiceSpecificException if VHAL returns error
1118      */
get(HalPropValue requestedPropValue)1119     public HalPropValue get(HalPropValue requestedPropValue)
1120             throws IllegalArgumentException, ServiceSpecificException {
1121         return getValueWithRetry(requestedPropValue);
1122     }
1123 
1124     /**
1125      * Set property.
1126      *
1127      * @throws IllegalArgumentException if argument is invalid
1128      * @throws ServiceSpecificException if VHAL returns error
1129      */
set(HalPropValue propValue)1130     public void set(HalPropValue propValue)
1131             throws IllegalArgumentException, ServiceSpecificException {
1132         setValueWithRetry(propValue);
1133     }
1134 
1135     @CheckResult
set(int propId)1136     HalPropValueSetter set(int propId) {
1137         return set(propId, GLOBAL_AREA_ID);
1138     }
1139 
1140     @CheckResult
set(int propId, int areaId)1141     HalPropValueSetter set(int propId, int areaId) {
1142         return new HalPropValueSetter(propId, areaId);
1143     }
1144 
hasReadAccess(int accessLevel)1145     private static boolean hasReadAccess(int accessLevel) {
1146         return accessLevel == VehiclePropertyAccess.READ
1147                 || accessLevel == VehiclePropertyAccess.READ_WRITE;
1148     }
1149 
isPropIdAreaIdReadable(HalPropConfig config, int areaIdAccess)1150     private static boolean isPropIdAreaIdReadable(HalPropConfig config, int areaIdAccess) {
1151         return (areaIdAccess == VehiclePropertyAccess.NONE)
1152                 ? hasReadAccess(config.getAccess()) : hasReadAccess(areaIdAccess);
1153     }
1154 
1155     /**
1156      * Returns whether the property is readable and not static.
1157      */
isPropertySubscribable(HalPropConfig config)1158     static boolean isPropertySubscribable(HalPropConfig config) {
1159         if (isStaticProperty(config)) {
1160             Slogf.w(CarLog.TAG_HAL, "Subscribe to a static property: "
1161                     + toPropertyIdString(config.getPropId()) + ", do nothing");
1162             return false;
1163         }
1164         if (config.getAreaConfigs().length == 0) {
1165             boolean hasReadAccess = hasReadAccess(config.getAccess());
1166             if (!hasReadAccess) {
1167                 Slogf.w(CarLog.TAG_HAL, "Cannot subscribe to "
1168                         + toPropertyIdString(config.getPropId())
1169                         + " the property's access mode does not contain READ");
1170             }
1171             return hasReadAccess;
1172         }
1173         for (HalAreaConfig halAreaConfig : config.getAreaConfigs()) {
1174             if (!isPropIdAreaIdReadable(config, halAreaConfig.getAccess())) {
1175                 Slogf.w(CarLog.TAG_HAL, "Cannot subscribe to "
1176                         + toPropertyIdString(config.getPropId()) + " at areaId "
1177                         + toAreaIdString(config.getPropId(), halAreaConfig.getAreaId())
1178                         + " the property's access mode does not contain READ");
1179                 return false;
1180             }
1181         }
1182         return true;
1183     }
1184 
1185     /**
1186      * Sets a passed propertyId+areaId from the shell command.
1187      *
1188      * @param propertyId Property ID
1189      * @param areaId     Area ID
1190      * @param data       Comma-separated value.
1191      */
setPropertyFromCommand(int propertyId, int areaId, String data, IndentingPrintWriter writer)1192     public void setPropertyFromCommand(int propertyId, int areaId, String data,
1193             IndentingPrintWriter writer) throws IllegalArgumentException, ServiceSpecificException {
1194         long timestampNanos = SystemClock.elapsedRealtimeNanos();
1195         HalPropValue halPropValue = createPropValueForInjecting(mPropValueBuilder, propertyId,
1196                 areaId, List.of(data.split(DATA_DELIMITER)), timestampNanos);
1197         if (halPropValue == null) {
1198             throw new IllegalArgumentException(
1199                     "Unsupported property type: propertyId=" + toPropertyIdString(propertyId)
1200                             + ", areaId=" + toAreaIdString(propertyId, areaId));
1201         }
1202         set(halPropValue);
1203     }
1204 
1205     private final ArraySet<HalServiceBase> mServicesToDispatch = new ArraySet<>();
1206 
1207     @Override
onPropertyEvent(ArrayList<HalPropValue> propValues)1208     public void onPropertyEvent(ArrayList<HalPropValue> propValues) {
1209         mHandler.post(() -> handleOnPropertyEvent(propValues));
1210     }
1211 
1212     @Override
onPropertySetError(ArrayList<VehiclePropError> errors)1213     public void onPropertySetError(ArrayList<VehiclePropError> errors) {
1214         mHandler.post(() -> handleOnPropertySetError(errors));
1215     }
1216 
1217     @Override
1218     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)1219     public void dump(IndentingPrintWriter writer) {
1220         synchronized (mLock) {
1221             writer.println("**dump HAL services**");
1222             for (int i = 0; i < mAllServices.size(); i++) {
1223                 mAllServices.get(i).dump(writer);
1224             }
1225             // Dump all VHAL property configure.
1226             dumpPropertyConfigs(writer, -1);
1227             writer.printf("**All Events, now ns:%d**\n",
1228                     SystemClock.elapsedRealtimeNanos());
1229             for (int i = 0; i < mEventLog.size(); i++) {
1230                 VehiclePropertyEventInfo info = mEventLog.valueAt(i);
1231                 writer.printf("event count:%d, lastEvent: ", info.mEventCount);
1232                 dumpPropValue(writer, info.mLastEvent);
1233             }
1234             writer.println("**Property handlers**");
1235             for (int i = 0; i < mPropertyHandlers.size(); i++) {
1236                 int propId = mPropertyHandlers.keyAt(i);
1237                 HalServiceBase service = mPropertyHandlers.valueAt(i);
1238                 writer.printf("Property Id: %d // 0x%x name: %s, service: %s\n", propId, propId,
1239                         VehiclePropertyIds.toString(propId), service);
1240             }
1241         }
1242     }
1243 
1244      /**
1245      * Dumps or debug VHAL.
1246      */
1247     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpVhal(ParcelFileDescriptor fd, List<String> options)1248     public void dumpVhal(ParcelFileDescriptor fd, List<String> options) throws RemoteException {
1249         mVehicleStub.dump(fd.getFileDescriptor(), options);
1250     }
1251 
1252     /**
1253      * Dumps the list of HALs.
1254      */
dumpListHals(PrintWriter writer)1255     public void dumpListHals(PrintWriter writer) {
1256         synchronized (mLock) {
1257             for (int i = 0; i < mAllServices.size(); i++) {
1258                 writer.println(mAllServices.get(i).getClass().getName());
1259             }
1260         }
1261     }
1262 
1263     /**
1264      * Dumps the given HALs.
1265      */
dumpSpecificHals(PrintWriter writer, String... halNames)1266     public void dumpSpecificHals(PrintWriter writer, String... halNames) {
1267         synchronized (mLock) {
1268             ArrayMap<String, HalServiceBase> byName = new ArrayMap<>();
1269             for (int index = 0; index < mAllServices.size(); index++) {
1270                 HalServiceBase halService = mAllServices.get(index);
1271                 byName.put(halService.getClass().getSimpleName(), halService);
1272             }
1273             for (String halName : halNames) {
1274                 HalServiceBase service = byName.get(halName);
1275                 if (service == null) {
1276                     writer.printf("No HAL named %s. Valid options are: %s\n",
1277                             halName, byName.keySet());
1278                     continue;
1279                 }
1280                 service.dump(writer);
1281             }
1282         }
1283     }
1284 
1285     /**
1286      * Dumps vehicle property values.
1287      *
1288      * @param propertyId property id, dump all properties' value if it is {@code -1}.
1289      * @param areaId areaId of the property, dump the property for all areaIds in the config
1290      *               if it is {@code -1}
1291      */
dumpPropertyValueByCommand(PrintWriter writer, int propertyId, int areaId)1292     public void dumpPropertyValueByCommand(PrintWriter writer, int propertyId, int areaId) {
1293         if (propertyId == -1) {
1294             writer.println("**All property values**");
1295             synchronized (mLock) {
1296                 for (int i = 0; i < mAllProperties.size(); i++) {
1297                     HalPropConfig config = mAllProperties.valueAt(i);
1298                     dumpPropertyValueByConfig(writer, config);
1299                 }
1300             }
1301         } else if (areaId == -1) {
1302             synchronized (mLock) {
1303                 HalPropConfig config = mAllProperties.get(propertyId);
1304                 if (config == null) {
1305                     writer.printf("Property: %s not supported by HAL\n",
1306                             toPropertyIdString(propertyId));
1307                     return;
1308                 }
1309                 dumpPropertyValueByConfig(writer, config);
1310             }
1311         } else {
1312             try {
1313                 HalPropValue value = get(propertyId, areaId);
1314                 dumpPropValue(writer, value);
1315             } catch (RuntimeException e) {
1316                 writer.printf("Cannot get property value for property: %s in areaId: %s.\n",
1317                         toPropertyIdString(propertyId), toAreaIdString(propertyId, areaId));
1318             }
1319         }
1320     }
1321 
1322     /**
1323      * Gets all property configs from VHAL.
1324      */
getAllPropConfigs()1325     public HalPropConfig[] getAllPropConfigs() throws RemoteException, ServiceSpecificException {
1326         return mVehicleStub.getAllPropConfigs();
1327     }
1328 
1329     /**
1330      * Gets the property config for a property, returns {@code null} if not supported.
1331      */
getPropConfig(int propId)1332     public @Nullable HalPropConfig getPropConfig(int propId) {
1333         synchronized (mLock) {
1334             return mAllProperties.get(propId);
1335         }
1336     }
1337 
1338     /**
1339      * Checks whether we are connected to AIDL VHAL: {@code true} or HIDL VHAL: {@code false}.
1340      */
isAidlVhal()1341     public boolean isAidlVhal() {
1342         return mVehicleStub.isAidlVhal();
1343     }
1344 
1345     /**
1346      * Checks if fake VHAL mode is enabled.
1347      *
1348      * @return {@code true} if car service is connected to FakeVehicleStub.
1349      */
isFakeModeEnabled()1350     public boolean isFakeModeEnabled() {
1351         return mVehicleStub.isFakeModeEnabled();
1352     }
1353 
dumpPropertyValueByConfig(PrintWriter writer, HalPropConfig config)1354     private void dumpPropertyValueByConfig(PrintWriter writer, HalPropConfig config) {
1355         int propertyId = config.getPropId();
1356         HalAreaConfig[] areaConfigs = config.getAreaConfigs();
1357         if (areaConfigs == null || areaConfigs.length == 0) {
1358             try {
1359                 HalPropValue value = get(config.getPropId());
1360                 dumpPropValue(writer, value);
1361             } catch (RuntimeException e) {
1362                 writer.printf("Can not get property value for property: %s, areaId: %s\n",
1363                         toPropertyIdString(propertyId), toAreaIdString(propertyId, /*areaId=*/0));
1364             }
1365         } else {
1366             for (HalAreaConfig areaConfig : areaConfigs) {
1367                 int areaId = areaConfig.getAreaId();
1368                 try {
1369                     HalPropValue value = get(propertyId, areaId);
1370                     dumpPropValue(writer, value);
1371                 } catch (RuntimeException e) {
1372                     writer.printf(
1373                             "Can not get property value for property: %s in areaId: %s\n",
1374                             toPropertyIdString(propertyId), toAreaIdString(propertyId, areaId));
1375                 }
1376             }
1377         }
1378     }
1379 
1380     /**
1381      * Dump VHAL property configs.
1382      * Dump all properties if {@code propertyId} is equal to {@code -1}.
1383      *
1384      * @param propertyId the property ID
1385      */
dumpPropertyConfigs(PrintWriter writer, int propertyId)1386     public void dumpPropertyConfigs(PrintWriter writer, int propertyId) {
1387         HalPropConfig[] configs;
1388         synchronized (mLock) {
1389             configs = new HalPropConfig[mAllProperties.size()];
1390             for (int i = 0; i < mAllProperties.size(); i++) {
1391                 configs[i] = mAllProperties.valueAt(i);
1392             }
1393         }
1394 
1395         if (propertyId == -1) {
1396             writer.println("**All properties**");
1397             for (HalPropConfig config : configs) {
1398                 dumpPropertyConfigsHelp(writer, config);
1399             }
1400             return;
1401         }
1402         for (HalPropConfig config : configs) {
1403             if (config.getPropId() == propertyId) {
1404                 dumpPropertyConfigsHelp(writer, config);
1405                 return;
1406             }
1407         }
1408     }
1409 
1410     /** Dumps VehiclePropertyConfigs */
dumpPropertyConfigsHelp(PrintWriter writer, HalPropConfig config)1411     private static void dumpPropertyConfigsHelp(PrintWriter writer, HalPropConfig config) {
1412         int propertyId = config.getPropId();
1413         writer.printf(
1414                 "Property:%s, group:%s, areaType:%s, valueType:%s,\n    access:%s, changeMode:%s, "
1415                         + "configArray:%s, minSampleRateHz:%f, maxSampleRateHz:%f\n",
1416                 toPropertyIdString(propertyId), toGroupString(propertyId),
1417                 toAreaTypeString(propertyId), toValueTypeString(propertyId),
1418                 toAccessString(config.getAccess()), toChangeModeString(config.getChangeMode()),
1419                 Arrays.toString(config.getConfigArray()), config.getMinSampleRate(),
1420                 config.getMaxSampleRate());
1421         if (config.getAreaConfigs() == null) {
1422             return;
1423         }
1424         for (HalAreaConfig area : config.getAreaConfigs()) {
1425             writer.printf("        areaId:%s, access:%s, f min:%f, f max:%f, i min:%d, i max:%d,"
1426                             + " i64 min:%d, i64 max:%d\n", toAreaIdString(propertyId,
1427                             area.getAreaId()), toAccessString(area.getAccess()),
1428                     area.getMinFloatValue(), area.getMaxFloatValue(), area.getMinInt32Value(),
1429                     area.getMaxInt32Value(), area.getMinInt64Value(), area.getMaxInt64Value());
1430         }
1431     }
1432 
1433     /**
1434      * Inject a VHAL event
1435      *
1436      * @param propertyId       the property ID as defined in the HAL
1437      * @param areaId           the area ID that this event services
1438      * @param value            the data value of the event
1439      * @param delayTimeSeconds add a certain duration to event timestamp
1440      */
injectVhalEvent(int propertyId, int areaId, String value, int delayTimeSeconds)1441     public void injectVhalEvent(int propertyId, int areaId, String value, int delayTimeSeconds)
1442             throws NumberFormatException {
1443         long timestampNanos = SystemClock.elapsedRealtimeNanos() + TimeUnit.SECONDS.toNanos(
1444                 delayTimeSeconds);
1445         HalPropValue v = createPropValueForInjecting(mPropValueBuilder, propertyId, areaId,
1446                 Arrays.asList(value.split(DATA_DELIMITER)), timestampNanos);
1447         if (v == null) {
1448             return;
1449         }
1450         mHandler.post(() -> handleOnPropertyEvent(Lists.newArrayList(v)));
1451     }
1452 
1453     /**
1454      * Injects continuous VHAL events.
1455      *
1456      * @param property the Vehicle property Id as defined in the HAL
1457      * @param zone the zone that this event services
1458      * @param value the data value of the event
1459      * @param sampleRate the sample rate for events in Hz
1460      * @param timeDurationInSec the duration for injecting events in seconds
1461      */
injectContinuousVhalEvent(int property, int zone, String value, float sampleRate, long timeDurationInSec)1462     public void injectContinuousVhalEvent(int property, int zone, String value,
1463             float sampleRate, long timeDurationInSec) {
1464 
1465         HalPropValue v = createPropValueForInjecting(mPropValueBuilder, property, zone,
1466                 new ArrayList<>(Arrays.asList(value.split(DATA_DELIMITER))), 0);
1467         if (v == null) {
1468             return;
1469         }
1470         // rate in Hz
1471         if (sampleRate <= 0) {
1472             Slogf.e(CarLog.TAG_HAL, "Inject events at an invalid sample rate: " + sampleRate);
1473             return;
1474         }
1475         long period = (long) (1000 / sampleRate);
1476         long stopTime = timeDurationInSec * 1000 + SystemClock.elapsedRealtime();
1477         Timer timer = new Timer();
1478         timer.schedule(new TimerTask() {
1479             @Override
1480             public void run() {
1481                 if (stopTime < SystemClock.elapsedRealtime()) {
1482                     timer.cancel();
1483                     timer.purge();
1484                 } else {
1485                     // Avoid the fake events be covered by real Event
1486                     long timestamp = SystemClock.elapsedRealtimeNanos()
1487                             + TimeUnit.SECONDS.toNanos(timeDurationInSec);
1488                     HalPropValue v = createPropValueForInjecting(mPropValueBuilder, property, zone,
1489                             new ArrayList<>(Arrays.asList(value.split(DATA_DELIMITER))), timestamp);
1490                     mHandler.post(() -> handleOnPropertyEvent(Lists.newArrayList(v)));
1491                 }
1492             }
1493         }, /* delay= */0, period);
1494     }
1495 
1496     // Returns null if the property type is unsupported.
1497     @Nullable
createPropValueForInjecting(HalPropValueBuilder builder, int propId, int zoneId, List<String> dataList, long timestamp)1498     private static HalPropValue createPropValueForInjecting(HalPropValueBuilder builder,
1499             int propId, int zoneId, List<String> dataList, long timestamp) {
1500         int propertyType = propId & VehiclePropertyType.MASK;
1501         // Values can be comma separated list
1502         switch (propertyType) {
1503             case VehiclePropertyType.BOOLEAN:
1504                 boolean boolValue = Boolean.parseBoolean(dataList.get(0));
1505                 return builder.build(propId, zoneId, timestamp, VehiclePropertyStatus.AVAILABLE,
1506                         boolValue ? 1 : 0);
1507             case VehiclePropertyType.INT64:
1508             case VehiclePropertyType.INT64_VEC:
1509                 long[] longValues = new long[dataList.size()];
1510                 for (int i = 0; i < dataList.size(); i++) {
1511                     longValues[i] = Long.decode(dataList.get(i));
1512                 }
1513                 return builder.build(propId, zoneId, timestamp, VehiclePropertyStatus.AVAILABLE,
1514                         longValues);
1515             case VehiclePropertyType.INT32:
1516             case VehiclePropertyType.INT32_VEC:
1517                 int[] intValues = new int[dataList.size()];
1518                 for (int i = 0; i < dataList.size(); i++) {
1519                     intValues[i] = Integer.decode(dataList.get(i));
1520                 }
1521                 return builder.build(propId, zoneId, timestamp, VehiclePropertyStatus.AVAILABLE,
1522                         intValues);
1523             case VehiclePropertyType.FLOAT:
1524             case VehiclePropertyType.FLOAT_VEC:
1525                 float[] floatValues = new float[dataList.size()];
1526                 for (int i = 0; i < dataList.size(); i++) {
1527                     floatValues[i] = Float.parseFloat(dataList.get(i));
1528                 }
1529                 return builder.build(propId, zoneId, timestamp, VehiclePropertyStatus.AVAILABLE,
1530                         floatValues);
1531             default:
1532                 Slogf.e(CarLog.TAG_HAL, "Property type unsupported:" + propertyType);
1533                 return null;
1534         }
1535     }
1536 
1537     private static class VehiclePropertyEventInfo {
1538         private int mEventCount;
1539         private HalPropValue mLastEvent;
1540 
VehiclePropertyEventInfo(HalPropValue event)1541         private VehiclePropertyEventInfo(HalPropValue event) {
1542             mEventCount = 1;
1543             mLastEvent = event;
1544         }
1545 
addNewEvent(HalPropValue event)1546         private void addNewEvent(HalPropValue event) {
1547             mEventCount++;
1548             mLastEvent = event;
1549         }
1550     }
1551 
1552     final class HalPropValueSetter {
1553         final int mPropId;
1554         final int mAreaId;
1555 
HalPropValueSetter(int propId, int areaId)1556         private HalPropValueSetter(int propId, int areaId) {
1557             mPropId = propId;
1558             mAreaId = areaId;
1559         }
1560 
1561         /**
1562          * Set the property to the given value.
1563          *
1564          * @throws IllegalArgumentException if argument is invalid
1565          * @throws ServiceSpecificException if VHAL returns error
1566          */
to(boolean value)1567         void to(boolean value) throws IllegalArgumentException, ServiceSpecificException {
1568             to(value ? 1 : 0);
1569         }
1570 
1571         /**
1572          * Set the property to the given value.
1573          *
1574          * @throws IllegalArgumentException if argument is invalid
1575          * @throws ServiceSpecificException if VHAL returns error
1576          */
to(int value)1577         void to(int value) throws IllegalArgumentException, ServiceSpecificException {
1578             HalPropValue propValue = mPropValueBuilder.build(mPropId, mAreaId, value);
1579             submit(propValue);
1580         }
1581 
1582         /**
1583          * Set the property to the given values.
1584          *
1585          * @throws IllegalArgumentException if argument is invalid
1586          * @throws ServiceSpecificException if VHAL returns error
1587          */
to(int[] values)1588         void to(int[] values) throws IllegalArgumentException, ServiceSpecificException {
1589             HalPropValue propValue = mPropValueBuilder.build(mPropId, mAreaId, values);
1590             submit(propValue);
1591         }
1592 
1593         /**
1594          * Set the property to the given values.
1595          *
1596          * @throws IllegalArgumentException if argument is invalid
1597          * @throws ServiceSpecificException if VHAL returns error
1598          */
to(Collection<Integer> values)1599         void to(Collection<Integer> values)
1600                 throws IllegalArgumentException, ServiceSpecificException {
1601             int[] intValues = new int[values.size()];
1602             int i = 0;
1603             for (int value : values) {
1604                 intValues[i] = value;
1605                 i++;
1606             }
1607             HalPropValue propValue = mPropValueBuilder.build(mPropId, mAreaId, intValues);
1608             submit(propValue);
1609         }
1610 
submit(HalPropValue propValue)1611         void submit(HalPropValue propValue)
1612                 throws IllegalArgumentException, ServiceSpecificException {
1613             if (DBG) {
1614                 Slogf.d(CarLog.TAG_HAL, "set - " + propValue);
1615             }
1616             setValueWithRetry(propValue);
1617         }
1618     }
1619 
dumpPropValue(PrintWriter writer, HalPropValue value)1620     private static void dumpPropValue(PrintWriter writer, HalPropValue value) {
1621         writer.println(value);
1622     }
1623 
1624     interface RetriableAction {
run(HalPropValue requestValue)1625         @Nullable HalPropValue run(HalPropValue requestValue)
1626                 throws ServiceSpecificException, RemoteException;
1627     }
1628 
invokeRetriable(RetriableAction action, String operation, HalPropValue requestValue, long maxDurationForRetryMs, long sleepBetweenRetryMs, int maxRetries)1629     private static HalPropValue invokeRetriable(RetriableAction action,
1630             String operation, HalPropValue requestValue, long maxDurationForRetryMs,
1631             long sleepBetweenRetryMs, int maxRetries)
1632             throws ServiceSpecificException, IllegalArgumentException {
1633         Retrier retrier = new Retrier(action, operation, requestValue, maxDurationForRetryMs,
1634                 sleepBetweenRetryMs, maxRetries);
1635         HalPropValue result = retrier.invokeAction();
1636         if (DBG) {
1637             Slogf.d(CarLog.TAG_HAL,
1638                     "Invoked retriable action for %s - RequestValue: %s - ResultValue: %s, for "
1639                             + "retrier: %s",
1640                     operation, requestValue, result, retrier);
1641         }
1642         return result;
1643     }
1644 
cloneState(PairSparseArray<RateInfo> state)1645     private PairSparseArray<RateInfo> cloneState(PairSparseArray<RateInfo> state) {
1646         PairSparseArray<RateInfo> cloned = new PairSparseArray<>();
1647         for (int i = 0; i < state.size(); i++) {
1648             int[] keyPair = state.keyPairAt(i);
1649             cloned.put(keyPair[0], keyPair[1], state.valueAt(i));
1650         }
1651         return cloned;
1652     }
1653 
isStaticProperty(HalPropConfig config)1654     private static boolean isStaticProperty(HalPropConfig config) {
1655         return config.getChangeMode() == VehiclePropertyChangeMode.STATIC;
1656     }
1657 
1658     private static final class Retrier {
1659         private final RetriableAction mAction;
1660         private final String mOperation;
1661         private final HalPropValue mRequestValue;
1662         private final long mMaxDurationForRetryMs;
1663         private final long mSleepBetweenRetryMs;
1664         private final int mMaxRetries;
1665         private final long mStartTime;
1666         private int mRetryCount = 0;
1667 
Retrier(RetriableAction action, String operation, HalPropValue requestValue, long maxDurationForRetryMs, long sleepBetweenRetryMs, int maxRetries)1668         Retrier(RetriableAction action,
1669                 String operation, HalPropValue requestValue, long maxDurationForRetryMs,
1670                 long sleepBetweenRetryMs, int maxRetries) {
1671             mAction = action;
1672             mOperation = operation;
1673             mRequestValue = requestValue;
1674             mMaxDurationForRetryMs = maxDurationForRetryMs;
1675             mSleepBetweenRetryMs = sleepBetweenRetryMs;
1676             mMaxRetries = maxRetries;
1677             mStartTime = uptimeMillis();
1678         }
1679 
invokeAction()1680         HalPropValue invokeAction()
1681                 throws ServiceSpecificException, IllegalArgumentException {
1682             mRetryCount++;
1683 
1684             try {
1685                 return mAction.run(mRequestValue);
1686             } catch (ServiceSpecificException e) {
1687                 switch (e.errorCode) {
1688                     case StatusCode.INVALID_ARG:
1689                         throw new IllegalArgumentException(errorMessage(mOperation, mRequestValue,
1690                             e.toString()));
1691                     case StatusCode.TRY_AGAIN:
1692                         return sleepAndTryAgain(e);
1693                     default:
1694                         throw e;
1695                 }
1696             } catch (RemoteException e) {
1697                 return sleepAndTryAgain(e);
1698             }
1699         }
1700 
toString()1701         public String toString() {
1702             return "Retrier{"
1703                     + ", Operation=" + mOperation
1704                     + ", RequestValue=" + mRequestValue
1705                     + ", MaxDurationForRetryMs=" + mMaxDurationForRetryMs
1706                     + ", SleepBetweenRetriesMs=" + mSleepBetweenRetryMs
1707                     + ", MaxRetries=" + mMaxDurationForRetryMs
1708                     + ", StartTime=" + mStartTime
1709                     + "}";
1710         }
1711 
sleepAndTryAgain(Exception e)1712         private HalPropValue sleepAndTryAgain(Exception e)
1713                 throws ServiceSpecificException, IllegalArgumentException {
1714             Slogf.d(CarLog.TAG_HAL, "trying the request: "
1715                     + toPropertyIdString(mRequestValue.getPropId()) + ", "
1716                     + toAreaIdString(mRequestValue.getPropId(), mRequestValue.getAreaId())
1717                     + " again...");
1718             try {
1719                 Thread.sleep(mSleepBetweenRetryMs);
1720             } catch (InterruptedException interruptedException) {
1721                 Thread.currentThread().interrupt();
1722                 Slogf.w(CarLog.TAG_HAL, "Thread was interrupted while waiting for vehicle HAL.",
1723                         interruptedException);
1724                 throw new ServiceSpecificException(StatusCode.INTERNAL_ERROR,
1725                         errorMessage(mOperation, mRequestValue, interruptedException.toString()));
1726             }
1727 
1728             if (mMaxRetries != 0) {
1729                 // If mMaxRetries is specified, check the retry count.
1730                 if (mMaxRetries == mRetryCount) {
1731                     throw new ServiceSpecificException(StatusCode.TRY_AGAIN,
1732                             errorMessage(mOperation, mRequestValue,
1733                                     "cannot get property after " + mRetryCount + " retires, "
1734                                     + "last exception: " + e));
1735                 }
1736             } else if ((uptimeMillis() - mStartTime) >= mMaxDurationForRetryMs) {
1737                 // Otherwise, check whether we have reached timeout.
1738                 throw new ServiceSpecificException(StatusCode.TRY_AGAIN,
1739                         errorMessage(mOperation, mRequestValue,
1740                                 "cannot get property within " + mMaxDurationForRetryMs
1741                                 + "ms, last exception: " + e));
1742             }
1743             return invokeAction();
1744         }
1745     }
1746 
1747 
1748     /**
1749      * Queries HalPropValue with list of GetVehicleHalRequest objects.
1750      *
1751      * <p>This method gets the HalPropValue using async methods.
1752      */
getAsync(List<VehicleStub.AsyncGetSetRequest> getVehicleStubAsyncRequests, VehicleStub.VehicleStubCallbackInterface getVehicleStubAsyncCallback)1753     public void getAsync(List<VehicleStub.AsyncGetSetRequest> getVehicleStubAsyncRequests,
1754             VehicleStub.VehicleStubCallbackInterface getVehicleStubAsyncCallback) {
1755         mVehicleStub.getAsync(getVehicleStubAsyncRequests, getVehicleStubAsyncCallback);
1756     }
1757 
1758     /**
1759      * Sets vehicle property value asynchronously.
1760      */
setAsync(List<VehicleStub.AsyncGetSetRequest> setVehicleStubAsyncRequests, VehicleStub.VehicleStubCallbackInterface setVehicleStubAsyncCallback)1761     public void setAsync(List<VehicleStub.AsyncGetSetRequest> setVehicleStubAsyncRequests,
1762             VehicleStub.VehicleStubCallbackInterface setVehicleStubAsyncCallback) {
1763         mVehicleStub.setAsync(setVehicleStubAsyncRequests, setVehicleStubAsyncCallback);
1764     }
1765 
1766     /**
1767      * Cancels all the on-going async requests with the given request IDs.
1768      */
cancelRequests(List<Integer> vehicleStubRequestIds)1769     public void cancelRequests(List<Integer> vehicleStubRequestIds) {
1770         mVehicleStub.cancelRequests(vehicleStubRequestIds);
1771     }
1772 }
1773