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.convertVhalStatusCodeToCarPropertyManagerErrorCodes; 20 21 import android.annotation.Nullable; 22 import android.car.builtin.util.Slogf; 23 import android.car.hardware.property.CarPropertyManager; 24 import android.hardware.automotive.vehicle.RawPropValues; 25 import android.hardware.automotive.vehicle.StatusCode; 26 import android.hardware.automotive.vehicle.SubscribeOptions; 27 import android.hardware.automotive.vehicle.VehicleArea; 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.VehiclePropertyAccess; 33 import android.hardware.automotive.vehicle.VehiclePropertyChangeMode; 34 import android.hardware.automotive.vehicle.VehiclePropertyType; 35 import android.os.Handler; 36 import android.os.RemoteException; 37 import android.os.ServiceSpecificException; 38 import android.os.SystemClock; 39 import android.util.ArrayMap; 40 import android.util.ArraySet; 41 import android.util.SparseArray; 42 43 import com.android.car.CarLog; 44 import com.android.car.CarServiceUtils; 45 import com.android.car.IVehicleDeathRecipient; 46 import com.android.car.VehicleStub; 47 import com.android.car.hal.AidlHalPropConfig; 48 import com.android.car.hal.HalAreaConfig; 49 import com.android.car.hal.HalPropConfig; 50 import com.android.car.hal.HalPropValue; 51 import com.android.car.hal.HalPropValueBuilder; 52 import com.android.car.hal.VehicleHalCallback; 53 import com.android.car.internal.property.CarPropertyErrorCodes; 54 import com.android.car.internal.util.PairSparseArray; 55 import com.android.internal.annotations.GuardedBy; 56 import com.android.internal.annotations.VisibleForTesting; 57 58 import java.io.BufferedReader; 59 import java.io.File; 60 import java.io.FileDescriptor; 61 import java.io.FileReader; 62 import java.io.IOException; 63 import java.io.InputStream; 64 import java.util.ArrayList; 65 import java.util.List; 66 import java.util.Map; 67 import java.util.Set; 68 69 /** 70 * FakeVehicleStub represents a fake Vhal implementation. 71 */ 72 public final class FakeVehicleStub extends VehicleStub { 73 74 private static final String TAG = CarLog.tagFor(FakeVehicleStub.class); 75 private static final List<Integer> SPECIAL_PROPERTIES = List.of( 76 VehicleProperty.VHAL_HEARTBEAT, 77 VehicleProperty.INITIAL_USER_INFO, 78 VehicleProperty.SWITCH_USER, 79 VehicleProperty.CREATE_USER, 80 VehicleProperty.REMOVE_USER, 81 VehicleProperty.USER_IDENTIFICATION_ASSOCIATION, 82 VehicleProperty.AP_POWER_STATE_REPORT, 83 VehicleProperty.AP_POWER_STATE_REQ, 84 VehicleProperty.VEHICLE_MAP_SERVICE, 85 VehicleProperty.OBD2_FREEZE_FRAME_CLEAR, 86 VehicleProperty.OBD2_FREEZE_FRAME, 87 VehicleProperty.OBD2_FREEZE_FRAME_INFO 88 ); 89 private static final String FAKE_VHAL_CONFIG_DIRECTORY = "/data/system/car/fake_vhal_config/"; 90 private static final String DEFAULT_CONFIG_FILE_NAME = "DefaultProperties.json"; 91 private static final String FAKE_MODE_ENABLE_FILE_NAME = "ENABLE"; 92 private static final int AREA_ID_GLOBAL = 0; 93 94 private final SparseArray<HalPropConfig> mPropConfigsByPropId; 95 private final VehicleStub mRealVehicle; 96 private final HalPropValueBuilder mHalPropValueBuilder; 97 private final FakeVhalConfigParser mParser; 98 private final List<File> mCustomConfigFiles; 99 private final Handler mHandler; 100 private final List<Integer> mHvacPowerSupportedAreas; 101 private final List<Integer> mHvacPowerDependentProps; 102 103 private final Object mLock = new Object(); 104 @GuardedBy("mLock") 105 private final PairSparseArray<HalPropValue> mPropValuesByPropIdAreaId; 106 @GuardedBy("mLock") 107 private final PairSparseArray<Set<FakeVhalSubscriptionClient>> 108 mOnChangeSubscribeClientByPropIdAreaId = new PairSparseArray<>(); 109 @GuardedBy("mLock") 110 private final Map<FakeVhalSubscriptionClient, PairSparseArray<ContinuousPropUpdater>> 111 mUpdaterByPropIdAreaIdByClient = new ArrayMap<>(); 112 113 /** 114 * Checks if fake mode is enabled. 115 * 116 * @return {@code true} if ENABLE file exists. 117 */ doesEnableFileExist()118 public static boolean doesEnableFileExist() { 119 return new File(FAKE_VHAL_CONFIG_DIRECTORY + FAKE_MODE_ENABLE_FILE_NAME).exists(); 120 } 121 122 /** 123 * Initializes a {@link FakeVehicleStub} instance. 124 * 125 * @param realVehicle The real Vhal to be connected to handle special properties. 126 * @throws RemoteException if the remote operation through mRealVehicle fails. 127 * @throws IOException if unable to read the config file stream. 128 * @throws IllegalArgumentException if a JSONException is caught or some parsing error occurred. 129 */ FakeVehicleStub(VehicleStub realVehicle)130 public FakeVehicleStub(VehicleStub realVehicle) throws RemoteException, IOException, 131 IllegalArgumentException { 132 this(realVehicle, new FakeVhalConfigParser(), getCustomConfigFiles()); 133 } 134 135 /** 136 * Initializes a {@link FakeVehicleStub} instance with {@link FakeVhalConfigParser} for testing. 137 * 138 * @param realVehicle The real Vhal to be connected to handle special properties. 139 * @param parser The parser to parse config files. 140 * @param customConfigFiles The {@link List} of custom config files. 141 * @throws RemoteException if failed to get configs for special property from real Vehicle HAL. 142 * @throws IOException if unable to read the config file stream. 143 * @throws IllegalArgumentException if a JSONException is caught or some parsing error occurred. 144 */ 145 @VisibleForTesting FakeVehicleStub(VehicleStub realVehicle, FakeVhalConfigParser parser, List<File> customConfigFiles)146 FakeVehicleStub(VehicleStub realVehicle, FakeVhalConfigParser parser, 147 List<File> customConfigFiles) throws RemoteException, IOException, 148 IllegalArgumentException { 149 mRealVehicle = realVehicle; 150 mHalPropValueBuilder = new HalPropValueBuilder(/* isAidl= */ true); 151 mParser = parser; 152 mCustomConfigFiles = customConfigFiles; 153 SparseArray<ConfigDeclaration> configDeclarationsByPropId = parseConfigFiles(); 154 mPropConfigsByPropId = extractPropConfigs(configDeclarationsByPropId); 155 mPropValuesByPropIdAreaId = extractPropValues(configDeclarationsByPropId); 156 mHvacPowerSupportedAreas = getHvacPowerSupportedAreaId(); 157 mHvacPowerDependentProps = getHvacPowerDependentProps(); 158 mHandler = new Handler(CarServiceUtils.getHandlerThread(getClass().getSimpleName()) 159 .getLooper()); 160 Slogf.d(TAG, "A FakeVehicleStub instance is created."); 161 } 162 163 /** 164 * FakeVehicleStub is neither an AIDL VHAL nor HIDL VHAL. But it acts like an AIDL VHAL. 165 * 166 * @return {@code true} since FakeVehicleStub acts like an AIDL VHAL. 167 */ 168 @Override isAidlVhal()169 public boolean isAidlVhal() { 170 return true; 171 } 172 173 /** 174 * Gets {@link HalPropValueBuilder} for building a {@link HalPropValue}. 175 * 176 * @return a builder to build a {@link HalPropValue}. 177 */ 178 @Override getHalPropValueBuilder()179 public HalPropValueBuilder getHalPropValueBuilder() { 180 return mHalPropValueBuilder; 181 } 182 183 /** 184 * Gets properties asynchronously. 185 * 186 * @param getVehicleStubAsyncRequests The async request list. 187 * @param getVehicleStubAsyncCallback The callback for getting property values. 188 */ 189 @Override getAsync(List<AsyncGetSetRequest> getVehicleStubAsyncRequests, VehicleStubCallbackInterface getVehicleStubAsyncCallback)190 public void getAsync(List<AsyncGetSetRequest> getVehicleStubAsyncRequests, 191 VehicleStubCallbackInterface getVehicleStubAsyncCallback) { 192 List<GetVehicleStubAsyncResult> onGetAsyncResultList = new ArrayList<>(); 193 for (int i = 0; i < getVehicleStubAsyncRequests.size(); i++) { 194 AsyncGetSetRequest request = getVehicleStubAsyncRequests.get(i); 195 GetVehicleStubAsyncResult result; 196 try { 197 HalPropValue halPropValue = get(request.getHalPropValue()); 198 result = new GetVehicleStubAsyncResult(request.getServiceRequestId(), 199 halPropValue); 200 if (halPropValue == null) { 201 result = new GetVehicleStubAsyncResult(request.getServiceRequestId(), 202 new CarPropertyErrorCodes( 203 CarPropertyManager.STATUS_ERROR_NOT_AVAILABLE, 204 /* vendorErrorCode= */ 0, 205 /* systemErrorCode */ 0)); 206 } 207 } catch (ServiceSpecificException e) { 208 CarPropertyErrorCodes carPropertyErrorCodes = 209 convertVhalStatusCodeToCarPropertyManagerErrorCodes(e.errorCode); 210 result = new GetVehicleStubAsyncResult(request.getServiceRequestId(), 211 carPropertyErrorCodes); 212 } catch (RemoteException e) { 213 result = new GetVehicleStubAsyncResult(request.getServiceRequestId(), 214 new CarPropertyErrorCodes( 215 CarPropertyManager.STATUS_ERROR_INTERNAL_ERROR, 216 /* vendorErrorCode= */ 0, 217 /* systemErrorCode */ 0)); 218 } 219 onGetAsyncResultList.add(result); 220 } 221 mHandler.post(() -> { 222 getVehicleStubAsyncCallback.onGetAsyncResults(onGetAsyncResultList); 223 }); 224 } 225 226 /** 227 * Sets properties asynchronously. 228 * 229 * @param setVehicleStubAsyncRequests The async request list. 230 * @param setVehicleStubAsyncCallback the callback for setting property values. 231 */ 232 @Override setAsync(List<AsyncGetSetRequest> setVehicleStubAsyncRequests, VehicleStubCallbackInterface setVehicleStubAsyncCallback)233 public void setAsync(List<AsyncGetSetRequest> setVehicleStubAsyncRequests, 234 VehicleStubCallbackInterface setVehicleStubAsyncCallback) { 235 List<SetVehicleStubAsyncResult> onSetAsyncResultsList = new ArrayList<>(); 236 for (int i = 0; i < setVehicleStubAsyncRequests.size(); i++) { 237 AsyncGetSetRequest setRequest = setVehicleStubAsyncRequests.get(i); 238 int serviceRequestId = setRequest.getServiceRequestId(); 239 SetVehicleStubAsyncResult result; 240 try { 241 set(setRequest.getHalPropValue()); 242 result = new SetVehicleStubAsyncResult(serviceRequestId); 243 } catch (RemoteException e) { 244 result = new SetVehicleStubAsyncResult(serviceRequestId, 245 new CarPropertyErrorCodes( 246 CarPropertyManager.STATUS_ERROR_INTERNAL_ERROR, 247 /* vendorErrorCode= */ 0, 248 /* systemErrorCode */ 0)); 249 } catch (ServiceSpecificException e) { 250 CarPropertyErrorCodes carPropertyErrorCodes = 251 convertVhalStatusCodeToCarPropertyManagerErrorCodes(e.errorCode); 252 result = new SetVehicleStubAsyncResult(serviceRequestId, carPropertyErrorCodes); 253 } 254 onSetAsyncResultsList.add(result); 255 } 256 mHandler.post(() -> { 257 setVehicleStubAsyncCallback.onSetAsyncResults(onSetAsyncResultsList); 258 }); 259 } 260 261 /** 262 * Checks if FakeVehicleStub connects to a valid Vhal. 263 * 264 * @return {@code true} if connects to a valid Vhal. 265 */ 266 @Override isValid()267 public boolean isValid() { 268 return mRealVehicle.isValid(); 269 } 270 271 /** 272 * Gets the interface descriptor for the connecting vehicle HAL. 273 * 274 * @throws IllegalStateException If unable to get the descriptor. 275 */ 276 @Override getInterfaceDescriptor()277 public String getInterfaceDescriptor() throws IllegalStateException { 278 return "com.android.car.hal.fakevhal.FakeVehicleStub"; 279 } 280 281 /** 282 * Registers a death recipient that would be called when Vhal died. 283 * 284 * @param recipient A death recipient. 285 * @throws IllegalStateException If unable to register the death recipient. 286 */ 287 @Override linkToDeath(IVehicleDeathRecipient recipient)288 public void linkToDeath(IVehicleDeathRecipient recipient) throws IllegalStateException { 289 mRealVehicle.linkToDeath(recipient); 290 } 291 292 /** 293 * Unlinks a previously linked death recipient. 294 * 295 * @param recipient A previously linked death recipient. 296 */ 297 @Override unlinkToDeath(IVehicleDeathRecipient recipient)298 public void unlinkToDeath(IVehicleDeathRecipient recipient) { 299 mRealVehicle.unlinkToDeath(recipient); 300 } 301 302 /** 303 * Gets all property configs. 304 * 305 * @return an array of all property configs. 306 */ 307 @Override getAllPropConfigs()308 public HalPropConfig[] getAllPropConfigs() { 309 HalPropConfig[] propConfigs = new HalPropConfig[mPropConfigsByPropId.size()]; 310 for (int i = 0; i < mPropConfigsByPropId.size(); i++) { 311 propConfigs[i] = mPropConfigsByPropId.valueAt(i); 312 } 313 return propConfigs; 314 } 315 316 /** 317 * Gets a new {@code SubscriptionClient} that could be used to subscribe/unsubscribe. 318 * 319 * @param callback A callback that could be used to receive events. 320 * @return a {@code SubscriptionClient} that could be used to subscribe/unsubscribe. 321 */ 322 @Override newSubscriptionClient(VehicleHalCallback callback)323 public SubscriptionClient newSubscriptionClient(VehicleHalCallback callback) { 324 return new FakeVhalSubscriptionClient(callback, 325 mRealVehicle.newSubscriptionClient(callback)); 326 } 327 getAccess(int propId, int areaId)328 private int getAccess(int propId, int areaId) { 329 HalPropConfig halPropConfig = mPropConfigsByPropId.get(propId); 330 HalAreaConfig[] halAreaConfigs = halPropConfig.getAreaConfigs(); 331 for (int i = 0; i < halAreaConfigs.length; i++) { 332 if (halAreaConfigs[i].getAreaId() != areaId) { 333 continue; 334 } 335 int areaAccess = halAreaConfigs[i].getAccess(); 336 if (areaAccess != VehiclePropertyAccess.NONE) { 337 return areaAccess; 338 } 339 break; 340 } 341 return halPropConfig.getAccess(); 342 } 343 344 /** 345 * Gets a property value. 346 * 347 * @param requestedPropValue The property to get. 348 * @return the property value. 349 * @throws RemoteException if getting value for special props through real vehicle HAL fails. 350 * @throws ServiceSpecificException if propId or areaId is not supported. 351 */ 352 @Override 353 @Nullable get(HalPropValue requestedPropValue)354 public HalPropValue get(HalPropValue requestedPropValue) throws RemoteException, 355 ServiceSpecificException { 356 int propId = requestedPropValue.getPropId(); 357 checkPropIdSupported(propId); 358 int areaId = isPropertyGlobal(propId) ? AREA_ID_GLOBAL : requestedPropValue.getAreaId(); 359 checkAreaIdSupported(propId, areaId); 360 361 // For HVAC power dependent properties, check if HVAC_POWER_ON is on. 362 if (isHvacPowerDependentProp(propId)) { 363 checkPropAvailable(propId, areaId); 364 } 365 // Check access permission. 366 int access = getAccess(propId, areaId); 367 if (access != VehiclePropertyAccess.READ && access != VehiclePropertyAccess.READ_WRITE) { 368 throw new ServiceSpecificException(StatusCode.ACCESS_DENIED, "This property " + propId 369 + " doesn't have read permission."); 370 } 371 372 if (isSpecialProperty(propId)) { 373 return mRealVehicle.get(requestedPropValue); 374 } 375 376 // PropId config exists but the value map doesn't have this propId, this may be caused by: 377 // 1. This property is a global property, and it doesn't have default prop value. 378 // 2. This property has area configs, and it has neither default prop value nor area value. 379 synchronized (mLock) { 380 HalPropValue halPropValue = mPropValuesByPropIdAreaId.get(propId, areaId); 381 if (halPropValue == null) { 382 if (isPropertyGlobal(propId)) { 383 throw new ServiceSpecificException(StatusCode.NOT_AVAILABLE, 384 "propId: " + propId + " has no property value."); 385 } 386 throw new ServiceSpecificException(StatusCode.NOT_AVAILABLE, 387 "propId: " + propId + ", areaId: " + areaId + " has no property value."); 388 } 389 return halPropValue; 390 } 391 } 392 393 /** 394 * Sets a property value. 395 * 396 * @param propValue The property to set. 397 * @throws RemoteException if setting value for special props through real vehicle HAL fails. 398 * @throws ServiceSpecificException if propId or areaId is not supported. 399 */ 400 @Override set(HalPropValue propValue)401 public void set(HalPropValue propValue) throws RemoteException, 402 ServiceSpecificException { 403 int propId = propValue.getPropId(); 404 checkPropIdSupported(propId); 405 int areaId = isPropertyGlobal(propId) ? AREA_ID_GLOBAL : propValue.getAreaId(); 406 checkAreaIdSupported(propId, areaId); 407 408 // For HVAC power dependent properties, check if HVAC_POWER_ON is on. 409 if (isHvacPowerDependentProp(propId)) { 410 checkPropAvailable(propId, areaId); 411 } 412 // Check access permission. 413 int access = getAccess(propId, areaId); 414 if (access != VehiclePropertyAccess.WRITE && access != VehiclePropertyAccess.READ_WRITE) { 415 throw new ServiceSpecificException(StatusCode.ACCESS_DENIED, "This property " + propId 416 + " doesn't have write permission."); 417 } 418 419 if (isSpecialProperty(propValue.getPropId())) { 420 mRealVehicle.set(propValue); 421 return; 422 } 423 424 RawPropValues rawPropValues = ((VehiclePropValue) propValue.toVehiclePropValue()).value; 425 426 // Check if the set values are within the value config range. 427 if (!withinRange(propId, areaId, rawPropValues)) { 428 throw new ServiceSpecificException(StatusCode.INVALID_ARG, 429 "The set value is outside the range."); 430 } 431 432 HalPropValue updatedValue = buildHalPropValue(propId, areaId, 433 SystemClock.elapsedRealtimeNanos(), rawPropValues); 434 Set<FakeVhalSubscriptionClient> clients; 435 436 synchronized (mLock) { 437 mPropValuesByPropIdAreaId.put(propId, areaId, updatedValue); 438 clients = mOnChangeSubscribeClientByPropIdAreaId.get(propId, areaId, new ArraySet<>()); 439 } 440 clients.forEach(c -> c.onPropertyEvent(updatedValue)); 441 } 442 443 /** 444 * Dumps VHAL debug information. 445 * 446 * @param fd The file descriptor to print output. 447 * @param args Optional additional arguments for the debug command. Can be empty. 448 * @throws RemoteException if the remote operation fails. 449 * @throws ServiceSpecificException if VHAL returns service specific error. 450 */ 451 @Override dump(FileDescriptor fd, List<String> args)452 public void dump(FileDescriptor fd, List<String> args) throws RemoteException, 453 ServiceSpecificException { 454 mRealVehicle.dump(fd, args); 455 } 456 457 /** 458 * @return {@code true} if car service is connected to FakeVehicleStub. 459 */ 460 @Override isFakeModeEnabled()461 public boolean isFakeModeEnabled() { 462 return true; 463 } 464 465 private final class FakeVhalSubscriptionClient implements SubscriptionClient { 466 private final VehicleHalCallback mCallBack; 467 private final SubscriptionClient mRealClient; 468 FakeVhalSubscriptionClient(VehicleHalCallback callback, SubscriptionClient realVehicleClient)469 FakeVhalSubscriptionClient(VehicleHalCallback callback, 470 SubscriptionClient realVehicleClient) { 471 mCallBack = callback; 472 mRealClient = realVehicleClient; 473 Slogf.d(TAG, "A FakeVhalSubscriptionClient instance is created."); 474 } 475 onPropertyEvent(HalPropValue value)476 public void onPropertyEvent(HalPropValue value) { 477 mCallBack.onPropertyEvent(new ArrayList<>(List.of(value))); 478 } 479 480 @Override subscribe(SubscribeOptions[] options)481 public void subscribe(SubscribeOptions[] options) throws RemoteException { 482 FakeVehicleStub.this.subscribe(this, options); 483 } 484 485 @Override unsubscribe(int propId)486 public void unsubscribe(int propId) throws RemoteException { 487 // Check if this propId is supported. 488 checkPropIdSupported(propId); 489 // Check if this propId is a special property. 490 if (isSpecialProperty(propId)) { 491 mRealClient.unsubscribe(propId); 492 return; 493 } 494 FakeVehicleStub.this.unsubscribe(this, propId); 495 } 496 } 497 498 private final class ContinuousPropUpdater implements Runnable { 499 private final FakeVhalSubscriptionClient mClient; 500 private final int mPropId; 501 private final int mAreaId; 502 private final float mSampleRate; 503 private final Object mUpdaterLock = new Object(); 504 @GuardedBy("mUpdaterLock") 505 private boolean mStopped; 506 ContinuousPropUpdater(FakeVhalSubscriptionClient client, int propId, int areaId, float sampleRate)507 ContinuousPropUpdater(FakeVhalSubscriptionClient client, int propId, int areaId, 508 float sampleRate) { 509 mClient = client; 510 mPropId = propId; 511 mAreaId = areaId; 512 mSampleRate = sampleRate; 513 mHandler.post(this); 514 Slogf.d(TAG, "A runnable updater is created for CONTINUOUS property."); 515 } 516 517 @Override run()518 public void run() { 519 synchronized (mUpdaterLock) { 520 if (mStopped) { 521 return; 522 } 523 mHandler.postDelayed(this, (long) (1000 / mSampleRate)); 524 } 525 526 // It is possible that mStopped is updated to true at the same time. We will have one 527 // additional event here. We cannot hold lock because we don't want to hold lock while 528 // calling client's callback; 529 mClient.onPropertyEvent(updateTimeStamp(mPropId, mAreaId)); 530 } 531 stop()532 public void stop() { 533 synchronized (mUpdaterLock) { 534 mStopped = true; 535 mHandler.removeCallbacks(this); 536 } 537 } 538 } 539 540 /** 541 * Parses default and custom config files. 542 * 543 * @return a {@link SparseArray} mapped from propId to its {@link ConfigDeclaration}. 544 * @throws IOException if FakeVhalConfigParser throws IOException. 545 * @throws IllegalArgumentException If default file doesn't exist or parsing errors occurred. 546 */ parseConfigFiles()547 private SparseArray<ConfigDeclaration> parseConfigFiles() throws IOException, 548 IllegalArgumentException { 549 InputStream defaultConfigInputStream = this.getClass().getClassLoader() 550 .getResourceAsStream(DEFAULT_CONFIG_FILE_NAME); 551 SparseArray<ConfigDeclaration> configDeclarations; 552 SparseArray<ConfigDeclaration> customConfigDeclarations; 553 // Parse default config file. 554 configDeclarations = mParser.parseJsonConfig(defaultConfigInputStream); 555 556 // Parse all custom config files. 557 for (int i = 0; i < mCustomConfigFiles.size(); i++) { 558 File customFile = mCustomConfigFiles.get(i); 559 try { 560 customConfigDeclarations = mParser.parseJsonConfig(customFile); 561 } catch (Exception e) { 562 Slogf.w(TAG, e, "Failed to parse custom config file: %s", 563 customFile.getPath()); 564 continue; 565 } 566 combineConfigDeclarations(configDeclarations, customConfigDeclarations); 567 } 568 569 return configDeclarations; 570 } 571 572 /** 573 * Gets all custom config files which are going to be parsed. 574 * 575 * @return a {@link List} of files. 576 */ getCustomConfigFiles()577 private static List<File> getCustomConfigFiles() throws IOException { 578 List<File> customConfigFileList = new ArrayList<>(); 579 File file = new File(FAKE_VHAL_CONFIG_DIRECTORY + FAKE_MODE_ENABLE_FILE_NAME); 580 try (BufferedReader reader = new BufferedReader(new FileReader(file))) { 581 String line; 582 while ((line = reader.readLine()) != null) { 583 customConfigFileList.add(new File(FAKE_VHAL_CONFIG_DIRECTORY 584 + line.replaceAll("\\.\\.", "").replaceAll("\\/", ""))); 585 } 586 } 587 return customConfigFileList; 588 } 589 590 /** 591 * Combines parsing results together. 592 * 593 * @param result The {@link SparseArray} to gets new property configs. 594 * @param newList The {@link SparseArray} whose property config will be added to result. 595 * @return a combined {@link SparseArray} result. 596 */ combineConfigDeclarations( SparseArray<ConfigDeclaration> result, SparseArray<ConfigDeclaration> newList)597 private static SparseArray<ConfigDeclaration> combineConfigDeclarations( 598 SparseArray<ConfigDeclaration> result, SparseArray<ConfigDeclaration> newList) { 599 for (int i = 0; i < newList.size(); i++) { 600 result.put(newList.keyAt(i), newList.valueAt(i)); 601 } 602 return result; 603 } 604 605 /** 606 * Extracts {@link HalPropConfig} for all properties from the parsing result and real VHAL. 607 * 608 * @param configDeclarationsByPropId The parsing result. 609 * @throws RemoteException if getting configs for special props through real vehicle HAL fails. 610 * @return a {@link SparseArray} mapped from propId to its configs. 611 */ extractPropConfigs(SparseArray<ConfigDeclaration> configDeclarationsByPropId)612 private SparseArray<HalPropConfig> extractPropConfigs(SparseArray<ConfigDeclaration> 613 configDeclarationsByPropId) throws RemoteException { 614 SparseArray<HalPropConfig> propConfigsByPropId = new SparseArray<>(); 615 for (int i = 0; i < configDeclarationsByPropId.size(); i++) { 616 VehiclePropConfig vehiclePropConfig = configDeclarationsByPropId.valueAt(i).getConfig(); 617 propConfigsByPropId.put(vehiclePropConfig.prop, 618 new AidlHalPropConfig(vehiclePropConfig)); 619 } 620 // If the special property is supported in this configuration, then override with configs 621 // from real vehicle. 622 overrideConfigsForSpecialProp(propConfigsByPropId); 623 return propConfigsByPropId; 624 } 625 626 /** 627 * Extracts {@link HalPropValue} for all properties from the parsing result. 628 * 629 * @param configDeclarationsByPropId The parsing result. 630 * @return a {@link Map} mapped from propId, areaId to its value. 631 */ extractPropValues( SparseArray<ConfigDeclaration> configDeclarationsByPropId)632 private PairSparseArray<HalPropValue> extractPropValues( 633 SparseArray<ConfigDeclaration> configDeclarationsByPropId) { 634 long timestamp = SystemClock.elapsedRealtimeNanos(); 635 PairSparseArray<HalPropValue> propValuesByPropIdAreaId = new PairSparseArray<>(); 636 for (int i = 0; i < configDeclarationsByPropId.size(); i++) { 637 // Get configDeclaration of a property. 638 ConfigDeclaration configDeclaration = configDeclarationsByPropId.valueAt(i); 639 // Get propId. 640 int propId = configDeclaration.getConfig().prop; 641 // Get areaConfigs array to know what areaIds are supported. 642 VehicleAreaConfig[] areaConfigs = configDeclaration.getConfig().areaConfigs; 643 // Get default rawPropValues. 644 RawPropValues defaultRawPropValues = configDeclaration.getInitialValue(); 645 // Get area rawPropValues map. 646 SparseArray<RawPropValues> rawPropValuesByAreaId = configDeclaration 647 .getInitialAreaValuesByAreaId(); 648 649 // If this property is a global property. 650 if (isPropertyGlobal(propId)) { 651 // If no default prop value exists, this propId won't be added to the 652 // propValuesByAreaIdByPropId map. Get this propId value will throw 653 // ServiceSpecificException with StatusCode.INVALID_ARG. 654 if (defaultRawPropValues == null) { 655 continue; 656 } 657 // Set the areaId to be 0. 658 propValuesByPropIdAreaId.put(propId, AREA_ID_GLOBAL, 659 buildHalPropValue(propId, AREA_ID_GLOBAL, timestamp, defaultRawPropValues)); 660 continue; 661 } 662 663 // If this property has supported area configs. 664 for (int j = 0; j < areaConfigs.length; j++) { 665 // Get areaId. 666 int areaId = areaConfigs[j].areaId; 667 // Set default area prop value to be defaultRawPropValues. If area value doesn't 668 // exist, then use the property default value. 669 RawPropValues areaRawPropValues = defaultRawPropValues; 670 // If area prop value exists, then use area value. 671 if (rawPropValuesByAreaId.contains(areaId)) { 672 areaRawPropValues = rawPropValuesByAreaId.get(areaId); 673 } 674 // Neither area prop value nor default prop value exists. This propId won't be in 675 // the value map. Get this propId value will throw ServiceSpecificException 676 // with StatusCode.INVALID_ARG. 677 if (areaRawPropValues == null) { 678 continue; 679 } 680 propValuesByPropIdAreaId.put(propId, areaId, 681 buildHalPropValue(propId, areaId, timestamp, areaRawPropValues)); 682 } 683 } 684 return propValuesByPropIdAreaId; 685 } 686 687 /** 688 * Gets all supported areaIds by HVAC_POWER_ON. 689 * 690 * @return a {@link List} of areaIds supported by HVAC_POWER_ON. 691 */ getHvacPowerSupportedAreaId()692 private List<Integer> getHvacPowerSupportedAreaId() { 693 try { 694 checkPropIdSupported(VehicleProperty.HVAC_POWER_ON); 695 return getAllSupportedAreaId(VehicleProperty.HVAC_POWER_ON); 696 } catch (Exception e) { 697 Slogf.i(TAG, "%d is not supported.", VehicleProperty.HVAC_POWER_ON); 698 return new ArrayList<>(); 699 } 700 } 701 702 /** 703 * Gets the HVAC power dependent properties from HVAC_POWER_ON config array. 704 * 705 * @return a {@link List} of HVAC properties which are dependent to HVAC_POWER_ON. 706 */ getHvacPowerDependentProps()707 private List<Integer> getHvacPowerDependentProps() { 708 List<Integer> hvacProps = new ArrayList<>(); 709 try { 710 checkPropIdSupported(VehicleProperty.HVAC_POWER_ON); 711 int[] configArray = mPropConfigsByPropId.get(VehicleProperty.HVAC_POWER_ON) 712 .getConfigArray(); 713 for (int propId : configArray) { 714 hvacProps.add(propId); 715 } 716 } catch (Exception e) { 717 Slogf.i(TAG, "%d is not supported.", VehicleProperty.HVAC_POWER_ON); 718 } 719 return hvacProps; 720 } 721 722 /** 723 * Overrides prop configs for special properties from real vehicle HAL. 724 * 725 * @throws RemoteException if getting prop configs from real vehicle HAL fails. 726 */ overrideConfigsForSpecialProp(SparseArray<HalPropConfig> fakePropConfigsByPropId)727 private void overrideConfigsForSpecialProp(SparseArray<HalPropConfig> fakePropConfigsByPropId) 728 throws RemoteException { 729 HalPropConfig[] realVehiclePropConfigs = mRealVehicle.getAllPropConfigs(); 730 for (int i = 0; i < realVehiclePropConfigs.length; i++) { 731 HalPropConfig propConfig = realVehiclePropConfigs[i]; 732 int propId = propConfig.getPropId(); 733 if (isSpecialProperty(propId) && fakePropConfigsByPropId.contains(propId)) { 734 fakePropConfigsByPropId.put(propConfig.getPropId(), propConfig); 735 } 736 } 737 } 738 739 /** 740 * Checks if a property is a global property. 741 * 742 * @param propId The property to be checked. 743 * @return {@code true} if this property is a global property. 744 */ isPropertyGlobal(int propId)745 private boolean isPropertyGlobal(int propId) { 746 return (propId & VehicleArea.MASK) == VehicleArea.GLOBAL; 747 } 748 749 /** 750 * Builds a {@link HalPropValue}. 751 * 752 * @param propId The propId of the prop value to be built. 753 * @param areaId The areaId of the prop value to be built. 754 * @param timestamp The elapsed time in nanoseconds when mPropConfigsByPropId is initialized. 755 * @param rawPropValues The {@link RawPropValues} contains property values. 756 * @return a {@link HalPropValue} built by propId, areaId, timestamp and value. 757 */ buildHalPropValue(int propId, int areaId, long timestamp, RawPropValues rawPropValues)758 private HalPropValue buildHalPropValue(int propId, int areaId, long timestamp, 759 RawPropValues rawPropValues) { 760 VehiclePropValue propValue = new VehiclePropValue(); 761 propValue.prop = propId; 762 propValue.areaId = areaId; 763 propValue.timestamp = timestamp; 764 propValue.value = rawPropValues; 765 return mHalPropValueBuilder.build(propValue); 766 } 767 768 /** 769 * Checks if a property is a special property. 770 * 771 * @param propId The property to be checked. 772 * @return {@code true} if the property is special. 773 */ isSpecialProperty(int propId)774 private static boolean isSpecialProperty(int propId) { 775 return SPECIAL_PROPERTIES.contains(propId); 776 } 777 778 /** 779 * Checks if a property is an HVAC power affected property. 780 * 781 * @param propId The property to be checked. 782 * @return {@code true} if the property is one of the HVAC power affected properties. 783 */ isHvacPowerDependentProp(int propId)784 private boolean isHvacPowerDependentProp(int propId) { 785 return mHvacPowerDependentProps.contains(propId); 786 } 787 788 /** 789 * Checks if a HVAC power dependent property is available. 790 * 791 * @param propId The property to be checked. 792 * @param areaId The areaId to be checked. 793 * @throws RemoteException if the remote operation through real vehicle HAL in get method fails. 794 * @throws ServiceSpecificException if there is no matched areaId in HVAC_POWER_ON to check or 795 * the property is not available. 796 */ checkPropAvailable(int propId, int areaId)797 private void checkPropAvailable(int propId, int areaId) throws RemoteException, 798 ServiceSpecificException { 799 HalPropValue propValues = get(mHalPropValueBuilder.build(VehicleProperty.HVAC_POWER_ON, 800 getMatchedAreaIdInHvacPower(areaId))); 801 if (propValues.getInt32ValuesSize() >= 1 && propValues.getInt32Value(0) == 0) { 802 throw new ServiceSpecificException(StatusCode.NOT_AVAILABLE, "HVAC_POWER_ON is off." 803 + " PropId: " + propId + " is not available."); 804 } 805 } 806 807 /** 808 * Gets matched areaId from HVAC_POWER_ON supported areaIds. 809 * 810 * @param areaId The specified areaId to find the match. 811 * @return the matched areaId. 812 * @throws ServiceSpecificException if no matched areaId found. 813 */ getMatchedAreaIdInHvacPower(int areaId)814 private int getMatchedAreaIdInHvacPower(int areaId) { 815 for (int i = 0; i < mHvacPowerSupportedAreas.size(); i++) { 816 int supportedAreaId = mHvacPowerSupportedAreas.get(i); 817 if ((areaId | supportedAreaId) == supportedAreaId) { 818 return supportedAreaId; 819 } 820 } 821 throw new ServiceSpecificException(StatusCode.INVALID_ARG, "This areaId: " + areaId 822 + " doesn't match any supported areaIds in HVAC_POWER_ON"); 823 } 824 825 /** 826 * Generates a list of all supported areaId for a certain property. 827 * 828 * @param propId The property to get all supported areaIds. 829 * @return A {@link List} of all supported areaId. 830 */ getAllSupportedAreaId(int propId)831 private List<Integer> getAllSupportedAreaId(int propId) { 832 List<Integer> allSupportedAreaId = new ArrayList<>(); 833 HalAreaConfig[] areaConfigs = mPropConfigsByPropId.get(propId).getAreaConfigs(); 834 for (int i = 0; i < areaConfigs.length; i++) { 835 allSupportedAreaId.add(areaConfigs[i].getAreaId()); 836 } 837 return allSupportedAreaId; 838 } 839 840 /** 841 * Checks if the set value is within the value range. 842 * 843 * @return {@code true} if set value is within the prop config range. 844 */ withinRange(int propId, int areaId, RawPropValues rawPropValues)845 private boolean withinRange(int propId, int areaId, RawPropValues rawPropValues) { 846 // For global property without areaId. 847 if (isPropertyGlobal(propId) && getAllSupportedAreaId(propId).isEmpty()) { 848 return true; 849 } 850 851 // For non-global properties and global properties with areaIds. 852 int index = getAllSupportedAreaId(propId).indexOf(areaId); 853 854 HalAreaConfig areaConfig = mPropConfigsByPropId.get(propId).getAreaConfigs()[index]; 855 856 int[] int32Values = rawPropValues.int32Values; 857 long[] int64Values = rawPropValues.int64Values; 858 float[] floatValues = rawPropValues.floatValues; 859 // If max and min values exists, then check the boundaries. If max and min values are all 860 // 0s, return true. 861 switch (getPropType(propId)) { 862 case VehiclePropertyType.INT32: 863 case VehiclePropertyType.INT32_VEC: 864 int minInt32Value = areaConfig.getMinInt32Value(); 865 int maxInt32Value = areaConfig.getMaxInt32Value(); 866 if (minInt32Value != maxInt32Value || minInt32Value != 0) { 867 for (int int32Value : int32Values) { 868 if (int32Value > maxInt32Value || int32Value < minInt32Value) { 869 Slogf.e(TAG, "For propId: %d, areaId: %d, the valid min value is: " 870 + "%d, max value is: %d, but the given value is: %d.", propId, 871 areaId, minInt32Value, maxInt32Value, int32Value); 872 return false; 873 } 874 } 875 } 876 break; 877 case VehiclePropertyType.INT64: 878 case VehiclePropertyType.INT64_VEC: 879 long minInt64Value = areaConfig.getMinInt64Value(); 880 long maxInt64Value = areaConfig.getMaxInt64Value(); 881 if (minInt64Value != maxInt64Value || minInt64Value != 0) { 882 for (long int64Value : int64Values) { 883 if (int64Value > maxInt64Value || int64Value < minInt64Value) { 884 Slogf.e(TAG, "For propId: %d, areaId: %d, the valid min value is: " 885 + "%d, max value is: %d, but the given value is: %d.", propId, 886 areaId, minInt64Value, maxInt64Value, int64Value); 887 return false; 888 } 889 } 890 } 891 break; 892 case VehiclePropertyType.FLOAT: 893 case VehiclePropertyType.FLOAT_VEC: 894 float minFloatValue = areaConfig.getMinFloatValue(); 895 float maxFloatValue = areaConfig.getMaxFloatValue(); 896 if (minFloatValue != maxFloatValue || minFloatValue != 0) { 897 for (float floatValue : floatValues) { 898 if (floatValue > maxFloatValue || floatValue < minFloatValue) { 899 Slogf.e(TAG, "For propId: %d, areaId: %d, the valid min value is: " 900 + "%f, max value is: %f, but the given value is: %d.", propId, 901 areaId, minFloatValue, maxFloatValue, floatValue); 902 return false; 903 } 904 } 905 } 906 break; 907 default: 908 Slogf.d(TAG, "Skip checking range for propId: %d because it is mixed type.", 909 propId); 910 } 911 return true; 912 } 913 914 /** 915 * Gets the type of property. 916 * 917 * @param propId The property to get the type. 918 * @return The type. 919 */ getPropType(int propId)920 private static int getPropType(int propId) { 921 return propId & VehiclePropertyType.MASK; 922 } 923 924 /** 925 * Checks if a property is supported. If not, throw a {@link ServiceSpecificException}. 926 * 927 * @param propId The property to be checked. 928 */ checkPropIdSupported(int propId)929 private void checkPropIdSupported(int propId) { 930 // Check if the property config exists. 931 if (!mPropConfigsByPropId.contains(propId)) { 932 throw new ServiceSpecificException(StatusCode.INVALID_ARG, "The propId: " + propId 933 + " is not supported."); 934 } 935 } 936 937 /** 938 * Checks if an areaId of a property is supported. 939 * 940 * @param propId The property to be checked. 941 * @param areaId The area to be checked. 942 */ checkAreaIdSupported(int propId, int areaId)943 private void checkAreaIdSupported(int propId, int areaId) { 944 List<Integer> supportedAreaIds = getAllSupportedAreaId(propId); 945 // For global property, areaId will be ignored if the area config array is empty. 946 if ((isPropertyGlobal(propId) && supportedAreaIds.isEmpty()) 947 || supportedAreaIds.contains(areaId)) { 948 return; 949 } 950 throw new ServiceSpecificException(StatusCode.INVALID_ARG, "The areaId: " + areaId 951 + " is not supported."); 952 } 953 954 /** 955 * Subscribes properties. 956 * 957 * @param client The client subscribes properties. 958 * @param options The array of subscribe options. 959 * @throws RemoteException if remote operation through real SubscriptionClient fails. 960 */ subscribe(FakeVhalSubscriptionClient client, SubscribeOptions[] options)961 private void subscribe(FakeVhalSubscriptionClient client, SubscribeOptions[] options) 962 throws RemoteException { 963 for (int i = 0; i < options.length; i++) { 964 int propId = options[i].propId; 965 966 // Check if this propId is supported. 967 checkPropIdSupported(propId); 968 969 // Check if this propId is a special property. 970 if (isSpecialProperty(propId)) { 971 client.mRealClient.subscribe(new SubscribeOptions[]{options[i]}); 972 return; 973 } 974 975 int[] areaIds = isPropertyGlobal(propId) ? new int[]{AREA_ID_GLOBAL} 976 : getSubscribedAreaIds(propId, options[i].areaIds); 977 978 int changeMode = mPropConfigsByPropId.get(propId).getChangeMode(); 979 switch (changeMode) { 980 case VehiclePropertyChangeMode.STATIC: 981 throw new ServiceSpecificException(StatusCode.INVALID_ARG, 982 "Static property cannot be subscribed."); 983 case VehiclePropertyChangeMode.ON_CHANGE: 984 subscribeOnChangeProp(client, propId, areaIds); 985 break; 986 case VehiclePropertyChangeMode.CONTINUOUS: 987 // Check if sample rate is within minSampleRate and maxSampleRate, and 988 // return a valid sample rate. 989 float sampleRate = getSampleRateWithinRange(options[i].sampleRate, propId); 990 subscribeContinuousProp(client, propId, areaIds, sampleRate); 991 break; 992 default: 993 Slogf.w(TAG, "This change mode: %d is not supported.", changeMode); 994 } 995 } 996 } 997 998 /** 999 * Subscribes an ON_CHANGE property. 1000 * 1001 * @param client The client that subscribes a property. 1002 * @param propId The property to be subscribed. 1003 * @param areaIds The list of areaIds to be subscribed. 1004 */ subscribeOnChangeProp(FakeVhalSubscriptionClient client, int propId, int[] areaIds)1005 private void subscribeOnChangeProp(FakeVhalSubscriptionClient client, int propId, 1006 int[] areaIds) { 1007 synchronized (mLock) { 1008 for (int areaId : areaIds) { 1009 checkAreaIdSupported(propId, areaId); 1010 Slogf.d(TAG, "FakeVhalSubscriptionClient subscribes ON_CHANGE property, " 1011 + "propId: %d, areaId: ", propId, areaId); 1012 // Update the map from propId, areaId to client set in FakeVehicleStub. 1013 Set<FakeVehicleStub.FakeVhalSubscriptionClient> subscriptionClientSet = 1014 mOnChangeSubscribeClientByPropIdAreaId.get(propId, areaId); 1015 if (subscriptionClientSet == null) { 1016 subscriptionClientSet = new ArraySet<>(); 1017 mOnChangeSubscribeClientByPropIdAreaId.put(propId, areaId, 1018 subscriptionClientSet); 1019 } 1020 subscriptionClientSet.add(client); 1021 } 1022 } 1023 } 1024 1025 /** 1026 * Subscribes a CONTINUOUS property. 1027 * 1028 * @param client The client that subscribes a property. 1029 * @param propId The property to be subscribed. 1030 * @param areaIds The list of areaIds to be subscribed. 1031 * @param sampleRate The rate of subscription. 1032 */ subscribeContinuousProp(FakeVhalSubscriptionClient client, int propId, int[] areaIds, float sampleRate)1033 private void subscribeContinuousProp(FakeVhalSubscriptionClient client, int propId, 1034 int[] areaIds, float sampleRate) { 1035 synchronized (mLock) { 1036 for (int areaId : areaIds) { 1037 checkAreaIdSupported(propId, areaId); 1038 Slogf.d(TAG, "FakeVhalSubscriptionClient subscribes CONTINUOUS property, " 1039 + "propId: %d, areaId: %d", propId, areaId); 1040 PairSparseArray<ContinuousPropUpdater> updaterByPropIdAreaId = 1041 mUpdaterByPropIdAreaIdByClient.get(client); 1042 // Check if this client has subscribed CONTINUOUS properties. 1043 if (updaterByPropIdAreaId == null) { 1044 updaterByPropIdAreaId = new PairSparseArray<>(); 1045 mUpdaterByPropIdAreaIdByClient.put(client, updaterByPropIdAreaId); 1046 } 1047 // Check if this client subscribes to the propId, areaId pair 1048 int indexOfPropIdAreaId = updaterByPropIdAreaId.indexOfKeyPair(propId, areaId); 1049 if (indexOfPropIdAreaId >= 0) { 1050 // If current subscription rate is same as the new sample rate. 1051 ContinuousPropUpdater oldUpdater = 1052 updaterByPropIdAreaId.valueAt(indexOfPropIdAreaId); 1053 if (oldUpdater.mSampleRate == sampleRate) { 1054 Slogf.w(TAG, "Sample rate is same as current rate. No update."); 1055 continue; 1056 } 1057 // If sample rate is not same. Remove old updater from mHandler's message queue. 1058 oldUpdater.stop(); 1059 updaterByPropIdAreaId.removeAt(indexOfPropIdAreaId); 1060 } 1061 ContinuousPropUpdater updater = new ContinuousPropUpdater(client, propId, areaId, 1062 sampleRate); 1063 updaterByPropIdAreaId.put(propId, areaId, updater); 1064 } 1065 } 1066 } 1067 1068 /** 1069 * Unsubscribes a property. 1070 * 1071 * @param client The client that unsubscribes this property. 1072 * @param propId The property to be unsubscribed. 1073 */ unsubscribe(FakeVhalSubscriptionClient client, int propId)1074 private void unsubscribe(FakeVhalSubscriptionClient client, int propId) { 1075 int changeMode = mPropConfigsByPropId.get(propId).getChangeMode(); 1076 switch (changeMode) { 1077 case VehiclePropertyChangeMode.STATIC: 1078 throw new ServiceSpecificException(StatusCode.INVALID_ARG, 1079 "Static property cannot be unsubscribed."); 1080 case VehiclePropertyChangeMode.ON_CHANGE: 1081 unsubscribeOnChangeProp(client, propId); 1082 break; 1083 case VehiclePropertyChangeMode.CONTINUOUS: 1084 unsubscribeContinuousProp(client, propId); 1085 break; 1086 default: 1087 Slogf.w(TAG, "This change mode: %d is not supported.", changeMode); 1088 } 1089 } 1090 1091 /** 1092 * Unsubscribes ON_CHANGE property. 1093 * 1094 * @param client The client that unsubscribes this property. 1095 * @param propId The property to be unsubscribed. 1096 */ unsubscribeOnChangeProp(FakeVhalSubscriptionClient client, int propId)1097 private void unsubscribeOnChangeProp(FakeVhalSubscriptionClient client, int propId) { 1098 synchronized (mLock) { 1099 List<Integer> areaIdsToDelete = new ArrayList<>(); 1100 for (int i = 0; i < mOnChangeSubscribeClientByPropIdAreaId.size(); i++) { 1101 int[] propIdAreaId = mOnChangeSubscribeClientByPropIdAreaId.keyPairAt(i); 1102 if (propIdAreaId[0] != propId) { 1103 continue; 1104 } 1105 Set<FakeVhalSubscriptionClient> clientSet = 1106 mOnChangeSubscribeClientByPropIdAreaId.valueAt(i); 1107 clientSet.remove(client); 1108 Slogf.d(TAG, "FakeVhalSubscriptionClient unsubscribes ON_CHANGE property, " 1109 + "propId: %d, areaId: %d", propId, propIdAreaId[1]); 1110 if (clientSet.isEmpty()) { 1111 areaIdsToDelete.add(propIdAreaId[1]); 1112 } 1113 } 1114 for (int i = 0; i < areaIdsToDelete.size(); i++) { 1115 mOnChangeSubscribeClientByPropIdAreaId.remove(propId, areaIdsToDelete.get(i)); 1116 } 1117 } 1118 } 1119 1120 /** 1121 * Unsubscribes CONTINUOUS property. 1122 * 1123 * @param client The client that unsubscribes this property. 1124 * @param propId The property to be unsubscribed. 1125 */ unsubscribeContinuousProp(FakeVhalSubscriptionClient client, int propId)1126 private void unsubscribeContinuousProp(FakeVhalSubscriptionClient client, int propId) { 1127 synchronized (mLock) { 1128 if (!mUpdaterByPropIdAreaIdByClient.containsKey(client)) { 1129 Slogf.w(TAG, "This client hasn't subscribed any CONTINUOUS property."); 1130 return; 1131 } 1132 List<Integer> areaIdsToDelete = new ArrayList<>(); 1133 PairSparseArray<ContinuousPropUpdater> updaterByPropIdAreaId = 1134 mUpdaterByPropIdAreaIdByClient.get(client); 1135 for (int i = 0; i < updaterByPropIdAreaId.size(); i++) { 1136 int[] propIdAreaId = updaterByPropIdAreaId.keyPairAt(i); 1137 if (propIdAreaId[0] != propId) { 1138 continue; 1139 } 1140 updaterByPropIdAreaId.valueAt(i).stop(); 1141 Slogf.d(TAG, "FakeVhalSubscriptionClient unsubscribes CONTINUOUS property," 1142 + " propId: %d, areaId: %d", propId, propIdAreaId[1]); 1143 areaIdsToDelete.add(propIdAreaId[1]); 1144 } 1145 for (int i = 0; i < areaIdsToDelete.size(); i++) { 1146 updaterByPropIdAreaId.remove(propId, areaIdsToDelete.get(i)); 1147 } 1148 if (updaterByPropIdAreaId.size() == 0) { 1149 mUpdaterByPropIdAreaIdByClient.remove(client); 1150 } 1151 } 1152 } 1153 1154 /** 1155 * Gets the array of subscribed areaIds. 1156 * 1157 * @param propId The property to be subscribed. 1158 * @param areaIds The areaIds from SubscribeOptions. 1159 * @return an {@code array} of subscribed areaIds. 1160 */ getSubscribedAreaIds(int propId, int[] areaIds)1161 private int[] getSubscribedAreaIds(int propId, int[] areaIds) { 1162 if (areaIds != null && areaIds.length != 0) { 1163 return areaIds; 1164 } 1165 // If areaIds field is empty or null, subscribe all supported areaIds. 1166 return CarServiceUtils.toIntArray(getAllSupportedAreaId(propId)); 1167 } 1168 1169 /** 1170 * Gets the subscription sample rate within range. 1171 * 1172 * @param sampleRate The requested sample rate. 1173 * @param propId The property to be subscribed. 1174 * @return The valid sample rate. 1175 */ getSampleRateWithinRange(float sampleRate, int propId)1176 private float getSampleRateWithinRange(float sampleRate, int propId) { 1177 float minSampleRate = mPropConfigsByPropId.get(propId).getMinSampleRate(); 1178 float maxSampleRate = mPropConfigsByPropId.get(propId).getMaxSampleRate(); 1179 if (sampleRate < minSampleRate) { 1180 sampleRate = minSampleRate; 1181 } 1182 if (sampleRate > maxSampleRate) { 1183 sampleRate = maxSampleRate; 1184 } 1185 return sampleRate; 1186 } 1187 1188 /** 1189 * Updates the timeStamp of a property. 1190 * 1191 * @param propId The property gets current timeStamp. 1192 * @param areaId The property with specific area gets current timeStamp. 1193 */ updateTimeStamp(int propId, int areaId)1194 private HalPropValue updateTimeStamp(int propId, int areaId) { 1195 synchronized (mLock) { 1196 HalPropValue propValue = mPropValuesByPropIdAreaId.get(propId, areaId); 1197 RawPropValues rawPropValues = ((VehiclePropValue) propValue.toVehiclePropValue()).value; 1198 HalPropValue updatedValue = buildHalPropValue(propId, areaId, 1199 SystemClock.elapsedRealtimeNanos(), rawPropValues); 1200 mPropValuesByPropIdAreaId.put(propId, areaId, updatedValue); 1201 return updatedValue; 1202 } 1203 } 1204 } 1205