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.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.car.hardware.CarPropertyValue; 26 import android.hardware.automotive.vehicle.RawPropValues; 27 import android.hardware.automotive.vehicle.StatusCode; 28 import android.hardware.automotive.vehicle.VehicleArea; 29 import android.hardware.automotive.vehicle.VehiclePropValue; 30 import android.hardware.automotive.vehicle.VehiclePropertyAccess; 31 import android.hardware.automotive.vehicle.VehiclePropertyType; 32 import android.os.Handler; 33 import android.os.RemoteException; 34 import android.os.ServiceSpecificException; 35 import android.os.SystemClock; 36 import android.util.Pair; 37 import android.util.SparseArray; 38 39 import com.android.car.CarLog; 40 import com.android.car.CarServiceUtils; 41 import com.android.car.IVehicleDeathRecipient; 42 import com.android.car.VehicleStub; 43 import com.android.car.hal.HalAreaConfig; 44 import com.android.car.hal.HalPropConfig; 45 import com.android.car.hal.HalPropValue; 46 import com.android.car.hal.HalPropValueBuilder; 47 import com.android.car.internal.property.CarPropertyErrorCodes; 48 import com.android.car.internal.util.IndentingPrintWriter; 49 import com.android.car.internal.util.PairSparseArray; 50 import com.android.internal.annotations.GuardedBy; 51 52 import java.io.FileDescriptor; 53 import java.io.FileOutputStream; 54 import java.io.PrintWriter; 55 import java.util.ArrayList; 56 import java.util.List; 57 58 public abstract class VehicleStubWrapper extends VehicleStub { 59 60 private static final String TAG = CarLog.tagFor(VehicleStubWrapper.class); 61 62 static final int AREA_ID_GLOBAL = 0; 63 64 final SparseArray<HalPropConfig> mPropConfigsByPropId; 65 final VehicleStub mRealVehicle; 66 @GuardedBy("mLock") 67 private final PairSparseArray<HalPropValue> mPropValuesByPropIdAreaId; 68 final Handler mHandler; 69 private final Object mLock = new Object(); 70 VehicleStubWrapper(VehicleStub vehicleStub, Pair<SparseArray<HalPropConfig>, PairSparseArray<HalPropValue>> propConfigsByPropIdPropValuesByPropIdAreaIdPair)71 public VehicleStubWrapper(VehicleStub vehicleStub, Pair<SparseArray<HalPropConfig>, 72 PairSparseArray<HalPropValue>> propConfigsByPropIdPropValuesByPropIdAreaIdPair) { 73 mRealVehicle = vehicleStub; 74 mHandler = new Handler(CarServiceUtils.getHandlerThread(getClass().getSimpleName()) 75 .getLooper()); 76 mPropConfigsByPropId = propConfigsByPropIdPropValuesByPropIdAreaIdPair.first; 77 mPropValuesByPropIdAreaId = propConfigsByPropIdPropValuesByPropIdAreaIdPair.second; 78 } 79 80 /** 81 * Checks if a property is a global property. 82 * 83 * @param propId The property to be checked. 84 * @return {@code true} if this property is a global property. 85 */ isPropertyGlobal(int propId)86 /* package */ static boolean isPropertyGlobal(int propId) { 87 return (propId & VehicleArea.MASK) == VehicleArea.GLOBAL; 88 } 89 90 /** 91 * Puts the {@code HalPropValue} based on the propId and areaId. 92 * 93 * @param propId The given propId 94 * @param areaId The given areaId 95 * @param halPropValue The given HalPropValue 96 */ putPropValue(int propId, int areaId, HalPropValue halPropValue)97 /* package */ void putPropValue(int propId, int areaId, HalPropValue halPropValue) { 98 synchronized (mLock) { 99 mPropValuesByPropIdAreaId.put(propId, areaId, halPropValue); 100 } 101 } 102 103 /** 104 * Gets the current {@code HalPropValue} given the propId and areaId. 105 * 106 * @param propId The given propId 107 * @param areaId The Given areaId 108 * @return The HalPropValue if it exists. 109 */ 110 @Nullable getPropValue(int propId, int areaId)111 /* package */ HalPropValue getPropValue(int propId, int areaId) { 112 synchronized (mLock) { 113 return mPropValuesByPropIdAreaId.get(propId, areaId); 114 } 115 } 116 117 /** 118 * Generates a list of all supported areaId for a certain property. 119 * 120 * @param propId The property to get all supported areaIds. 121 * @return A {@link List} of all supported areaId. 122 */ getAllSupportedAreaId(int propId)123 /* package */ List<Integer> getAllSupportedAreaId(int propId) { 124 return getAllSupportedAreaId(propId, mPropConfigsByPropId); 125 } 126 127 /** 128 * Generates a list of all supported areaId for a certain property from the given configs by 129 * propId. 130 * 131 * @param propId The property to get all supported areaIds. 132 * @param allProperties The given configs by propId to get all supported areaIds for. 133 * @return A {@link List} of all supported areaId. 134 */ getAllSupportedAreaId(int propId, SparseArray<HalPropConfig> allProperties)135 /* package */ static List<Integer> getAllSupportedAreaId(int propId, 136 SparseArray<HalPropConfig> allProperties) { 137 List<Integer> allSupportedAreaId = new ArrayList<>(); 138 HalAreaConfig[] areaConfigs = allProperties.get(propId).getAreaConfigs(); 139 for (int i = 0; i < areaConfigs.length; i++) { 140 allSupportedAreaId.add(areaConfigs[i].getAreaId()); 141 } 142 return allSupportedAreaId; 143 } 144 145 /** 146 * Checks if a property is supported. If not, throw a {@link ServiceSpecificException}. 147 * 148 * @param propId The property to be checked. 149 */ checkPropIdSupported(int propId)150 /* package */ void checkPropIdSupported(int propId) { 151 // Check if the property config exists. 152 if (!mPropConfigsByPropId.contains(propId)) { 153 throw new ServiceSpecificException(StatusCode.INVALID_ARG, "The propId: " + propId 154 + " is not supported."); 155 } 156 } 157 158 /** 159 * Checks if an areaId of a property is supported. 160 * 161 * @param propId The property to be checked. 162 * @param areaId The area to be checked. 163 */ checkAreaIdSupported(int propId, int areaId)164 /* package */ void checkAreaIdSupported(int propId, int areaId) { 165 List<Integer> supportedAreaIds = getAllSupportedAreaId(propId); 166 // For global property, areaId will be ignored if the area config array is empty. 167 if ((isPropertyGlobal(propId) && supportedAreaIds.isEmpty()) 168 || supportedAreaIds.contains(areaId)) { 169 return; 170 } 171 throw new ServiceSpecificException(StatusCode.INVALID_ARG, "The areaId: " + areaId 172 + " is not supported."); 173 } 174 175 /** 176 * Gets the HalPropValue based on propId and areaId with checks. 177 * @param propId The given propId 178 * @param areaId The given areaId 179 * @return The given HalPropValue. 180 */ getFakeHalPropValue(int propId, int areaId)181 /* package */ HalPropValue getFakeHalPropValue(int propId, int areaId) { 182 // PropId config exists but the value map doesn't have this propId, this may be caused by: 183 // 1. This property is a global property, and it doesn't have default prop value. 184 // 2. This property has area configs, and it has neither default prop value nor area value. 185 synchronized (mLock) { 186 HalPropValue halPropValue = mPropValuesByPropIdAreaId.get(propId, areaId); 187 if (halPropValue == null) { 188 if (isPropertyGlobal(propId)) { 189 throw new ServiceSpecificException(StatusCode.NOT_AVAILABLE, 190 "propId: " + propId + " has no property value."); 191 } 192 throw new ServiceSpecificException(StatusCode.NOT_AVAILABLE, 193 "propId: " + propId + ", areaId: " + areaId + " has no property value."); 194 } 195 return halPropValue; 196 } 197 } 198 199 /** 200 * Gets the access of the given propId and areaId. 201 * @param propId The given propId. 202 * @param areaId The given areaId. 203 * @return The access type of the given propId areaId. 204 */ getAccess(int propId, int areaId)205 /* package */ int getAccess(int propId, int areaId) { 206 HalPropConfig halPropConfig = mPropConfigsByPropId.get(propId); 207 HalAreaConfig[] halAreaConfigs = halPropConfig.getAreaConfigs(); 208 for (int i = 0; i < halAreaConfigs.length; i++) { 209 if (halAreaConfigs[i].getAreaId() != areaId) { 210 continue; 211 } 212 int areaAccess = halAreaConfigs[i].getAccess(); 213 if (areaAccess != VehiclePropertyAccess.NONE) { 214 return areaAccess; 215 } 216 break; 217 } 218 return halPropConfig.getAccess(); 219 } 220 221 /** 222 * Checks if the set value is within the value range. 223 * 224 * @return {@code true} if set value is within the prop config range. 225 */ isWithinRange(int propId, int areaId, RawPropValues rawPropValues)226 /* package */ boolean isWithinRange(int propId, int areaId, RawPropValues rawPropValues) { 227 // For global property without areaId. 228 if (isPropertyGlobal(propId) && getAllSupportedAreaId(propId).isEmpty()) { 229 return true; 230 } 231 232 // For non-global properties and global properties with areaIds. 233 int index = getAllSupportedAreaId(propId).indexOf(areaId); 234 235 HalAreaConfig areaConfig = mPropConfigsByPropId.get(propId).getAreaConfigs()[index]; 236 237 int[] int32Values = rawPropValues.int32Values; 238 long[] int64Values = rawPropValues.int64Values; 239 float[] floatValues = rawPropValues.floatValues; 240 // If max and min values exists, then check the boundaries. If max and min values are all 241 // 0s, return true. 242 switch (getPropType(propId)) { 243 case VehiclePropertyType.INT32: 244 case VehiclePropertyType.INT32_VEC: 245 int minInt32Value = areaConfig.getMinInt32Value(); 246 int maxInt32Value = areaConfig.getMaxInt32Value(); 247 if (minInt32Value != maxInt32Value || minInt32Value != 0) { 248 for (int int32Value : int32Values) { 249 if (int32Value > maxInt32Value || int32Value < minInt32Value) { 250 Slogf.e(TAG, "For propId: %d, areaId: %d, the valid min value is: " 251 + "%d, max value is: %d, but the given value is: %d.", 252 propId, areaId, minInt32Value, maxInt32Value, int32Value); 253 return false; 254 } 255 } 256 } 257 break; 258 case VehiclePropertyType.INT64: 259 case VehiclePropertyType.INT64_VEC: 260 long minInt64Value = areaConfig.getMinInt64Value(); 261 long maxInt64Value = areaConfig.getMaxInt64Value(); 262 if (minInt64Value != maxInt64Value || minInt64Value != 0) { 263 for (long int64Value : int64Values) { 264 if (int64Value > maxInt64Value || int64Value < minInt64Value) { 265 Slogf.e(TAG, "For propId: %d, areaId: %d, the valid min value is: " 266 + "%d, max value is: %d, but the given value is: %d.", 267 propId, areaId, minInt64Value, maxInt64Value, int64Value); 268 return false; 269 } 270 } 271 } 272 break; 273 case VehiclePropertyType.FLOAT: 274 case VehiclePropertyType.FLOAT_VEC: 275 float minFloatValue = areaConfig.getMinFloatValue(); 276 float maxFloatValue = areaConfig.getMaxFloatValue(); 277 if (minFloatValue != maxFloatValue || minFloatValue != 0) { 278 for (float floatValue : floatValues) { 279 if (floatValue > maxFloatValue || floatValue < minFloatValue) { 280 Slogf.e(TAG, "For propId: %d, areaId: %d, the valid min value is: " 281 + "%f, max value is: %f, but the given value is: %d.", 282 propId, areaId, minFloatValue, maxFloatValue, floatValue); 283 return false; 284 } 285 } 286 } 287 break; 288 default: 289 Slogf.d(TAG, "Skip checking range for propId: %d because it is mixed type.", 290 propId); 291 } 292 return true; 293 } 294 295 /** 296 * Verifies the propId areaId has read access. 297 * @param propId The given propId. 298 * @param areaId The given areaId. 299 */ verifyReadAccess(int propId, int areaId)300 /* package */ void verifyReadAccess(int propId, int areaId) { 301 int access = getAccess(propId, areaId); 302 if (access != VehiclePropertyAccess.READ && access != VehiclePropertyAccess.READ_WRITE) { 303 throw new ServiceSpecificException(StatusCode.ACCESS_DENIED, "This property " + propId 304 + " doesn't have read permission."); 305 } 306 } 307 308 /** 309 * Verifies the propId areaId has write access. 310 * @param propId The given propId. 311 * @param areaId The given areaId. 312 */ verifyWriteAccess(int propId, int areaId)313 /* package */ void verifyWriteAccess(int propId, int areaId) { 314 int access = getAccess(propId, areaId); 315 if (access != VehiclePropertyAccess.WRITE && access != VehiclePropertyAccess.READ_WRITE) { 316 throw new ServiceSpecificException(StatusCode.ACCESS_DENIED, "This property " + propId 317 + " doesn't have write permission."); 318 } 319 } 320 321 /** 322 * Gets the type of property. 323 * 324 * @param propId The property to get the type. 325 * @return The type. 326 */ getPropType(int propId)327 /* package */ static int getPropType(int propId) { 328 return propId & VehiclePropertyType.MASK; 329 } 330 331 /** 332 * Builds a {@link HalPropValue} from the given {@link HalPropValue} and checks if the 333 * raw values are within the allowed range. 334 * 335 * @param propValue the {@link HalPropValue} to build from 336 * @return the built {@link HalPropValue} 337 * @throws ServiceSpecificException if the raw values are not within the allowed range 338 */ buildRawPropValueAndCheckRange(HalPropValue propValue)339 /* package */ HalPropValue buildRawPropValueAndCheckRange(HalPropValue propValue) { 340 int propId = propValue.getPropId(); 341 int areaId = propValue.getAreaId(); 342 RawPropValues rawPropValues = ((VehiclePropValue) propValue.toVehiclePropValue()).value; 343 344 // Check if the set values are within the value config range. 345 if (!isWithinRange(propId, areaId, rawPropValues)) { 346 throw new ServiceSpecificException(StatusCode.INVALID_ARG, 347 "The set value is outside the range."); 348 } 349 350 return buildHalPropValue(propId, areaId, 351 SystemClock.elapsedRealtimeNanos(), rawPropValues); 352 } 353 354 /** 355 * Builds a {@link HalPropValue}. 356 * 357 * @param propId The propId of the prop value to be built. 358 * @param areaId The areaId of the prop value to be built. 359 * @param timestamp The elapsed time in nanoseconds when mPropConfigsByPropId is initialized. 360 * @param rawPropValues The {@link RawPropValues} contains property values. 361 * @return a {@link HalPropValue} built by propId, areaId, timestamp and value. 362 */ buildHalPropValue(int propId, int areaId, long timestamp, RawPropValues rawPropValues)363 /* package */ HalPropValue buildHalPropValue(int propId, int areaId, long timestamp, 364 RawPropValues rawPropValues) { 365 return buildHalPropValue(propId, areaId, timestamp, rawPropValues, 366 getHalPropValueBuilder()); 367 } 368 369 /** 370 * Builds a {@link HalPropValue} from the given parameters using a {@link HalPropValueBuilder}. 371 * 372 * @param propId the property ID 373 * @param areaId the area ID 374 * @param timestamp the timestamp 375 * @param rawPropValues the raw property values 376 * @param halPropValueBuilder the {@link HalPropValueBuilder} to use 377 * @return the built {@link HalPropValue} 378 */ buildHalPropValue(int propId, int areaId, long timestamp, RawPropValues rawPropValues, HalPropValueBuilder halPropValueBuilder)379 /* package */ static HalPropValue buildHalPropValue(int propId, int areaId, long timestamp, 380 RawPropValues rawPropValues, HalPropValueBuilder halPropValueBuilder) { 381 VehiclePropValue propValue = new VehiclePropValue(); 382 propValue.prop = propId; 383 propValue.areaId = areaId; 384 propValue.timestamp = timestamp; 385 propValue.value = rawPropValues; 386 return halPropValueBuilder.build(propValue); 387 } 388 389 /** 390 * Builds a {@link HalPropValue} from the given parameters using a {@link HalPropValueBuilder} 391 * 392 */ buildHalPropValue(CarPropertyValue carPropertyValue, int halPropId, long timestamp)393 /* package */ HalPropValue buildHalPropValue(CarPropertyValue carPropertyValue, 394 int halPropId, long timestamp) { 395 return getHalPropValueBuilder().build(carPropertyValue, halPropId, timestamp, 396 mPropConfigsByPropId.get(halPropId)); 397 } 398 399 /** 400 * Gets properties asynchronously. 401 * 402 * @param getVehicleStubAsyncRequests The async request list. 403 * @param getVehicleStubAsyncCallback The callback for getting property values. 404 */ 405 @Override getAsync(List<AsyncGetSetRequest> getVehicleStubAsyncRequests, VehicleStubCallbackInterface getVehicleStubAsyncCallback)406 public void getAsync(List<AsyncGetSetRequest> getVehicleStubAsyncRequests, 407 VehicleStubCallbackInterface getVehicleStubAsyncCallback) { 408 List<GetVehicleStubAsyncResult> onGetAsyncResultList = new ArrayList<>(); 409 for (int i = 0; i < getVehicleStubAsyncRequests.size(); i++) { 410 AsyncGetSetRequest request = getVehicleStubAsyncRequests.get(i); 411 GetVehicleStubAsyncResult result; 412 try { 413 HalPropValue halPropValue = get(request.getHalPropValue()); 414 result = new GetVehicleStubAsyncResult(request.getServiceRequestId(), 415 halPropValue); 416 if (halPropValue == null) { 417 result = new GetVehicleStubAsyncResult(request.getServiceRequestId(), 418 ERROR_CODES_NOT_AVAILABLE); 419 } 420 } catch (ServiceSpecificException e) { 421 CarPropertyErrorCodes carPropertyErrorCodes = 422 createFromVhalStatusCode(e.errorCode); 423 result = new GetVehicleStubAsyncResult(request.getServiceRequestId(), 424 carPropertyErrorCodes); 425 } catch (RemoteException e) { 426 result = new GetVehicleStubAsyncResult(request.getServiceRequestId(), 427 ERROR_CODES_INTERNAL); 428 } 429 onGetAsyncResultList.add(result); 430 } 431 mHandler.post(() -> { 432 getVehicleStubAsyncCallback.onGetAsyncResults(onGetAsyncResultList); 433 }); 434 } 435 436 /** 437 * Sets properties asynchronously. 438 * 439 * @param setVehicleStubAsyncRequests The async request list. 440 * @param setVehicleStubAsyncCallback the callback for setting property values. 441 */ 442 @Override setAsync(List<AsyncGetSetRequest> setVehicleStubAsyncRequests, VehicleStubCallbackInterface setVehicleStubAsyncCallback)443 public void setAsync(List<AsyncGetSetRequest> setVehicleStubAsyncRequests, 444 VehicleStubCallbackInterface setVehicleStubAsyncCallback) { 445 List<SetVehicleStubAsyncResult> onSetAsyncResultsList = new ArrayList<>(); 446 for (int i = 0; i < setVehicleStubAsyncRequests.size(); i++) { 447 AsyncGetSetRequest setRequest = setVehicleStubAsyncRequests.get(i); 448 int serviceRequestId = setRequest.getServiceRequestId(); 449 SetVehicleStubAsyncResult result; 450 try { 451 set(setRequest.getHalPropValue()); 452 result = new SetVehicleStubAsyncResult(serviceRequestId); 453 } catch (RemoteException e) { 454 result = new SetVehicleStubAsyncResult(serviceRequestId, 455 ERROR_CODES_INTERNAL); 456 } catch (ServiceSpecificException e) { 457 CarPropertyErrorCodes carPropertyErrorCodes = 458 createFromVhalStatusCode(e.errorCode); 459 result = new SetVehicleStubAsyncResult(serviceRequestId, carPropertyErrorCodes); 460 } 461 onSetAsyncResultsList.add(result); 462 } 463 mHandler.post(() -> { 464 setVehicleStubAsyncCallback.onSetAsyncResults(onSetAsyncResultsList); 465 }); 466 } 467 468 /** 469 * Checks if FakeVehicleStub connects to a valid Vhal. 470 * 471 * @return {@code true} if connects to a valid Vhal. 472 */ 473 @Override isValid()474 public boolean isValid() { 475 return mRealVehicle.isValid(); 476 } 477 478 /** 479 * Registers a death recipient that would be called when Vhal died. 480 * 481 * @param recipient A death recipient. 482 * @throws IllegalStateException If unable to register the death recipient. 483 */ 484 @Override linkToDeath(IVehicleDeathRecipient recipient)485 public void linkToDeath(IVehicleDeathRecipient recipient) throws IllegalStateException { 486 mRealVehicle.linkToDeath(recipient); 487 } 488 489 /** 490 * Unlinks a previously linked death recipient. 491 * 492 * @param recipient A previously linked death recipient. 493 */ 494 @Override unlinkToDeath(IVehicleDeathRecipient recipient)495 public void unlinkToDeath(IVehicleDeathRecipient recipient) { 496 mRealVehicle.unlinkToDeath(recipient); 497 } 498 499 /** 500 * @return {@code true} if car service is connected to FakeVehicleStub. 501 */ 502 @Override isFakeModeEnabled()503 public boolean isFakeModeEnabled() { 504 return false; 505 } 506 507 @Override dump(FileDescriptor fd, List<String> args)508 public void dump(FileDescriptor fd, List<String> args) throws RemoteException, 509 ServiceSpecificException { 510 IndentingPrintWriter writer = new IndentingPrintWriter(new PrintWriter( 511 new FileOutputStream(fd))); 512 synchronized (mLock) { 513 writer.println("Fake values: "); 514 writer.increaseIndent(); 515 for (int i = 0; i < mPropValuesByPropIdAreaId.size(); i++) { 516 HalPropValue propValue = mPropValuesByPropIdAreaId.valueAt(i); 517 writer.println("HalPropValue: " + propValue); 518 } 519 writer.decreaseIndent(); 520 } 521 mRealVehicle.dump(fd, args); 522 } 523 524 /** 525 * @return The real vehicle stub. 526 */ 527 @Override getRealVehicleStub()528 public VehicleStub getRealVehicleStub() { 529 return mRealVehicle; 530 } 531 } 532