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 package com.android.car.hal;
17 
18 import static com.android.car.hal.CarPropertyUtils.toCarPropertyValue;
19 import static com.android.car.hal.CarPropertyUtils.toVehiclePropValue;
20 
21 import static java.lang.Integer.toHexString;
22 
23 import android.annotation.Nullable;
24 import android.car.hardware.CarPropertyConfig;
25 import android.car.hardware.CarPropertyValue;
26 import android.car.hardware.property.CarPropertyEvent;
27 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
28 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
29 import android.util.Log;
30 import android.util.SparseArray;
31 
32 import com.android.car.CarLog;
33 import com.android.internal.annotations.GuardedBy;
34 
35 import java.io.PrintWriter;
36 import java.util.Collection;
37 import java.util.HashSet;
38 import java.util.LinkedList;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Set;
42 import java.util.concurrent.ConcurrentHashMap;
43 
44 /**
45  * Common interface for HAL services that send Vehicle Properties back and forth via ICarProperty.
46  * Services that communicate by passing vehicle properties back and forth via ICarProperty should
47  * extend this class.
48  */
49 public class PropertyHalService extends HalServiceBase {
50     private final boolean mDbg = true;
51     private final LinkedList<CarPropertyEvent> mEventsToDispatch = new LinkedList<>();
52     private final Map<Integer, CarPropertyConfig<?>> mProps =
53             new ConcurrentHashMap<>();
54     private final SparseArray<Float> mRates = new SparseArray<Float>();
55     private static final String TAG = "PropertyHalService";
56     private final VehicleHal mVehicleHal;
57     private final PropertyHalServiceIds mPropIds;
58 
59     @GuardedBy("mLock")
60     private PropertyHalListener mListener;
61 
62     private Set<Integer> mSubscribedPropIds;
63 
64     private final Object mLock = new Object();
65 
66     /**
67      * Converts manager property ID to Vehicle HAL property ID.
68      * If property is not supported, it will return {@link #NOT_SUPPORTED_PROPERTY}.
69      */
managerToHalPropId(int propId)70     private int managerToHalPropId(int propId) {
71         if (mProps.containsKey(propId)) {
72             return propId;
73         } else {
74             return NOT_SUPPORTED_PROPERTY;
75         }
76     }
77 
78     /**
79      * Converts Vehicle HAL property ID to manager property ID.
80      * If property is not supported, it will return {@link #NOT_SUPPORTED_PROPERTY}.
81      */
halToManagerPropId(int halPropId)82     private int halToManagerPropId(int halPropId) {
83         if (mProps.containsKey(halPropId)) {
84             return halPropId;
85         } else {
86             return NOT_SUPPORTED_PROPERTY;
87         }
88     }
89 
90     /**
91      * PropertyHalListener used to send events to CarPropertyService
92      */
93     public interface PropertyHalListener {
94         /**
95          * This event is sent whenever the property value is updated
96          * @param event
97          */
onPropertyChange(List<CarPropertyEvent> events)98         void onPropertyChange(List<CarPropertyEvent> events);
99         /**
100          * This event is sent when the set property call fails
101          * @param property
102          * @param area
103          */
onPropertySetError(int property, int area)104         void onPropertySetError(int property, int area);
105     }
106 
PropertyHalService(VehicleHal vehicleHal)107     public PropertyHalService(VehicleHal vehicleHal) {
108         mPropIds = new PropertyHalServiceIds();
109         mSubscribedPropIds = new HashSet<Integer>();
110         mVehicleHal = vehicleHal;
111         if (mDbg) {
112             Log.d(TAG, "started PropertyHalService");
113         }
114     }
115 
116     /**
117      * Set the listener for the HAL service
118      * @param listener
119      */
setListener(PropertyHalListener listener)120     public void setListener(PropertyHalListener listener) {
121         synchronized (mLock) {
122             mListener = listener;
123         }
124     }
125 
126     /**
127      *
128      * @return List<CarPropertyConfig> List of configs available.
129      */
getPropertyList()130     public Map<Integer, CarPropertyConfig<?>> getPropertyList() {
131         if (mDbg) {
132             Log.d(TAG, "getPropertyList");
133         }
134         return mProps;
135     }
136 
137     /**
138      * Returns property or null if property is not ready yet.
139      * @param mgrPropId
140      * @param areaId
141      */
142     @Nullable
getProperty(int mgrPropId, int areaId)143     public CarPropertyValue getProperty(int mgrPropId, int areaId) {
144         int halPropId = managerToHalPropId(mgrPropId);
145         if (halPropId == NOT_SUPPORTED_PROPERTY) {
146             throw new IllegalArgumentException("Invalid property Id : 0x" + toHexString(mgrPropId));
147         }
148 
149         VehiclePropValue value = null;
150         try {
151             value = mVehicleHal.get(halPropId, areaId);
152         } catch (PropertyTimeoutException e) {
153             Log.e(CarLog.TAG_PROPERTY, "get, property not ready 0x" + toHexString(halPropId), e);
154         }
155 
156         return value == null ? null : toCarPropertyValue(value, mgrPropId);
157     }
158 
159     /**
160      * Returns sample rate for the property
161      * @param propId
162      */
getSampleRate(int propId)163     public float getSampleRate(int propId) {
164         return mVehicleHal.getSampleRate(propId);
165     }
166 
167     /**
168      * Get the read permission string for the property.
169      * @param propId
170      */
171     @Nullable
getReadPermission(int propId)172     public String getReadPermission(int propId) {
173         return mPropIds.getReadPermission(propId);
174     }
175 
176     /**
177      * Get the write permission string for the property.
178      * @param propId
179      */
180     @Nullable
getWritePermission(int propId)181     public String getWritePermission(int propId) {
182         return mPropIds.getWritePermission(propId);
183     }
184 
185     /**
186      * Return true if property is a display_units property
187      * @param propId
188      */
isDisplayUnitsProperty(int propId)189     public boolean isDisplayUnitsProperty(int propId) {
190         return mPropIds.isPropertyToChangeUnits(propId);
191     }
192 
193     /**
194      * Set the property value.
195      * @param prop
196      */
setProperty(CarPropertyValue prop)197     public void setProperty(CarPropertyValue prop) {
198         int halPropId = managerToHalPropId(prop.getPropertyId());
199         if (halPropId == NOT_SUPPORTED_PROPERTY) {
200             throw new IllegalArgumentException("Invalid property Id : 0x"
201                     + toHexString(prop.getPropertyId()));
202         }
203         VehiclePropValue halProp = toVehiclePropValue(prop, halPropId);
204         try {
205             mVehicleHal.set(halProp);
206         } catch (PropertyTimeoutException e) {
207             Log.e(CarLog.TAG_PROPERTY, "set, property not ready 0x" + toHexString(halPropId), e);
208             throw new RuntimeException(e);
209         }
210     }
211 
212     /**
213      * Subscribe to this property at the specified update rate.
214      * @param propId
215      * @param rate
216      */
subscribeProperty(int propId, float rate)217     public void subscribeProperty(int propId, float rate) {
218         if (mDbg) {
219             Log.d(TAG, "subscribeProperty propId=0x" + toHexString(propId) + ", rate=" + rate);
220         }
221         int halPropId = managerToHalPropId(propId);
222         if (halPropId == NOT_SUPPORTED_PROPERTY) {
223             throw new IllegalArgumentException("Invalid property Id : 0x"
224                     + toHexString(propId));
225         }
226         // Validate the min/max rate
227         CarPropertyConfig cfg = mProps.get(propId);
228         if (rate > cfg.getMaxSampleRate()) {
229             rate = cfg.getMaxSampleRate();
230         } else if (rate < cfg.getMinSampleRate()) {
231             rate = cfg.getMinSampleRate();
232         }
233         synchronized (mSubscribedPropIds) {
234             mSubscribedPropIds.add(halPropId);
235         }
236         mVehicleHal.subscribeProperty(this, halPropId, rate);
237     }
238 
239     /**
240      * Unsubscribe the property and turn off update events for it.
241      * @param propId
242      */
unsubscribeProperty(int propId)243     public void unsubscribeProperty(int propId) {
244         if (mDbg) {
245             Log.d(TAG, "unsubscribeProperty propId=0x" + toHexString(propId));
246         }
247         int halPropId = managerToHalPropId(propId);
248         if (halPropId == NOT_SUPPORTED_PROPERTY) {
249             throw new IllegalArgumentException("Invalid property Id : 0x"
250                     + toHexString(propId));
251         }
252         synchronized (mSubscribedPropIds) {
253             if (mSubscribedPropIds.contains(halPropId)) {
254                 mSubscribedPropIds.remove(halPropId);
255                 mVehicleHal.unsubscribeProperty(this, halPropId);
256             }
257         }
258     }
259 
260     @Override
init()261     public void init() {
262         if (mDbg) {
263             Log.d(TAG, "init()");
264         }
265     }
266 
267     @Override
release()268     public void release() {
269         if (mDbg) {
270             Log.d(TAG, "release()");
271         }
272         synchronized (mSubscribedPropIds) {
273             for (Integer prop : mSubscribedPropIds) {
274                 mVehicleHal.unsubscribeProperty(this, prop);
275             }
276             mSubscribedPropIds.clear();
277         }
278         mProps.clear();
279 
280         synchronized (mLock) {
281             mListener = null;
282         }
283     }
284 
285     @Override
takeSupportedProperties( Collection<VehiclePropConfig> allProperties)286     public Collection<VehiclePropConfig> takeSupportedProperties(
287             Collection<VehiclePropConfig> allProperties) {
288         List<VehiclePropConfig> taken = new LinkedList<>();
289 
290         for (VehiclePropConfig p : allProperties) {
291             if (mPropIds.isSupportedProperty(p.prop)) {
292                 CarPropertyConfig config = CarPropertyUtils.toCarPropertyConfig(p, p.prop);
293                 taken.add(p);
294                 mProps.put(p.prop, config);
295                 if (mDbg) {
296                     Log.d(TAG, "takeSupportedProperties: " + toHexString(p.prop));
297                 }
298             }
299         }
300         if (mDbg) {
301             Log.d(TAG, "takeSupportedProperties() took " + taken.size() + " properties");
302         }
303         return taken;
304     }
305 
306     @Override
handleHalEvents(List<VehiclePropValue> values)307     public void handleHalEvents(List<VehiclePropValue> values) {
308         PropertyHalListener listener;
309         synchronized (mLock) {
310             listener = mListener;
311         }
312         if (listener != null) {
313             for (VehiclePropValue v : values) {
314                 if (v == null) {
315                     continue;
316                 }
317                 int mgrPropId = halToManagerPropId(v.prop);
318                 if (mgrPropId == NOT_SUPPORTED_PROPERTY) {
319                     Log.e(TAG, "Property is not supported: 0x" + toHexString(v.prop));
320                     continue;
321                 }
322                 CarPropertyValue<?> propVal = toCarPropertyValue(v, mgrPropId);
323                 CarPropertyEvent event = new CarPropertyEvent(
324                         CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, propVal);
325                 if (event != null) {
326                     mEventsToDispatch.add(event);
327                 }
328             }
329             listener.onPropertyChange(mEventsToDispatch);
330             mEventsToDispatch.clear();
331         }
332     }
333 
334     @Override
handlePropertySetError(int property, int area)335     public void handlePropertySetError(int property, int area) {
336         PropertyHalListener listener;
337         synchronized (mLock) {
338             listener = mListener;
339         }
340         if (listener != null) {
341             listener.onPropertySetError(property, area);
342         }
343     }
344 
345     @Override
dump(PrintWriter writer)346     public void dump(PrintWriter writer) {
347         writer.println(TAG);
348         writer.println("  Properties available:");
349         for (CarPropertyConfig prop : mProps.values()) {
350             writer.println("    " + prop.toString());
351         }
352     }
353 }
354