1 /* 2 * Copyright (C) 2018 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 android.car.hardware.property; 18 19 import static com.android.car.internal.property.CarPropertyHelper.SYNC_OP_LIMIT_TRY_AGAIN; 20 21 import static java.lang.Integer.toHexString; 22 import static java.util.Objects.requireNonNull; 23 24 import android.annotation.FloatRange; 25 import android.annotation.IntDef; 26 import android.annotation.NonNull; 27 import android.annotation.Nullable; 28 import android.car.Car; 29 import android.car.CarManagerBase; 30 import android.car.VehicleAreaType; 31 import android.car.VehiclePropertyIds; 32 import android.car.annotation.AddedInOrBefore; 33 import android.car.hardware.CarPropertyConfig; 34 import android.car.hardware.CarPropertyValue; 35 import android.os.Build; 36 import android.os.Handler; 37 import android.os.RemoteException; 38 import android.os.ServiceSpecificException; 39 import android.os.SystemClock; 40 import android.util.ArraySet; 41 import android.util.Log; 42 import android.util.SparseArray; 43 44 import com.android.car.internal.CarPropertyEventCallbackController; 45 import com.android.car.internal.SingleMessageHandler; 46 import com.android.internal.annotations.GuardedBy; 47 48 import java.lang.annotation.Retention; 49 import java.lang.annotation.RetentionPolicy; 50 import java.lang.ref.WeakReference; 51 import java.util.ArrayList; 52 import java.util.List; 53 import java.util.concurrent.Callable; 54 55 /** 56 * Provides an application interface for interacting with the Vehicle specific properties. 57 * For details about the individual properties, see the descriptions in 58 * hardware/interfaces/automotive/vehicle/types.hal 59 */ 60 public class CarPropertyManager extends CarManagerBase { 61 private static final boolean DBG = false; 62 private static final String TAG = "CarPropertyManager"; 63 private static final int MSG_GENERIC_EVENT = 0; 64 private static final int SYNC_OP_RETRY_SLEEP_IN_MS = 10; 65 private static final int SYNC_OP_RETRY_MAX_COUNT = 10; 66 67 private final SingleMessageHandler<CarPropertyEvent> mHandler; 68 private final ICarProperty mService; 69 private final int mAppTargetSdk; 70 71 private final CarPropertyEventListenerToService mCarPropertyEventToService = 72 new CarPropertyEventListenerToService(this); 73 74 // This lock is shared with all CarPropertyEventCallbackController instances to prevent 75 // potential deadlock. 76 private final Object mLock = new Object(); 77 @GuardedBy("mLock") 78 private final SparseArray<CarPropertyEventCallbackController> 79 mPropertyIdToCarPropertyEventCallbackController = new SparseArray<>(); 80 81 private final CarPropertyEventCallbackController.RegistrationUpdateCallback 82 mRegistrationUpdateCallback = 83 new CarPropertyEventCallbackController.RegistrationUpdateCallback() { 84 @Override 85 public boolean register(int propertyId, float updateRateHz) { 86 try { 87 mService.registerListener(propertyId, updateRateHz, 88 mCarPropertyEventToService); 89 } catch (RemoteException e) { 90 handleRemoteExceptionFromCarService(e); 91 return false; 92 } 93 return true; 94 } 95 96 @Override 97 public void unregister(int propertyId) { 98 try { 99 mService.unregisterListener(propertyId, mCarPropertyEventToService); 100 } catch (RemoteException e) { 101 handleRemoteExceptionFromCarService(e); 102 } 103 } 104 }; 105 106 /** 107 * Application registers {@link CarPropertyEventCallback} object to receive updates and changes 108 * to subscribed Vehicle specific properties. 109 */ 110 public interface CarPropertyEventCallback { 111 /** 112 * Called when a property is updated 113 * @param value Property that has been updated. 114 */ 115 @AddedInOrBefore(majorVersion = 33) onChangeEvent(CarPropertyValue value)116 void onChangeEvent(CarPropertyValue value); 117 118 /** 119 * Called when an error is detected when setting a property. 120 * 121 * @param propId Property ID which is detected an error. 122 * @param zone Zone which is detected an error. 123 * 124 * @see CarPropertyEventCallback#onErrorEvent(int, int, int) 125 */ 126 @AddedInOrBefore(majorVersion = 33) onErrorEvent(int propId, int zone)127 void onErrorEvent(int propId, int zone); 128 129 /** 130 * Called when an error is detected when setting a property. 131 * 132 * <p>Clients which changed the property value in the areaId most recently will receive 133 * this callback. If multiple clients set a property for the same area id simultaneously, 134 * which one takes precedence is undefined. Typically, the last set operation 135 * (in the order that they are issued to car's ECU) overrides the previous set operations. 136 * The delivered error reflects the error happened in the last set operation. 137 * 138 * @param propId Property ID which is detected an error. 139 * @param areaId AreaId which is detected an error. 140 * @param errorCode Error code is raised in the car. 141 */ 142 @AddedInOrBefore(majorVersion = 33) onErrorEvent(int propId, int areaId, @CarSetPropertyErrorCode int errorCode)143 default void onErrorEvent(int propId, int areaId, @CarSetPropertyErrorCode int errorCode) { 144 if (DBG) { 145 Log.d(TAG, "onErrorEvent propertyId: 0x" + toHexString(propId) + " areaId:0x" 146 + toHexString(areaId) + " ErrorCode: " + errorCode); 147 } 148 onErrorEvent(propId, areaId); 149 } 150 } 151 152 /** Read ONCHANGE sensors. */ 153 @AddedInOrBefore(majorVersion = 33) 154 public static final float SENSOR_RATE_ONCHANGE = 0f; 155 /** Read sensors at the rate of 1 hertz */ 156 @AddedInOrBefore(majorVersion = 33) 157 public static final float SENSOR_RATE_NORMAL = 1f; 158 /** Read sensors at the rate of 5 hertz */ 159 @AddedInOrBefore(majorVersion = 33) 160 public static final float SENSOR_RATE_UI = 5f; 161 /** Read sensors at the rate of 10 hertz */ 162 @AddedInOrBefore(majorVersion = 33) 163 public static final float SENSOR_RATE_FAST = 10f; 164 /** Read sensors at the rate of 100 hertz */ 165 @AddedInOrBefore(majorVersion = 33) 166 public static final float SENSOR_RATE_FASTEST = 100f; 167 168 169 170 /** 171 * Status to indicate that set operation failed. Try it again. 172 */ 173 @AddedInOrBefore(majorVersion = 33) 174 public static final int CAR_SET_PROPERTY_ERROR_CODE_TRY_AGAIN = 1; 175 176 /** 177 * Status to indicate that set operation failed because of an invalid argument. 178 */ 179 @AddedInOrBefore(majorVersion = 33) 180 public static final int CAR_SET_PROPERTY_ERROR_CODE_INVALID_ARG = 2; 181 182 /** 183 * Status to indicate that set operation failed because the property is not available. 184 */ 185 @AddedInOrBefore(majorVersion = 33) 186 public static final int CAR_SET_PROPERTY_ERROR_CODE_PROPERTY_NOT_AVAILABLE = 3; 187 188 /** 189 * Status to indicate that set operation failed because car denied access to the property. 190 */ 191 @AddedInOrBefore(majorVersion = 33) 192 public static final int CAR_SET_PROPERTY_ERROR_CODE_ACCESS_DENIED = 4; 193 194 /** 195 * Status to indicate that set operation failed because of an general error in cars. 196 */ 197 @AddedInOrBefore(majorVersion = 33) 198 public static final int CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN = 5; 199 200 /** @hide */ 201 @IntDef(prefix = {"CAR_SET_PROPERTY_ERROR_CODE_"}, value = { 202 CAR_SET_PROPERTY_ERROR_CODE_TRY_AGAIN, 203 CAR_SET_PROPERTY_ERROR_CODE_INVALID_ARG, 204 CAR_SET_PROPERTY_ERROR_CODE_PROPERTY_NOT_AVAILABLE, 205 CAR_SET_PROPERTY_ERROR_CODE_ACCESS_DENIED, 206 CAR_SET_PROPERTY_ERROR_CODE_UNKNOWN, 207 }) 208 @Retention(RetentionPolicy.SOURCE) 209 public @interface CarSetPropertyErrorCode {} 210 211 /** 212 * Get an instance of the CarPropertyManager. 213 * 214 * Should not be obtained directly by clients, use {@link Car#getCarManager(String)} instead. 215 * @param car Car instance 216 * @param service ICarProperty instance 217 * @hide 218 */ CarPropertyManager(Car car, @NonNull ICarProperty service)219 public CarPropertyManager(Car car, @NonNull ICarProperty service) { 220 super(car); 221 mService = service; 222 mAppTargetSdk = getContext().getApplicationInfo().targetSdkVersion; 223 224 Handler eventHandler = getEventHandler(); 225 if (eventHandler == null) { 226 mHandler = null; 227 return; 228 } 229 mHandler = new SingleMessageHandler<CarPropertyEvent>(eventHandler.getLooper(), 230 MSG_GENERIC_EVENT) { 231 @Override 232 protected void handleEvent(CarPropertyEvent carPropertyEvent) { 233 CarPropertyEventCallbackController carPropertyEventCallbackController; 234 synchronized (mLock) { 235 carPropertyEventCallbackController = 236 mPropertyIdToCarPropertyEventCallbackController.get( 237 carPropertyEvent.getCarPropertyValue().getPropertyId()); 238 } 239 if (carPropertyEventCallbackController == null) { 240 return; 241 } 242 switch (carPropertyEvent.getEventType()) { 243 case CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE: 244 carPropertyEventCallbackController.forwardPropertyChanged(carPropertyEvent); 245 break; 246 case CarPropertyEvent.PROPERTY_EVENT_ERROR: 247 carPropertyEventCallbackController.forwardErrorEvent(carPropertyEvent); 248 break; 249 default: 250 throw new IllegalArgumentException(); 251 } 252 } 253 }; 254 } 255 256 /** 257 * Register {@link CarPropertyEventCallback} to get property updates. Multiple callbacks 258 * can be registered for a single property or the same callback can be used for different 259 * properties. If the same callback is registered again for the same property, it will be 260 * updated to new updateRateHz. 261 * <p>Rate could be one of the following: 262 * <ul> 263 * <li>{@link CarPropertyManager#SENSOR_RATE_ONCHANGE}</li> 264 * <li>{@link CarPropertyManager#SENSOR_RATE_NORMAL}</li> 265 * <li>{@link CarPropertyManager#SENSOR_RATE_UI}</li> 266 * <li>{@link CarPropertyManager#SENSOR_RATE_FAST}</li> 267 * <li>{@link CarPropertyManager#SENSOR_RATE_FASTEST}</li> 268 * </ul> 269 * <p> 270 * <b>Note:</b>Rate has no effect if the property has one of the following change modes: 271 * <ul> 272 * <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_STATIC}</li> 273 * <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}</li> 274 * </ul> 275 * <b>Note:</b>If listener registers for updates for a 276 * {@link CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE} property, it will receive the 277 * property's current value upon registration. 278 * See {@link CarPropertyConfig#getChangeMode()} for details. 279 * If updateRateHz is higher than {@link CarPropertyConfig#getMaxSampleRate()}, it will be 280 * registered with max sample updateRateHz. 281 * If updateRateHz is lower than {@link CarPropertyConfig#getMinSampleRate()}, it will be 282 * registered with min sample updateRateHz. 283 * 284 * @param carPropertyEventCallback CarPropertyEventCallback to be registered. 285 * @param propertyId PropertyId to subscribe 286 * @param updateRateHz how fast the property events are delivered in Hz. 287 * @return true if the listener is successfully registered. 288 * @throws SecurityException if missing the appropriate permission. 289 */ 290 @AddedInOrBefore(majorVersion = 33) registerCallback(@onNull CarPropertyEventCallback carPropertyEventCallback, int propertyId, @FloatRange(from = 0.0, to = 100.0) float updateRateHz)291 public boolean registerCallback(@NonNull CarPropertyEventCallback carPropertyEventCallback, 292 int propertyId, @FloatRange(from = 0.0, to = 100.0) float updateRateHz) { 293 requireNonNull(carPropertyEventCallback); 294 CarPropertyConfig<?> carPropertyConfig = getCarPropertyConfig(propertyId); 295 if (carPropertyConfig == null) { 296 Log.e(TAG, "registerListener: propId is not in carPropertyConfig list: " 297 + VehiclePropertyIds.toString(propertyId)); 298 return false; 299 } 300 if (carPropertyConfig.getChangeMode() 301 != CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS) { 302 updateRateHz = SENSOR_RATE_ONCHANGE; 303 } else if (updateRateHz > carPropertyConfig.getMaxSampleRate()) { 304 updateRateHz = carPropertyConfig.getMaxSampleRate(); 305 } else if (updateRateHz < carPropertyConfig.getMinSampleRate()) { 306 updateRateHz = carPropertyConfig.getMinSampleRate(); 307 } 308 CarPropertyEventCallbackController carPropertyEventCallbackController; 309 synchronized (mLock) { 310 carPropertyEventCallbackController = 311 mPropertyIdToCarPropertyEventCallbackController.get(propertyId); 312 if (carPropertyEventCallbackController == null) { 313 carPropertyEventCallbackController = new CarPropertyEventCallbackController( 314 propertyId, mLock, mRegistrationUpdateCallback); 315 mPropertyIdToCarPropertyEventCallbackController.put(propertyId, 316 carPropertyEventCallbackController); 317 } 318 } 319 return carPropertyEventCallbackController.add(carPropertyEventCallback, updateRateHz); 320 } 321 322 private static class CarPropertyEventListenerToService extends ICarPropertyEventListener.Stub { 323 private final WeakReference<CarPropertyManager> mCarPropertyManager; 324 CarPropertyEventListenerToService(CarPropertyManager carPropertyManager)325 CarPropertyEventListenerToService(CarPropertyManager carPropertyManager) { 326 mCarPropertyManager = new WeakReference<>(carPropertyManager); 327 } 328 329 @Override onEvent(List<CarPropertyEvent> carPropertyEvents)330 public void onEvent(List<CarPropertyEvent> carPropertyEvents) throws RemoteException { 331 CarPropertyManager carPropertyManager = mCarPropertyManager.get(); 332 if (carPropertyManager != null) { 333 carPropertyManager.handleEvents(carPropertyEvents); 334 } 335 } 336 } 337 handleEvents(List<CarPropertyEvent> carPropertyEvents)338 private void handleEvents(List<CarPropertyEvent> carPropertyEvents) { 339 if (mHandler != null) { 340 mHandler.sendEvents(carPropertyEvents); 341 } 342 } 343 344 /** 345 * Stop getting property updates for the given {@link CarPropertyEventCallback}. If there are 346 * multiple registrations for this {@link CarPropertyEventCallback}, all listening will be 347 * stopped. 348 */ 349 @AddedInOrBefore(majorVersion = 33) unregisterCallback(@onNull CarPropertyEventCallback carPropertyEventCallback)350 public void unregisterCallback(@NonNull CarPropertyEventCallback carPropertyEventCallback) { 351 requireNonNull(carPropertyEventCallback); 352 int[] propertyIds; 353 synchronized (mLock) { 354 propertyIds = new int[mPropertyIdToCarPropertyEventCallbackController.size()]; 355 for (int i = 0; i < mPropertyIdToCarPropertyEventCallbackController.size(); i++) { 356 propertyIds[i] = mPropertyIdToCarPropertyEventCallbackController.keyAt(i); 357 } 358 } 359 for (int propertyId : propertyIds) { 360 unregisterCallback(carPropertyEventCallback, propertyId); 361 } 362 } 363 364 /** 365 * Stop getting update for {@code propertyId} to the given {@link CarPropertyEventCallback}. If 366 * the same {@link CarPropertyEventCallback} is used for other properties, those subscriptions 367 * will not be affected. 368 */ 369 @AddedInOrBefore(majorVersion = 33) unregisterCallback(@onNull CarPropertyEventCallback carPropertyEventCallback, int propertyId)370 public void unregisterCallback(@NonNull CarPropertyEventCallback carPropertyEventCallback, 371 int propertyId) { 372 requireNonNull(carPropertyEventCallback); 373 CarPropertyEventCallbackController carPropertyEventCallbackController; 374 synchronized (mLock) { 375 carPropertyEventCallbackController = 376 mPropertyIdToCarPropertyEventCallbackController.get(propertyId); 377 } 378 if (carPropertyEventCallbackController == null) { 379 return; 380 } 381 synchronized (mLock) { 382 boolean allCallbacksRemoved = carPropertyEventCallbackController.remove( 383 carPropertyEventCallback); 384 if (allCallbacksRemoved) { 385 mPropertyIdToCarPropertyEventCallbackController.remove(propertyId); 386 } 387 } 388 } 389 390 /** 391 * @return List of properties implemented by this car that the application may access. 392 */ 393 @NonNull 394 @AddedInOrBefore(majorVersion = 33) getPropertyList()395 public List<CarPropertyConfig> getPropertyList() { 396 List<CarPropertyConfig> configs; 397 try { 398 configs = mService.getPropertyList(); 399 } catch (RemoteException e) { 400 Log.e(TAG, "getPropertyList exception ", e); 401 return handleRemoteExceptionFromCarService(e, new ArrayList<CarPropertyConfig>()); 402 } 403 return configs; 404 } 405 406 /** 407 * @param propertyIds property ID list 408 * @return List of properties implemented by this car in given property ID list that application 409 * may access. 410 */ 411 @NonNull 412 @AddedInOrBefore(majorVersion = 33) getPropertyList(@onNull ArraySet<Integer> propertyIds)413 public List<CarPropertyConfig> getPropertyList(@NonNull ArraySet<Integer> propertyIds) { 414 int[] propIds = new int[propertyIds.size()]; 415 int idx = 0; 416 for (int propId : propertyIds) { 417 checkSupportedProperty(propId); 418 propIds[idx++] = propId; 419 } 420 List<CarPropertyConfig> configs; 421 try { 422 configs = mService.getPropertyConfigList(propIds); 423 } catch (RemoteException e) { 424 Log.e(TAG, "getPropertyList exception ", e); 425 return handleRemoteExceptionFromCarService(e, new ArrayList<CarPropertyConfig>()); 426 } 427 return configs; 428 } 429 430 /** 431 * Get CarPropertyConfig by property Id. 432 * 433 * @param propId Property ID 434 * @return {@link CarPropertyConfig} for the selected property. 435 * Null if the property is not available. 436 */ 437 @Nullable 438 @AddedInOrBefore(majorVersion = 33) getCarPropertyConfig(int propId)439 public CarPropertyConfig<?> getCarPropertyConfig(int propId) { 440 checkSupportedProperty(propId); 441 List<CarPropertyConfig> configs; 442 try { 443 configs = mService.getPropertyConfigList(new int[] {propId}); 444 } catch (RemoteException e) { 445 Log.e(TAG, "getPropertyList exception ", e); 446 return handleRemoteExceptionFromCarService(e, null); 447 } 448 return configs.size() == 0 ? null : configs.get(0); 449 } 450 451 /** 452 * Returns areaId contains the seletcted area for the property. 453 * 454 * @param propId Property ID 455 * @param area Area enum such as Enums in {@link android.car.VehicleAreaSeat}. 456 * @throws IllegalArgumentException if the property is not available in the vehicle for 457 * the selected area. 458 * @return AreaId contains the selected area for the property. 459 */ 460 @AddedInOrBefore(majorVersion = 33) getAreaId(int propId, int area)461 public int getAreaId(int propId, int area) { 462 checkSupportedProperty(propId); 463 464 CarPropertyConfig<?> propConfig = getCarPropertyConfig(propId); 465 if (propConfig == null) { 466 throw new IllegalArgumentException("The property propId: 0x" + toHexString(propId) 467 + " is not available"); 468 } 469 // For the global property, areaId is 0 470 if (propConfig.isGlobalProperty()) { 471 return VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL; 472 } 473 for (int areaId : propConfig.getAreaIds()) { 474 if ((area & areaId) == area) { 475 return areaId; 476 } 477 } 478 479 throw new IllegalArgumentException("The property propId: 0x" + toHexString(propId) 480 + " is not available at the area: 0x" + toHexString(area)); 481 } 482 483 /** 484 * Return read permission string for given property ID. 485 * 486 * @param propId Property ID to query 487 * @return String Permission needed to read this property. NULL if propId not available. 488 * @hide 489 */ 490 @Nullable 491 @AddedInOrBefore(majorVersion = 33) getReadPermission(int propId)492 public String getReadPermission(int propId) { 493 if (DBG) { 494 Log.d(TAG, "getReadPermission, propId: 0x" + toHexString(propId)); 495 } 496 checkSupportedProperty(propId); 497 try { 498 return mService.getReadPermission(propId); 499 } catch (RemoteException e) { 500 return handleRemoteExceptionFromCarService(e, ""); 501 } 502 } 503 504 /** 505 * Return write permission string for given property ID. 506 * 507 * @param propId Property ID to query 508 * @return String Permission needed to write this property. NULL if propId not available. 509 * @hide 510 */ 511 @Nullable 512 @AddedInOrBefore(majorVersion = 33) getWritePermission(int propId)513 public String getWritePermission(int propId) { 514 if (DBG) { 515 Log.d(TAG, "getWritePermission, propId: 0x" + toHexString(propId)); 516 } 517 checkSupportedProperty(propId); 518 try { 519 return mService.getWritePermission(propId); 520 } catch (RemoteException e) { 521 return handleRemoteExceptionFromCarService(e, ""); 522 } 523 } 524 525 526 /** 527 * Check whether a given property is available or disabled based on the car's current state. 528 * @param propId Property Id 529 * @param area AreaId of property 530 * @return true if STATUS_AVAILABLE, false otherwise (eg STATUS_UNAVAILABLE) 531 */ 532 @AddedInOrBefore(majorVersion = 33) isPropertyAvailable(int propId, int area)533 public boolean isPropertyAvailable(int propId, int area) { 534 checkSupportedProperty(propId); 535 try { 536 CarPropertyValue propValue = runSyncOperation(() -> { 537 return mService.getProperty(propId, area); 538 }); 539 return (propValue != null) 540 && (propValue.getStatus() == CarPropertyValue.STATUS_AVAILABLE); 541 } catch (RemoteException e) { 542 return handleRemoteExceptionFromCarService(e, false); 543 } catch (ServiceSpecificException e) { 544 Log.e(TAG, "unable to get property, error: " + e); 545 return false; 546 } 547 } 548 549 /** 550 * Returns value of a bool property 551 * 552 * <p> This method may take couple seconds to complete, so it needs to be called from an 553 * non-main thread. 554 * 555 * <p> Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion} equal 556 * or later than {@link Build.VERSION_CODES#R} will receive the following exceptions when 557 * request is failed. 558 * <ul> 559 * <li>{@link CarInternalErrorException} 560 * <li>{@link PropertyAccessDeniedSecurityException} 561 * <li>{@link PropertyNotAvailableAndRetryException} 562 * <li>{@link PropertyNotAvailableException} 563 * <li>{@link IllegalArgumentException} 564 * </ul> 565 * <p> Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion} 566 * earlier than {@link Build.VERSION_CODES#R} will receive the following exceptions if the call 567 * fails. 568 * <ul> 569 * <li>{@link IllegalStateException} when there is an error detected in cars. 570 * <li>{@link IllegalArgumentException} when the property in the areaId is not supplied. 571 * </ul> 572 * 573 * @param prop Property ID to get 574 * @param area Area of the property to get 575 * 576 * @throws {@link CarInternalErrorException} when there is an error detected in cars. 577 * @throws {@link PropertyAccessDeniedSecurityException} when cars denied the access of the 578 * property. 579 * @throws {@link PropertyNotAvailableAndRetryException} when the property is temporarily 580 * not available and likely that retrying will be successful. 581 * @throws {@link PropertyNotAvailableException} when the property is temporarily not available. 582 * @throws {@link IllegalArgumentException} when the property in the areaId is not supplied. 583 * 584 * @return value of a bool property, {@code false} if can not get value from cars. 585 */ 586 @AddedInOrBefore(majorVersion = 33) getBooleanProperty(int prop, int area)587 public boolean getBooleanProperty(int prop, int area) { 588 checkSupportedProperty(prop); 589 CarPropertyValue<Boolean> carProp = getProperty(Boolean.class, prop, area); 590 return handleNullAndPropertyStatus(carProp, area, false); 591 } 592 593 /** 594 * Returns value of a float property 595 * 596 * <p> This method may take couple seconds to complete, so it needs to be called from an 597 * non-main thread. 598 * 599 * <p> This method has the same exception behavior as {@link #getBooleanProperty(int, int)}. 600 * 601 * @param prop Property ID to get 602 * @param area Area of the property to get 603 * 604 * @throws {@link CarInternalErrorException} when there is an error detected in cars. 605 * @throws {@link PropertyAccessDeniedSecurityException} when cars denied the access of the 606 * property. 607 * @throws {@link PropertyNotAvailableAndRetryException} when the property is temporarily 608 * not available and likely that retrying will be successful. 609 * @throws {@link PropertyNotAvailableException} when the property is temporarily not available. 610 * @throws {@link IllegalArgumentException} when the property in the areaId is not supplied. 611 * 612 * @return value of a float property, 0 if can not get value from the cars. 613 */ 614 @AddedInOrBefore(majorVersion = 33) getFloatProperty(int prop, int area)615 public float getFloatProperty(int prop, int area) { 616 checkSupportedProperty(prop); 617 CarPropertyValue<Float> carProp = getProperty(Float.class, prop, area); 618 return handleNullAndPropertyStatus(carProp, area, 0f); 619 } 620 621 /** 622 * Returns value of an integer property 623 * 624 * <p> This method may take couple seconds to complete, so it needs to be called form an 625 * non-main thread. 626 * 627 * <p> This method has the same exception behavior as {@link #getBooleanProperty(int, int)}. 628 * 629 * @param prop Property ID to get 630 * @param area Zone of the property to get 631 * 632 * @throws {@link CarInternalErrorException} when there is an error detected in cars. 633 * @throws {@link PropertyAccessDeniedSecurityException} when cars denied the access of the 634 * property. 635 * @throws {@link PropertyNotAvailableAndRetryException} when the property is temporarily 636 * not available and likely that retrying will be successful. 637 * @throws {@link PropertyNotAvailableException} when the property is temporarily not available. 638 * @throws {@link IllegalArgumentException} when the property in the areaId is not supplied. 639 * 640 * @return value of an integer property, 0 if can not get the value from cars. 641 */ 642 @AddedInOrBefore(majorVersion = 33) getIntProperty(int prop, int area)643 public int getIntProperty(int prop, int area) { 644 checkSupportedProperty(prop); 645 CarPropertyValue<Integer> carProp = getProperty(Integer.class, prop, area); 646 return handleNullAndPropertyStatus(carProp, area, 0); 647 } 648 649 /** 650 * Returns value of an integer array property 651 * 652 * <p> This method may take couple seconds to complete, so it needs to be called from an 653 * non-main thread. 654 * 655 * <p> This method has the same exception behavior as {@link #getBooleanProperty(int, int)}. 656 * 657 * @param prop Property ID to get 658 * @param area Zone of the property to get 659 * 660 * @throws {@link CarInternalErrorException} when there is an error detected in cars. 661 * @throws {@link PropertyAccessDeniedSecurityException} when cars denied the access of the 662 * property. 663 * @throws {@link PropertyNotAvailableAndRetryException} when the property is temporarily 664 * not available and likely that retrying will be successful. 665 * @throws {@link PropertyNotAvailableException} when the property is temporarily not available. 666 * @throws {@link IllegalArgumentException} when the property in the areaId is not supplied. 667 * 668 * @return value of an integer array property, an empty integer array if can not get the value 669 * from cars. 670 */ 671 @NonNull 672 @AddedInOrBefore(majorVersion = 33) getIntArrayProperty(int prop, int area)673 public int[] getIntArrayProperty(int prop, int area) { 674 checkSupportedProperty(prop); 675 CarPropertyValue<Integer[]> carProp = getProperty(Integer[].class, prop, area); 676 Integer[] res = handleNullAndPropertyStatus(carProp, area, new Integer[0]); 677 return toIntArray(res); 678 } 679 toIntArray(Integer[] input)680 private static int[] toIntArray(Integer[] input) { 681 int len = input.length; 682 int[] arr = new int[len]; 683 for (int i = 0; i < len; i++) { 684 arr[i] = input[i]; 685 } 686 return arr; 687 } 688 handleNullAndPropertyStatus(CarPropertyValue<T> propertyValue, int areaId, T defaultValue)689 private <T> T handleNullAndPropertyStatus(CarPropertyValue<T> propertyValue, int areaId, 690 T defaultValue) { 691 692 if (propertyValue == null) { 693 return defaultValue; 694 } 695 696 // Keeps the same behavior as android R. 697 if (mAppTargetSdk < Build.VERSION_CODES.S) { 698 return propertyValue.getStatus() == CarPropertyValue.STATUS_AVAILABLE 699 ? propertyValue.getValue() : defaultValue; 700 } 701 702 // throws new exceptions in android S. 703 switch (propertyValue.getStatus()) { 704 case CarPropertyValue.STATUS_ERROR: 705 throw new CarInternalErrorException(propertyValue.getPropertyId(), areaId); 706 case CarPropertyValue.STATUS_UNAVAILABLE: 707 throw new PropertyNotAvailableException(propertyValue.getPropertyId(), areaId); 708 default: 709 return propertyValue.getValue(); 710 } 711 } 712 runSyncOperation(Callable<V> c)713 private static <V> V runSyncOperation(Callable<V> c) 714 throws RemoteException, ServiceSpecificException { 715 int retryCount = 0; 716 while (retryCount < SYNC_OP_RETRY_MAX_COUNT) { 717 retryCount++; 718 try { 719 return c.call(); 720 } catch (ServiceSpecificException e) { 721 if (e.errorCode != SYNC_OP_LIMIT_TRY_AGAIN) { 722 throw e; 723 } 724 // If car service don't have enough binder thread to handle this request. Sleep for 725 // 10ms and try again. 726 Log.d(TAG, "too many sync request, sleeping for " + SYNC_OP_RETRY_SLEEP_IN_MS 727 + " ms before retry"); 728 SystemClock.sleep(SYNC_OP_RETRY_SLEEP_IN_MS); 729 continue; 730 } catch (RuntimeException | RemoteException e) { 731 throw e; 732 } catch (Exception e) { 733 Log.e(TAG, "catching unexpected exception for getProperty/setProperty", e); 734 return null; 735 } 736 } 737 throw new ServiceSpecificException(VehicleHalStatusCode.STATUS_INTERNAL_ERROR, 738 "failed to call car service sync operations after " + retryCount + " retries"); 739 } 740 741 /** 742 * Return CarPropertyValue 743 * 744 * <p> This method may take couple seconds to complete, so it needs to be called from an 745 * non-main thread. 746 * 747 * <p> Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion} equal 748 * or later than {@link Build.VERSION_CODES#R} will receive the following exceptions when 749 * request is failed. 750 * <ul> 751 * <li>{@link CarInternalErrorException} 752 * <li>{@link PropertyAccessDeniedSecurityException} 753 * <li>{@link PropertyNotAvailableAndRetryException} 754 * <li>{@link PropertyNotAvailableException} 755 * <li>{@link IllegalArgumentException} 756 * </ul> 757 * <p> Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion} 758 * earlier than {@link Build.VERSION_CODES#R} will receive the following exceptions when request 759 * is failed. 760 * <ul> 761 * <li>{@link IllegalStateException} when there is an error detected in cars. 762 * <li>{@link IllegalArgumentException} when the property in the areaId is not supplied. 763 * </ul> 764 * 765 * @param clazz The class object for the CarPropertyValue 766 * @param propId Property ID to get 767 * @param areaId Zone of the property to get 768 * 769 * @throws {@link CarInternalErrorException} when there is an error detected in cars. 770 * @throws {@link PropertyAccessDeniedSecurityException} when cars denied the access of the 771 * property. 772 * @throws {@link PropertyNotAvailableAndRetryException} when the property is temporarily 773 * not available and likely that retrying will be successful. 774 * @throws {@link PropertyNotAvailableException} when the property is temporarily not available. 775 * @throws {@link IllegalArgumentException} when the property in the areaId is not supplied. 776 * 777 * @return CarPropertyValue. Null if property's id is invalid. 778 */ 779 @SuppressWarnings("unchecked") 780 @Nullable 781 @AddedInOrBefore(majorVersion = 33) getProperty(@onNull Class<E> clazz, int propId, int areaId)782 public <E> CarPropertyValue<E> getProperty(@NonNull Class<E> clazz, int propId, int areaId) { 783 if (DBG) { 784 Log.d(TAG, "getProperty, propId: 0x" + toHexString(propId) 785 + ", areaId: 0x" + toHexString(areaId) + ", class: " + clazz); 786 } 787 788 checkSupportedProperty(propId); 789 790 try { 791 CarPropertyValue<E> propVal = mService.getProperty(propId, areaId); 792 if (propVal != null && propVal.getValue() != null) { 793 Class<?> actualClass = propVal.getValue().getClass(); 794 if (actualClass != clazz) { 795 throw new IllegalArgumentException("Invalid property type. " + "Expected: " 796 + clazz + ", but was: " + actualClass); 797 } 798 } 799 return propVal; 800 } catch (RemoteException e) { 801 return handleRemoteExceptionFromCarService(e, null); 802 } catch (ServiceSpecificException e) { 803 // For pre R apps, throws the old exceptions. 804 if (mAppTargetSdk < Build.VERSION_CODES.R) { 805 if (e.errorCode == VehicleHalStatusCode.STATUS_TRY_AGAIN) { 806 return null; 807 } else { 808 throw new IllegalStateException(String.format("Failed to get property: 0x%x, " 809 + "areaId: 0x%x", propId, areaId)); 810 } 811 } 812 return handleCarServiceSpecificException(e, propId, areaId, null); 813 } 814 } 815 816 /** 817 * Query CarPropertyValue with property id and areaId. 818 * 819 * <p> This method may take couple seconds to complete, so it needs to be called from an 820 * non-main thread. 821 * 822 * <p> Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion} equal 823 * or later than {@link Build.VERSION_CODES#R} will receive the following exceptions when 824 * request is failed. 825 * <ul> 826 * <li>{@link CarInternalErrorException} 827 * <li>{@link PropertyAccessDeniedSecurityException} 828 * <li>{@link PropertyNotAvailableAndRetryException} 829 * <li>{@link PropertyNotAvailableException} 830 * <li>{@link IllegalArgumentException} 831 * </ul> 832 * <p> Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion} 833 * earlier than {@link Build.VERSION_CODES#R} will receive the following exceptions when request 834 * is failed. 835 * <ul> 836 * <li>{@link IllegalStateException} when there is an error detected in cars. 837 * <li>{@link IllegalArgumentException} when the property in the areaId is not supplied. 838 * </ul> 839 * 840 * @param propId Property Id 841 * @param areaId areaId 842 * @param <E> Value type of the property 843 * 844 * @throws {@link CarInternalErrorException} when there is an error detected in cars. 845 * @throws {@link PropertyAccessDeniedSecurityException} when cars denied the access of the 846 * property. 847 * @throws {@link PropertyNotAvailableAndRetryException} when the property is temporarily 848 * not available and likely that retrying will be successful. 849 * @throws {@link PropertyNotAvailableException} when the property is temporarily not available. 850 * @throws {@link IllegalArgumentException} when the property in the areaId is not supplied. 851 * 852 * @return CarPropertyValue. Null if property's id is invalid. 853 */ 854 @Nullable 855 @AddedInOrBefore(majorVersion = 33) getProperty(int propId, int areaId)856 public <E> CarPropertyValue<E> getProperty(int propId, int areaId) { 857 checkSupportedProperty(propId); 858 859 try { 860 CarPropertyValue<E> propVal = runSyncOperation(() -> { 861 return mService.getProperty(propId, areaId); 862 }); 863 return propVal; 864 } catch (RemoteException e) { 865 return handleRemoteExceptionFromCarService(e, null); 866 } catch (ServiceSpecificException e) { 867 if (mAppTargetSdk < Build.VERSION_CODES.R) { 868 if (e.errorCode == VehicleHalStatusCode.STATUS_TRY_AGAIN) { 869 return null; 870 } else { 871 throw new IllegalStateException(String.format("Failed to get property: 0x%x, " 872 + "areaId: 0x%x", propId, areaId)); 873 } 874 } 875 return handleCarServiceSpecificException(e, propId, areaId, null); 876 } 877 } 878 879 /** 880 * Set value of car property by areaId. 881 * 882 * <p>If multiple clients set a property for the same area id simultaneously, which one takes 883 * precedence is undefined. Typically, the last set operation (in the order that they are issued 884 * to the car's ECU) overrides the previous set operations. 885 * 886 * <p> This method may take couple seconds to complete, so it needs to be called form an 887 * non-main thread. 888 * 889 * <p> Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion} equal 890 * or later than {@link Build.VERSION_CODES#R} will receive the following exceptions when 891 * request is failed. 892 * <ul> 893 * <li>{@link CarInternalErrorException} 894 * <li>{@link PropertyAccessDeniedSecurityException} 895 * <li>{@link PropertyNotAvailableAndRetryException} 896 * <li>{@link PropertyNotAvailableException} 897 * <li>{@link IllegalArgumentException} 898 * </ul> 899 * <p> Clients that declare a {@link android.content.pm.ApplicationInfo#targetSdkVersion} 900 * earlier than {@link Build.VERSION_CODES#R} will receive the following exceptions when request 901 * is failed. 902 * <ul> 903 * <li>{@link RuntimeException} when the property is temporarily not available. 904 * <li>{@link IllegalStateException} when there is an error detected in cars. 905 * <li>{@link IllegalArgumentException} when the property in the areaId is not supplied 906 * </ul> 907 * 908 * @param clazz The class object for the CarPropertyValue 909 * @param propId Property ID 910 * @param areaId areaId 911 * @param val Value of CarPropertyValue 912 * @param <E> data type of the given property, for example property that was 913 * defined as {@code VEHICLE_VALUE_TYPE_INT32} in vehicle HAL could be accessed using 914 * {@code Integer.class}. 915 * 916 * @throws {@link CarInternalErrorException} when there is an error detected in cars. 917 * @throws {@link PropertyAccessDeniedSecurityException} when cars denied the access of the 918 * property. 919 * @throws {@link PropertyNotAvailableException} when the property is temporarily not available. 920 * @throws {@link PropertyNotAvailableAndRetryException} when the property is temporarily 921 * not available and likely that retrying will be successful. 922 * @throws {@link IllegalStateException} when get an unexpected error code. 923 * @throws {@link IllegalArgumentException} when the property in the areaId is not supplied. 924 */ 925 @AddedInOrBefore(majorVersion = 33) setProperty(@onNull Class<E> clazz, int propId, int areaId, @NonNull E val)926 public <E> void setProperty(@NonNull Class<E> clazz, int propId, int areaId, @NonNull E val) { 927 if (DBG) { 928 Log.d(TAG, "setProperty, propId: 0x" + toHexString(propId) 929 + ", areaId: 0x" + toHexString(areaId) + ", class: " + clazz + ", val: " + val); 930 } 931 checkSupportedProperty(propId); 932 try { 933 runSyncOperation(() -> { 934 mService.setProperty(new CarPropertyValue<>(propId, areaId, val), 935 mCarPropertyEventToService); 936 return null; 937 }); 938 } catch (RemoteException e) { 939 handleRemoteExceptionFromCarService(e); 940 return; 941 } catch (ServiceSpecificException e) { 942 if (mAppTargetSdk < Build.VERSION_CODES.R) { 943 if (e.errorCode == VehicleHalStatusCode.STATUS_TRY_AGAIN) { 944 throw new RuntimeException(String.format("Failed to set property: 0x%x, " 945 + "areaId: 0x%x", propId, areaId)); 946 } else { 947 throw new IllegalStateException(String.format("Failed to set property: 0x%x, " 948 + "areaId: 0x%x", propId, areaId)); 949 } 950 } 951 handleCarServiceSpecificException(e, propId, areaId, null); 952 } 953 } 954 955 /** 956 * Modifies a property. If the property modification doesn't occur, an error event shall be 957 * generated and propagated back to the application. 958 * 959 * <p> This method may take couple seconds to complete, so it needs to be called from an 960 * non-main thread. 961 * 962 * @param prop Property ID to modify 963 * @param areaId AreaId to apply the modification. 964 * @param val Value to set 965 */ 966 @AddedInOrBefore(majorVersion = 33) setBooleanProperty(int prop, int areaId, boolean val)967 public void setBooleanProperty(int prop, int areaId, boolean val) { 968 setProperty(Boolean.class, prop, areaId, val); 969 } 970 971 /** 972 * Set float value of property 973 * 974 * <p> This method may take couple seconds to complete, so it needs to be called from an 975 * non-main thread. 976 * 977 * @param prop Property ID to modify 978 * @param areaId AreaId to apply the modification 979 * @param val Value to set 980 */ 981 @AddedInOrBefore(majorVersion = 33) setFloatProperty(int prop, int areaId, float val)982 public void setFloatProperty(int prop, int areaId, float val) { 983 setProperty(Float.class, prop, areaId, val); 984 } 985 986 /** 987 * Set int value of property 988 * 989 * <p> This method may take couple seconds to complete, so it needs to be called from an 990 * non-main thread. 991 * 992 * @param prop Property ID to modify 993 * @param areaId AreaId to apply the modification 994 * @param val Value to set 995 */ 996 @AddedInOrBefore(majorVersion = 33) setIntProperty(int prop, int areaId, int val)997 public void setIntProperty(int prop, int areaId, int val) { 998 setProperty(Integer.class, prop, areaId, val); 999 } 1000 1001 // Handles ServiceSpecificException in CarService for R and later version. handleCarServiceSpecificException( ServiceSpecificException e, int propId, int areaId, T returnValue)1002 private <T> T handleCarServiceSpecificException( 1003 ServiceSpecificException e, int propId, int areaId, T returnValue) { 1004 // We are not passing the error message down, so log it here. 1005 Log.w(TAG, "received ServiceSpecificException: " + e); 1006 int errorCode = e.errorCode; 1007 switch (errorCode) { 1008 case VehicleHalStatusCode.STATUS_NOT_AVAILABLE: 1009 throw new PropertyNotAvailableException(propId, areaId); 1010 case VehicleHalStatusCode.STATUS_TRY_AGAIN: 1011 throw new PropertyNotAvailableAndRetryException(propId, areaId); 1012 case VehicleHalStatusCode.STATUS_ACCESS_DENIED: 1013 throw new PropertyAccessDeniedSecurityException(propId, areaId); 1014 case VehicleHalStatusCode.STATUS_INTERNAL_ERROR: 1015 throw new CarInternalErrorException(propId, areaId); 1016 default: 1017 Log.e(TAG, "Invalid errorCode: " + errorCode + " in CarService"); 1018 } 1019 return returnValue; 1020 } 1021 1022 /** 1023 * Checks if the given property can be exposed to by this manager. 1024 * 1025 * <p>For example, properties related to user management should only be manipulated by 1026 * {@code UserHalService}. 1027 * 1028 * @param propId property to be checked 1029 * 1030 * @throws IllegalArgumentException if the property is not supported. 1031 */ checkSupportedProperty(int propId)1032 private void checkSupportedProperty(int propId) { 1033 switch (propId) { 1034 case VehiclePropertyIds.INITIAL_USER_INFO: 1035 case VehiclePropertyIds.SWITCH_USER: 1036 case VehiclePropertyIds.CREATE_USER: 1037 case VehiclePropertyIds.REMOVE_USER: 1038 case VehiclePropertyIds.USER_IDENTIFICATION_ASSOCIATION: 1039 throw new IllegalArgumentException("Unsupported property: " 1040 + VehiclePropertyIds.toString(propId) + " (" + propId + ")"); 1041 } 1042 } 1043 1044 /** @hide */ 1045 @Override 1046 @AddedInOrBefore(majorVersion = 33) onCarDisconnected()1047 public void onCarDisconnected() { 1048 synchronized (mLock) { 1049 mPropertyIdToCarPropertyEventCallbackController.clear(); 1050 } 1051 } 1052 } 1053