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