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 com.android.car; 18 19 import static java.lang.Integer.toHexString; 20 21 import android.car.Car; 22 import android.car.hardware.CarPropertyConfig; 23 import android.car.hardware.CarPropertyValue; 24 import android.car.hardware.property.CarPropertyEvent; 25 import android.car.hardware.property.ICarProperty; 26 import android.car.hardware.property.ICarPropertyEventListener; 27 import android.content.Context; 28 import android.os.IBinder; 29 import android.os.RemoteException; 30 import android.util.Log; 31 import android.util.Pair; 32 import android.util.SparseArray; 33 34 import com.android.car.hal.PropertyHalService; 35 36 import java.io.PrintWriter; 37 import java.util.ArrayList; 38 import java.util.HashMap; 39 import java.util.LinkedList; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.concurrent.ConcurrentHashMap; 43 import java.util.concurrent.CopyOnWriteArrayList; 44 45 /** 46 * This class implements the binder interface for ICarProperty.aidl to make it easier to create 47 * multiple managers that deal with Vehicle Properties. To create a new service, simply extend 48 * this class and call the super() constructor with the appropriate arguments for the new service. 49 * {@link CarHvacService} shows the basic usage. 50 */ 51 public class CarPropertyService extends ICarProperty.Stub 52 implements CarServiceBase, PropertyHalService.PropertyHalListener { 53 private static final boolean DBG = true; 54 private static final String TAG = "Property.service"; 55 private final Context mContext; 56 private final Map<IBinder, Client> mClientMap = new ConcurrentHashMap<>(); 57 private Map<Integer, CarPropertyConfig<?>> mConfigs; 58 private final PropertyHalService mHal; 59 private boolean mListenerIsSet = false; 60 private final Map<Integer, List<Client>> mPropIdClientMap = new ConcurrentHashMap<>(); 61 private final Object mLock = new Object(); 62 CarPropertyService(Context context, PropertyHalService hal)63 public CarPropertyService(Context context, PropertyHalService hal) { 64 if (DBG) { 65 Log.d(TAG, "CarPropertyService started!"); 66 } 67 mHal = hal; 68 mContext = context; 69 } 70 71 // Helper class to keep track of listeners to this service 72 private class Client implements IBinder.DeathRecipient { 73 private final ICarPropertyEventListener mListener; 74 private final IBinder mListenerBinder; 75 private final SparseArray<Float> mRateMap = new SparseArray<Float>(); // key is propId 76 Client(ICarPropertyEventListener listener)77 Client(ICarPropertyEventListener listener) { 78 mListener = listener; 79 mListenerBinder = listener.asBinder(); 80 81 try { 82 mListenerBinder.linkToDeath(this, 0); 83 } catch (RemoteException e) { 84 throw new IllegalStateException("Client already dead", e); 85 } 86 mClientMap.put(mListenerBinder, this); 87 } 88 addProperty(int propId, float rate)89 void addProperty(int propId, float rate) { 90 mRateMap.put(propId, rate); 91 } 92 93 /** 94 * Client died. Remove the listener from HAL service and unregister if this is the last 95 * client. 96 */ 97 @Override binderDied()98 public void binderDied() { 99 if (DBG) { 100 Log.d(TAG, "binderDied " + mListenerBinder); 101 } 102 103 for (int i = 0; i < mRateMap.size(); i++) { 104 int propId = mRateMap.keyAt(i); 105 CarPropertyService.this.unregisterListenerBinderLocked(propId, mListenerBinder); 106 } 107 this.release(); 108 } 109 getListener()110 ICarPropertyEventListener getListener() { 111 return mListener; 112 } 113 getListenerBinder()114 IBinder getListenerBinder() { 115 return mListenerBinder; 116 } 117 getRate(int propId)118 float getRate(int propId) { 119 // Return 0 if no key found, since that is the slowest rate. 120 return mRateMap.get(propId, (float) 0); 121 } 122 release()123 void release() { 124 mListenerBinder.unlinkToDeath(this, 0); 125 mClientMap.remove(mListenerBinder); 126 } 127 removeProperty(int propId)128 void removeProperty(int propId) { 129 mRateMap.remove(propId); 130 if (mRateMap.size() == 0) { 131 // Last property was released, remove the client. 132 this.release(); 133 } 134 } 135 } 136 137 @Override init()138 public void init() { 139 if (mConfigs == null) { 140 // Cache the configs list to avoid subsequent binder calls 141 mConfigs = mHal.getPropertyList(); 142 if (DBG) { 143 Log.d(TAG, "cache CarPropertyConfigs " + mConfigs.size()); 144 } 145 } 146 } 147 148 @Override release()149 public void release() { 150 for (Client c : mClientMap.values()) { 151 c.release(); 152 } 153 mClientMap.clear(); 154 mPropIdClientMap.clear(); 155 mHal.setListener(null); 156 mListenerIsSet = false; 157 } 158 159 @Override dump(PrintWriter writer)160 public void dump(PrintWriter writer) { 161 } 162 163 @Override registerListener(int propId, float rate, ICarPropertyEventListener listener)164 public void registerListener(int propId, float rate, ICarPropertyEventListener listener) { 165 if (DBG) { 166 Log.d(TAG, "registerListener: propId=0x" + toHexString(propId) + " rate=" + rate); 167 } 168 if (mConfigs.get(propId) == null) { 169 // Do not attempt to register an invalid propId 170 Log.e(TAG, "registerListener: propId is not in config list: " + propId); 171 return; 172 } 173 ICarImpl.assertPermission(mContext, mHal.getReadPermission(propId)); 174 if (listener == null) { 175 Log.e(TAG, "registerListener: Listener is null."); 176 throw new IllegalArgumentException("listener cannot be null."); 177 } 178 179 IBinder listenerBinder = listener.asBinder(); 180 181 synchronized (mLock) { 182 // Get the client for this listener 183 Client client = mClientMap.get(listenerBinder); 184 if (client == null) { 185 client = new Client(listener); 186 } 187 client.addProperty(propId, rate); 188 // Insert the client into the propId --> clients map 189 List<Client> clients = mPropIdClientMap.get(propId); 190 if (clients == null) { 191 clients = new CopyOnWriteArrayList<Client>(); 192 mPropIdClientMap.put(propId, clients); 193 } 194 if (!clients.contains(client)) { 195 clients.add(client); 196 } 197 // Set the HAL listener if necessary 198 if (!mListenerIsSet) { 199 mHal.setListener(this); 200 } 201 // Set the new rate 202 if (rate > mHal.getSampleRate(propId)) { 203 mHal.subscribeProperty(propId, rate); 204 } 205 } 206 // Send the latest value(s) to the registering listener only 207 List<CarPropertyEvent> events = new LinkedList<CarPropertyEvent>(); 208 if (mConfigs.get(propId).isGlobalProperty()) { 209 CarPropertyValue value = mHal.getProperty(propId, 0); 210 // CarPropertyEvent without a CarPropertyValue can not be used by any listeners. 211 if (value != null) { 212 CarPropertyEvent event = new CarPropertyEvent( 213 CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, value); 214 events.add(event); 215 } 216 } else { 217 for (int areaId : mConfigs.get(propId).getAreaIds()) { 218 CarPropertyValue value = mHal.getProperty(propId, areaId); 219 if (value != null) { 220 CarPropertyEvent event = new CarPropertyEvent( 221 CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, value); 222 events.add(event); 223 } 224 } 225 } 226 try { 227 listener.onEvent(events); 228 } catch (RemoteException ex) { 229 // If we cannot send a record, its likely the connection snapped. Let the binder 230 // death handle the situation. 231 Log.e(TAG, "onEvent calling failed: " + ex); 232 } 233 } 234 235 @Override unregisterListener(int propId, ICarPropertyEventListener listener)236 public void unregisterListener(int propId, ICarPropertyEventListener listener) { 237 if (DBG) { 238 Log.d(TAG, "unregisterListener propId=0x" + toHexString(propId)); 239 } 240 ICarImpl.assertPermission(mContext, mHal.getReadPermission(propId)); 241 if (listener == null) { 242 Log.e(TAG, "unregisterListener: Listener is null."); 243 throw new IllegalArgumentException("Listener is null"); 244 } 245 246 IBinder listenerBinder = listener.asBinder(); 247 synchronized (mLock) { 248 unregisterListenerBinderLocked(propId, listenerBinder); 249 } 250 } 251 unregisterListenerBinderLocked(int propId, IBinder listenerBinder)252 private void unregisterListenerBinderLocked(int propId, IBinder listenerBinder) { 253 Client client = mClientMap.get(listenerBinder); 254 List<Client> propertyClients = mPropIdClientMap.get(propId); 255 if (mConfigs.get(propId) == null) { 256 // Do not attempt to register an invalid propId 257 Log.e(TAG, "unregisterListener: propId is not in config list:0x" + toHexString(propId)); 258 return; 259 } 260 if ((client == null) || (propertyClients == null)) { 261 Log.e(TAG, "unregisterListenerBinderLocked: Listener was not previously registered."); 262 } else { 263 if (propertyClients.remove(client)) { 264 client.removeProperty(propId); 265 } else { 266 Log.e(TAG, "unregisterListenerBinderLocked: Listener was not registered for " 267 + "propId=0x" + toHexString(propId)); 268 } 269 270 if (propertyClients.isEmpty()) { 271 // Last listener for this property unsubscribed. Clean up 272 mHal.unsubscribeProperty(propId); 273 mPropIdClientMap.remove(propId); 274 if (mPropIdClientMap.isEmpty()) { 275 // No more properties are subscribed. Turn off the listener. 276 mHal.setListener(null); 277 mListenerIsSet = false; 278 } 279 } else { 280 // Other listeners are still subscribed. Calculate the new rate 281 float maxRate = 0; 282 for (Client c : propertyClients) { 283 float rate = c.getRate(propId); 284 if (rate > maxRate) { 285 maxRate = rate; 286 } 287 } 288 // Set the new rate 289 mHal.subscribeProperty(propId, maxRate); 290 } 291 } 292 } 293 294 /** 295 * Return the list of properties that the caller may access. 296 */ 297 @Override getPropertyList()298 public List<CarPropertyConfig> getPropertyList() { 299 List<CarPropertyConfig> returnList = new ArrayList<CarPropertyConfig>(); 300 for (CarPropertyConfig c : mConfigs.values()) { 301 if (ICarImpl.hasPermission(mContext, mHal.getReadPermission(c.getPropertyId()))) { 302 // Only add properties the list if the process has permissions to read it 303 returnList.add(c); 304 } 305 } 306 if (DBG) { 307 Log.d(TAG, "getPropertyList returns " + returnList.size() + " configs"); 308 } 309 return returnList; 310 } 311 312 @Override getProperty(int prop, int zone)313 public CarPropertyValue getProperty(int prop, int zone) { 314 if (mConfigs.get(prop) == null) { 315 // Do not attempt to register an invalid propId 316 Log.e(TAG, "getProperty: propId is not in config list:0x" + toHexString(prop)); 317 return null; 318 } 319 ICarImpl.assertPermission(mContext, mHal.getReadPermission(prop)); 320 return mHal.getProperty(prop, zone); 321 } 322 323 @Override getReadPermission(int propId)324 public String getReadPermission(int propId) { 325 if (mConfigs.get(propId) == null) { 326 // Property ID does not exist 327 Log.e(TAG, "getReadPermission: propId is not in config list:0x" + toHexString(propId)); 328 return null; 329 } 330 return mHal.getReadPermission(propId); 331 } 332 333 @Override getWritePermission(int propId)334 public String getWritePermission(int propId) { 335 if (mConfigs.get(propId) == null) { 336 // Property ID does not exist 337 Log.e(TAG, "getWritePermission: propId is not in config list:0x" + toHexString(propId)); 338 return null; 339 } 340 return mHal.getWritePermission(propId); 341 } 342 343 @Override setProperty(CarPropertyValue prop)344 public void setProperty(CarPropertyValue prop) { 345 int propId = prop.getPropertyId(); 346 if (mConfigs.get(propId) == null) { 347 // Do not attempt to register an invalid propId 348 Log.e(TAG, "setProperty: propId is not in config list:0x" + toHexString(propId)); 349 return; 350 } 351 ICarImpl.assertPermission(mContext, mHal.getWritePermission(propId)); 352 // need an extra permission for writing display units properties. 353 if (mHal.isDisplayUnitsProperty(propId)) { 354 ICarImpl.assertPermission(mContext, Car.PERMISSION_VENDOR_EXTENSION); 355 } 356 mHal.setProperty(prop); 357 } 358 359 // Implement PropertyHalListener interface 360 @Override onPropertyChange(List<CarPropertyEvent> events)361 public void onPropertyChange(List<CarPropertyEvent> events) { 362 Map<IBinder, Pair<ICarPropertyEventListener, List<CarPropertyEvent>>> eventsToDispatch = 363 new HashMap<>(); 364 365 for (CarPropertyEvent event : events) { 366 int propId = event.getCarPropertyValue().getPropertyId(); 367 List<Client> clients = mPropIdClientMap.get(propId); 368 if (clients == null) { 369 Log.e(TAG, "onPropertyChange: no listener registered for propId=0x" 370 + toHexString(propId)); 371 continue; 372 } 373 374 for (Client c : clients) { 375 IBinder listenerBinder = c.getListenerBinder(); 376 Pair<ICarPropertyEventListener, List<CarPropertyEvent>> p = 377 eventsToDispatch.get(listenerBinder); 378 if (p == null) { 379 // Initialize the linked list for the listener 380 p = new Pair<>(c.getListener(), new LinkedList<CarPropertyEvent>()); 381 eventsToDispatch.put(listenerBinder, p); 382 } 383 p.second.add(event); 384 } 385 } 386 // Parse the dispatch list to send events 387 for (Pair<ICarPropertyEventListener, List<CarPropertyEvent>> p: eventsToDispatch.values()) { 388 try { 389 p.first.onEvent(p.second); 390 } catch (RemoteException ex) { 391 // If we cannot send a record, its likely the connection snapped. Let binder 392 // death handle the situation. 393 Log.e(TAG, "onEvent calling failed: " + ex); 394 } 395 } 396 } 397 398 @Override onPropertySetError(int property, int area)399 public void onPropertySetError(int property, int area) { 400 List<Client> clients = mPropIdClientMap.get(property); 401 if (clients != null) { 402 List<CarPropertyEvent> eventList = new LinkedList<>(); 403 eventList.add(CarPropertyEvent.createErrorEvent(property, area)); 404 for (Client c : clients) { 405 try { 406 c.getListener().onEvent(eventList); 407 } catch (RemoteException ex) { 408 // If we cannot send a record, its likely the connection snapped. Let the binder 409 // death handle the situation. 410 Log.e(TAG, "onEvent calling failed: " + ex); 411 } 412 } 413 } else { 414 Log.e(TAG, "onPropertySetError called with no listener registered for propId=0x" 415 + toHexString(property)); 416 } 417 } 418 } 419