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