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 */
16package com.android.car.hal;
17
18import static com.android.car.hal.CarPropertyUtils.toCarPropertyValue;
19import static com.android.car.hal.CarPropertyUtils.toVehiclePropValue;
20
21import static java.lang.Integer.toHexString;
22
23import android.annotation.Nullable;
24import android.car.hardware.CarPropertyConfig;
25import android.car.hardware.CarPropertyValue;
26import android.car.hardware.property.CarPropertyEvent;
27import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
28import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
29import android.util.Log;
30import android.util.SparseArray;
31
32import com.android.car.CarLog;
33import com.android.internal.annotations.GuardedBy;
34
35import java.io.PrintWriter;
36import java.util.Collection;
37import java.util.HashSet;
38import java.util.LinkedList;
39import java.util.List;
40import java.util.Map;
41import java.util.Set;
42import 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 */
49public 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     */
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     */
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         */
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         */
104        void onPropertySetError(int property, int area);
105    }
106
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     */
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     */
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
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     */
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
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
181    public String getWritePermission(int propId) {
182        return mPropIds.getWritePermission(propId);
183    }
184
185    /**
186     * Set the property value.
187     * @param prop
188     */
189    public void setProperty(CarPropertyValue prop) {
190        int halPropId = managerToHalPropId(prop.getPropertyId());
191        if (halPropId == NOT_SUPPORTED_PROPERTY) {
192            throw new IllegalArgumentException("Invalid property Id : 0x"
193                    + toHexString(prop.getPropertyId()));
194        }
195        VehiclePropValue halProp = toVehiclePropValue(prop, halPropId);
196        try {
197            mVehicleHal.set(halProp);
198        } catch (PropertyTimeoutException e) {
199            Log.e(CarLog.TAG_PROPERTY, "set, property not ready 0x" + toHexString(halPropId), e);
200            throw new RuntimeException(e);
201        }
202    }
203
204    /**
205     * Subscribe to this property at the specified update rate.
206     * @param propId
207     * @param rate
208     */
209    public void subscribeProperty(int propId, float rate) {
210        if (mDbg) {
211            Log.d(TAG, "subscribeProperty propId=0x" + toHexString(propId) + ", rate=" + rate);
212        }
213        int halPropId = managerToHalPropId(propId);
214        if (halPropId == NOT_SUPPORTED_PROPERTY) {
215            throw new IllegalArgumentException("Invalid property Id : 0x"
216                    + toHexString(propId));
217        }
218        // Validate the min/max rate
219        CarPropertyConfig cfg = mProps.get(propId);
220        if (rate > cfg.getMaxSampleRate()) {
221            rate = cfg.getMaxSampleRate();
222        } else if (rate < cfg.getMinSampleRate()) {
223            rate = cfg.getMinSampleRate();
224        }
225        synchronized (mSubscribedPropIds) {
226            mSubscribedPropIds.add(halPropId);
227        }
228        mVehicleHal.subscribeProperty(this, halPropId, rate);
229    }
230
231    /**
232     * Unsubscribe the property and turn off update events for it.
233     * @param propId
234     */
235    public void unsubscribeProperty(int propId) {
236        if (mDbg) {
237            Log.d(TAG, "unsubscribeProperty propId=0x" + toHexString(propId));
238        }
239        int halPropId = managerToHalPropId(propId);
240        if (halPropId == NOT_SUPPORTED_PROPERTY) {
241            throw new IllegalArgumentException("Invalid property Id : 0x"
242                    + toHexString(propId));
243        }
244        synchronized (mSubscribedPropIds) {
245            if (mSubscribedPropIds.contains(halPropId)) {
246                mSubscribedPropIds.remove(halPropId);
247                mVehicleHal.unsubscribeProperty(this, halPropId);
248            }
249        }
250    }
251
252    @Override
253    public void init() {
254        if (mDbg) {
255            Log.d(TAG, "init()");
256        }
257    }
258
259    @Override
260    public void release() {
261        if (mDbg) {
262            Log.d(TAG, "release()");
263        }
264        synchronized (mSubscribedPropIds) {
265            for (Integer prop : mSubscribedPropIds) {
266                mVehicleHal.unsubscribeProperty(this, prop);
267            }
268            mSubscribedPropIds.clear();
269        }
270        mProps.clear();
271
272        synchronized (mLock) {
273            mListener = null;
274        }
275    }
276
277    @Override
278    public Collection<VehiclePropConfig> takeSupportedProperties(
279            Collection<VehiclePropConfig> allProperties) {
280        List<VehiclePropConfig> taken = new LinkedList<>();
281
282        for (VehiclePropConfig p : allProperties) {
283            if (mPropIds.isSupportedProperty(p.prop)) {
284                CarPropertyConfig config = CarPropertyUtils.toCarPropertyConfig(p, p.prop);
285                taken.add(p);
286                mProps.put(p.prop, config);
287                if (mDbg) {
288                    Log.d(TAG, "takeSupportedProperties: " + toHexString(p.prop));
289                }
290            }
291        }
292        if (mDbg) {
293            Log.d(TAG, "takeSupportedProperties() took " + taken.size() + " properties");
294        }
295        return taken;
296    }
297
298    @Override
299    public void handleHalEvents(List<VehiclePropValue> values) {
300        PropertyHalListener listener;
301        synchronized (mLock) {
302            listener = mListener;
303        }
304        if (listener != null) {
305            for (VehiclePropValue v : values) {
306                int mgrPropId = halToManagerPropId(v.prop);
307                if (mgrPropId == NOT_SUPPORTED_PROPERTY) {
308                    Log.e(TAG, "Property is not supported: 0x" + toHexString(v.prop));
309                    continue;
310                }
311                CarPropertyValue<?> propVal = toCarPropertyValue(v, mgrPropId);
312                CarPropertyEvent event = new CarPropertyEvent(
313                        CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, propVal);
314                if (event != null) {
315                    mEventsToDispatch.add(event);
316                }
317            }
318            listener.onPropertyChange(mEventsToDispatch);
319            mEventsToDispatch.clear();
320        }
321    }
322
323    @Override
324    public void handlePropertySetError(int property, int area) {
325        PropertyHalListener listener;
326        synchronized (mLock) {
327            listener = mListener;
328        }
329        if (listener != null) {
330            listener.onPropertySetError(property, area);
331        }
332    }
333
334    @Override
335    public void dump(PrintWriter writer) {
336        writer.println(TAG);
337        writer.println("  Properties available:");
338        for (CarPropertyConfig prop : mProps.values()) {
339            writer.println("    " + prop.toString());
340        }
341    }
342}
343