• 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 static com.android.car.internal.property.CarPropertyErrorCodes.ERROR_CODES_INTERNAL;
20 import static com.android.car.internal.property.CarPropertyErrorCodes.ERROR_CODES_NOT_AVAILABLE;
21 import static com.android.car.internal.property.CarPropertyErrorCodes.createFromVhalStatusCode;
22 
23 import android.annotation.Nullable;
24 import android.car.builtin.util.Slogf;
25 import android.hardware.automotive.vehicle.RawPropValues;
26 import android.hardware.automotive.vehicle.StatusCode;
27 import android.hardware.automotive.vehicle.SubscribeOptions;
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.VehiclePropertyChangeMode;
33 import android.os.RemoteException;
34 import android.os.ServiceSpecificException;
35 import android.os.SystemClock;
36 import android.util.ArrayMap;
37 import android.util.ArraySet;
38 import android.util.Pair;
39 import android.util.SparseArray;
40 
41 import com.android.car.CarLog;
42 import com.android.car.CarServiceUtils;
43 import com.android.car.VehicleStub;
44 import com.android.car.hal.AidlHalPropConfig;
45 import com.android.car.hal.HalPropConfig;
46 import com.android.car.hal.HalPropValue;
47 import com.android.car.hal.HalPropValueBuilder;
48 import com.android.car.hal.VehicleHalCallback;
49 import com.android.car.internal.property.PropIdAreaId;
50 import com.android.car.internal.util.PairSparseArray;
51 import com.android.internal.annotations.GuardedBy;
52 import com.android.internal.annotations.VisibleForTesting;
53 
54 import java.io.BufferedReader;
55 import java.io.File;
56 import java.io.FileReader;
57 import java.io.IOException;
58 import java.io.InputStream;
59 import java.util.ArrayList;
60 import java.util.List;
61 import java.util.Map;
62 import java.util.Set;
63 
64 /**
65  * FakeVehicleStub represents a fake Vhal implementation.
66  */
67 public final class FakeVehicleStub extends VehicleStubWrapper {
68 
69     private static final String TAG = CarLog.tagFor(FakeVehicleStub.class);
70     private static final List<Integer> SPECIAL_PROPERTIES = List.of(
71             VehicleProperty.VHAL_HEARTBEAT,
72             VehicleProperty.INITIAL_USER_INFO,
73             VehicleProperty.SWITCH_USER,
74             VehicleProperty.CREATE_USER,
75             VehicleProperty.REMOVE_USER,
76             VehicleProperty.USER_IDENTIFICATION_ASSOCIATION,
77             VehicleProperty.AP_POWER_STATE_REPORT,
78             VehicleProperty.AP_POWER_STATE_REQ,
79             VehicleProperty.VEHICLE_MAP_SERVICE,
80             VehicleProperty.OBD2_FREEZE_FRAME_CLEAR,
81             VehicleProperty.OBD2_FREEZE_FRAME,
82             VehicleProperty.OBD2_FREEZE_FRAME_INFO
83     );
84     private static final String FAKE_VHAL_CONFIG_DIRECTORY = "/data/system/car/fake_vhal_config/";
85     private static final String DEFAULT_CONFIG_FILE_NAME = "DefaultProperties.json";
86     private static final String FAKE_MODE_ENABLE_FILE_NAME = "ENABLE";
87 
88     private final HalPropValueBuilder mHalPropValueBuilder;
89     private final List<Integer> mHvacPowerSupportedAreas;
90     private final List<Integer> mHvacPowerDependentProps;
91 
92     @GuardedBy("mLock")
93     private final PairSparseArray<Set<FakeVhalSubscriptionClient>>
94             mOnChangeSubscribeClientByPropIdAreaId = new PairSparseArray<>();
95     @GuardedBy("mLock")
96     private final Map<FakeVhalSubscriptionClient, PairSparseArray<ContinuousPropUpdater>>
97             mUpdaterByPropIdAreaIdByClient = new ArrayMap<>();
98     private final Object mLock = new Object();
99 
100     /**
101      * Checks if fake mode is enabled.
102      *
103      * @return {@code true} if ENABLE file exists.
104      */
doesEnableFileExist()105     public static boolean doesEnableFileExist() {
106         return new File(FAKE_VHAL_CONFIG_DIRECTORY + FAKE_MODE_ENABLE_FILE_NAME).exists();
107     }
108 
109     /**
110      * Initializes a {@link FakeVehicleStub} instance.
111      *
112      * @param realVehicle The real Vhal to be connected to handle special properties.
113      * @throws RemoteException if the remote operation through mRealVehicle fails.
114      * @throws IOException if unable to read the config file stream.
115      * @throws IllegalArgumentException if a JSONException is caught or some parsing error occurred.
116      */
FakeVehicleStub(VehicleStub realVehicle)117     public FakeVehicleStub(VehicleStub realVehicle) throws RemoteException, IOException,
118             IllegalArgumentException {
119         this(realVehicle, new FakeVhalConfigParser(), getCustomConfigFiles());
120     }
121 
122     /**
123      * Initializes a {@link FakeVehicleStub} instance with {@link FakeVhalConfigParser} for testing.
124      *
125      * @param realVehicle The real Vhal to be connected to handle special properties.
126      * @param parser The parser to parse config files.
127      * @param customConfigFiles The {@link List} of custom config files.
128      * @throws RemoteException if failed to get configs for special property from real Vehicle HAL.
129      * @throws IOException if unable to read the config file stream.
130      * @throws IllegalArgumentException if a JSONException is caught or some parsing error occurred.
131      */
132     @VisibleForTesting
FakeVehicleStub(VehicleStub realVehicle, FakeVhalConfigParser parser, List<File> customConfigFiles)133     FakeVehicleStub(VehicleStub realVehicle, FakeVhalConfigParser parser,
134             List<File> customConfigFiles) throws RemoteException, IOException,
135             IllegalArgumentException {
136         super(realVehicle, new Pair<>(extractPropConfigs(parseConfigFiles(parser,
137                         customConfigFiles), realVehicle), extractPropValues(parseConfigFiles(
138                                 parser, customConfigFiles))));
139         mHalPropValueBuilder = new HalPropValueBuilder(/* isAidl= */ true);
140         mHvacPowerSupportedAreas = getHvacPowerSupportedAreaId();
141         mHvacPowerDependentProps = getHvacPowerDependentProps();
142         Slogf.d(TAG, "A FakeVehicleStub instance is created.");
143     }
144 
145     /**
146      * FakeVehicleStub is neither an AIDL VHAL nor HIDL VHAL. But it acts like an AIDL VHAL.
147      *
148      * @return {@code true} since FakeVehicleStub acts like an AIDL VHAL.
149      */
150     @Override
isAidlVhal()151     public boolean isAidlVhal() {
152         return true;
153     }
154 
155     /**
156      * Gets {@link HalPropValueBuilder} for building a {@link HalPropValue}.
157      *
158      * @return a builder to build a {@link HalPropValue}.
159      */
160     @Override
getHalPropValueBuilder()161     public HalPropValueBuilder getHalPropValueBuilder() {
162         return mHalPropValueBuilder;
163     }
164 
165     /**
166      * Gets properties asynchronously.
167      *
168      * @param getVehicleStubAsyncRequests The async request list.
169      * @param getVehicleStubAsyncCallback The callback for getting property values.
170      */
171     @Override
getAsync(List<AsyncGetSetRequest> getVehicleStubAsyncRequests, VehicleStubCallbackInterface getVehicleStubAsyncCallback)172     public void getAsync(List<AsyncGetSetRequest> getVehicleStubAsyncRequests,
173             VehicleStubCallbackInterface getVehicleStubAsyncCallback) {
174         List<GetVehicleStubAsyncResult> onGetAsyncResultList = new ArrayList<>();
175         for (int i = 0; i < getVehicleStubAsyncRequests.size(); i++) {
176             AsyncGetSetRequest request = getVehicleStubAsyncRequests.get(i);
177             GetVehicleStubAsyncResult result;
178             try {
179                 HalPropValue halPropValue = get(request.getHalPropValue());
180                 result = new GetVehicleStubAsyncResult(request.getServiceRequestId(),
181                     halPropValue);
182                 if (halPropValue == null) {
183                     result = new GetVehicleStubAsyncResult(request.getServiceRequestId(),
184                             ERROR_CODES_NOT_AVAILABLE);
185                 }
186             } catch (ServiceSpecificException e) {
187                 result = new GetVehicleStubAsyncResult(request.getServiceRequestId(),
188                         createFromVhalStatusCode(e.errorCode));
189             } catch (RemoteException e) {
190                 result = new GetVehicleStubAsyncResult(request.getServiceRequestId(),
191                         ERROR_CODES_INTERNAL);
192             }
193             onGetAsyncResultList.add(result);
194         }
195         mHandler.post(() -> {
196             getVehicleStubAsyncCallback.onGetAsyncResults(onGetAsyncResultList);
197         });
198     }
199 
200     /**
201      * Sets properties asynchronously.
202      *
203      * @param setVehicleStubAsyncRequests The async request list.
204      * @param setVehicleStubAsyncCallback the callback for setting property values.
205      */
206     @Override
setAsync(List<AsyncGetSetRequest> setVehicleStubAsyncRequests, VehicleStubCallbackInterface setVehicleStubAsyncCallback)207     public void setAsync(List<AsyncGetSetRequest> setVehicleStubAsyncRequests,
208             VehicleStubCallbackInterface setVehicleStubAsyncCallback) {
209         List<SetVehicleStubAsyncResult> onSetAsyncResultsList = new ArrayList<>();
210         for (int i = 0; i < setVehicleStubAsyncRequests.size(); i++) {
211             AsyncGetSetRequest setRequest = setVehicleStubAsyncRequests.get(i);
212             int serviceRequestId = setRequest.getServiceRequestId();
213             SetVehicleStubAsyncResult result;
214             try {
215                 set(setRequest.getHalPropValue());
216                 result = new SetVehicleStubAsyncResult(serviceRequestId);
217             } catch (RemoteException e) {
218                 result = new SetVehicleStubAsyncResult(serviceRequestId,
219                         ERROR_CODES_INTERNAL);
220             } catch (ServiceSpecificException e) {
221                 result = new SetVehicleStubAsyncResult(serviceRequestId,
222                         createFromVhalStatusCode(e.errorCode));
223             }
224             onSetAsyncResultsList.add(result);
225         }
226         mHandler.post(() -> {
227             setVehicleStubAsyncCallback.onSetAsyncResults(onSetAsyncResultsList);
228         });
229     }
230 
231     /**
232      * Checks if FakeVehicleStub connects to a valid Vhal.
233      *
234      * @return {@code true} if connects to a valid Vhal.
235      */
236     @Override
isValid()237     public boolean isValid() {
238         return mRealVehicle.isValid();
239     }
240 
241     /**
242      * Gets the interface descriptor for the connecting vehicle HAL.
243      *
244      * @throws IllegalStateException If unable to get the descriptor.
245      */
246     @Override
getInterfaceDescriptor()247     public String getInterfaceDescriptor() throws IllegalStateException {
248         return "com.android.car.hal.fakevhal.FakeVehicleStub";
249     }
250 
251     /**
252      * Gets all property configs.
253      *
254      * @return an array of all property configs.
255      */
256     @Override
getAllPropConfigs()257     public HalPropConfig[] getAllPropConfigs() {
258         HalPropConfig[] propConfigs = new HalPropConfig[mPropConfigsByPropId.size()];
259         for (int i = 0; i < mPropConfigsByPropId.size(); i++) {
260             propConfigs[i] = mPropConfigsByPropId.valueAt(i);
261         }
262         return propConfigs;
263     }
264 
265     /**
266      * Gets a new {@code SubscriptionClient} that could be used to subscribe/unsubscribe.
267      *
268      * @param callback A callback that could be used to receive events.
269      * @return a {@code SubscriptionClient} that could be used to subscribe/unsubscribe.
270      */
271     @Override
newSubscriptionClient(VehicleHalCallback callback)272     public SubscriptionClient newSubscriptionClient(VehicleHalCallback callback) {
273         return new FakeVhalSubscriptionClient(callback,
274                 mRealVehicle.newSubscriptionClient(callback));
275     }
276 
277     /**
278      * Gets a property value.
279      *
280      * @param requestedPropValue The property to get.
281      * @return the property value.
282      * @throws RemoteException if getting value for special props through real vehicle HAL fails.
283      * @throws ServiceSpecificException if propId or areaId is not supported.
284      */
285     @Override
286     @Nullable
get(HalPropValue requestedPropValue)287     public HalPropValue get(HalPropValue requestedPropValue) throws RemoteException,
288             ServiceSpecificException {
289         int propId = requestedPropValue.getPropId();
290         checkPropIdSupported(propId);
291         int areaId = isPropertyGlobal(propId) ? AREA_ID_GLOBAL : requestedPropValue.getAreaId();
292         checkAreaIdSupported(propId, areaId);
293 
294         // For HVAC power dependent properties, check if HVAC_POWER_ON is on.
295         if (isHvacPowerDependentProp(propId)) {
296             checkPropAvailable(propId, areaId);
297         }
298         // Check access permission.
299         verifyReadAccess(propId, areaId);
300 
301         if (isSpecialProperty(propId)) {
302             return mRealVehicle.get(requestedPropValue);
303         }
304 
305         return getFakeHalPropValue(propId, areaId);
306     }
307 
308     /**
309      * Sets a property value.
310      *
311      * @param propValue The property to set.
312      * @throws RemoteException if setting value for special props through real vehicle HAL fails.
313      * @throws ServiceSpecificException if propId or areaId is not supported.
314      */
315     @Override
set(HalPropValue propValue)316     public void set(HalPropValue propValue) throws RemoteException,
317                 ServiceSpecificException {
318         int propId = propValue.getPropId();
319         checkPropIdSupported(propId);
320         int areaId = isPropertyGlobal(propId) ? AREA_ID_GLOBAL : propValue.getAreaId();
321         checkAreaIdSupported(propId, areaId);
322 
323         // For HVAC power dependent properties, check if HVAC_POWER_ON is on.
324         if (isHvacPowerDependentProp(propId)) {
325             checkPropAvailable(propId, areaId);
326         }
327 
328         // Check access permission.
329         verifyWriteAccess(propId, areaId);
330 
331         if (isSpecialProperty(propValue.getPropId())) {
332             mRealVehicle.set(propValue);
333             return;
334         }
335 
336         HalPropValue updatedValue = buildRawPropValueAndCheckRange(propValue);
337         Set<FakeVhalSubscriptionClient> clients;
338 
339         synchronized (mLock) {
340             putPropValue(propId, areaId, updatedValue);
341             clients = mOnChangeSubscribeClientByPropIdAreaId.get(propId, areaId, new ArraySet<>());
342         }
343         clients.forEach(c -> c.onPropertyEvent(updatedValue));
344     }
345 
346     /**
347      * @return {@code true} if car service is connected to FakeVehicleStub.
348      */
349     @Override
isFakeModeEnabled()350     public boolean isFakeModeEnabled() {
351         return true;
352     }
353 
354     private final class FakeVhalSubscriptionClient implements SubscriptionClient {
355         private final VehicleHalCallback mCallback;
356         private final SubscriptionClient mRealClient;
357 
FakeVhalSubscriptionClient(VehicleHalCallback callback, SubscriptionClient realVehicleClient)358         FakeVhalSubscriptionClient(VehicleHalCallback callback,
359                 SubscriptionClient realVehicleClient) {
360             mCallback = callback;
361             mRealClient = realVehicleClient;
362             Slogf.d(TAG, "A FakeVhalSubscriptionClient instance is created.");
363         }
364 
onPropertyEvent(HalPropValue value)365         public void onPropertyEvent(HalPropValue value) {
366             mCallback.onPropertyEvent(new ArrayList<>(List.of(value)));
367         }
368 
369         @Override
subscribe(SubscribeOptions[] options)370         public void subscribe(SubscribeOptions[] options) throws RemoteException {
371             FakeVehicleStub.this.subscribe(this, options);
372         }
373 
374         @Override
unsubscribe(int propId)375         public void unsubscribe(int propId) throws RemoteException {
376             // Check if this propId is supported.
377             checkPropIdSupported(propId);
378             // Check if this propId is a special property.
379             if (isSpecialProperty(propId)) {
380                 mRealClient.unsubscribe(propId);
381                 return;
382             }
383             FakeVehicleStub.this.unsubscribe(this, propId);
384         }
385 
386         @Override
registerSupportedValuesChange(List<PropIdAreaId> propIdAreaIds)387         public void registerSupportedValuesChange(List<PropIdAreaId> propIdAreaIds) {
388             // TODO(371636116): Implement this.
389             throw new UnsupportedOperationException();
390         }
391 
392         @Override
unregisterSupportedValuesChange(List<PropIdAreaId> propIdAreaIds)393         public void unregisterSupportedValuesChange(List<PropIdAreaId> propIdAreaIds) {
394             // TODO(371636116): Implement this.
395             throw new UnsupportedOperationException();
396         }
397     }
398 
399     private final class ContinuousPropUpdater implements Runnable {
400         private final FakeVhalSubscriptionClient mClient;
401         private final int mPropId;
402         private final int mAreaId;
403         private final float mSampleRate;
404         private final Object mUpdaterLock = new Object();
405         @GuardedBy("mUpdaterLock")
406         private boolean mStopped;
407 
ContinuousPropUpdater(FakeVhalSubscriptionClient client, int propId, int areaId, float sampleRate)408         ContinuousPropUpdater(FakeVhalSubscriptionClient client, int propId, int areaId,
409                 float sampleRate) {
410             mClient = client;
411             mPropId = propId;
412             mAreaId = areaId;
413             mSampleRate = sampleRate;
414             mHandler.post(this);
415             Slogf.d(TAG, "A runnable updater is created for CONTINUOUS property.");
416         }
417 
418         @Override
run()419         public void run() {
420             synchronized (mUpdaterLock) {
421                 if (mStopped) {
422                     return;
423                 }
424                 mHandler.postDelayed(this, (long) (1000 / mSampleRate));
425             }
426 
427             // It is possible that mStopped is updated to true at the same time. We will have one
428             // additional event here. We cannot hold lock because we don't want to hold lock while
429             // calling client's callback;
430             mClient.onPropertyEvent(updateTimeStamp(mPropId, mAreaId));
431         }
432 
stop()433         public void stop() {
434             synchronized (mUpdaterLock) {
435                 mStopped = true;
436                 mHandler.removeCallbacks(this);
437             }
438         }
439     }
440 
441     /**
442      * Parses default and custom config files.
443      *
444      * @return a {@link SparseArray} mapped from propId to its {@link ConfigDeclaration}.
445      * @throws IOException if FakeVhalConfigParser throws IOException.
446      * @throws IllegalArgumentException If default file doesn't exist or parsing errors occurred.
447      */
parseConfigFiles(FakeVhalConfigParser parser, List<File> customConfigFiles)448     private static SparseArray<ConfigDeclaration> parseConfigFiles(FakeVhalConfigParser parser,
449             List<File> customConfigFiles) throws IOException, IllegalArgumentException {
450         InputStream defaultConfigInputStream = FakeVehicleStub.class.getClassLoader()
451                 .getResourceAsStream(DEFAULT_CONFIG_FILE_NAME);
452         SparseArray<ConfigDeclaration> configDeclarations;
453         SparseArray<ConfigDeclaration> customConfigDeclarations;
454         // Parse default config file.
455         configDeclarations = parser.parseJsonConfig(defaultConfigInputStream);
456 
457         // Parse all custom config files.
458         for (int i = 0; i < customConfigFiles.size(); i++) {
459             File customFile = customConfigFiles.get(i);
460             try {
461                 customConfigDeclarations = parser.parseJsonConfig(customFile);
462             } catch (Exception e) {
463                 Slogf.w(TAG, e, "Failed to parse custom config file: %s",
464                         customFile.getPath());
465                 continue;
466             }
467             combineConfigDeclarations(configDeclarations, customConfigDeclarations);
468         }
469 
470         return configDeclarations;
471     }
472 
473     /**
474      * Gets all custom config files which are going to be parsed.
475      *
476      * @return a {@link List} of files.
477      */
getCustomConfigFiles()478     private static List<File> getCustomConfigFiles() throws IOException {
479         List<File> customConfigFileList = new ArrayList<>();
480         File file = new File(FAKE_VHAL_CONFIG_DIRECTORY + FAKE_MODE_ENABLE_FILE_NAME);
481         try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
482             String line;
483             while ((line = reader.readLine()) != null) {
484                 customConfigFileList.add(new File(FAKE_VHAL_CONFIG_DIRECTORY
485                         + line.replaceAll("\\.\\.", "").replaceAll("\\/", "")));
486             }
487         }
488         return customConfigFileList;
489     }
490 
491     /**
492      * Combines parsing results together.
493      *
494      * @param result The {@link SparseArray} to gets new property configs.
495      * @param newList The {@link SparseArray} whose property config will be added to result.
496      * @return a combined {@link SparseArray} result.
497      */
combineConfigDeclarations( SparseArray<ConfigDeclaration> result, SparseArray<ConfigDeclaration> newList)498     private static SparseArray<ConfigDeclaration> combineConfigDeclarations(
499             SparseArray<ConfigDeclaration> result, SparseArray<ConfigDeclaration> newList) {
500         for (int i = 0; i < newList.size(); i++) {
501             result.put(newList.keyAt(i), newList.valueAt(i));
502         }
503         return result;
504     }
505 
506     /**
507      * Extracts {@link HalPropConfig} for all properties from the parsing result and real VHAL.
508      *
509      * @param configDeclarationsByPropId The parsing result.
510      * @throws RemoteException if getting configs for special props through real vehicle HAL fails.
511      * @return a {@link SparseArray} mapped from propId to its configs.
512      */
extractPropConfigs(SparseArray<ConfigDeclaration> configDeclarationsByPropId, VehicleStub realVehicleStub)513     private static SparseArray<HalPropConfig> extractPropConfigs(SparseArray<ConfigDeclaration>
514             configDeclarationsByPropId, VehicleStub realVehicleStub) throws RemoteException {
515         SparseArray<HalPropConfig> propConfigsByPropId = new SparseArray<>();
516         for (int i = 0; i < configDeclarationsByPropId.size(); i++) {
517             VehiclePropConfig vehiclePropConfig = configDeclarationsByPropId.valueAt(i).getConfig();
518             propConfigsByPropId.put(vehiclePropConfig.prop,
519                     new AidlHalPropConfig(vehiclePropConfig));
520         }
521         // If the special property is supported in this configuration, then override with configs
522         // from real vehicle.
523         overrideConfigsForSpecialProp(propConfigsByPropId, realVehicleStub);
524         return propConfigsByPropId;
525     }
526 
527     /**
528      * Extracts {@link HalPropValue} for all properties from the parsing result.
529      *
530      * @param configDeclarationsByPropId The parsing result.
531      * @return a {@link Map} mapped from propId, areaId to its value.
532      */
extractPropValues( SparseArray<ConfigDeclaration> configDeclarationsByPropId)533     private static PairSparseArray<HalPropValue> extractPropValues(
534             SparseArray<ConfigDeclaration> configDeclarationsByPropId) {
535         HalPropValueBuilder halPropValueBuilder = new HalPropValueBuilder(/* isAidl= */ true);
536         long timestamp = SystemClock.elapsedRealtimeNanos();
537         PairSparseArray<HalPropValue> propValuesByPropIdAreaId = new PairSparseArray<>();
538         for (int i = 0; i < configDeclarationsByPropId.size(); i++) {
539             // Get configDeclaration of a property.
540             ConfigDeclaration configDeclaration = configDeclarationsByPropId.valueAt(i);
541             // Get propId.
542             int propId = configDeclaration.getConfig().prop;
543             // Get areaConfigs array to know what areaIds are supported.
544             VehicleAreaConfig[] areaConfigs = configDeclaration.getConfig().areaConfigs;
545             // Get default rawPropValues.
546             RawPropValues defaultRawPropValues = configDeclaration.getInitialValue();
547             // Get area rawPropValues map.
548             SparseArray<RawPropValues> rawPropValuesByAreaId = configDeclaration
549                     .getInitialAreaValuesByAreaId();
550 
551             // If this property is a global property.
552             if (isPropertyGlobal(propId)) {
553                 // If no default prop value exists, this propId won't be added to the
554                 // propValuesByAreaIdByPropId map. Get this propId value will throw
555                 // ServiceSpecificException with StatusCode.INVALID_ARG.
556                 if (defaultRawPropValues == null) {
557                     continue;
558                 }
559                 // Set the areaId to be 0.
560                 propValuesByPropIdAreaId.put(propId, AREA_ID_GLOBAL,
561                         buildHalPropValue(propId, AREA_ID_GLOBAL, timestamp,
562                                 defaultRawPropValues, halPropValueBuilder));
563                 continue;
564             }
565 
566             // If this property has supported area configs.
567             for (int j = 0; j < areaConfigs.length; j++) {
568                 // Get areaId.
569                 int areaId = areaConfigs[j].areaId;
570                 // Set default area prop value to be defaultRawPropValues. If area value doesn't
571                 // exist, then use the property default value.
572                 RawPropValues areaRawPropValues = defaultRawPropValues;
573                 // If area prop value exists, then use area value.
574                 if (rawPropValuesByAreaId.contains(areaId)) {
575                     areaRawPropValues = rawPropValuesByAreaId.get(areaId);
576                 }
577                 // Neither area prop value nor default prop value exists. This propId won't be in
578                 // the value map. Get this propId value will throw ServiceSpecificException
579                 // with StatusCode.INVALID_ARG.
580                 if (areaRawPropValues == null) {
581                     continue;
582                 }
583                 propValuesByPropIdAreaId.put(propId, areaId,
584                         buildHalPropValue(propId, areaId, timestamp, areaRawPropValues,
585                                 halPropValueBuilder));
586             }
587         }
588         return propValuesByPropIdAreaId;
589     }
590 
591     /**
592      * Gets all supported areaIds by HVAC_POWER_ON.
593      *
594      * @return a {@link List} of areaIds supported by HVAC_POWER_ON.
595      */
getHvacPowerSupportedAreaId()596     private List<Integer> getHvacPowerSupportedAreaId() {
597         try {
598             checkPropIdSupported(VehicleProperty.HVAC_POWER_ON);
599             return getAllSupportedAreaId(VehicleProperty.HVAC_POWER_ON);
600         } catch (Exception e) {
601             Slogf.i(TAG, "%d is not supported.", VehicleProperty.HVAC_POWER_ON);
602             return new ArrayList<>();
603         }
604     }
605 
606     /**
607      * Gets the HVAC power dependent properties from HVAC_POWER_ON config array.
608      *
609      * @return a {@link List} of HVAC properties which are dependent to HVAC_POWER_ON.
610      */
getHvacPowerDependentProps()611     private List<Integer> getHvacPowerDependentProps() {
612         List<Integer> hvacProps = new ArrayList<>();
613         try {
614             checkPropIdSupported(VehicleProperty.HVAC_POWER_ON);
615             int[] configArray = mPropConfigsByPropId.get(VehicleProperty.HVAC_POWER_ON)
616                     .getConfigArray();
617             for (int propId : configArray) {
618                 hvacProps.add(propId);
619             }
620         } catch (Exception e) {
621             Slogf.i(TAG, "%d is not supported.", VehicleProperty.HVAC_POWER_ON);
622         }
623         return hvacProps;
624     }
625 
626     /**
627      * Overrides prop configs for special properties from real vehicle HAL.
628      *
629      * @throws RemoteException if getting prop configs from real vehicle HAL fails.
630      */
overrideConfigsForSpecialProp(SparseArray<HalPropConfig> fakePropConfigsByPropId, VehicleStub realVehicleStub)631     private static void overrideConfigsForSpecialProp(SparseArray<HalPropConfig>
632             fakePropConfigsByPropId, VehicleStub realVehicleStub) throws RemoteException {
633         HalPropConfig[] realVehiclePropConfigs = realVehicleStub.getAllPropConfigs();
634         for (int i = 0; i < realVehiclePropConfigs.length; i++) {
635             HalPropConfig propConfig = realVehiclePropConfigs[i];
636             int propId = propConfig.getPropId();
637             if (isSpecialProperty(propId) && fakePropConfigsByPropId.contains(propId)) {
638                 fakePropConfigsByPropId.put(propConfig.getPropId(), propConfig);
639             }
640         }
641     }
642 
643     /**
644      * Checks if a property is a special property.
645      *
646      * @param propId The property to be checked.
647      * @return {@code true} if the property is special.
648      */
isSpecialProperty(int propId)649     private static boolean isSpecialProperty(int propId) {
650         return SPECIAL_PROPERTIES.contains(propId);
651     }
652 
653     /**
654      * Checks if a property is an HVAC power affected property.
655      *
656      * @param propId The property to be checked.
657      * @return {@code true} if the property is one of the HVAC power affected properties.
658      */
isHvacPowerDependentProp(int propId)659     private boolean isHvacPowerDependentProp(int propId) {
660         return mHvacPowerDependentProps.contains(propId);
661     }
662 
663     /**
664      * Checks if a HVAC power dependent property is available.
665      *
666      * @param propId The property to be checked.
667      * @param areaId The areaId to be checked.
668      * @throws RemoteException if the remote operation through real vehicle HAL in get method fails.
669      * @throws ServiceSpecificException if there is no matched areaId in HVAC_POWER_ON to check or
670      * the property is not available.
671      */
checkPropAvailable(int propId, int areaId)672     private void checkPropAvailable(int propId, int areaId) throws RemoteException,
673             ServiceSpecificException {
674         HalPropValue propValues = get(mHalPropValueBuilder.build(VehicleProperty.HVAC_POWER_ON,
675                 getMatchedAreaIdInHvacPower(areaId)));
676         if (propValues.getInt32ValuesSize() >= 1 && propValues.getInt32Value(0) == 0) {
677             throw new ServiceSpecificException(StatusCode.NOT_AVAILABLE, "HVAC_POWER_ON is off."
678                     + " PropId: " + propId + " is not available.");
679         }
680     }
681 
682     /**
683      * Gets matched areaId from HVAC_POWER_ON supported areaIds.
684      *
685      * @param areaId The specified areaId to find the match.
686      * @return the matched areaId.
687      * @throws ServiceSpecificException if no matched areaId found.
688      */
getMatchedAreaIdInHvacPower(int areaId)689     private int getMatchedAreaIdInHvacPower(int areaId) {
690         for (int i = 0; i < mHvacPowerSupportedAreas.size(); i++) {
691             int supportedAreaId = mHvacPowerSupportedAreas.get(i);
692             if ((areaId | supportedAreaId) == supportedAreaId) {
693                 return supportedAreaId;
694             }
695         }
696         throw new ServiceSpecificException(StatusCode.INVALID_ARG, "This areaId: " + areaId
697                 + " doesn't match any supported areaIds in HVAC_POWER_ON");
698     }
699 
700     /**
701      * Subscribes properties.
702      *
703      * @param client The client subscribes properties.
704      * @param options The array of subscribe options.
705      * @throws RemoteException if remote operation through real SubscriptionClient fails.
706      */
subscribe(FakeVhalSubscriptionClient client, SubscribeOptions[] options)707     private void subscribe(FakeVhalSubscriptionClient client, SubscribeOptions[] options)
708             throws RemoteException {
709         for (int i = 0; i < options.length; i++) {
710             int propId = options[i].propId;
711 
712             // Check if this propId is supported.
713             checkPropIdSupported(propId);
714 
715             // Check if this propId is a special property.
716             if (isSpecialProperty(propId)) {
717                 client.mRealClient.subscribe(new SubscribeOptions[]{options[i]});
718                 return;
719             }
720 
721             int[] areaIds = isPropertyGlobal(propId) ? new int[]{AREA_ID_GLOBAL}
722                 : getSubscribedAreaIds(propId, options[i].areaIds);
723 
724             int changeMode = mPropConfigsByPropId.get(propId).getChangeMode();
725             switch (changeMode) {
726                 case VehiclePropertyChangeMode.STATIC:
727                     throw new ServiceSpecificException(StatusCode.INVALID_ARG,
728                         "Static property cannot be subscribed.");
729                 case VehiclePropertyChangeMode.ON_CHANGE:
730                     subscribeOnChangeProp(client, propId, areaIds);
731                     break;
732                 case VehiclePropertyChangeMode.CONTINUOUS:
733                     // Check if sample rate is within minSampleRate and maxSampleRate, and
734                     // return a valid sample rate.
735                     float sampleRate = getSampleRateWithinRange(options[i].sampleRate, propId);
736                     subscribeContinuousProp(client, propId, areaIds, sampleRate);
737                     break;
738                 default:
739                     Slogf.w(TAG, "This change mode: %d is not supported.", changeMode);
740             }
741         }
742     }
743 
744     /**
745      * Subscribes an ON_CHANGE property.
746      *
747      * @param client The client that subscribes a property.
748      * @param propId The property to be subscribed.
749      * @param areaIds The list of areaIds to be subscribed.
750      */
subscribeOnChangeProp(FakeVhalSubscriptionClient client, int propId, int[] areaIds)751     private void subscribeOnChangeProp(FakeVhalSubscriptionClient client, int propId,
752             int[] areaIds) {
753         synchronized (mLock) {
754             for (int areaId : areaIds) {
755                 checkAreaIdSupported(propId, areaId);
756                 Slogf.d(TAG, "FakeVhalSubscriptionClient subscribes ON_CHANGE property, "
757                         + "propId: %d,  areaId: ", propId, areaId);
758                 // Update the map from propId, areaId to client set in FakeVehicleStub.
759                 Set<FakeVehicleStub.FakeVhalSubscriptionClient> subscriptionClientSet =
760                         mOnChangeSubscribeClientByPropIdAreaId.get(propId, areaId);
761                 if (subscriptionClientSet == null) {
762                     subscriptionClientSet = new ArraySet<>();
763                     mOnChangeSubscribeClientByPropIdAreaId.put(propId, areaId,
764                             subscriptionClientSet);
765                 }
766                 subscriptionClientSet.add(client);
767             }
768         }
769     }
770 
771     /**
772      * Subscribes a CONTINUOUS property.
773      *
774      * @param client The client that subscribes a property.
775      * @param propId The property to be subscribed.
776      * @param areaIds The list of areaIds to be subscribed.
777      * @param sampleRate The rate of subscription.
778      */
subscribeContinuousProp(FakeVhalSubscriptionClient client, int propId, int[] areaIds, float sampleRate)779     private void subscribeContinuousProp(FakeVhalSubscriptionClient client, int propId,
780             int[] areaIds, float sampleRate) {
781         synchronized (mLock) {
782             for (int areaId : areaIds) {
783                 checkAreaIdSupported(propId, areaId);
784                 Slogf.d(TAG, "FakeVhalSubscriptionClient subscribes CONTINUOUS property, "
785                         + "propId: %d,  areaId: %d", propId, areaId);
786                 PairSparseArray<ContinuousPropUpdater> updaterByPropIdAreaId =
787                         mUpdaterByPropIdAreaIdByClient.get(client);
788                 // Check if this client has subscribed CONTINUOUS properties.
789                 if (updaterByPropIdAreaId == null) {
790                     updaterByPropIdAreaId = new PairSparseArray<>();
791                     mUpdaterByPropIdAreaIdByClient.put(client, updaterByPropIdAreaId);
792                 }
793                 // Check if this client subscribes to the propId, areaId pair
794                 int indexOfPropIdAreaId = updaterByPropIdAreaId.indexOfKeyPair(propId, areaId);
795                 if (indexOfPropIdAreaId >= 0) {
796                     // If current subscription rate is same as the new sample rate.
797                     ContinuousPropUpdater oldUpdater =
798                             updaterByPropIdAreaId.valueAt(indexOfPropIdAreaId);
799                     if (oldUpdater.mSampleRate == sampleRate) {
800                         Slogf.w(TAG, "Sample rate is same as current rate. No update.");
801                         continue;
802                     }
803                     // If sample rate is not same. Remove old updater from mHandler's message queue.
804                     oldUpdater.stop();
805                     updaterByPropIdAreaId.removeAt(indexOfPropIdAreaId);
806                 }
807                 ContinuousPropUpdater updater = new ContinuousPropUpdater(client, propId, areaId,
808                         sampleRate);
809                 updaterByPropIdAreaId.put(propId, areaId, updater);
810             }
811         }
812     }
813 
814     /**
815      * Unsubscribes a property.
816      *
817      * @param client The client that unsubscribes this property.
818      * @param propId The property to be unsubscribed.
819      */
unsubscribe(FakeVhalSubscriptionClient client, int propId)820     private void unsubscribe(FakeVhalSubscriptionClient client, int propId) {
821         int changeMode = mPropConfigsByPropId.get(propId).getChangeMode();
822         switch (changeMode) {
823             case VehiclePropertyChangeMode.STATIC:
824                 throw new ServiceSpecificException(StatusCode.INVALID_ARG,
825                     "Static property cannot be unsubscribed.");
826             case VehiclePropertyChangeMode.ON_CHANGE:
827                 unsubscribeOnChangeProp(client, propId);
828                 break;
829             case VehiclePropertyChangeMode.CONTINUOUS:
830                 unsubscribeContinuousProp(client, propId);
831                 break;
832             default:
833                 Slogf.w(TAG, "This change mode: %d is not supported.", changeMode);
834         }
835     }
836 
837     /**
838      * Unsubscribes ON_CHANGE property.
839      *
840      * @param client The client that unsubscribes this property.
841      * @param propId The property to be unsubscribed.
842      */
unsubscribeOnChangeProp(FakeVhalSubscriptionClient client, int propId)843     private void unsubscribeOnChangeProp(FakeVhalSubscriptionClient client, int propId) {
844         synchronized (mLock) {
845             List<Integer> areaIdsToDelete = new ArrayList<>();
846             for (int i = 0; i < mOnChangeSubscribeClientByPropIdAreaId.size(); i++) {
847                 int[] propIdAreaId = mOnChangeSubscribeClientByPropIdAreaId.keyPairAt(i);
848                 if (propIdAreaId[0] != propId) {
849                     continue;
850                 }
851                 Set<FakeVhalSubscriptionClient> clientSet =
852                         mOnChangeSubscribeClientByPropIdAreaId.valueAt(i);
853                 clientSet.remove(client);
854                 Slogf.d(TAG, "FakeVhalSubscriptionClient unsubscribes ON_CHANGE property, "
855                         + "propId: %d, areaId: %d", propId, propIdAreaId[1]);
856                 if (clientSet.isEmpty()) {
857                     areaIdsToDelete.add(propIdAreaId[1]);
858                 }
859             }
860             for (int i = 0; i < areaIdsToDelete.size(); i++) {
861                 mOnChangeSubscribeClientByPropIdAreaId.remove(propId, areaIdsToDelete.get(i));
862             }
863         }
864     }
865 
866     /**
867      * Unsubscribes CONTINUOUS property.
868      *
869      * @param client The client that unsubscribes this property.
870      * @param propId The property to be unsubscribed.
871      */
unsubscribeContinuousProp(FakeVhalSubscriptionClient client, int propId)872     private void unsubscribeContinuousProp(FakeVhalSubscriptionClient client, int propId) {
873         synchronized (mLock) {
874             if (!mUpdaterByPropIdAreaIdByClient.containsKey(client)) {
875                 Slogf.w(TAG, "This client hasn't subscribed any CONTINUOUS property.");
876                 return;
877             }
878             List<Integer> areaIdsToDelete = new ArrayList<>();
879             PairSparseArray<ContinuousPropUpdater> updaterByPropIdAreaId =
880                     mUpdaterByPropIdAreaIdByClient.get(client);
881             for (int i = 0; i < updaterByPropIdAreaId.size(); i++) {
882                 int[] propIdAreaId = updaterByPropIdAreaId.keyPairAt(i);
883                 if (propIdAreaId[0] != propId) {
884                     continue;
885                 }
886                 updaterByPropIdAreaId.valueAt(i).stop();
887                 Slogf.d(TAG, "FakeVhalSubscriptionClient unsubscribes CONTINUOUS property,"
888                         + " propId: %d,  areaId: %d", propId, propIdAreaId[1]);
889                 areaIdsToDelete.add(propIdAreaId[1]);
890             }
891             for (int i = 0; i < areaIdsToDelete.size(); i++) {
892                 updaterByPropIdAreaId.remove(propId, areaIdsToDelete.get(i));
893             }
894             if (updaterByPropIdAreaId.size() == 0) {
895                 mUpdaterByPropIdAreaIdByClient.remove(client);
896             }
897         }
898     }
899 
900     /**
901      * Gets the array of subscribed areaIds.
902      *
903      * @param propId The property to be subscribed.
904      * @param areaIds The areaIds from SubscribeOptions.
905      * @return an {@code array} of subscribed areaIds.
906      */
getSubscribedAreaIds(int propId, int[] areaIds)907     private int[] getSubscribedAreaIds(int propId, int[] areaIds) {
908         if (areaIds != null && areaIds.length != 0) {
909             return areaIds;
910         }
911         // If areaIds field  is empty or null, subscribe all supported areaIds.
912         return CarServiceUtils.toIntArray(getAllSupportedAreaId(propId));
913     }
914 
915     /**
916      * Gets the subscription sample rate within range.
917      *
918      * @param sampleRate The requested sample rate.
919      * @param propId The property to be subscribed.
920      * @return The valid sample rate.
921      */
getSampleRateWithinRange(float sampleRate, int propId)922     private float getSampleRateWithinRange(float sampleRate, int propId) {
923         float minSampleRate = mPropConfigsByPropId.get(propId).getMinSampleRate();
924         float maxSampleRate = mPropConfigsByPropId.get(propId).getMaxSampleRate();
925         if (sampleRate < minSampleRate) {
926             sampleRate = minSampleRate;
927         }
928         if (sampleRate > maxSampleRate) {
929             sampleRate = maxSampleRate;
930         }
931         return sampleRate;
932     }
933 
934     /**
935      * Updates the timeStamp of a property.
936      *
937      * @param propId The property gets current timeStamp.
938      * @param areaId The property with specific area gets current timeStamp.
939      */
updateTimeStamp(int propId, int areaId)940     private HalPropValue updateTimeStamp(int propId, int areaId) {
941         synchronized (mLock) {
942             HalPropValue propValue = getPropValue(propId, areaId);
943             RawPropValues rawPropValues = ((VehiclePropValue) propValue.toVehiclePropValue()).value;
944             HalPropValue updatedValue = buildHalPropValue(propId, areaId,
945                     SystemClock.elapsedRealtimeNanos(), rawPropValues);
946             putPropValue(propId, areaId, updatedValue);
947             return updatedValue;
948         }
949     }
950 }
951