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