• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.fakevhal;
18 
19 import android.annotation.Nullable;
20 import android.car.builtin.util.Slogf;
21 import android.car.hardware.property.CarPropertyManager;
22 import android.hardware.automotive.vehicle.RawPropValues;
23 import android.hardware.automotive.vehicle.StatusCode;
24 import android.hardware.automotive.vehicle.SubscribeOptions;
25 import android.hardware.automotive.vehicle.VehicleArea;
26 import android.hardware.automotive.vehicle.VehicleAreaConfig;
27 import android.hardware.automotive.vehicle.VehiclePropConfig;
28 import android.hardware.automotive.vehicle.VehiclePropValue;
29 import android.hardware.automotive.vehicle.VehicleProperty;
30 import android.hardware.automotive.vehicle.VehiclePropertyAccess;
31 import android.hardware.automotive.vehicle.VehiclePropertyChangeMode;
32 import android.hardware.automotive.vehicle.VehiclePropertyType;
33 import android.os.Handler;
34 import android.os.RemoteException;
35 import android.os.ServiceSpecificException;
36 import android.os.SystemClock;
37 import android.util.ArrayMap;
38 import android.util.ArraySet;
39 import android.util.Pair;
40 import android.util.SparseArray;
41 
42 import com.android.car.CarLog;
43 import com.android.car.CarServiceUtils;
44 import com.android.car.IVehicleDeathRecipient;
45 import com.android.car.VehicleStub;
46 import com.android.car.hal.AidlHalPropConfig;
47 import com.android.car.hal.HalAreaConfig;
48 import com.android.car.hal.HalPropConfig;
49 import com.android.car.hal.HalPropValue;
50 import com.android.car.hal.HalPropValueBuilder;
51 import com.android.car.hal.VehicleHalCallback;
52 import com.android.internal.annotations.GuardedBy;
53 import com.android.internal.annotations.VisibleForTesting;
54 
55 import java.io.BufferedReader;
56 import java.io.File;
57 import java.io.FileDescriptor;
58 import java.io.FileReader;
59 import java.io.IOException;
60 import java.io.InputStream;
61 import java.util.ArrayList;
62 import java.util.List;
63 import java.util.Map;
64 import java.util.Set;
65 
66 /**
67  * FakeVehicleStub represents a fake Vhal implementation.
68  */
69 public final class FakeVehicleStub extends VehicleStub {
70 
71     private static final String TAG = CarLog.tagFor(FakeVehicleStub.class);
72     private static final List<Integer> SPECIAL_PROPERTIES = List.of(
73             VehicleProperty.VHAL_HEARTBEAT,
74             VehicleProperty.INITIAL_USER_INFO,
75             VehicleProperty.SWITCH_USER,
76             VehicleProperty.CREATE_USER,
77             VehicleProperty.REMOVE_USER,
78             VehicleProperty.USER_IDENTIFICATION_ASSOCIATION,
79             VehicleProperty.AP_POWER_STATE_REPORT,
80             VehicleProperty.AP_POWER_STATE_REQ,
81             VehicleProperty.VEHICLE_MAP_SERVICE,
82             VehicleProperty.OBD2_FREEZE_FRAME_CLEAR,
83             VehicleProperty.OBD2_FREEZE_FRAME,
84             VehicleProperty.OBD2_FREEZE_FRAME_INFO
85     );
86     private static final String FAKE_VHAL_CONFIG_DIRECTORY = "/data/system/car/fake_vhal_config/";
87     private static final String DEFAULT_CONFIG_FILE_NAME = "DefaultProperties.json";
88     private static final String FAKE_MODE_ENABLE_FILE_NAME = "ENABLE";
89     private static final int AREA_ID_GLOBAL = 0;
90 
91     private final SparseArray<ConfigDeclaration> mConfigDeclarationsByPropId;
92     private final SparseArray<HalPropConfig> mPropConfigsByPropId;
93     private final VehicleStub mRealVehicle;
94     private final HalPropValueBuilder mHalPropValueBuilder;
95     private final FakeVhalConfigParser mParser;
96     private final List<File> mCustomConfigFiles;
97     private final Handler mHandler;
98     private final List<Integer> mHvacPowerSupportedAreas;
99     private final List<Integer> mHvacPowerDependentProps;
100 
101     private final Object mLock = new Object();
102     @GuardedBy("mLock")
103     private final Map<Pair<Integer, Integer>, HalPropValue> mPropValuesByPropIdAreaId;
104     @GuardedBy("mLock")
105     private final Map<Pair<Integer, Integer>, Set<FakeVhalSubscriptionClient>>
106             mOnChangeSubscribeClientByPropIdAreaId;
107     @GuardedBy("mLock")
108     private final Map<FakeVhalSubscriptionClient,
109             Map<Pair<Integer, Integer>, ContinuousPropUpdater>> mUpdaterByPropIdAreaIdByClient;
110 
111     /**
112      * Checks if fake mode is enabled.
113      *
114      * @return {@code true} if ENABLE file exists.
115      */
doesEnableFileExist()116     public static boolean doesEnableFileExist() {
117         return new File(FAKE_VHAL_CONFIG_DIRECTORY + FAKE_MODE_ENABLE_FILE_NAME).exists();
118     }
119 
120     /**
121      * Initializes a {@link FakeVehicleStub} instance.
122      *
123      * @param realVehicle The real Vhal to be connected to handle special properties.
124      * @throws RemoteException if the remote operation through mRealVehicle fails.
125      * @throws IOException if unable to read the config file stream.
126      * @throws IllegalArgumentException if a JSONException is caught or some parsing error occurred.
127      */
FakeVehicleStub(VehicleStub realVehicle)128     public FakeVehicleStub(VehicleStub realVehicle) throws RemoteException, IOException,
129             IllegalArgumentException {
130         this(realVehicle, new FakeVhalConfigParser(), getCustomConfigFiles());
131     }
132 
133     /**
134      * Initializes a {@link FakeVehicleStub} instance with {@link FakeVhalConfigParser} for testing.
135      *
136      * @param realVehicle The real Vhal to be connected to handle special properties.
137      * @param parser The parser to parse config files.
138      * @param customConfigFiles The {@link List} of custom config files.
139      * @throws RemoteException if failed to get configs for special property from real Vehicle HAL.
140      * @throws IOException if unable to read the config file stream.
141      * @throws IllegalArgumentException if a JSONException is caught or some parsing error occurred.
142      */
143     @VisibleForTesting
FakeVehicleStub(VehicleStub realVehicle, FakeVhalConfigParser parser, List<File> customConfigFiles)144     FakeVehicleStub(VehicleStub realVehicle, FakeVhalConfigParser parser,
145             List<File> customConfigFiles) throws RemoteException, IOException,
146             IllegalArgumentException {
147         mRealVehicle = realVehicle;
148         mHalPropValueBuilder = new HalPropValueBuilder(/* isAidl= */ true);
149         mParser = parser;
150         mCustomConfigFiles = customConfigFiles;
151         mConfigDeclarationsByPropId = parseConfigFiles();
152         mPropConfigsByPropId = extractPropConfigs(mConfigDeclarationsByPropId);
153         mPropValuesByPropIdAreaId = extractPropValues(mConfigDeclarationsByPropId);
154         mHvacPowerSupportedAreas = getHvacPowerSupportedAreaId();
155         mHvacPowerDependentProps = getHvacPowerDependentProps();
156         mHandler = new Handler(CarServiceUtils.getHandlerThread(getClass().getSimpleName())
157                 .getLooper());
158         mOnChangeSubscribeClientByPropIdAreaId = new ArrayMap<>();
159         mUpdaterByPropIdAreaIdByClient = new ArrayMap<>();
160         Slogf.d(TAG, "A FakeVehicleStub instance is created.");
161     }
162 
163     /**
164      * FakeVehicleStub is neither an AIDL VHAL nor HIDL VHAL. But it acts like an AIDL VHAL.
165      *
166      * @return {@code true} since FakeVehicleStub acts like an AIDL VHAL.
167      */
168     @Override
isAidlVhal()169     public boolean isAidlVhal() {
170         return true;
171     }
172 
173     /**
174      * Gets {@link HalPropValueBuilder} for building a {@link HalPropValue}.
175      *
176      * @return a builder to build a {@link HalPropValue}.
177      */
178     @Override
getHalPropValueBuilder()179     public HalPropValueBuilder getHalPropValueBuilder() {
180         return mHalPropValueBuilder;
181     }
182 
183     /**
184      * Gets properties asynchronously.
185      *
186      * @param getVehicleStubAsyncRequests The async request list.
187      * @param getVehicleStubAsyncCallback The callback for getting property values.
188      */
189     @Override
getAsync(List<AsyncGetSetRequest> getVehicleStubAsyncRequests, VehicleStubCallbackInterface getVehicleStubAsyncCallback)190     public void getAsync(List<AsyncGetSetRequest> getVehicleStubAsyncRequests,
191             VehicleStubCallbackInterface getVehicleStubAsyncCallback) {
192         List<GetVehicleStubAsyncResult> onGetAsyncResultList = new ArrayList<>();
193         for (int i = 0; i < getVehicleStubAsyncRequests.size(); i++) {
194             AsyncGetSetRequest request = getVehicleStubAsyncRequests.get(i);
195             GetVehicleStubAsyncResult result;
196             try {
197                 HalPropValue halPropValue = get(request.getHalPropValue());
198                 result = new GetVehicleStubAsyncResult(request.getServiceRequestId(),
199                     halPropValue);
200                 if (halPropValue == null) {
201                     result = new GetVehicleStubAsyncResult(request.getServiceRequestId(),
202                         CarPropertyManager.STATUS_ERROR_NOT_AVAILABLE, /* vendorErrorCode= */ 0);
203                 }
204             } catch (ServiceSpecificException e) {
205                 int[] errorCodes = convertHalToCarPropertyManagerError(e.errorCode);
206                 result = new GetVehicleStubAsyncResult(request.getServiceRequestId(), errorCodes[0],
207                         errorCodes[1]);
208             } catch (RemoteException e) {
209                 result = new GetVehicleStubAsyncResult(request.getServiceRequestId(),
210                     CarPropertyManager.STATUS_ERROR_INTERNAL_ERROR, /* vendorErrorCode= */ 0);
211             }
212             onGetAsyncResultList.add(result);
213         }
214         mHandler.post(() -> {
215             getVehicleStubAsyncCallback.onGetAsyncResults(onGetAsyncResultList);
216         });
217     }
218 
219     /**
220      * Sets properties asynchronously.
221      *
222      * @param setVehicleStubAsyncRequests The async request list.
223      * @param setVehicleStubAsyncCallback the callback for setting property values.
224      */
225     @Override
setAsync(List<AsyncGetSetRequest> setVehicleStubAsyncRequests, VehicleStubCallbackInterface setVehicleStubAsyncCallback)226     public void setAsync(List<AsyncGetSetRequest> setVehicleStubAsyncRequests,
227             VehicleStubCallbackInterface setVehicleStubAsyncCallback) {
228         List<SetVehicleStubAsyncResult> onSetAsyncResultsList = new ArrayList<>();
229         for (int i = 0; i < setVehicleStubAsyncRequests.size(); i++) {
230             AsyncGetSetRequest setRequest = setVehicleStubAsyncRequests.get(i);
231             int serviceRequestId = setRequest.getServiceRequestId();
232             SetVehicleStubAsyncResult result;
233             try {
234                 set(setRequest.getHalPropValue());
235                 result = new SetVehicleStubAsyncResult(serviceRequestId);
236             } catch (RemoteException e) {
237                 result = new SetVehicleStubAsyncResult(serviceRequestId,
238                         CarPropertyManager.STATUS_ERROR_INTERNAL_ERROR, /* vendorErrorCode= */ 0);
239             } catch (ServiceSpecificException e) {
240                 int[] errorCodes = convertHalToCarPropertyManagerError(e.errorCode);
241                 result = new SetVehicleStubAsyncResult(serviceRequestId, errorCodes[0],
242                         errorCodes[1]);
243             }
244             onSetAsyncResultsList.add(result);
245         }
246         mHandler.post(() -> {
247             setVehicleStubAsyncCallback.onSetAsyncResults(onSetAsyncResultsList);
248         });
249     }
250 
251     /**
252      * Checks if FakeVehicleStub connects to a valid Vhal.
253      *
254      * @return {@code true} if connects to a valid Vhal.
255      */
256     @Override
isValid()257     public boolean isValid() {
258         return mRealVehicle.isValid();
259     }
260 
261     /**
262      * Gets the interface descriptor for the connecting vehicle HAL.
263      *
264      * @throws IllegalStateException If unable to get the descriptor.
265      */
266     @Override
getInterfaceDescriptor()267     public String getInterfaceDescriptor() throws IllegalStateException {
268         return "com.android.car.hal.fakevhal.FakeVehicleStub";
269     }
270 
271     /**
272      * Registers a death recipient that would be called when Vhal died.
273      *
274      * @param recipient A death recipient.
275      * @throws IllegalStateException If unable to register the death recipient.
276      */
277     @Override
linkToDeath(IVehicleDeathRecipient recipient)278     public void linkToDeath(IVehicleDeathRecipient recipient) throws IllegalStateException {
279         mRealVehicle.linkToDeath(recipient);
280     }
281 
282     /**
283      * Unlinks a previously linked death recipient.
284      *
285      * @param recipient A previously linked death recipient.
286      */
287     @Override
unlinkToDeath(IVehicleDeathRecipient recipient)288     public void unlinkToDeath(IVehicleDeathRecipient recipient) {
289         mRealVehicle.unlinkToDeath(recipient);
290     }
291 
292     /**
293      * Gets all property configs.
294      *
295      * @return an array of all property configs.
296      */
297     @Override
getAllPropConfigs()298     public HalPropConfig[] getAllPropConfigs() {
299         HalPropConfig[] propConfigs = new HalPropConfig[mPropConfigsByPropId.size()];
300         for (int i = 0; i < mPropConfigsByPropId.size(); i++) {
301             propConfigs[i] = mPropConfigsByPropId.valueAt(i);
302         }
303         return propConfigs;
304     }
305 
306     /**
307      * Gets a new {@code SubscriptionClient} that could be used to subscribe/unsubscribe.
308      *
309      * @param callback A callback that could be used to receive events.
310      * @return a {@code SubscriptionClient} that could be used to subscribe/unsubscribe.
311      */
312     @Override
newSubscriptionClient(VehicleHalCallback callback)313     public SubscriptionClient newSubscriptionClient(VehicleHalCallback callback) {
314         return new FakeVhalSubscriptionClient(callback,
315                 mRealVehicle.newSubscriptionClient(callback));
316     }
317 
318     /**
319      * Gets a property value.
320      *
321      * @param requestedPropValue The property to get.
322      * @return the property value.
323      * @throws RemoteException if getting value for special props through real vehicle HAL fails.
324      * @throws ServiceSpecificException if propId or areaId is not supported.
325      */
326     @Override
327     @Nullable
get(HalPropValue requestedPropValue)328     public HalPropValue get(HalPropValue requestedPropValue) throws RemoteException,
329             ServiceSpecificException {
330         int propId = requestedPropValue.getPropId();
331         checkPropIdSupported(propId);
332         int areaId = isPropertyGlobal(propId) ? AREA_ID_GLOBAL : requestedPropValue.getAreaId();
333         checkAreaIdSupported(propId, areaId);
334 
335         // For HVAC power dependent properties, check if HVAC_POWER_ON is on.
336         if (isHvacPowerDependentProp(propId)) {
337             checkPropAvailable(propId, areaId);
338         }
339         // Check access permission.
340         int access = mPropConfigsByPropId.get(propId).getAccess();
341         if (access != VehiclePropertyAccess.READ && access != VehiclePropertyAccess.READ_WRITE) {
342             throw new ServiceSpecificException(StatusCode.ACCESS_DENIED, "This property " + propId
343                     + " doesn't have read permission.");
344         }
345 
346         if (isSpecialProperty(propId)) {
347             return mRealVehicle.get(requestedPropValue);
348         }
349 
350         // PropId config exists but the value map doesn't have this propId, this may be caused by:
351         // 1. This property is a global property, and it doesn't have default prop value.
352         // 2. This property has area configs, and it has neither default prop value nor area value.
353         Pair<Integer, Integer> propIdAreaId = Pair.create(propId, areaId);
354         synchronized (mLock) {
355             if (!mPropValuesByPropIdAreaId.containsKey(propIdAreaId)) {
356                 if (isPropertyGlobal(propId)) {
357                     throw new ServiceSpecificException(StatusCode.NOT_AVAILABLE,
358                         "propId: " + propId + " has no property value.");
359                 }
360                 throw new ServiceSpecificException(StatusCode.NOT_AVAILABLE,
361                     "propId: " + propId + ", areaId: " + areaId + " has no property value.");
362             }
363             return mPropValuesByPropIdAreaId.get(propIdAreaId);
364         }
365     }
366 
367     /**
368      * Sets a property value.
369      *
370      * @param propValue The property to set.
371      * @throws RemoteException if setting value for special props through real vehicle HAL fails.
372      * @throws ServiceSpecificException if propId or areaId is not supported.
373      */
374     @Override
set(HalPropValue propValue)375     public void set(HalPropValue propValue) throws RemoteException,
376                 ServiceSpecificException {
377         int propId = propValue.getPropId();
378         checkPropIdSupported(propId);
379         int areaId = isPropertyGlobal(propId) ? AREA_ID_GLOBAL : propValue.getAreaId();
380         checkAreaIdSupported(propId, areaId);
381 
382         // For HVAC power dependent properties, check if HVAC_POWER_ON is on.
383         if (isHvacPowerDependentProp(propId)) {
384             checkPropAvailable(propId, areaId);
385         }
386         // Check access permission.
387         int access = mPropConfigsByPropId.get(propId).getAccess();
388         if (access != VehiclePropertyAccess.WRITE && access != VehiclePropertyAccess.READ_WRITE) {
389             throw new ServiceSpecificException(StatusCode.ACCESS_DENIED, "This property " + propId
390                     + " doesn't have write permission.");
391         }
392 
393         if (isSpecialProperty(propValue.getPropId())) {
394             mRealVehicle.set(propValue);
395             return;
396         }
397 
398         RawPropValues rawPropValues = ((VehiclePropValue) propValue.toVehiclePropValue()).value;
399 
400         // Check if the set values are within the value config range.
401         if (!withinRange(propId, areaId, rawPropValues)) {
402             throw new ServiceSpecificException(StatusCode.INVALID_ARG,
403                     "The set value is outside the range.");
404         }
405 
406         HalPropValue updatedValue = buildHalPropValue(propId, areaId,
407                 SystemClock.elapsedRealtimeNanos(), rawPropValues);
408         Pair<Integer, Integer> propIdAreaId = Pair.create(propId, areaId);
409         Set<FakeVhalSubscriptionClient> clients = new ArraySet<>();
410 
411         synchronized (mLock) {
412             mPropValuesByPropIdAreaId.put(propIdAreaId, updatedValue);
413             if (mOnChangeSubscribeClientByPropIdAreaId.containsKey(propIdAreaId)) {
414                 clients = mOnChangeSubscribeClientByPropIdAreaId.get(propIdAreaId);
415             }
416         }
417         clients.forEach(c -> c.onPropertyEvent(updatedValue));
418     }
419 
420     /**
421      * Dumps VHAL debug information.
422      *
423      * @param fd The file descriptor to print output.
424      * @param args Optional additional arguments for the debug command. Can be empty.
425      * @throws RemoteException if the remote operation fails.
426      * @throws ServiceSpecificException if VHAL returns service specific error.
427      */
428     @Override
dump(FileDescriptor fd, List<String> args)429     public void dump(FileDescriptor fd, List<String> args) throws RemoteException,
430             ServiceSpecificException {
431         mRealVehicle.dump(fd, args);
432     }
433 
434     /**
435      * @return {@code true} if car service is connected to FakeVehicleStub.
436      */
437     @Override
isFakeModeEnabled()438     public boolean isFakeModeEnabled() {
439         return true;
440     }
441 
442     private final class FakeVhalSubscriptionClient implements SubscriptionClient {
443         private final VehicleHalCallback mCallBack;
444         private final SubscriptionClient mRealClient;
445 
FakeVhalSubscriptionClient(VehicleHalCallback callback, SubscriptionClient realVehicleClient)446         FakeVhalSubscriptionClient(VehicleHalCallback callback,
447                 SubscriptionClient realVehicleClient) {
448             mCallBack = callback;
449             mRealClient = realVehicleClient;
450             Slogf.d(TAG, "A FakeVhalSubscriptionClient instance is created.");
451         }
452 
onPropertyEvent(HalPropValue value)453         public void onPropertyEvent(HalPropValue value) {
454             mCallBack.onPropertyEvent(new ArrayList(List.of(value)));
455         }
456 
457         @Override
subscribe(SubscribeOptions[] options)458         public void subscribe(SubscribeOptions[] options) throws RemoteException {
459             FakeVehicleStub.this.subscribe(this, options);
460         }
461 
462         @Override
unsubscribe(int propId)463         public void unsubscribe(int propId) throws RemoteException {
464             // Check if this propId is supported.
465             checkPropIdSupported(propId);
466             // Check if this propId is a special property.
467             if (isSpecialProperty(propId)) {
468                 mRealClient.unsubscribe(propId);
469                 return;
470             }
471             FakeVehicleStub.this.unsubscribe(this, propId);
472         }
473     }
474 
475     private final class ContinuousPropUpdater implements Runnable {
476         private final FakeVhalSubscriptionClient mClient;
477         private final int mPropId;
478         private final int mAreaId;
479         private final float mSampleRate;
480         private final Object mUpdaterLock = new Object();
481         @GuardedBy("mUpdaterLock")
482         private boolean mStopped;
483 
ContinuousPropUpdater(FakeVhalSubscriptionClient client, int propId, int areaId, float sampleRate)484         ContinuousPropUpdater(FakeVhalSubscriptionClient client, int propId, int areaId,
485                 float sampleRate) {
486             mClient = client;
487             mPropId = propId;
488             mAreaId = areaId;
489             mSampleRate = sampleRate;
490             mHandler.post(this);
491             Slogf.d(TAG, "A runnable updater is created for CONTINUOUS property.");
492         }
493 
494         @Override
run()495         public void run() {
496             synchronized (mUpdaterLock) {
497                 if (mStopped) {
498                     return;
499                 }
500                 mHandler.postDelayed(this, (long) (1000 / mSampleRate));
501             }
502 
503             // It is possible that mStopped is updated to true at the same time. We will have one
504             // additional event here. We cannot hold lock because we don't want to hold lock while
505             // calling client's callback;
506             mClient.onPropertyEvent(updateTimeStamp(mPropId, mAreaId));
507         }
508 
stop()509         public void stop() {
510             synchronized (mUpdaterLock) {
511                 mStopped = true;
512                 mHandler.removeCallbacks(this);
513             }
514         }
515     }
516 
517     /**
518      * Parses default and custom config files.
519      *
520      * @return a {@link SparseArray} mapped from propId to its {@link ConfigDeclaration}.
521      * @throws IOException if FakeVhalConfigParser throws IOException.
522      * @throws IllegalArgumentException If default file doesn't exist or parsing errors occurred.
523      */
parseConfigFiles()524     private SparseArray<ConfigDeclaration> parseConfigFiles() throws IOException,
525             IllegalArgumentException {
526         InputStream defaultConfigInputStream = this.getClass().getClassLoader()
527                 .getResourceAsStream(DEFAULT_CONFIG_FILE_NAME);
528         SparseArray<ConfigDeclaration> configDeclarations;
529         SparseArray<ConfigDeclaration> customConfigDeclarations;
530         // Parse default config file.
531         configDeclarations = mParser.parseJsonConfig(defaultConfigInputStream);
532 
533         // Parse all custom config files.
534         for (int i = 0; i < mCustomConfigFiles.size(); i++) {
535             File customFile = mCustomConfigFiles.get(i);
536             try {
537                 customConfigDeclarations = mParser.parseJsonConfig(customFile);
538             } catch (Exception e) {
539                 Slogf.w(TAG, e, "Failed to parse custom config file: %s",
540                         customFile.getPath());
541                 continue;
542             }
543             combineConfigDeclarations(configDeclarations, customConfigDeclarations);
544         }
545 
546         return configDeclarations;
547     }
548 
549     /**
550      * Gets all custom config files which are going to be parsed.
551      *
552      * @return a {@link List} of files.
553      */
getCustomConfigFiles()554     private static List<File> getCustomConfigFiles() throws IOException {
555         List<File> customConfigFileList = new ArrayList<>();
556         File file = new File(FAKE_VHAL_CONFIG_DIRECTORY + FAKE_MODE_ENABLE_FILE_NAME);
557         try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
558             String line;
559             while ((line = reader.readLine()) != null) {
560                 customConfigFileList.add(new File(FAKE_VHAL_CONFIG_DIRECTORY
561                         + line.replaceAll("\\.\\.", "").replaceAll("\\/", "")));
562             }
563         }
564         return customConfigFileList;
565     }
566 
567     /**
568      * Combines parsing results together.
569      *
570      * @param result The {@link SparseArray} to gets new property configs.
571      * @param newList The {@link SparseArray} whose property config will be added to result.
572      * @return a combined {@link SparseArray} result.
573      */
combineConfigDeclarations( SparseArray<ConfigDeclaration> result, SparseArray<ConfigDeclaration> newList)574     private static SparseArray<ConfigDeclaration> combineConfigDeclarations(
575             SparseArray<ConfigDeclaration> result, SparseArray<ConfigDeclaration> newList) {
576         for (int i = 0; i < newList.size(); i++) {
577             result.put(newList.keyAt(i), newList.valueAt(i));
578         }
579         return result;
580     }
581 
582     /**
583      * Extracts {@link HalPropConfig} for all properties from the parsing result and real VHAL.
584      *
585      * @param configDeclarationsByPropId The parsing result.
586      * @throws RemoteException if getting configs for special props through real vehicle HAL fails.
587      * @return a {@link SparseArray} mapped from propId to its configs.
588      */
extractPropConfigs(SparseArray<ConfigDeclaration> configDeclarationsByPropId)589     private SparseArray<HalPropConfig> extractPropConfigs(SparseArray<ConfigDeclaration>
590             configDeclarationsByPropId) throws RemoteException {
591         SparseArray<HalPropConfig> propConfigsByPropId = new SparseArray<>();
592         for (int i = 0; i < configDeclarationsByPropId.size(); i++) {
593             VehiclePropConfig vehiclePropConfig = configDeclarationsByPropId.valueAt(i).getConfig();
594             propConfigsByPropId.put(vehiclePropConfig.prop,
595                     new AidlHalPropConfig(vehiclePropConfig));
596         }
597         // If the special property is supported in this configuration, then override with configs
598         // from real vehicle.
599         overrideConfigsForSpecialProp(propConfigsByPropId);
600         return propConfigsByPropId;
601     }
602 
603     /**
604      * Extracts {@link HalPropValue} for all properties from the parsing result.
605      *
606      * @param configDeclarationsByPropId The parsing result.
607      * @return a {@link Map} mapped from propId, areaId to its value.
608      */
extractPropValues( SparseArray<ConfigDeclaration> configDeclarationsByPropId)609     private Map<Pair<Integer, Integer>, HalPropValue> extractPropValues(
610             SparseArray<ConfigDeclaration> configDeclarationsByPropId) {
611         long timestamp = SystemClock.elapsedRealtimeNanos();
612         Map<Pair<Integer, Integer>, HalPropValue> propValuesByPropIdAreaId = new ArrayMap<>();
613         for (int i = 0; i < configDeclarationsByPropId.size(); i++) {
614             // Get configDeclaration of a property.
615             ConfigDeclaration configDeclaration = configDeclarationsByPropId.valueAt(i);
616             // Get propId.
617             int propId = configDeclaration.getConfig().prop;
618             // Get areaConfigs array to know what areaIds are supported.
619             VehicleAreaConfig[] areaConfigs = configDeclaration.getConfig().areaConfigs;
620             // Get default rawPropValues.
621             RawPropValues defaultRawPropValues = configDeclaration.getInitialValue();
622             // Get area rawPropValues map.
623             SparseArray<RawPropValues> rawPropValuesByAreaId = configDeclaration
624                     .getInitialAreaValuesByAreaId();
625 
626             // If this property is a global property.
627             if (isPropertyGlobal(propId)) {
628                 // If no default prop value exists, this propId won't be added to the
629                 // propValuesByAreaIdByPropId map. Get this propId value will throw
630                 // ServiceSpecificException with StatusCode.INVALID_ARG.
631                 if (defaultRawPropValues == null) {
632                     continue;
633                 }
634                 // Set the areaId to be 0.
635                 propValuesByPropIdAreaId.put(Pair.create(propId, AREA_ID_GLOBAL),
636                         buildHalPropValue(propId, AREA_ID_GLOBAL, timestamp, defaultRawPropValues));
637                 continue;
638             }
639 
640             // If this property has supported area configs.
641             for (int j = 0; j < areaConfigs.length; j++) {
642                 // Get areaId.
643                 int areaId = areaConfigs[j].areaId;
644                 // Set default area prop value to be defaultRawPropValues. If area value doesn't
645                 // exist, then use the property default value.
646                 RawPropValues areaRawPropValues = defaultRawPropValues;
647                 // If area prop value exists, then use area value.
648                 if (rawPropValuesByAreaId.contains(areaId)) {
649                     areaRawPropValues = rawPropValuesByAreaId.get(areaId);
650                 }
651                 // Neither area prop value nor default prop value exists. This propId won't be in
652                 // the value map. Get this propId value will throw ServiceSpecificException
653                 // with StatusCode.INVALID_ARG.
654                 if (areaRawPropValues == null) {
655                     continue;
656                 }
657                 propValuesByPropIdAreaId.put(Pair.create(propId, areaId), buildHalPropValue(propId,
658                         areaId, timestamp, areaRawPropValues));
659             }
660         }
661         return propValuesByPropIdAreaId;
662     }
663 
664     /**
665      * Gets all supported areaIds by HVAC_POWER_ON.
666      *
667      * @return a {@link List} of areaIds supported by HVAC_POWER_ON.
668      */
getHvacPowerSupportedAreaId()669     private List<Integer> getHvacPowerSupportedAreaId() {
670         try {
671             checkPropIdSupported(VehicleProperty.HVAC_POWER_ON);
672             return getAllSupportedAreaId(VehicleProperty.HVAC_POWER_ON);
673         } catch (Exception e) {
674             Slogf.i(TAG, "%d is not supported.", VehicleProperty.HVAC_POWER_ON);
675             return new ArrayList<>();
676         }
677     }
678 
679     /**
680      * Gets the HVAC power dependent properties from HVAC_POWER_ON config array.
681      *
682      * @return a {@link List} of HVAC properties which are dependent to HVAC_POWER_ON.
683      */
getHvacPowerDependentProps()684     private List<Integer> getHvacPowerDependentProps() {
685         List<Integer> hvacProps = new ArrayList<>();
686         try {
687             checkPropIdSupported(VehicleProperty.HVAC_POWER_ON);
688             int[] configArray = mPropConfigsByPropId.get(VehicleProperty.HVAC_POWER_ON)
689                     .getConfigArray();
690             for (int propId : configArray) {
691                 hvacProps.add(propId);
692             }
693         } catch (Exception e) {
694             Slogf.i(TAG, "%d is not supported.", VehicleProperty.HVAC_POWER_ON);
695         }
696         return hvacProps;
697     }
698 
699     /**
700      * Overrides prop configs for special properties from real vehicle HAL.
701      *
702      * @throws RemoteException if getting prop configs from real vehicle HAL fails.
703      */
overrideConfigsForSpecialProp(SparseArray<HalPropConfig> fakePropConfigsByPropId)704     private void overrideConfigsForSpecialProp(SparseArray<HalPropConfig> fakePropConfigsByPropId)
705             throws RemoteException {
706         HalPropConfig[] realVehiclePropConfigs = mRealVehicle.getAllPropConfigs();
707         for (int i = 0; i < realVehiclePropConfigs.length; i++) {
708             HalPropConfig propConfig = realVehiclePropConfigs[i];
709             int propId = propConfig.getPropId();
710             if (isSpecialProperty(propId) && fakePropConfigsByPropId.contains(propId)) {
711                 fakePropConfigsByPropId.put(propConfig.getPropId(), propConfig);
712             }
713         }
714     }
715 
716     /**
717      * Checks if a property is a global property.
718      *
719      * @param propId The property to be checked.
720      * @return {@code true} if this property is a global property.
721      */
isPropertyGlobal(int propId)722     private boolean isPropertyGlobal(int propId) {
723         return (propId & VehicleArea.MASK) == VehicleArea.GLOBAL;
724     }
725 
726     /**
727      * Builds a {@link HalPropValue}.
728      *
729      * @param propId The propId of the prop value to be built.
730      * @param areaId The areaId of the prop value to be built.
731      * @param timestamp The elapsed time in nanoseconds when mPropConfigsByPropId is initialized.
732      * @param rawPropValues The {@link RawPropValues} contains property values.
733      * @return a {@link HalPropValue} built by propId, areaId, timestamp and value.
734      */
buildHalPropValue(int propId, int areaId, long timestamp, RawPropValues rawPropValues)735     private HalPropValue buildHalPropValue(int propId, int areaId, long timestamp,
736             RawPropValues rawPropValues) {
737         VehiclePropValue propValue = new VehiclePropValue();
738         propValue.prop = propId;
739         propValue.areaId = areaId;
740         propValue.timestamp = timestamp;
741         propValue.value = rawPropValues;
742         return mHalPropValueBuilder.build(propValue);
743     }
744 
745     /**
746      * Checks if a property is a special property.
747      *
748      * @param propId The property to be checked.
749      * @return {@code true} if the property is special.
750      */
isSpecialProperty(int propId)751     private static boolean isSpecialProperty(int propId) {
752         return SPECIAL_PROPERTIES.contains(propId);
753     }
754 
755     /**
756      * Checks if a property is an HVAC power affected property.
757      *
758      * @param propId The property to be checked.
759      * @return {@code true} if the property is one of the HVAC power affected properties.
760      */
isHvacPowerDependentProp(int propId)761     private boolean isHvacPowerDependentProp(int propId) {
762         return mHvacPowerDependentProps.contains(propId);
763     }
764 
765     /**
766      * Checks if a HVAC power dependent property is available.
767      *
768      * @param propId The property to be checked.
769      * @param areaId The areaId to be checked.
770      * @throws RemoteException if the remote operation through real vehicle HAL in get method fails.
771      * @throws ServiceSpecificException if there is no matched areaId in HVAC_POWER_ON to check or
772      * the property is not available.
773      */
checkPropAvailable(int propId, int areaId)774     private void checkPropAvailable(int propId, int areaId) throws RemoteException,
775             ServiceSpecificException {
776         HalPropValue propValues = get(mHalPropValueBuilder.build(VehicleProperty.HVAC_POWER_ON,
777                 getMatchedAreaIdInHvacPower(areaId)));
778         if (propValues.getInt32ValuesSize() >= 1 && propValues.getInt32Value(0) == 0) {
779             throw new ServiceSpecificException(StatusCode.NOT_AVAILABLE, "HVAC_POWER_ON is off."
780                     + " PropId: " + propId + " is not available.");
781         }
782     }
783 
784     /**
785      * Gets matched areaId from HVAC_POWER_ON supported areaIds.
786      *
787      * @param areaId The specified areaId to find the match.
788      * @return the matched areaId.
789      * @throws ServiceSpecificException if no matched areaId found.
790      */
getMatchedAreaIdInHvacPower(int areaId)791     private int getMatchedAreaIdInHvacPower(int areaId) {
792         for (int i = 0; i < mHvacPowerSupportedAreas.size(); i++) {
793             int supportedAreaId = mHvacPowerSupportedAreas.get(i);
794             if ((areaId | supportedAreaId) == supportedAreaId) {
795                 return supportedAreaId;
796             }
797         }
798         throw new ServiceSpecificException(StatusCode.INVALID_ARG, "This areaId: " + areaId
799                 + " doesn't match any supported areaIds in HVAC_POWER_ON");
800     }
801 
802     /**
803      * Generates a list of all supported areaId for a certain property.
804      *
805      * @param propId The property to get all supported areaIds.
806      * @return A {@link List} of all supported areaId.
807      */
getAllSupportedAreaId(int propId)808     private List<Integer> getAllSupportedAreaId(int propId) {
809         List<Integer> allSupportedAreaId = new ArrayList<>();
810         HalAreaConfig[] areaConfigs = mPropConfigsByPropId.get(propId).getAreaConfigs();
811         for (int i = 0; i < areaConfigs.length; i++) {
812             allSupportedAreaId.add(areaConfigs[i].getAreaId());
813         }
814         return allSupportedAreaId;
815     }
816 
817     /**
818      * Checks if the set value is within the value range.
819      *
820      * @return {@code true} if set value is within the prop config range.
821      */
withinRange(int propId, int areaId, RawPropValues rawPropValues)822     private boolean withinRange(int propId, int areaId, RawPropValues rawPropValues) {
823         // For global property without areaId.
824         if (isPropertyGlobal(propId) && getAllSupportedAreaId(propId).isEmpty()) {
825             return true;
826         }
827 
828         // For non-global properties and global properties with areaIds.
829         int index = getAllSupportedAreaId(propId).indexOf(areaId);
830 
831         HalAreaConfig areaConfig = mPropConfigsByPropId.get(propId).getAreaConfigs()[index];
832 
833         int[] int32Values = rawPropValues.int32Values;
834         long[] int64Values = rawPropValues.int64Values;
835         float[] floatValues = rawPropValues.floatValues;
836         // If max and min values exists, then check the boundaries. If max and min values are all
837         // 0s, return true.
838         switch (getPropType(propId)) {
839             case VehiclePropertyType.INT32:
840             case VehiclePropertyType.INT32_VEC:
841                 int minInt32Value = areaConfig.getMinInt32Value();
842                 int maxInt32Value = areaConfig.getMaxInt32Value();
843                 if (minInt32Value != maxInt32Value || minInt32Value != 0) {
844                     for (int int32Value : int32Values) {
845                         if (int32Value > maxInt32Value || int32Value < minInt32Value) {
846                             Slogf.e(TAG, "For propId: %d, areaId: %d, the valid min value is: "
847                                     + "%d, max value is: %d, but the given value is: %d.", propId,
848                                     areaId, minInt32Value, maxInt32Value, int32Value);
849                             return false;
850                         }
851                     }
852                 }
853                 break;
854             case VehiclePropertyType.INT64:
855             case VehiclePropertyType.INT64_VEC:
856                 long minInt64Value = areaConfig.getMinInt64Value();
857                 long maxInt64Value = areaConfig.getMaxInt64Value();
858                 if (minInt64Value != maxInt64Value || minInt64Value != 0) {
859                     for (long int64Value : int64Values) {
860                         if (int64Value > maxInt64Value || int64Value < minInt64Value) {
861                             Slogf.e(TAG, "For propId: %d, areaId: %d, the valid min value is: "
862                                     + "%d, max value is: %d, but the given value is: %d.", propId,
863                                     areaId, minInt64Value, maxInt64Value, int64Value);
864                             return false;
865                         }
866                     }
867                 }
868                 break;
869             case VehiclePropertyType.FLOAT:
870             case VehiclePropertyType.FLOAT_VEC:
871                 float minFloatValue = areaConfig.getMinFloatValue();
872                 float maxFloatValue = areaConfig.getMaxFloatValue();
873                 if (minFloatValue != maxFloatValue || minFloatValue != 0) {
874                     for (float floatValue : floatValues) {
875                         if (floatValue > maxFloatValue || floatValue < minFloatValue) {
876                             Slogf.e(TAG, "For propId: %d, areaId: %d, the valid min value is: "
877                                     + "%f, max value is: %f, but the given value is: %d.", propId,
878                                     areaId, minFloatValue, maxFloatValue, floatValue);
879                             return false;
880                         }
881                     }
882                 }
883                 break;
884             default:
885                 Slogf.d(TAG, "Skip checking range for propId: %d because it is mixed type.",
886                         propId);
887         }
888         return true;
889     }
890 
891     /**
892      * Gets the type of property.
893      *
894      * @param propId The property to get the type.
895      * @return The type.
896      */
getPropType(int propId)897     private static int getPropType(int propId) {
898         return propId & VehiclePropertyType.MASK;
899     }
900 
901     /**
902      * Checks if a property is supported. If not, throw a {@link ServiceSpecificException}.
903      *
904      * @param propId The property to be checked.
905      */
checkPropIdSupported(int propId)906     private void checkPropIdSupported(int propId) {
907         // Check if the property config exists.
908         if (!mPropConfigsByPropId.contains(propId)) {
909             throw new ServiceSpecificException(StatusCode.INVALID_ARG, "The propId: " + propId
910                 + " is not supported.");
911         }
912     }
913 
914     /**
915      * Checks if an areaId of a property is supported.
916      *
917      * @param propId The property to be checked.
918      * @param areaId The area to be checked.
919      */
checkAreaIdSupported(int propId, int areaId)920     private void checkAreaIdSupported(int propId, int areaId) {
921         List<Integer> supportedAreaIds = getAllSupportedAreaId(propId);
922         // For global property, areaId will be ignored if the area config array is empty.
923         if ((isPropertyGlobal(propId) && supportedAreaIds.isEmpty())
924                 || supportedAreaIds.contains(areaId)) {
925             return;
926         }
927         throw new ServiceSpecificException(StatusCode.INVALID_ARG, "The areaId: " + areaId
928                 + " is not supported.");
929     }
930 
931     /**
932      * Subscribes properties.
933      *
934      * @param client The client subscribes properties.
935      * @param options The array of subscribe options.
936      * @throws RemoteException if remote operation through real SubscriptionClient fails.
937      */
subscribe(FakeVhalSubscriptionClient client, SubscribeOptions[] options)938     private void subscribe(FakeVhalSubscriptionClient client, SubscribeOptions[] options)
939             throws RemoteException {
940         for (int i = 0; i < options.length; i++) {
941             int propId = options[i].propId;
942 
943             // Check if this propId is supported.
944             checkPropIdSupported(propId);
945 
946             // Check if this propId is a special property.
947             if (isSpecialProperty(propId)) {
948                 client.mRealClient.subscribe(new SubscribeOptions[]{options[i]});
949                 return;
950             }
951 
952             int[] areaIds = isPropertyGlobal(propId) ? new int[]{AREA_ID_GLOBAL}
953                 : getSubscribedAreaIds(propId, options[i].areaIds);
954 
955             int changeMode = mPropConfigsByPropId.get(propId).getChangeMode();
956             switch (changeMode) {
957                 case VehiclePropertyChangeMode.STATIC:
958                     throw new ServiceSpecificException(StatusCode.INVALID_ARG,
959                         "Static property cannot be subscribed.");
960                 case VehiclePropertyChangeMode.ON_CHANGE:
961                     subscribeOnChangeProp(client, propId, areaIds);
962                     break;
963                 case VehiclePropertyChangeMode.CONTINUOUS:
964                     // Check if sample rate is within minSampleRate and maxSampleRate, and
965                     // return a valid sample rate.
966                     float sampleRate = getSampleRateWithinRange(options[i].sampleRate, propId);
967                     subscribeContinuousProp(client, propId, areaIds, sampleRate);
968                     break;
969                 default:
970                     Slogf.w(TAG, "This change mode: %d is not supported.", changeMode);
971             }
972         }
973     }
974 
975     /**
976      * Subscribes an ON_CHANGE property.
977      *
978      * @param client The client that subscribes a property.
979      * @param propId The property to be subscribed.
980      * @param areaIds The list of areaIds to be subscribed.
981      */
subscribeOnChangeProp(FakeVhalSubscriptionClient client, int propId, int[] areaIds)982     private void subscribeOnChangeProp(FakeVhalSubscriptionClient client, int propId,
983             int[] areaIds) {
984         synchronized (mLock) {
985             for (int areaId : areaIds) {
986                 checkAreaIdSupported(propId, areaId);
987                 Slogf.d(TAG, "FakeVhalSubscriptionClient subscribes ON_CHANGE property, "
988                         + "propId: %d,  areaId: ", propId, areaId);
989                 Pair<Integer, Integer> propIdAreaId = Pair.create(propId, areaId);
990                 // Update the map from propId, areaId to client set in FakeVehicleStub.
991                 if (!mOnChangeSubscribeClientByPropIdAreaId.containsKey(propIdAreaId)) {
992                     mOnChangeSubscribeClientByPropIdAreaId.put(propIdAreaId, new ArraySet<>());
993                 }
994                 mOnChangeSubscribeClientByPropIdAreaId.get(propIdAreaId).add(client);
995             }
996         }
997     }
998 
999     /**
1000      * Subscribes a CONTINUOUS property.
1001      *
1002      * @param client The client that subscribes a property.
1003      * @param propId The property to be subscribed.
1004      * @param areaIds The list of areaIds to be subscribed.
1005      * @param sampleRate The rate of subscription.
1006      */
subscribeContinuousProp(FakeVhalSubscriptionClient client, int propId, int[] areaIds, float sampleRate)1007     private void subscribeContinuousProp(FakeVhalSubscriptionClient client, int propId,
1008             int[] areaIds, float sampleRate) {
1009         synchronized (mLock) {
1010             for (int areaId : areaIds) {
1011                 checkAreaIdSupported(propId, areaId);
1012                 Slogf.d(TAG, "FakeVhalSubscriptionClient subscribes CONTINUOUS property, "
1013                         + "propId: %d,  areaId: %d", propId, areaId);
1014                 Pair<Integer, Integer> propIdAreaId = Pair.create(propId, areaId);
1015 
1016                 // Check if this client has subscribed CONTINUOUS properties.
1017                 if (!mUpdaterByPropIdAreaIdByClient.containsKey(client)) {
1018                     mUpdaterByPropIdAreaIdByClient.put(client, new ArrayMap<>());
1019                 }
1020                 Map<Pair<Integer, Integer>, ContinuousPropUpdater> updaterByPropIdAreaId =
1021                         mUpdaterByPropIdAreaIdByClient.get(client);
1022                 // Check if this client subscribes the propId, areaId pair
1023                 if (updaterByPropIdAreaId.containsKey(propIdAreaId)) {
1024                     // If current subscription rate is same as the new sample rate.
1025                     ContinuousPropUpdater oldUpdater = updaterByPropIdAreaId.get(propIdAreaId);
1026                     if (oldUpdater.mSampleRate == sampleRate) {
1027                         Slogf.w(TAG, "Sample rate is same as current rate. No update.");
1028                         continue;
1029                     }
1030                     // If sample rate is not same. Remove old updater from mHandler's message queue.
1031                     oldUpdater.stop();
1032                     updaterByPropIdAreaId.remove(propIdAreaId);
1033                 }
1034                 ContinuousPropUpdater updater = new ContinuousPropUpdater(client, propId, areaId,
1035                         sampleRate);
1036                 updaterByPropIdAreaId.put(propIdAreaId, updater);
1037             }
1038         }
1039     }
1040 
1041     /**
1042      * Unsubscribes a property.
1043      *
1044      * @param client The client that unsubscribes this property.
1045      * @param propId The property to be unsubscribed.
1046      */
unsubscribe(FakeVhalSubscriptionClient client, int propId)1047     private void unsubscribe(FakeVhalSubscriptionClient client, int propId) {
1048         int changeMode = mPropConfigsByPropId.get(propId).getChangeMode();
1049         switch (changeMode) {
1050             case VehiclePropertyChangeMode.STATIC:
1051                 throw new ServiceSpecificException(StatusCode.INVALID_ARG,
1052                     "Static property cannot be unsubscribed.");
1053             case VehiclePropertyChangeMode.ON_CHANGE:
1054                 unsubscribeOnChangeProp(client, propId);
1055                 break;
1056             case VehiclePropertyChangeMode.CONTINUOUS:
1057                 unsubscribeContinuousProp(client, propId);
1058                 break;
1059             default:
1060                 Slogf.w(TAG, "This change mode: %d is not supported.", changeMode);
1061         }
1062     }
1063 
1064     /**
1065      * Unsubscribes ON_CHANGE property.
1066      *
1067      * @param client The client that unsubscribes this property.
1068      * @param propId The property to be unsubscribed.
1069      */
unsubscribeOnChangeProp(FakeVhalSubscriptionClient client, int propId)1070     private void unsubscribeOnChangeProp(FakeVhalSubscriptionClient client, int propId) {
1071         synchronized (mLock) {
1072             List<Pair<Integer, Integer>> deletePairs = new ArrayList<>();
1073             for (Pair<Integer, Integer> propIdAreaId
1074                     : mOnChangeSubscribeClientByPropIdAreaId.keySet()) {
1075                 if (propIdAreaId.first == propId) {
1076                     Set<FakeVhalSubscriptionClient> clientSet =
1077                             mOnChangeSubscribeClientByPropIdAreaId.get(propIdAreaId);
1078                     clientSet.remove(client);
1079                     Slogf.d(TAG, "FakeVhalSubscriptionClient unsubscribes ON_CHANGE property, "
1080                             + "propId: %d, areaId: %d", propId, propIdAreaId.second);
1081                     if (clientSet.isEmpty()) {
1082                         deletePairs.add(propIdAreaId);
1083                     }
1084                 }
1085             }
1086             for (int i = 0; i < deletePairs.size(); i++) {
1087                 mOnChangeSubscribeClientByPropIdAreaId.remove(deletePairs.get(i));
1088             }
1089         }
1090     }
1091 
1092     /**
1093      * Unsubscribes CONTINUOUS property.
1094      *
1095      * @param client The client that unsubscribes this property.
1096      * @param propId The property to be unsubscribed.
1097      */
unsubscribeContinuousProp(FakeVhalSubscriptionClient client, int propId)1098     private void unsubscribeContinuousProp(FakeVhalSubscriptionClient client, int propId) {
1099         synchronized (mLock) {
1100             if (!mUpdaterByPropIdAreaIdByClient.containsKey(client)) {
1101                 Slogf.w(TAG, "This client hasn't subscribed any CONTINUOUS property.");
1102                 return;
1103             }
1104             List<Pair<Integer, Integer>> deletePairs = new ArrayList<>();
1105             Map<Pair<Integer, Integer>, ContinuousPropUpdater> updaterByPropIdAreaId =
1106                     mUpdaterByPropIdAreaIdByClient.get(client);
1107             for (Pair<Integer, Integer> propIdAreaId : updaterByPropIdAreaId.keySet()) {
1108                 if (propIdAreaId.first == propId) {
1109                     updaterByPropIdAreaId.get(propIdAreaId).stop();
1110                     Slogf.d(TAG, "FakeVhalSubscriptionClient unsubscribes CONTINUOUS property,"
1111                             + " propId: %d,  areaId: %d", propId, propIdAreaId.second);
1112                     deletePairs.add(propIdAreaId);
1113                 }
1114             }
1115             for (int i = 0; i < deletePairs.size(); i++) {
1116                 updaterByPropIdAreaId.remove(deletePairs.get(i));
1117             }
1118             if (updaterByPropIdAreaId.isEmpty()) {
1119                 mUpdaterByPropIdAreaIdByClient.remove(client);
1120             }
1121         }
1122     }
1123 
1124     /**
1125      * Gets the array of subscribed areaIds.
1126      *
1127      * @param propId The property to be subscribed.
1128      * @param areaIds The areaIds from SubscribeOptions.
1129      * @return an {@code array} of subscribed areaIds.
1130      */
getSubscribedAreaIds(int propId, int[] areaIds)1131     private int[] getSubscribedAreaIds(int propId, int[] areaIds) {
1132         if (areaIds != null && areaIds.length != 0) {
1133             return areaIds;
1134         }
1135         // If areaIds field  is empty or null, subscribe all supported areaIds.
1136         return CarServiceUtils.toIntArray(getAllSupportedAreaId(propId));
1137     }
1138 
1139     /**
1140      * Gets the subscription sample rate within range.
1141      *
1142      * @param sampleRate The requested sample rate.
1143      * @param propId The property to be subscribed.
1144      * @return The valid sample rate.
1145      */
getSampleRateWithinRange(float sampleRate, int propId)1146     private float getSampleRateWithinRange(float sampleRate, int propId) {
1147         float minSampleRate = mPropConfigsByPropId.get(propId).getMinSampleRate();
1148         float maxSampleRate = mPropConfigsByPropId.get(propId).getMaxSampleRate();
1149         if (sampleRate < minSampleRate) {
1150             sampleRate = minSampleRate;
1151         }
1152         if (sampleRate > maxSampleRate) {
1153             sampleRate = maxSampleRate;
1154         }
1155         return sampleRate;
1156     }
1157 
1158     /**
1159      * Updates the timeStamp of a property.
1160      *
1161      * @param propId The property gets current timeStamp.
1162      * @param areaId The property with specific area gets current timeStamp.
1163      */
updateTimeStamp(int propId, int areaId)1164     private HalPropValue updateTimeStamp(int propId, int areaId) {
1165         synchronized (mLock) {
1166             Pair<Integer, Integer> propIdAreaId = Pair.create(propId, areaId);
1167             HalPropValue propValue = mPropValuesByPropIdAreaId.get(propIdAreaId);
1168             RawPropValues rawPropValues = ((VehiclePropValue) propValue.toVehiclePropValue()).value;
1169             HalPropValue updatedValue = buildHalPropValue(propId, areaId,
1170                     SystemClock.elapsedRealtimeNanos(), rawPropValues);
1171             mPropValuesByPropIdAreaId.put(propIdAreaId, updatedValue);
1172             return updatedValue;
1173         }
1174     }
1175 }
1176