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.toMixedCarPropertyValue;
20 import static com.android.car.hal.CarPropertyUtils.toMixedVehiclePropValue;
21 import static com.android.car.hal.CarPropertyUtils.toVehiclePropValue;
22 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
23 
24 import static java.lang.Integer.toHexString;
25 
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.car.VehiclePropertyIds;
29 import android.car.hardware.CarPropertyConfig;
30 import android.car.hardware.CarPropertyValue;
31 import android.car.hardware.property.CarPropertyEvent;
32 import android.car.hardware.property.CarPropertyManager;
33 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
34 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
35 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
36 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyType;
37 import android.os.Build;
38 import android.os.ServiceSpecificException;
39 import android.util.Pair;
40 import android.util.Slog;
41 import android.util.SparseArray;
42 
43 import com.android.car.CarLog;
44 import com.android.car.CarServiceUtils;
45 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
46 import com.android.internal.annotations.GuardedBy;
47 
48 import java.io.PrintWriter;
49 import java.util.Collection;
50 import java.util.HashSet;
51 import java.util.LinkedList;
52 import java.util.List;
53 import java.util.Map;
54 import java.util.Set;
55 
56 /**
57  * Common interface for HAL services that send Vehicle Properties back and forth via ICarProperty.
58  * Services that communicate by passing vehicle properties back and forth via ICarProperty should
59  * extend this class.
60  */
61 public class PropertyHalService extends HalServiceBase {
62     private final boolean mDbg = true;
63     private final LinkedList<CarPropertyEvent> mEventsToDispatch = new LinkedList<>();
64     // Use SparseArray to save memory.
65     @GuardedBy("mLock")
66     private final SparseArray<CarPropertyConfig<?>> mMgrPropIdToCarPropConfig = new SparseArray<>();
67     @GuardedBy("mLock")
68     private final SparseArray<VehiclePropConfig> mHalPropIdToVehiclePropConfig =
69             new SparseArray<>();
70     @GuardedBy("mLock")
71     private final SparseArray<Pair<String, String>> mMgrPropIdToPermissions = new SparseArray<>();
72     // Only contains propId if the property Id is different in HAL and manager
73     private static final Map<Integer, Integer> PROPERTY_ID_HAL_TO_MANAGER = Map.of(
74             VehicleProperty.VEHICLE_SPEED_DISPLAY_UNITS,
75             VehiclePropertyIds.VEHICLE_SPEED_DISPLAY_UNITS);
76     // Only contains propId if the property Id is different in HAL and manager
77     private static final Map<Integer, Integer> PROPERTY_ID_MANAGER_TO_HAL = Map.of(
78             VehiclePropertyIds.VEHICLE_SPEED_DISPLAY_UNITS,
79             VehicleProperty.VEHICLE_SPEED_DISPLAY_UNITS);
80     private static final String TAG = CarLog.tagFor(PropertyHalService.class);
81     private final VehicleHal mVehicleHal;
82     private final PropertyHalServiceIds mPropIds;
83 
84     @GuardedBy("mLock")
85     private PropertyHalListener mListener;
86     @GuardedBy("mLock")
87     private Set<Integer> mSubscribedHalPropIds;
88 
89     private final Object mLock = new Object();
90 
91     /**
92      * Converts manager property ID to Vehicle HAL property ID.
93      */
managerToHalPropId(int mgrPropId)94     private static int managerToHalPropId(int mgrPropId) {
95         return PROPERTY_ID_MANAGER_TO_HAL.getOrDefault(mgrPropId, mgrPropId);
96     }
97 
98     /**
99      * Converts Vehicle HAL property ID to manager property ID.
100      */
halToManagerPropId(int halPropId)101     private static int halToManagerPropId(int halPropId) {
102         return PROPERTY_ID_HAL_TO_MANAGER.getOrDefault(halPropId, halPropId);
103     }
104 
105     // Checks if the property exists in this VHAL before calling methods in IVehicle.
isPropertySupportedInVehicle(int halPropId)106     private boolean isPropertySupportedInVehicle(int halPropId) {
107         return mHalPropIdToVehiclePropConfig.contains(halPropId);
108     }
109 
110     /**
111      * PropertyHalListener used to send events to CarPropertyService
112      */
113     public interface PropertyHalListener {
114         /**
115          * This event is sent whenever the property value is updated
116          * @param events
117          */
onPropertyChange(List<CarPropertyEvent> events)118         void onPropertyChange(List<CarPropertyEvent> events);
119         /**
120          * This event is sent when the set property call fails
121          * @param property
122          * @param area
123          */
onPropertySetError(int property, int area, @CarPropertyManager.CarSetPropertyErrorCode int errorCode)124         void onPropertySetError(int property, int area,
125                 @CarPropertyManager.CarSetPropertyErrorCode int errorCode);
126 
127     }
128 
PropertyHalService(VehicleHal vehicleHal)129     public PropertyHalService(VehicleHal vehicleHal) {
130         mPropIds = new PropertyHalServiceIds();
131         mSubscribedHalPropIds = new HashSet<Integer>();
132         mVehicleHal = vehicleHal;
133         if (mDbg) {
134             Slog.d(TAG, "started PropertyHalService");
135         }
136     }
137 
138     /**
139      * Set the listener for the HAL service
140      * @param listener
141      */
setListener(PropertyHalListener listener)142     public void setListener(PropertyHalListener listener) {
143         synchronized (mLock) {
144             mListener = listener;
145         }
146     }
147 
148     /**
149      *
150      * @return SparseArray<CarPropertyConfig> List of configs available.
151      */
getPropertyList()152     public SparseArray<CarPropertyConfig<?>> getPropertyList() {
153         if (mDbg) {
154             Slog.d(TAG, "getPropertyList");
155         }
156         synchronized (mLock) {
157             if (mMgrPropIdToCarPropConfig.size() == 0) {
158                 for (int i = 0; i < mHalPropIdToVehiclePropConfig.size(); i++) {
159                     VehiclePropConfig p = mHalPropIdToVehiclePropConfig.valueAt(i);
160                     int mgrPropId = halToManagerPropId(p.prop);
161                     CarPropertyConfig config = CarPropertyUtils.toCarPropertyConfig(p, mgrPropId);
162                     mMgrPropIdToCarPropConfig.put(mgrPropId, config);
163                 }
164             }
165         }
166         return mMgrPropIdToCarPropConfig;
167     }
168 
169     /**
170      * Returns property or null if property is not ready yet.
171      * @param mgrPropId property id in {@link VehiclePropertyIds}
172      * @param areaId area id
173      * @throws ServiceSpecificException if there is an exception in HAL.
174      */
175     @Nullable
getProperty(int mgrPropId, int areaId)176     public CarPropertyValue getProperty(int mgrPropId, int areaId) throws ServiceSpecificException {
177         int halPropId = managerToHalPropId(mgrPropId);
178         if (!isPropertySupportedInVehicle(halPropId)) {
179             throw new IllegalArgumentException("Invalid property Id : 0x" + toHexString(mgrPropId));
180         }
181 
182         // CarPropertyManager catches and rethrows exception, no need to handle here.
183         VehiclePropValue value = mVehicleHal.get(halPropId, areaId);
184         if (value == null) {
185             return null;
186         }
187         if (isMixedTypeProperty(halPropId)) {
188             VehiclePropConfig propConfig;
189             synchronized (mLock) {
190                 propConfig = mHalPropIdToVehiclePropConfig.get(halPropId);
191             }
192             boolean containStringType = propConfig.configArray.get(0) == 1;
193             boolean containBooleanType = propConfig.configArray.get(1) == 1;
194             return toMixedCarPropertyValue(value, mgrPropId, containBooleanType, containStringType);
195         }
196         return toCarPropertyValue(value, mgrPropId);
197     }
198 
199     /**
200      * Return property or null if property is not ready yet or there is an exception in HAL.
201      */
202     @Nullable
getPropertySafe(int mgrPropId, int areaId)203     public CarPropertyValue getPropertySafe(int mgrPropId, int areaId) {
204         try {
205             return getProperty(mgrPropId, areaId);
206         } catch (Exception e) {
207             Slog.e(TAG, "get property value failed for property id: 0x "
208                     + toHexString(mgrPropId) + " area id: 0x" + toHexString(areaId)
209                     + " exception: " + e);
210             return null;
211         }
212     }
213 
214     /**
215      * Returns sample rate for the property
216      * @param mgrPropId
217      */
getSampleRate(int mgrPropId)218     public float getSampleRate(int mgrPropId) {
219         int halPropId = managerToHalPropId(mgrPropId);
220         if (!isPropertySupportedInVehicle(halPropId)) {
221             throw new IllegalArgumentException("Invalid property Id : 0x" + toHexString(mgrPropId));
222         }
223         return mVehicleHal.getSampleRate(halPropId);
224     }
225 
226     /**
227      * Get the read permission string for the property.
228      * @param mgrPropId
229      */
230     @Nullable
getReadPermission(int mgrPropId)231     public String getReadPermission(int mgrPropId) {
232         int halPropId = managerToHalPropId(mgrPropId);
233         return mPropIds.getReadPermission(halPropId);
234     }
235 
236     /**
237      * Get the write permission string for the property.
238      * @param mgrPropId
239      */
240     @Nullable
getWritePermission(int mgrPropId)241     public String getWritePermission(int mgrPropId) {
242         int halPropId = managerToHalPropId(mgrPropId);
243         return mPropIds.getWritePermission(halPropId);
244     }
245 
246     /**
247      * Get permissions for all properties in the vehicle.
248      * @return a SparseArray. key: propertyId, value: Pair(readPermission, writePermission).
249      */
250     @NonNull
getPermissionsForAllProperties()251     public SparseArray<Pair<String, String>> getPermissionsForAllProperties() {
252         synchronized (mLock) {
253             if (mMgrPropIdToPermissions.size() != 0) {
254                 return mMgrPropIdToPermissions;
255             }
256             for (int i = 0; i < mHalPropIdToVehiclePropConfig.size(); i++) {
257                 int halPropId = mHalPropIdToVehiclePropConfig.keyAt(i);
258                 mMgrPropIdToPermissions.put(halToManagerPropId(halPropId),
259                         new Pair<>(mPropIds.getReadPermission(halPropId),
260                                 mPropIds.getWritePermission(halPropId)));
261             }
262         }
263         return mMgrPropIdToPermissions;
264     }
265 
266     /**
267      * Return true if property is a display_units property
268      * @param mgrPropId
269      */
isDisplayUnitsProperty(int mgrPropId)270     public boolean isDisplayUnitsProperty(int mgrPropId) {
271         int halPropId = managerToHalPropId(mgrPropId);
272         return mPropIds.isPropertyToChangeUnits(halPropId);
273     }
274 
275     /**
276      * Set the property value.
277      * @param prop
278      */
setProperty(CarPropertyValue prop)279     public void setProperty(CarPropertyValue prop) {
280         int halPropId = managerToHalPropId(prop.getPropertyId());
281         if (!isPropertySupportedInVehicle(halPropId)) {
282             throw new IllegalArgumentException("Invalid property Id : 0x"
283                     + toHexString(prop.getPropertyId()));
284         }
285 
286         VehiclePropValue halProp;
287         if (isMixedTypeProperty(halPropId)) {
288             // parse mixed type property value.
289             VehiclePropConfig propConfig;
290             synchronized (mLock) {
291                 propConfig = mHalPropIdToVehiclePropConfig.get(prop.getPropertyId());
292             }
293             int[] configArray = propConfig.configArray.stream().mapToInt(i->i).toArray();
294             halProp = toMixedVehiclePropValue(prop, halPropId, configArray);
295         } else {
296             halProp = toVehiclePropValue(prop, halPropId);
297         }
298         // CarPropertyManager catches and rethrows exception, no need to handle here.
299         mVehicleHal.set(halProp);
300     }
301 
302     /**
303      * Subscribe to this property at the specified update rate.
304      * @param mgrPropId
305      * @param rate
306      */
subscribeProperty(int mgrPropId, float rate)307     public void subscribeProperty(int mgrPropId, float rate) {
308         if (mDbg) {
309             Slog.d(TAG, "subscribeProperty propId=0x" + toHexString(mgrPropId) + ", rate=" + rate);
310         }
311         int halPropId = managerToHalPropId(mgrPropId);
312         if (!isPropertySupportedInVehicle(halPropId)) {
313             throw new IllegalArgumentException("Invalid property Id : 0x"
314                     + toHexString(mgrPropId));
315         }
316         synchronized (mLock) {
317             VehiclePropConfig cfg = mHalPropIdToVehiclePropConfig.get(halPropId);
318             if (rate > cfg.maxSampleRate) {
319                 rate = cfg.maxSampleRate;
320             } else if (rate < cfg.minSampleRate) {
321                 rate = cfg.minSampleRate;
322             }
323             mSubscribedHalPropIds.add(halPropId);
324         }
325 
326         mVehicleHal.subscribeProperty(this, halPropId, rate);
327     }
328 
329     /**
330      * Unsubscribe the property and turn off update events for it.
331      * @param mgrPropId
332      */
unsubscribeProperty(int mgrPropId)333     public void unsubscribeProperty(int mgrPropId) {
334         if (mDbg) {
335             Slog.d(TAG, "unsubscribeProperty propId=0x" + toHexString(mgrPropId));
336         }
337         int halPropId = managerToHalPropId(mgrPropId);
338         if (!isPropertySupportedInVehicle(halPropId)) {
339             throw new IllegalArgumentException("Invalid property Id : 0x"
340                     + toHexString(mgrPropId));
341         }
342         synchronized (mLock) {
343             if (mSubscribedHalPropIds.contains(halPropId)) {
344                 mSubscribedHalPropIds.remove(halPropId);
345                 mVehicleHal.unsubscribeProperty(this, halPropId);
346             }
347         }
348     }
349 
350     @Override
init()351     public void init() {
352         if (mDbg) {
353             Slog.d(TAG, "init()");
354         }
355     }
356 
357     @Override
release()358     public void release() {
359         if (mDbg) {
360             Slog.d(TAG, "release()");
361         }
362         synchronized (mLock) {
363             for (Integer halProp : mSubscribedHalPropIds) {
364                 mVehicleHal.unsubscribeProperty(this, halProp);
365             }
366             mSubscribedHalPropIds.clear();
367             mHalPropIdToVehiclePropConfig.clear();
368             mMgrPropIdToCarPropConfig.clear();
369             mMgrPropIdToPermissions.clear();
370             mListener = null;
371         }
372     }
373 
374     @Override
isSupportedProperty(int propId)375     public boolean isSupportedProperty(int propId) {
376         return mPropIds.isSupportedProperty(propId);
377     }
378 
379     @Override
getAllSupportedProperties()380     public int[] getAllSupportedProperties() {
381         return CarServiceUtils.EMPTY_INT_ARRAY;
382     }
383 
384     // The method is called in HAL init(). Avoid handling complex things in here.
385     @Override
takeProperties(Collection<VehiclePropConfig> allProperties)386     public void takeProperties(Collection<VehiclePropConfig> allProperties) {
387         for (VehiclePropConfig p : allProperties) {
388             if (mPropIds.isSupportedProperty(p.prop)) {
389                 synchronized (mLock) {
390                     mHalPropIdToVehiclePropConfig.put(p.prop, p);
391                 }
392                 if (mDbg) {
393                     Slog.d(TAG, "takeSupportedProperties: " + toHexString(p.prop));
394                 }
395             }
396         }
397         if (mDbg) {
398             Slog.d(TAG, "takeSupportedProperties() took " + allProperties.size()
399                     + " properties");
400         }
401         // If vehicle hal support to select permission for vendor properties.
402         VehiclePropConfig customizePermission;
403         synchronized (mLock) {
404             customizePermission = mHalPropIdToVehiclePropConfig.get(
405                     VehicleProperty.SUPPORT_CUSTOMIZE_VENDOR_PERMISSION);
406         }
407         if (customizePermission != null) {
408             mPropIds.customizeVendorPermission(customizePermission.configArray);
409         }
410     }
411 
412     @Override
onHalEvents(List<VehiclePropValue> values)413     public void onHalEvents(List<VehiclePropValue> values) {
414         PropertyHalListener listener;
415         synchronized (mLock) {
416             listener = mListener;
417         }
418         if (listener != null) {
419             for (VehiclePropValue v : values) {
420                 if (v == null) {
421                     continue;
422                 }
423                 if (!isPropertySupportedInVehicle(v.prop)) {
424                     Slog.e(TAG, "Property is not supported: 0x" + toHexString(v.prop));
425                     continue;
426                 }
427                 // Check payload if it is a userdebug build.
428                 if (Build.IS_DEBUGGABLE && !mPropIds.checkPayload(v)) {
429                     Slog.e(TAG, "Drop event for property: " + v + " because it is failed "
430                             + "in payload checking.");
431                     continue;
432                 }
433                 int mgrPropId = halToManagerPropId(v.prop);
434                 CarPropertyValue<?> propVal;
435                 if (isMixedTypeProperty(v.prop)) {
436                     // parse mixed type property value.
437                     VehiclePropConfig propConfig;
438                     synchronized (mLock) {
439                         propConfig = mHalPropIdToVehiclePropConfig.get(v.prop);
440                     }
441                     boolean containStringType = propConfig.configArray.get(0) == 1;
442                     boolean containBooleanType = propConfig.configArray.get(1) == 1;
443                     propVal = toMixedCarPropertyValue(v, mgrPropId, containBooleanType,
444                             containStringType);
445                 } else {
446                     propVal = toCarPropertyValue(v, mgrPropId);
447                 }
448                 CarPropertyEvent event = new CarPropertyEvent(
449                         CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, propVal);
450                 mEventsToDispatch.add(event);
451             }
452             listener.onPropertyChange(mEventsToDispatch);
453             mEventsToDispatch.clear();
454         }
455     }
456 
457     @Override
onPropertySetError(int halPropId, int area, @CarPropertyManager.CarSetPropertyErrorCode int errorCode)458     public void onPropertySetError(int halPropId, int area,
459             @CarPropertyManager.CarSetPropertyErrorCode int errorCode) {
460         PropertyHalListener listener;
461         synchronized (mLock) {
462             listener = mListener;
463         }
464         if (listener != null) {
465             int mgrPropId = halToManagerPropId(halPropId);
466             listener.onPropertySetError(mgrPropId, area, errorCode);
467         }
468     }
469 
470     @Override
471     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(PrintWriter writer)472     public void dump(PrintWriter writer) {
473         writer.println(TAG);
474         writer.println("  Properties available:");
475         synchronized (mLock) {
476             for (int i = 0; i < mHalPropIdToVehiclePropConfig.size(); i++) {
477                 VehiclePropConfig p = mHalPropIdToVehiclePropConfig.valueAt(i);
478                 writer.println("    " + p);
479             }
480         }
481     }
482 
isMixedTypeProperty(int halPropId)483     private static boolean isMixedTypeProperty(int halPropId) {
484         return (halPropId & VehiclePropertyType.MASK) == VehiclePropertyType.MIXED;
485     }
486 }
487