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.NonNull; 23 import android.annotation.Nullable; 24 import android.car.Car; 25 import android.car.CarManagerBase; 26 import android.car.hardware.CarPropertyConfig; 27 import android.car.hardware.CarPropertyValue; 28 import android.os.Handler; 29 import android.os.RemoteException; 30 import android.util.ArraySet; 31 import android.util.Log; 32 import android.util.SparseArray; 33 34 import com.android.car.internal.CarRatedFloatListeners; 35 import com.android.car.internal.SingleMessageHandler; 36 37 import java.lang.ref.WeakReference; 38 import java.util.ArrayList; 39 import java.util.List; 40 import java.util.function.Consumer; 41 42 43 /** 44 * Provides an application interface for interacting with the Vehicle specific properties. 45 * For details about the individual properties, see the descriptions in 46 * hardware/interfaces/automotive/vehicle/types.hal 47 */ 48 public class CarPropertyManager extends CarManagerBase { 49 private static final boolean DBG = false; 50 private static final String TAG = "CarPropertyManager"; 51 private static final int MSG_GENERIC_EVENT = 0; 52 private final SingleMessageHandler<CarPropertyEvent> mHandler; 53 private final ICarProperty mService; 54 55 private CarPropertyEventListenerToService mCarPropertyEventToService; 56 57 /** Record of locally active properties. Key is propertyId */ 58 private final SparseArray<CarPropertyListeners> mActivePropertyListener = 59 new SparseArray<>(); 60 /** Record of properties' configs. Key is propertyId */ 61 private final SparseArray<CarPropertyConfig> mConfigMap = new SparseArray<>(); 62 63 /** 64 * Application registers {@link CarPropertyEventCallback} object to receive updates and changes 65 * to subscribed Vehicle specific properties. 66 */ 67 public interface CarPropertyEventCallback { 68 /** 69 * Called when a property is updated 70 * @param value Property that has been updated. 71 */ onChangeEvent(CarPropertyValue value)72 void onChangeEvent(CarPropertyValue value); 73 74 /** 75 * Called when an error is detected with a property 76 * @param propId Property ID which is detected an error. 77 * @param zone Zone which is detected an error. 78 */ onErrorEvent(int propId, int zone)79 void onErrorEvent(int propId, int zone); 80 } 81 82 /** Read ON_CHANGE sensors */ 83 public static final float SENSOR_RATE_ONCHANGE = 0f; 84 /** Read sensors at the rate of 1 hertz */ 85 public static final float SENSOR_RATE_NORMAL = 1f; 86 /** Read sensors at the rate of 5 hertz */ 87 public static final float SENSOR_RATE_UI = 5f; 88 /** Read sensors at the rate of 10 hertz */ 89 public static final float SENSOR_RATE_FAST = 10f; 90 /** Read sensors at the rate of 100 hertz */ 91 public static final float SENSOR_RATE_FASTEST = 100f; 92 93 /** 94 * Get an instance of the CarPropertyManager. 95 * 96 * Should not be obtained directly by clients, use {@link Car#getCarManager(String)} instead. 97 * @param car Car instance 98 * @param service ICarProperty instance 99 * @hide 100 */ CarPropertyManager(Car car, @NonNull ICarProperty service)101 public CarPropertyManager(Car car, @NonNull ICarProperty service) { 102 super(car); 103 mService = service; 104 try { 105 List<CarPropertyConfig> configs = mService.getPropertyList(); 106 for (CarPropertyConfig carPropertyConfig : configs) { 107 mConfigMap.put(carPropertyConfig.getPropertyId(), carPropertyConfig); 108 } 109 } catch (Exception e) { 110 Log.e(TAG, "getPropertyList exception ", e); 111 throw new RuntimeException(e); 112 } 113 Handler eventHandler = getEventHandler(); 114 if (eventHandler == null) { 115 mHandler = null; 116 return; 117 } 118 mHandler = new SingleMessageHandler<CarPropertyEvent>(eventHandler.getLooper(), 119 MSG_GENERIC_EVENT) { 120 @Override 121 protected void handleEvent(CarPropertyEvent event) { 122 CarPropertyListeners listeners; 123 synchronized (mActivePropertyListener) { 124 listeners = mActivePropertyListener.get( 125 event.getCarPropertyValue().getPropertyId()); 126 } 127 if (listeners != null) { 128 switch (event.getEventType()) { 129 case CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE: 130 listeners.onPropertyChanged(event); 131 break; 132 case CarPropertyEvent.PROPERTY_EVENT_ERROR: 133 listeners.onErrorEvent(event); 134 break; 135 default: 136 throw new IllegalArgumentException(); 137 } 138 } 139 } 140 }; 141 } 142 143 /** 144 * Register {@link CarPropertyEventCallback} to get property updates. Multiple listeners 145 * can be registered for a single property or the same listener can be used for different 146 * properties. If the same listener is registered again for the same property, it will be 147 * updated to new rate. 148 * <p>Rate could be one of the following: 149 * <ul> 150 * <li>{@link CarPropertyManager#SENSOR_RATE_ONCHANGE}</li> 151 * <li>{@link CarPropertyManager#SENSOR_RATE_NORMAL}</li> 152 * <li>{@link CarPropertyManager#SENSOR_RATE_UI}</li> 153 * <li>{@link CarPropertyManager#SENSOR_RATE_FAST}</li> 154 * <li>{@link CarPropertyManager#SENSOR_RATE_FASTEST}</li> 155 * </ul> 156 * <p> 157 * <b>Note:</b>Rate has no effect if the property has one of the following change modes: 158 * <ul> 159 * <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_STATIC}</li> 160 * <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}</li> 161 * </ul> 162 * See {@link CarPropertyConfig#getChangeMode()} for details. 163 * If rate is higher than {@link CarPropertyConfig#getMaxSampleRate()}, it will be registered 164 * with max sample rate. 165 * If rate is lower than {@link CarPropertyConfig#getMinSampleRate()}, it will be registered 166 * with min sample rate. 167 * 168 * @param callback CarPropertyEventCallback to be registered. 169 * @param propertyId PropertyId to subscribe 170 * @param rate how fast the property events are delivered in Hz. 171 * @return true if the listener is successfully registered. 172 * @throws SecurityException if missing the appropriate permission. 173 */ registerCallback(@onNull CarPropertyEventCallback callback, int propertyId, @FloatRange(from = 0.0, to = 100.0) float rate)174 public boolean registerCallback(@NonNull CarPropertyEventCallback callback, 175 int propertyId, @FloatRange(from = 0.0, to = 100.0) float rate) { 176 synchronized (mActivePropertyListener) { 177 if (mCarPropertyEventToService == null) { 178 mCarPropertyEventToService = new CarPropertyEventListenerToService(this); 179 } 180 CarPropertyConfig config = mConfigMap.get(propertyId); 181 if (config == null) { 182 Log.e(TAG, "registerListener: propId is not in config list: " + propertyId); 183 return false; 184 } 185 if (config.getChangeMode() == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE) { 186 rate = SENSOR_RATE_ONCHANGE; 187 } 188 boolean needsServerUpdate = false; 189 CarPropertyListeners listeners; 190 listeners = mActivePropertyListener.get(propertyId); 191 if (listeners == null) { 192 listeners = new CarPropertyListeners(rate); 193 mActivePropertyListener.put(propertyId, listeners); 194 needsServerUpdate = true; 195 } 196 if (listeners.addAndUpdateRate(callback, rate)) { 197 needsServerUpdate = true; 198 } 199 if (needsServerUpdate) { 200 if (!registerOrUpdatePropertyListener(propertyId, rate)) { 201 return false; 202 } 203 } 204 } 205 return true; 206 } 207 registerOrUpdatePropertyListener(int propertyId, float rate)208 private boolean registerOrUpdatePropertyListener(int propertyId, float rate) { 209 try { 210 mService.registerListener(propertyId, rate, mCarPropertyEventToService); 211 } catch (RemoteException e) { 212 return handleRemoteExceptionFromCarService(e, false); 213 } 214 return true; 215 } 216 217 private static class CarPropertyEventListenerToService extends ICarPropertyEventListener.Stub{ 218 private final WeakReference<CarPropertyManager> mMgr; 219 CarPropertyEventListenerToService(CarPropertyManager mgr)220 CarPropertyEventListenerToService(CarPropertyManager mgr) { 221 mMgr = new WeakReference<>(mgr); 222 } 223 224 @Override onEvent(List<CarPropertyEvent> events)225 public void onEvent(List<CarPropertyEvent> events) throws RemoteException { 226 CarPropertyManager manager = mMgr.get(); 227 if (manager != null) { 228 manager.handleEvent(events); 229 } 230 } 231 } 232 handleEvent(List<CarPropertyEvent> events)233 private void handleEvent(List<CarPropertyEvent> events) { 234 if (mHandler != null) { 235 mHandler.sendEvents(events); 236 } 237 } 238 239 /** 240 * Stop getting property update for the given callback. If there are multiple registrations for 241 * this callback, all listening will be stopped. 242 * @param callback CarPropertyEventCallback to be unregistered. 243 */ unregisterCallback(@onNull CarPropertyEventCallback callback)244 public void unregisterCallback(@NonNull CarPropertyEventCallback callback) { 245 synchronized (mActivePropertyListener) { 246 int [] propertyIds = new int[mActivePropertyListener.size()]; 247 for (int i = 0; i < mActivePropertyListener.size(); i++) { 248 propertyIds[i] = mActivePropertyListener.keyAt(i); 249 } 250 for (int prop : propertyIds) { 251 doUnregisterListenerLocked(callback, prop); 252 } 253 } 254 } 255 256 /** 257 * Stop getting property update for the given callback and property. If the same callback is 258 * used for other properties, those subscriptions will not be affected. 259 * 260 * @param callback CarPropertyEventCallback to be unregistered. 261 * @param propertyId PropertyId to be unregistered. 262 */ unregisterCallback(@onNull CarPropertyEventCallback callback, int propertyId)263 public void unregisterCallback(@NonNull CarPropertyEventCallback callback, int propertyId) { 264 synchronized (mActivePropertyListener) { 265 doUnregisterListenerLocked(callback, propertyId); 266 } 267 } 268 doUnregisterListenerLocked(CarPropertyEventCallback listener, int propertyId)269 private void doUnregisterListenerLocked(CarPropertyEventCallback listener, int propertyId) { 270 CarPropertyListeners listeners = mActivePropertyListener.get(propertyId); 271 if (listeners != null) { 272 boolean needsServerUpdate = false; 273 if (listeners.contains(listener)) { 274 needsServerUpdate = listeners.remove(listener); 275 } 276 if (listeners.isEmpty()) { 277 try { 278 mService.unregisterListener(propertyId, mCarPropertyEventToService); 279 } catch (RemoteException e) { 280 handleRemoteExceptionFromCarService(e); 281 // continue for local clean-up 282 } 283 mActivePropertyListener.remove(propertyId); 284 } else if (needsServerUpdate) { 285 registerOrUpdatePropertyListener(propertyId, listeners.getRate()); 286 } 287 } 288 } 289 290 /** 291 * @return List of properties implemented by this car that the application may access. 292 */ 293 @NonNull getPropertyList()294 public List<CarPropertyConfig> getPropertyList() { 295 List<CarPropertyConfig> configs = new ArrayList<>(mConfigMap.size()); 296 for (int i = 0; i < mConfigMap.size(); i++) { 297 configs.add(mConfigMap.valueAt(i)); 298 } 299 return configs; 300 } 301 302 /** 303 * @param propertyIds property ID list 304 * @return List of properties implemented by this car in given property ID list that application 305 * may access. 306 */ 307 @NonNull getPropertyList(@onNull ArraySet<Integer> propertyIds)308 public List<CarPropertyConfig> getPropertyList(@NonNull ArraySet<Integer> propertyIds) { 309 List<CarPropertyConfig> configs = new ArrayList<>(); 310 for (int propId : propertyIds) { 311 CarPropertyConfig config = mConfigMap.get(propId); 312 if (config != null) { 313 configs.add(config); 314 } 315 } 316 return configs; 317 } 318 319 /** 320 * Return read permission string for given property ID. 321 * 322 * @param propId Property ID to query 323 * @return String Permission needed to read this property. NULL if propId not available. 324 * @hide 325 */ 326 @Nullable getReadPermission(int propId)327 public String getReadPermission(int propId) { 328 if (DBG) { 329 Log.d(TAG, "getReadPermission, propId: 0x" + toHexString(propId)); 330 } 331 try { 332 return mService.getReadPermission(propId); 333 } catch (RemoteException e) { 334 return handleRemoteExceptionFromCarService(e, ""); 335 } 336 } 337 338 /** 339 * Return write permission string for given property ID. 340 * 341 * @param propId Property ID to query 342 * @return String Permission needed to write this property. NULL if propId not available. 343 * @hide 344 */ 345 @Nullable getWritePermission(int propId)346 public String getWritePermission(int propId) { 347 if (DBG) { 348 Log.d(TAG, "getWritePermission, propId: 0x" + toHexString(propId)); 349 } 350 try { 351 return mService.getWritePermission(propId); 352 } catch (RemoteException e) { 353 return handleRemoteExceptionFromCarService(e, ""); 354 } 355 } 356 357 358 /** 359 * Check whether a given property is available or disabled based on the car's current state. 360 * @param propId Property Id 361 * @param area AreaId of property 362 * @return true if STATUS_AVAILABLE, false otherwise (eg STATUS_UNAVAILABLE) 363 */ isPropertyAvailable(int propId, int area)364 public boolean isPropertyAvailable(int propId, int area) { 365 try { 366 CarPropertyValue propValue = mService.getProperty(propId, area); 367 return (propValue != null) 368 && (propValue.getStatus() == CarPropertyValue.STATUS_AVAILABLE); 369 } catch (RemoteException e) { 370 return handleRemoteExceptionFromCarService(e, false); 371 } 372 } 373 374 /** 375 * Returns value of a bool property 376 * 377 * @param prop Property ID to get 378 * @param area Area of the property to get 379 * @return value of a bool property. 380 */ getBooleanProperty(int prop, int area)381 public boolean getBooleanProperty(int prop, int area) { 382 CarPropertyValue<Boolean> carProp = getProperty(Boolean.class, prop, area); 383 return carProp != null ? carProp.getValue() : false; 384 } 385 386 /** 387 * Returns value of a float property 388 * 389 * @param prop Property ID to get 390 * @param area Area of the property to get 391 */ getFloatProperty(int prop, int area)392 public float getFloatProperty(int prop, int area) { 393 CarPropertyValue<Float> carProp = getProperty(Float.class, prop, area); 394 return carProp != null ? carProp.getValue() : 0f; 395 } 396 397 /** 398 * Returns value of a integer property 399 * 400 * @param prop Property ID to get 401 * @param area Zone of the property to get 402 */ getIntProperty(int prop, int area)403 public int getIntProperty(int prop, int area) { 404 CarPropertyValue<Integer> carProp = getProperty(Integer.class, prop, area); 405 return carProp != null ? carProp.getValue() : 0; 406 } 407 408 /** 409 * Returns value of a integer array property 410 * 411 * @param prop Property ID to get 412 * @param area Zone of the property to get 413 */ 414 @NonNull getIntArrayProperty(int prop, int area)415 public int[] getIntArrayProperty(int prop, int area) { 416 CarPropertyValue<Integer[]> carProp = getProperty(Integer[].class, prop, area); 417 return carProp != null ? toIntArray(carProp.getValue()) : new int[0]; 418 } 419 toIntArray(Integer[] input)420 private static int[] toIntArray(Integer[] input) { 421 int len = input.length; 422 int[] arr = new int[len]; 423 for (int i = 0; i < len; i++) { 424 arr[i] = input[i]; 425 } 426 return arr; 427 } 428 429 /** 430 * Return CarPropertyValue 431 * 432 * @param clazz The class object for the CarPropertyValue 433 * @param propId Property ID to get 434 * @param areaId Zone of the property to get 435 * @throws IllegalArgumentException if there is invalid property type. 436 * @return CarPropertyValue. Null if property's id is invalid. 437 */ 438 @SuppressWarnings("unchecked") 439 @Nullable getProperty(@onNull Class<E> clazz, int propId, int areaId)440 public <E> CarPropertyValue<E> getProperty(@NonNull Class<E> clazz, int propId, int areaId) { 441 if (DBG) { 442 Log.d(TAG, "getProperty, propId: 0x" + toHexString(propId) 443 + ", areaId: 0x" + toHexString(areaId) + ", class: " + clazz); 444 } 445 try { 446 CarPropertyValue<E> propVal = mService.getProperty(propId, areaId); 447 if (propVal != null && propVal.getValue() != null) { 448 Class<?> actualClass = propVal.getValue().getClass(); 449 if (actualClass != clazz) { 450 throw new IllegalArgumentException("Invalid property type. " + "Expected: " 451 + clazz + ", but was: " + actualClass); 452 } 453 } 454 return propVal; 455 } catch (RemoteException e) { 456 return handleRemoteExceptionFromCarService(e, null); 457 } 458 } 459 460 /** 461 * Query CarPropertyValue with property id and areaId. 462 * @param propId Property Id 463 * @param areaId areaId 464 * @param <E> 465 * @return CarPropertyValue. Null if property's id is invalid. 466 */ 467 @Nullable getProperty(int propId, int areaId)468 public <E> CarPropertyValue<E> getProperty(int propId, int areaId) { 469 try { 470 CarPropertyValue<E> propVal = mService.getProperty(propId, areaId); 471 return propVal; 472 } catch (RemoteException e) { 473 return handleRemoteExceptionFromCarService(e, null); 474 } 475 } 476 477 /** 478 * Set value of car property by areaId. 479 * @param clazz The class object for the CarPropertyValue 480 * @param propId Property ID 481 * @param areaId areaId 482 * @param val Value of CarPropertyValue 483 * @param <E> data type of the given property, for example property that was 484 * defined as {@code VEHICLE_VALUE_TYPE_INT32} in vehicle HAL could be accessed using 485 * {@code Integer.class} 486 */ setProperty(@onNull Class<E> clazz, int propId, int areaId, @NonNull E val)487 public <E> void setProperty(@NonNull Class<E> clazz, int propId, int areaId, @NonNull E val) { 488 if (DBG) { 489 Log.d(TAG, "setProperty, propId: 0x" + toHexString(propId) 490 + ", areaId: 0x" + toHexString(areaId) + ", class: " + clazz + ", val: " + val); 491 } 492 try { 493 mService.setProperty(new CarPropertyValue<>(propId, areaId, val)); 494 } catch (RemoteException e) { 495 handleRemoteExceptionFromCarService(e); 496 } 497 } 498 499 /** 500 * Modifies a property. If the property modification doesn't occur, an error event shall be 501 * generated and propagated back to the application. 502 * 503 * @param prop Property ID to modify 504 * @param areaId AreaId to apply the modification. 505 * @param val Value to set 506 */ setBooleanProperty(int prop, int areaId, boolean val)507 public void setBooleanProperty(int prop, int areaId, boolean val) { 508 setProperty(Boolean.class, prop, areaId, val); 509 } 510 511 /** 512 * Set float value of property 513 * 514 * @param prop Property ID to modify 515 * @param areaId AreaId to apply the modification 516 * @param val Value to set 517 */ setFloatProperty(int prop, int areaId, float val)518 public void setFloatProperty(int prop, int areaId, float val) { 519 setProperty(Float.class, prop, areaId, val); 520 } 521 522 /** 523 * Set int value of property 524 * 525 * @param prop Property ID to modify 526 * @param areaId AreaId to apply the modification 527 * @param val Value to set 528 */ setIntProperty(int prop, int areaId, int val)529 public void setIntProperty(int prop, int areaId, int val) { 530 setProperty(Integer.class, prop, areaId, val); 531 } 532 533 534 private class CarPropertyListeners extends CarRatedFloatListeners<CarPropertyEventCallback> { CarPropertyListeners(float rate)535 CarPropertyListeners(float rate) { 536 super(rate); 537 } onPropertyChanged(final CarPropertyEvent event)538 void onPropertyChanged(final CarPropertyEvent event) { 539 // throw away old sensor data as oneway binder call can change order. 540 long updateTime = event.getCarPropertyValue().getTimestamp(); 541 int areaId = event.getCarPropertyValue().getAreaId(); 542 if (!needUpdateForAreaId(areaId, updateTime)) { 543 Log.w(TAG, "dropping old property data"); 544 return; 545 } 546 List<CarPropertyEventCallback> listeners; 547 synchronized (mActivePropertyListener) { 548 listeners = new ArrayList<>(getListeners()); 549 } 550 listeners.forEach(new Consumer<CarPropertyEventCallback>() { 551 @Override 552 public void accept(CarPropertyEventCallback listener) { 553 if (needUpdateForSelectedListener(listener, updateTime)) { 554 listener.onChangeEvent(event.getCarPropertyValue()); 555 } 556 } 557 }); 558 } 559 onErrorEvent(final CarPropertyEvent event)560 void onErrorEvent(final CarPropertyEvent event) { 561 List<CarPropertyEventCallback> listeners; 562 CarPropertyValue value = event.getCarPropertyValue(); 563 synchronized (mActivePropertyListener) { 564 listeners = new ArrayList<>(getListeners()); 565 } 566 listeners.forEach(new Consumer<CarPropertyEventCallback>() { 567 @Override 568 public void accept(CarPropertyEventCallback listener) { 569 if (DBG) { 570 Log.d(TAG, new StringBuilder().append("onErrorEvent for ") 571 .append("property: ").append(value.getPropertyId()) 572 .append(" areaId: ").append(value.getAreaId()) 573 .toString()); 574 } 575 listener.onErrorEvent(value.getPropertyId(), value.getAreaId()); 576 } 577 }); 578 } 579 } 580 581 /** @hide */ 582 @Override onCarDisconnected()583 public void onCarDisconnected() { 584 synchronized (mActivePropertyListener) { 585 mActivePropertyListener.clear(); 586 mCarPropertyEventToService = null; 587 } 588 } 589 } 590