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