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