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