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