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