• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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                 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 
addProperty(int propId, float rate)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
binderDied()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 
getListener()111         ICarPropertyEventListener getListener() {
112             return mListener;
113         }
114 
getListenerBinder()115         IBinder getListenerBinder() {
116             return mListenerBinder;
117         }
118 
getRate(int propId)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 
release()124         void release() {
125             mListenerBinder.unlinkToDeath(this, 0);
126             mClientMap.remove(mListenerBinder);
127         }
128 
removeProperty(int propId)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
init()139     public void init() {
140     }
141 
142     @Override
release()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
dump(PrintWriter writer)154     public void dump(PrintWriter writer) {
155     }
156 
157     @Override
registerListener(int propId, float rate, ICarPropertyEventListener listener)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
unregisterListener(int propId, ICarPropertyEventListener listener)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 
unregisterListenerBinderLocked(int propId, IBinder listenerBinder)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
getPropertyList()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
getProperty(int prop, int zone)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
setProperty(CarPropertyValue prop)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
onPropertyChange(List<CarPropertyEvent> events)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
onPropertySetError(int property, int area)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 
createErrorEvent(int property, int area)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