• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.hal.property.HalPropertyDebugUtils.toValueString;
20 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
21 
22 import android.annotation.Nullable;
23 import android.car.builtin.util.Slogf;
24 import android.car.hardware.CarPropertyValue;
25 import android.hardware.automotive.vehicle.VehiclePropError;
26 import android.hardware.automotive.vehicle.VehiclePropValue;
27 import android.os.RemoteException;
28 import android.os.ServiceSpecificException;
29 import android.os.SystemClock;
30 import android.util.ArraySet;
31 import android.util.Pair;
32 import android.util.SparseArray;
33 
34 import com.android.car.CarLog;
35 import com.android.car.VehicleStub;
36 import com.android.car.hal.HalPropConfig;
37 import com.android.car.hal.HalPropValue;
38 import com.android.car.hal.HalPropValueBuilder;
39 import com.android.car.hal.VehicleHalCallback;
40 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
41 import com.android.car.internal.property.PropIdAreaId;
42 import com.android.car.internal.util.IndentingPrintWriter;
43 import com.android.car.internal.util.PairSparseArray;
44 import com.android.internal.annotations.GuardedBy;
45 import com.android.internal.annotations.VisibleForTesting;
46 
47 import java.io.FileDescriptor;
48 import java.io.FileOutputStream;
49 import java.io.PrintWriter;
50 import java.util.ArrayList;
51 import java.util.List;
52 import java.util.concurrent.TimeUnit;
53 import java.util.function.Consumer;
54 import java.util.function.Function;
55 
56 public final class SimulationVehicleStub extends VehicleStubWrapper {
57 
58     private static final String TAG = CarLog.tagFor(SimulationVehicleStub.class);
59 
60     private final ArraySet<Integer> mPropertyIdsFromRealHardware;
61     @GuardedBy("mLock")
62     private final SparseArray<CarPropertyValue> mLastInjectedProperty;
63     private ReplayingVehicleHalCallback mReplayingVehicleHalCallback;
64     private final long mStartOfSimulationTime;
65     private final Object mLock = new Object();
66 
SimulationVehicleStub(VehicleStub stub, List<Integer> propertyIdsFromRealHardware, VehicleHalCallback realCallback)67     public SimulationVehicleStub(VehicleStub stub, List<Integer> propertyIdsFromRealHardware,
68             VehicleHalCallback realCallback)
69             throws RemoteException {
70         super(stub, getInitialPropValuesAndConfigs(stub));
71         mPropertyIdsFromRealHardware = new ArraySet<>(propertyIdsFromRealHardware);
72         mStartOfSimulationTime = SystemClock.elapsedRealtimeNanos();
73         mReplayingVehicleHalCallback = new ReplayingVehicleHalCallback(realCallback,
74                 new ArraySet<>(propertyIdsFromRealHardware));
75         mLastInjectedProperty = new SparseArray<>();
76     }
77 
78     @VisibleForTesting
setReplayingVehicleHalCallback(VehicleHalCallback callback, List<Integer> propertyIdsFromRealHardware)79     /* package */ void setReplayingVehicleHalCallback(VehicleHalCallback callback,
80             List<Integer> propertyIdsFromRealHardware) {
81         mReplayingVehicleHalCallback = new ReplayingVehicleHalCallback(callback,
82                 new ArraySet<>(propertyIdsFromRealHardware));
83     }
84 
85     private static Pair<SparseArray<HalPropConfig>, PairSparseArray<HalPropValue>>
getInitialPropValuesAndConfigs(VehicleStub realVehicleStub)86             getInitialPropValuesAndConfigs(VehicleStub realVehicleStub) throws RemoteException {
87         SparseArray<HalPropConfig> allProperties = getAllPropConfigSparseArray(realVehicleStub);
88         PairSparseArray<HalPropValue> propValuesByPropIdAreaId = new PairSparseArray<>();
89         for (int i = 0; i < allProperties.size(); i++) {
90             HalPropConfig halPropconfig = allProperties.valueAt(i);
91             int propId = halPropconfig.getPropId();
92             List<Integer> supportedAreaIds = getAllSupportedAreaId(propId, allProperties);
93             if (isPropertyGlobal(propId)) {
94                 supportedAreaIds = List.of(AREA_ID_GLOBAL);
95             }
96             // TODO(b/377378043): Make this a batched getAsync request.
97             for (int j = 0; j < supportedAreaIds.size(); j++) {
98                 try {
99                     HalPropValue propValueFromHardware = realVehicleStub.get(
100                             realVehicleStub.getHalPropValueBuilder().build(
101                                     propId, supportedAreaIds.get(j)));
102                     if (propValueFromHardware == null) {
103                         Slogf.w(TAG, "PropValueFromHardware is null for propId: %d, areaId: %d",
104                                 propId, supportedAreaIds.get(j));
105                         continue;
106                     }
107                     propValuesByPropIdAreaId.put(propValueFromHardware.getPropId(),
108                             propValueFromHardware.getAreaId(), propValueFromHardware);
109                 } catch (RemoteException | ServiceSpecificException e) {
110                     Slogf.w(TAG, "Unable to get propId: %d, areaId: %d from real vehicle stub",
111                             propId, supportedAreaIds.get(j), e);
112                 }
113             }
114         }
115         return new Pair<>(allProperties, propValuesByPropIdAreaId);
116     }
117 
118     @Override
isAidlVhal()119     public boolean isAidlVhal() {
120         return mRealVehicle.isAidlVhal();
121     }
122 
123     @Override
getHalPropValueBuilder()124     public HalPropValueBuilder getHalPropValueBuilder() {
125         return mRealVehicle.getHalPropValueBuilder();
126     }
127 
128     @Override
getInterfaceDescriptor()129     public String getInterfaceDescriptor() throws IllegalStateException {
130         return "com.android.car.hal.fakevhal.SimulationVehicleStub";
131     }
132 
getAllPropConfigSparseArray( VehicleStub realVehicleStub)133     private static SparseArray<HalPropConfig> getAllPropConfigSparseArray(
134             VehicleStub realVehicleStub) throws RemoteException {
135         HalPropConfig[] halPropConfigs = realVehicleStub.getAllPropConfigs();
136         SparseArray<HalPropConfig> sparseArray = new SparseArray<>();
137         for (int i = 0; i < halPropConfigs.length; i++) {
138             sparseArray.put(halPropConfigs[i].getPropId(), halPropConfigs[i]);
139         }
140         return sparseArray;
141     }
142 
143     @Override
getAllPropConfigs()144     public HalPropConfig[] getAllPropConfigs() throws RemoteException, ServiceSpecificException {
145         return mRealVehicle.getAllPropConfigs();
146     }
147 
148     @Override
newSubscriptionClient(VehicleHalCallback callback)149     public SubscriptionClient newSubscriptionClient(VehicleHalCallback callback) {
150         return mRealVehicle.newSubscriptionClient(mReplayingVehicleHalCallback);
151     }
152 
153     @Nullable
154     @Override
get(HalPropValue requestedPropValue)155     public HalPropValue get(HalPropValue requestedPropValue)
156             throws RemoteException, ServiceSpecificException {
157         if (mPropertyIdsFromRealHardware.contains(requestedPropValue.getPropId())) {
158             Slogf.d(TAG, "Get requestedPropValue from real hardware %s", requestedPropValue);
159             return mRealVehicle.get(requestedPropValue);
160         }
161         int propId = requestedPropValue.getPropId();
162         checkPropIdSupported(propId);
163         int areaId = isPropertyGlobal(propId) ? AREA_ID_GLOBAL : requestedPropValue.getAreaId();
164         checkAreaIdSupported(propId, areaId);
165         verifyReadAccess(propId, areaId);
166         HalPropValue halPropvalue = getFakeHalPropValue(propId, areaId);
167         Slogf.d(TAG, "Returning fake value for propertyId: %d, returning prop value of: %s",
168                 propId, halPropvalue);
169         return halPropvalue;
170     }
171 
172     /**
173      * Dumps VHAL debug information.
174      *
175      * @param fd The file descriptor to print output.
176      * @param args Optional additional arguments for the debug command. Can be empty.
177      * @throws RemoteException if the remote operation fails.
178      * @throws ServiceSpecificException if VHAL returns service specific error.
179      */
180     @Override
181     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(FileDescriptor fd, List<String> args)182     public void dump(FileDescriptor fd, List<String> args) throws RemoteException,
183             ServiceSpecificException {
184         IndentingPrintWriter writer = new IndentingPrintWriter(new PrintWriter(
185                 new FileOutputStream(fd)));
186         writer.println("Properties from realHardware: ");
187         writer.increaseIndent();
188         for (int i = 0; i < mPropertyIdsFromRealHardware.size(); i++) {
189             writer.println("property: " + mPropertyIdsFromRealHardware.valueAt(i));
190         }
191         writer.decreaseIndent();
192         super.dump(fd, args);
193     }
194 
195     @Override
set(HalPropValue propValue)196     public void set(HalPropValue propValue) throws RemoteException, ServiceSpecificException {
197         if (mPropertyIdsFromRealHardware.contains(propValue.getPropId())) {
198             Slogf.d(TAG, "Set requestedPropValue to real hardware %s", propValue);
199             mRealVehicle.set(propValue);
200             return;
201         }
202         Slogf.d(TAG, "Set requestedPropValue to fake stub %s", propValue);
203         int propId = propValue.getPropId();
204         checkPropIdSupported(propId);
205         int areaId = isPropertyGlobal(propId) ? AREA_ID_GLOBAL : propValue.getAreaId();
206         checkAreaIdSupported(propId, areaId);
207 
208         // Check access permission.
209         verifyWriteAccess(propId, areaId);
210 
211         HalPropValue updatedValue = buildRawPropValueAndCheckRange(propValue);
212         maybeInvokeCallback(updatedValue, propId, areaId);
213     }
214 
buildHalPropValueAndMaybeInvokeCallback(CarPropertyValue carPropertyValue)215     private void buildHalPropValueAndMaybeInvokeCallback(CarPropertyValue carPropertyValue) {
216         int propId = carPropertyValue.getPropertyId();
217         int areaId = carPropertyValue.getAreaId();
218         HalPropValue halPropValue = buildHalPropValue(carPropertyValue,
219                 carPropertyValue.getPropertyId(), SystemClock.elapsedRealtimeNanos());
220         if (maybeInvokeCallback(halPropValue, propId, areaId)) {
221             synchronized (mLock) {
222                 mLastInjectedProperty.put(propId, carPropertyValue);
223             }
224         }
225     }
226 
maybeInvokeCallback(HalPropValue halPropValue, int propId, int areaId)227     private boolean maybeInvokeCallback(HalPropValue halPropValue, int propId, int areaId) {
228         HalPropValue oldValue;
229         // Need mLock in case of race condition E.G.
230         // Thread 1 get returns 4
231         // Thread 2 get returns 4
232         // Thread 1 put 3
233         // Thread 2 put 4
234         // If lock is not present, thread 2 would not invoke onPropertyEvent change from 3 -> 4,
235         // client would assume the propValue would be 3
236         synchronized (mLock) {
237             if (mReplayingVehicleHalCallback == null) {
238                 Slogf.w(TAG, "Replaying Vehicle Hal Callback is null");
239                 return false;
240             }
241             oldValue = getPropValue(propId, areaId);
242             Slogf.d(TAG, "Fake value stored for propId: %d, areaId: %d, value: %s Storing "
243                             + "new value %s", propId, areaId, oldValue, halPropValue);
244             putPropValue(propId, areaId, halPropValue);
245         }
246         mReplayingVehicleHalCallback.getRealCallback().onInjectionPropertyEvent(List.of(
247                 halPropValue));
248         return true;
249     }
250 
251     /**
252      * Filters a list of items based on a set of property IDs.
253      *
254      * <p>This method iterates through a list of items and applies a provided function to extract a
255      * property ID from each item. It then checks if the extracted property ID exists within a
256      * given set of valid property IDs. Only items whose property IDs are present in the valid set
257      * are included in the returned filtered list.
258      *
259      * @param items The list of items to be filtered.
260      * @param propIdExtractor A function that extracts the property ID from an item.
261      * @param propertyIdsFromRealHardware A set of valid property IDs. Items with property IDs not
262      *                                    present in this set will be filtered out.
263      * @param <T> The type of items in the list.
264      * @return A new list containing only the items whose property IDs are present in
265      *         `propertyIdsFromRealHardware`.
266      */
filterProperties(List<T> items, Function<T, Integer> propIdExtractor, ArraySet<Integer> propertyIdsFromRealHardware)267     public static <T> List<T> filterProperties(List<T> items, Function<T, Integer>
268             propIdExtractor, ArraySet<Integer> propertyIdsFromRealHardware) {
269         List<T> filteredItems = new ArrayList<>();
270         for (int i = 0; i < items.size(); i++) {
271             T item = items.get(i);
272             int propId = propIdExtractor.apply(item);
273             if (!propertyIdsFromRealHardware.contains(propId)) {
274                 Slogf.d(TAG, "Filtering out real hardware due to %s property not being in "
275                         + "mPropertyIdsFromRealHardware", propId);
276                 continue;
277             }
278             filteredItems.add(item);
279         }
280         return filteredItems;
281     }
282 
283     private static final class ReplayingVehicleHalCallback implements VehicleHalCallback {
284 
285         private final VehicleHalCallback mRealCallback;
286         private final ArraySet<Integer> mPropertyIdsFromRealHardware;
287 
ReplayingVehicleHalCallback(VehicleHalCallback realCallback, ArraySet<Integer> propertyFromRealHardware)288         private ReplayingVehicleHalCallback(VehicleHalCallback realCallback,
289                 ArraySet<Integer> propertyFromRealHardware) {
290             mRealCallback = realCallback;
291             mPropertyIdsFromRealHardware = propertyFromRealHardware;
292         }
293 
filterAndInvokeCallback(List<T> items, Function<T, Integer> propIdExtractor, Consumer<List<T>> callback, String methodName)294         private <T> void filterAndInvokeCallback(List<T> items, Function<T, Integer>
295                 propIdExtractor, Consumer<List<T>> callback, String methodName) {
296             List<T> filteredItems = filterProperties(items, propIdExtractor,
297                     mPropertyIdsFromRealHardware);
298             if (filteredItems.isEmpty()) {
299                 Slogf.d(TAG, "All properties were filtered, not running %s", methodName);
300                 return;
301             }
302             callback.accept(filteredItems);
303         }
304 
305         @Override
onPropertyEvent(List<HalPropValue> values)306         public void onPropertyEvent(List<HalPropValue> values) {
307             filterAndInvokeCallback(values, HalPropValue::getPropId, mRealCallback::onPropertyEvent,
308                     "onPropertyEvent");
309         }
310 
311         @Override
onPropertySetError(List<VehiclePropError> errors)312         public void onPropertySetError(List<VehiclePropError> errors) {
313             filterAndInvokeCallback(errors, (VehiclePropError err) -> err.propId,
314                     mRealCallback::onPropertySetError, "onPropertySetError");
315         }
316 
317         @Override
onSupportedValuesChange(List<PropIdAreaId> propIdAreaIds)318         public void onSupportedValuesChange(List<PropIdAreaId> propIdAreaIds) {
319             filterAndInvokeCallback(propIdAreaIds, (PropIdAreaId propIdAreaId) ->
320                             propIdAreaId.propId, mRealCallback::onSupportedValuesChange,
321                     "onSupportedValuesChange");
322         }
323 
324         @Override
onInjectionPropertyEvent(List<HalPropValue> values)325         public void onInjectionPropertyEvent(List<HalPropValue> values) {
326             mRealCallback.onInjectionPropertyEvent(values);
327         }
328 
getRealCallback()329         private VehicleHalCallback getRealCallback() {
330             return mRealCallback;
331         }
332     }
333 
334     @Override
getSimulationStartTimestampNanos()335     public long getSimulationStartTimestampNanos() {
336         return mStartOfSimulationTime;
337     }
338 
339     @Override
isSimulatedModeEnabled()340     public boolean isSimulatedModeEnabled() {
341         return true;
342     }
343 
344     @Override
injectVehicleProperties(List<CarPropertyValue> carPropertyValues)345     public void injectVehicleProperties(List<CarPropertyValue> carPropertyValues) {
346         for (int i = 0; i < carPropertyValues.size(); i++) {
347             CarPropertyValue carPropertyValue = carPropertyValues.get(i);
348             int propId = carPropertyValue.getPropertyId();
349             int areaId = carPropertyValue.getAreaId();
350             // Skip this CarPropertyValue if the propertyId or areaId is not supported
351             try {
352                 checkPropIdSupported(propId);
353                 checkAreaIdSupported(propId, areaId);
354             } catch (ServiceSpecificException e) {
355                 throw new IllegalArgumentException("PropertyId or areaId not supported", e);
356             }
357             // Skip this CarPropertyValue if value was not within range.
358             HalPropValue halPropValue = buildHalPropValue(carPropertyValue,
359                     carPropertyValue.getPropertyId(), 0);
360             if (!isWithinRange(propId, areaId,
361                     ((VehiclePropValue) halPropValue.toVehiclePropValue()).value)) {
362                 throw new IllegalArgumentException("The property value is not within range "
363                         + toValueString(halPropValue));
364             }
365         }
366         for (int i = 0; i < carPropertyValues.size(); i++) {
367             CarPropertyValue carPropertyValue = carPropertyValues.get(i);
368             // If timeToInject < 0, will post immediately
369             long timeToInject = mStartOfSimulationTime + carPropertyValue.getTimestamp()
370                     - SystemClock.elapsedRealtimeNanos();
371             mHandler.postDelayed(() -> buildHalPropValueAndMaybeInvokeCallback(carPropertyValue),
372                     TimeUnit.NANOSECONDS.toMillis(timeToInject));
373         }
374     }
375 
376     @Override
377     @Nullable
getLastInjectedVehicleProperty(int propertyId)378     public CarPropertyValue getLastInjectedVehicleProperty(int propertyId) {
379         synchronized (mLock) {
380             return mLastInjectedProperty.get(propertyId);
381         }
382     }
383 }
384