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