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