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.car.CarApiUtil; 22 import android.car.CarManagerBase; 23 import android.car.CarNotConnectedException; 24 import android.car.hardware.CarPropertyConfig; 25 import android.car.hardware.CarPropertyValue; 26 import android.os.Handler; 27 import android.os.IBinder; 28 import android.os.RemoteException; 29 import android.util.ArraySet; 30 import android.util.Log; 31 import android.util.SparseArray; 32 33 import com.android.car.internal.CarRatedFloatListeners; 34 import com.android.car.internal.SingleMessageHandler; 35 36 import java.lang.ref.WeakReference; 37 import java.util.ArrayList; 38 import java.util.List; 39 import java.util.function.Consumer; 40 41 42 /** 43 * API for creating Car*Manager 44 * @hide 45 */ 46 public class CarPropertyManager implements CarManagerBase { 47 private final boolean mDbg; 48 private final SingleMessageHandler<CarPropertyEvent> mHandler; 49 private final ICarProperty mService; 50 private final String mTag; 51 private static final int MSG_GENERIC_EVENT = 0; 52 53 private CarPropertyEventListenerToService mCarPropertyEventToService; 54 55 56 /** Record of locally active properties. Key is propertyId */ 57 private final SparseArray<CarPropertyListeners> mActivePropertyListener = 58 new SparseArray<>(); 59 60 /** Callback functions for property events */ 61 public interface CarPropertyEventListener { 62 /** Called when a property is updated */ 63 void onChangeEvent(CarPropertyValue value); 64 65 /** Called when an error is detected with a property */ 66 void onErrorEvent(int propId, int zone); 67 } 68 69 /** 70 * Get an instance of the CarPropertyManager. 71 */ 72 public CarPropertyManager(IBinder service, Handler handler, boolean dbg, String tag) { 73 mDbg = dbg; 74 mTag = tag; 75 mService = ICarProperty.Stub.asInterface(service); 76 mHandler = new SingleMessageHandler<CarPropertyEvent>(handler.getLooper(), 77 MSG_GENERIC_EVENT) { 78 @Override 79 protected void handleEvent(CarPropertyEvent event) { 80 CarPropertyListeners listeners; 81 synchronized (mActivePropertyListener) { 82 listeners = mActivePropertyListener.get( 83 event.getCarPropertyValue().getPropertyId()); 84 } 85 if (listeners != null) { 86 switch (event.getEventType()) { 87 case CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE: 88 listeners.onPropertyChanged(event); 89 break; 90 case CarPropertyEvent.PROPERTY_EVENT_ERROR: 91 listeners.onErrorEvent(event); 92 break; 93 default: 94 throw new IllegalArgumentException(); 95 } 96 } 97 } 98 }; 99 } 100 101 /** Use to register or update Callback for properties */ 102 public boolean registerListener(CarPropertyEventListener listener, int propertyId, float rate) 103 throws CarNotConnectedException { 104 synchronized (mActivePropertyListener) { 105 if (mCarPropertyEventToService == null) { 106 mCarPropertyEventToService = new CarPropertyEventListenerToService(this); 107 } 108 boolean needsServerUpdate = false; 109 CarPropertyListeners listeners; 110 listeners = mActivePropertyListener.get(propertyId); 111 if (listeners == null) { 112 listeners = new CarPropertyListeners(rate); 113 mActivePropertyListener.put(propertyId, listeners); 114 needsServerUpdate = true; 115 } 116 if (listeners.addAndUpdateRate(listener, rate)) { 117 needsServerUpdate = true; 118 } 119 if (needsServerUpdate) { 120 if (!registerOrUpdatePropertyListener(propertyId, rate)) { 121 return false; 122 } 123 } 124 } 125 return true; 126 } 127 128 private boolean registerOrUpdatePropertyListener(int propertyId, float rate) 129 throws CarNotConnectedException { 130 try { 131 mService.registerListener(propertyId, rate, mCarPropertyEventToService); 132 } catch (IllegalStateException e) { 133 CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); 134 } catch (RemoteException e) { 135 throw new CarNotConnectedException(e); 136 } 137 return true; 138 } 139 140 private class CarPropertyEventListenerToService extends ICarPropertyEventListener.Stub{ 141 private final WeakReference<CarPropertyManager> mMgr; 142 143 CarPropertyEventListenerToService(CarPropertyManager mgr) { 144 mMgr = new WeakReference<>(mgr); 145 } 146 147 @Override 148 public void onEvent(List<CarPropertyEvent> events) throws RemoteException { 149 CarPropertyManager manager = mMgr.get(); 150 if (manager != null) { 151 manager.handleEvent(events); 152 } 153 } 154 } 155 156 private void handleEvent(List<CarPropertyEvent> events) { 157 mHandler.sendEvents(events); 158 } 159 160 /** 161 * Stop getting sensor update for the given listener. If there are multiple registrations for 162 * this listener, all listening will be stopped. 163 * @param listener 164 */ 165 public void unregisterListener(CarPropertyEventListener listener) { 166 synchronized (mActivePropertyListener) { 167 for (int i = 0; i < mActivePropertyListener.size(); i++) { 168 doUnregisterListenerLocked(listener, mActivePropertyListener.keyAt(i)); 169 } 170 } 171 } 172 173 /** 174 * Stop getting sensor update for the given listener and sensor. If the same listener is used 175 * for other sensors, those subscriptions will not be affected. 176 * @param listener 177 * @param propertyId 178 */ 179 public void unregisterListener(CarPropertyEventListener listener, int propertyId) { 180 synchronized (mActivePropertyListener) { 181 doUnregisterListenerLocked(listener, propertyId); 182 } 183 } 184 185 private void doUnregisterListenerLocked(CarPropertyEventListener listener, int propertyId) { 186 CarPropertyListeners listeners = mActivePropertyListener.get(propertyId); 187 if (listeners != null) { 188 boolean needsServerUpdate = false; 189 if (listeners.contains(listener)) { 190 needsServerUpdate = listeners.remove(listener); 191 } 192 if (listeners.isEmpty()) { 193 try { 194 mService.unregisterListener(propertyId, mCarPropertyEventToService); 195 } catch (RemoteException e) { 196 //ignore 197 } 198 mActivePropertyListener.remove(propertyId); 199 } else if (needsServerUpdate) { 200 try { 201 registerOrUpdatePropertyListener(propertyId, listeners.getRate()); 202 } catch (CarNotConnectedException e) { 203 // ignore 204 } 205 } 206 } 207 } 208 209 /** 210 * Returns the list of properties implemented by this car. 211 * 212 * @return Caller must check the property type and typecast to the appropriate subclass 213 * (CarPropertyBooleanProperty, CarPropertyFloatProperty, CarrPropertyIntProperty) 214 */ 215 public List<CarPropertyConfig> getPropertyList() throws CarNotConnectedException { 216 try { 217 return mService.getPropertyList(); 218 } catch (RemoteException e) { 219 Log.e(mTag, "getPropertyList exception ", e); 220 throw new CarNotConnectedException(e); 221 } 222 } 223 224 /** 225 * Returns the list of properties implemented by this car in given property id list. 226 * 227 * @return Caller must check the property type and typecast to the appropriate subclass 228 * (CarPropertyBooleanProperty, CarPropertyFloatProperty, CarrPropertyIntProperty) 229 */ 230 public List<CarPropertyConfig> getPropertyList(ArraySet<Integer> propertyIds) 231 throws CarNotConnectedException { 232 try { 233 List<CarPropertyConfig> configs = new ArrayList<>(); 234 for (CarPropertyConfig c : mService.getPropertyList()) { 235 if (propertyIds.contains(c.getPropertyId())) { 236 configs.add(c); 237 } 238 } 239 return configs; 240 } catch (RemoteException e) { 241 Log.e(mTag, "getPropertyList exception ", e); 242 throw new CarNotConnectedException(e); 243 } 244 245 } 246 247 /** 248 * Check whether a given property is available or disabled based on the car's current state. 249 * @return true if STATUS_AVAILABLE, false otherwise (eg STATUS_UNAVAILABLE) 250 * @throws CarNotConnectedException 251 */ 252 public boolean isPropertyAvailable(int propId, int area) throws CarNotConnectedException { 253 try { 254 CarPropertyValue propValue = mService.getProperty(propId, area); 255 return (propValue != null) 256 && (propValue.getStatus() == CarPropertyValue.STATUS_AVAILABLE); 257 } catch (RemoteException e) { 258 Log.e(mTag, "isPropertyAvailable failed with " + e.toString() 259 + ", propId: 0x" + toHexString(propId) + ", area: 0x" + toHexString(area), e); 260 throw new CarNotConnectedException(e); 261 } 262 } 263 264 /** 265 * Returns value of a bool property 266 * 267 * @param prop Property ID to get 268 * @param area Area of the property to get 269 */ 270 public boolean getBooleanProperty(int prop, int area) throws CarNotConnectedException { 271 CarPropertyValue<Boolean> carProp = getProperty(Boolean.class, prop, area); 272 return carProp != null ? carProp.getValue() : false; 273 } 274 275 /** 276 * Returns value of a float property 277 * 278 * @param prop Property ID to get 279 * @param area Area of the property to get 280 */ 281 public float getFloatProperty(int prop, int area) throws CarNotConnectedException { 282 CarPropertyValue<Float> carProp = getProperty(Float.class, prop, area); 283 return carProp != null ? carProp.getValue() : 0f; 284 } 285 286 /** 287 * Returns value of a integer property 288 * 289 * @param prop Property ID to get 290 * @param area Zone of the property to get 291 */ 292 public int getIntProperty(int prop, int area) throws CarNotConnectedException { 293 CarPropertyValue<Integer> carProp = getProperty(Integer.class, prop, area); 294 return carProp != null ? carProp.getValue() : 0; 295 } 296 297 /** Return CarPropertyValue */ 298 @SuppressWarnings("unchecked") 299 public <E> CarPropertyValue<E> getProperty(Class<E> clazz, int propId, int area) 300 throws CarNotConnectedException { 301 if (mDbg) { 302 Log.d(mTag, "getProperty, propId: 0x" + toHexString(propId) 303 + ", area: 0x" + toHexString(area) + ", class: " + clazz); 304 } 305 try { 306 CarPropertyValue<E> propVal = mService.getProperty(propId, area); 307 if (propVal != null && propVal.getValue() != null) { 308 Class<?> actualClass = propVal.getValue().getClass(); 309 if (actualClass != clazz) { 310 throw new IllegalArgumentException("Invalid property type. " + "Expected: " 311 + clazz + ", but was: " + actualClass); 312 } 313 } 314 return propVal; 315 } catch (RemoteException e) { 316 Log.e(mTag, "getProperty failed with " + e.toString() 317 + ", propId: 0x" + toHexString(propId) + ", area: 0x" + toHexString(area), e); 318 throw new CarNotConnectedException(e); 319 } 320 } 321 322 /** Return raw CarPropertyValue */ 323 public <E> CarPropertyValue<E> getProperty(int propId, int area) 324 throws CarNotConnectedException { 325 try { 326 CarPropertyValue<E> propVal = mService.getProperty(propId, area); 327 return propVal; 328 } catch (RemoteException e) { 329 Log.e(mTag, "getProperty failed with " + e.toString() 330 + ", propId: 0x" + toHexString(propId) + ", area: 0x" + toHexString(area), e); 331 throw new CarNotConnectedException(e); 332 } 333 } 334 335 /** Set CarPropertyValue */ 336 public <E> void setProperty(Class<E> clazz, int propId, int area, E val) 337 throws CarNotConnectedException { 338 if (mDbg) { 339 Log.d(mTag, "setProperty, propId: 0x" + toHexString(propId) 340 + ", area: 0x" + toHexString(area) + ", class: " + clazz + ", val: " + val); 341 } 342 try { 343 mService.setProperty(new CarPropertyValue<>(propId, area, val)); 344 } catch (RemoteException e) { 345 Log.e(mTag, "setProperty failed with " + e.toString(), e); 346 throw new CarNotConnectedException(e); 347 } 348 } 349 350 /** 351 * Modifies a property. If the property modification doesn't occur, an error event shall be 352 * generated and propagated back to the application. 353 * 354 * @param prop Property ID to modify 355 * @param area Area to apply the modification. 356 * @param val Value to set 357 */ 358 public void setBooleanProperty(int prop, int area, boolean val) 359 throws CarNotConnectedException { 360 setProperty(Boolean.class, prop, area, val); 361 } 362 363 /** Set float value of property*/ 364 public void setFloatProperty(int prop, int area, float val) throws CarNotConnectedException { 365 setProperty(Float.class, prop, area, val); 366 } 367 /** Set int value of property*/ 368 public void setIntProperty(int prop, int area, int val) throws CarNotConnectedException { 369 setProperty(Integer.class, prop, area, val); 370 } 371 372 373 private class CarPropertyListeners extends CarRatedFloatListeners<CarPropertyEventListener> { 374 CarPropertyListeners(float rate) { 375 super(rate); 376 } 377 void onPropertyChanged(final CarPropertyEvent event) { 378 // throw away old sensor data as oneway binder call can change order. 379 long updateTime = event.getCarPropertyValue().getTimestamp(); 380 if (updateTime < mLastUpdateTime) { 381 Log.w(mTag, "dropping old property data"); 382 return; 383 } 384 mLastUpdateTime = updateTime; 385 List<CarPropertyEventListener> listeners; 386 synchronized (mActivePropertyListener) { 387 listeners = new ArrayList<>(getListeners()); 388 } 389 listeners.forEach(new Consumer<CarPropertyEventListener>() { 390 @Override 391 public void accept(CarPropertyEventListener listener) { 392 listener.onChangeEvent(event.getCarPropertyValue()); 393 } 394 }); 395 } 396 397 void onErrorEvent(final CarPropertyEvent event) { 398 List<CarPropertyEventListener> listeners; 399 CarPropertyValue value = event.getCarPropertyValue(); 400 synchronized (mActivePropertyListener) { 401 listeners = new ArrayList<>(getListeners()); 402 } 403 listeners.forEach(new Consumer<CarPropertyEventListener>() { 404 @Override 405 public void accept(CarPropertyEventListener listener) { 406 listener.onErrorEvent(value.getPropertyId(), value.getAreaId()); 407 } 408 }); 409 } 410 } 411 412 /** @hide */ 413 @Override 414 public void onCarDisconnected() { 415 synchronized (mActivePropertyListener) { 416 mActivePropertyListener.clear(); 417 mCarPropertyEventToService = null; 418 } 419 } 420 } 421