1/*
2 * Copyright (C) 2015 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
17package com.android.car.hal;
18
19import android.os.HandlerThread;
20import android.util.ArraySet;
21import android.util.Log;
22import android.util.SparseArray;
23
24import com.android.car.CarLog;
25import com.android.car.vehiclenetwork.VehicleNetwork;
26import com.android.car.vehiclenetwork.VehicleNetwork.VehicleNetworkListener;
27import com.android.car.vehiclenetwork.VehicleNetworkConsts;
28import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehiclePropAccess;
29import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehiclePropChangeMode;
30import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropConfig;
31import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropConfigs;
32import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropValue;
33import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropValues;
34import com.android.car.vehiclenetwork.VehiclePropValueUtil;
35import com.android.internal.annotations.VisibleForTesting;
36
37import java.io.PrintWriter;
38import java.util.Collection;
39import java.util.HashMap;
40import java.util.LinkedList;
41import java.util.List;
42
43/**
44 * Abstraction for vehicle HAL. This class handles interface with native HAL and do basic parsing
45 * of received data (type check). Then each event is sent to corresponding {@link HalServiceBase}
46 * implementation. It is responsibility of {@link HalServiceBase} to convert data to corresponding
47 * Car*Service for Car*Manager API.
48 */
49public class VehicleHal implements VehicleNetworkListener {
50
51    private static final boolean DBG = true;
52
53    static {
54        createInstance();
55    }
56
57    private static VehicleHal sInstance;
58
59    public static synchronized VehicleHal getInstance() {
60        if (sInstance == null) {
61            createInstance();
62        }
63        return sInstance;
64    }
65
66    private static synchronized void createInstance() {
67        sInstance = new VehicleHal();
68        // init is handled in a separate thread to prevent blocking the calling thread for too
69        // long.
70        sInstance.init();
71    }
72
73    public static synchronized void releaseInstance() {
74        if (sInstance != null) {
75            sInstance.release();
76            sInstance = null;
77        }
78    }
79
80    private final HandlerThread mHandlerThread;
81    private final VehicleNetwork mVehicleNetwork;
82    private final SensorHalService mSensorHal;
83    private final InfoHalService mInfoHal;
84    private final AudioHalService mAudioHal;
85    private final RadioHalService mRadioHal;
86    private final PowerHalService mPowerHal;
87    private final HvacHalService mHvacHal;
88    private final InputHalService mInputHal;
89
90    /** stores handler for each HAL property. Property events are sent to handler. */
91    private final SparseArray<HalServiceBase> mPropertyHandlers = new SparseArray<HalServiceBase>();
92    /** This is for iterating all HalServices with fixed order. */
93    private final HalServiceBase[] mAllServices;
94    private final ArraySet<Integer> mSubscribedProperties = new ArraySet<Integer>();
95    private final HashMap<Integer, VehiclePropConfig> mUnclaimedProperties = new HashMap<>();
96    private final List<VehiclePropConfig> mAllProperties = new LinkedList<>();
97
98    private VehicleHal() {
99        mHandlerThread = new HandlerThread("VEHICLE-HAL");
100        mHandlerThread.start();
101        // passing this should be safe as long as it is just kept and not used in constructor
102        mPowerHal = new PowerHalService(this);
103        mSensorHal = new SensorHalService(this);
104        mInfoHal = new InfoHalService(this);
105        mAudioHal = new AudioHalService(this);
106        mRadioHal = new RadioHalService(this);
107        mHvacHal = new HvacHalService(this);
108        mInputHal = new InputHalService();
109        mAllServices = new HalServiceBase[] {
110                mPowerHal,
111                mAudioHal,
112                mHvacHal,
113                mInfoHal,
114                mSensorHal,
115                mRadioHal,
116                mInputHal
117                };
118        mVehicleNetwork = VehicleNetwork.createVehicleNetwork(this, mHandlerThread.getLooper());
119    }
120
121    /** Dummy version only for testing */
122    @VisibleForTesting
123    public VehicleHal(PowerHalService powerHal, SensorHalService sensorHal, InfoHalService infoHal,
124            AudioHalService audioHal, RadioHalService radioHal, HvacHalService hvacHal,
125            VehicleNetwork vehicleNetwork) {
126        mHandlerThread = null;
127        mPowerHal = powerHal;
128        mSensorHal = sensorHal;
129        mInfoHal = infoHal;
130        mAudioHal = audioHal;
131        mRadioHal = radioHal;
132        mHvacHal = hvacHal;
133        mInputHal = null;
134        mAllServices = null;
135        mVehicleNetwork = vehicleNetwork;
136    }
137
138    private void init() {
139        VehiclePropConfigs properties = mVehicleNetwork.listProperties();
140        // needs copy as getConfigsList gives unmodifiable one.
141        List<VehiclePropConfig> propertiesList =
142                new LinkedList<VehiclePropConfig>(properties.getConfigsList());
143        for (HalServiceBase service: mAllServices) {
144            List<VehiclePropConfig> taken = service.takeSupportedProperties(propertiesList);
145            if (taken == null) {
146                continue;
147            }
148            if (DBG) {
149                Log.i(CarLog.TAG_HAL, "HalService " + service + " take properties " + taken.size());
150            }
151            synchronized (this) {
152                for (VehiclePropConfig p: taken) {
153                    mPropertyHandlers.append(p.getProp(), service);
154                }
155            }
156            propertiesList.removeAll(taken);
157            service.init();
158        }
159        synchronized (this) {
160            for (VehiclePropConfig p: propertiesList) {
161                mUnclaimedProperties.put(p.getProp(), p);
162            }
163            mAllProperties.addAll(properties.getConfigsList());
164        }
165    }
166
167    private void release() {
168        // release in reverse order from init
169        for (int i = mAllServices.length - 1; i >= 0; i--) {
170            mAllServices[i].release();
171        }
172        synchronized (this) {
173            for (int p : mSubscribedProperties) {
174                mVehicleNetwork.unsubscribe(p);
175            }
176            mSubscribedProperties.clear();
177            mUnclaimedProperties.clear();
178            mAllProperties.clear();
179        }
180        // keep the looper thread as should be kept for the whole life cycle.
181    }
182
183    public void startMocking() {
184        reinitHals();
185    }
186
187    public void stopMocking() {
188        reinitHals();
189    }
190
191    private void reinitHals() {
192        release();
193        init();
194    }
195
196    public SensorHalService getSensorHal() {
197        return mSensorHal;
198    }
199
200    public InfoHalService getInfoHal() {
201        return mInfoHal;
202    }
203
204    public AudioHalService getAudioHal() {
205        return mAudioHal;
206    }
207
208    public RadioHalService getRadioHal() {
209        return mRadioHal;
210    }
211
212    public PowerHalService getPowerHal() {
213        return mPowerHal;
214    }
215
216    public HvacHalService getHvacHal() {
217        return mHvacHal;
218    }
219
220    public InputHalService getInputHal() {
221        return mInputHal;
222    }
223
224    private void assertServiceOwnerLocked(HalServiceBase service, int property) {
225        if (service != mPropertyHandlers.get(property)) {
226            throw new IllegalArgumentException("not owned");
227        }
228    }
229
230    /**
231     * Subscribe given property. Only Hal service owning the property can subscribe it.
232     * @param service
233     * @param property
234     * @param samplingRateHz
235     */
236    public void subscribeProperty(HalServiceBase service, int property,
237            float samplingRateHz) throws IllegalArgumentException {
238        synchronized (this) {
239            assertServiceOwnerLocked(service, property);
240            mSubscribedProperties.add(property);
241        }
242        mVehicleNetwork.subscribe(property, samplingRateHz);
243    }
244
245    public void unsubscribeProperty(HalServiceBase service, int property) {
246        synchronized (this) {
247            assertServiceOwnerLocked(service, property);
248            mSubscribedProperties.remove(property);
249        }
250        mVehicleNetwork.unsubscribe(property);
251    }
252
253    public VehicleNetwork getVehicleNetwork() {
254        return mVehicleNetwork;
255    }
256
257    public static boolean isPropertySubscribable(VehiclePropConfig config) {
258        if (config.hasAccess() & VehiclePropAccess.VEHICLE_PROP_ACCESS_READ == 0 ||
259                config.getChangeMode() ==
260                VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_STATIC) {
261            return false;
262        }
263        return true;
264    }
265
266    public static void dumpProperties(PrintWriter writer, Collection<VehiclePropConfig> configs) {
267        for (VehiclePropConfig config : configs) {
268            writer.println("property " +
269                    VehicleNetworkConsts.getVehiclePropertyName(config.getProp()));
270        }
271    }
272
273    private final ArraySet<HalServiceBase> mServicesToDispatch = new ArraySet<HalServiceBase>();
274
275    @Override
276    public void onVehicleNetworkEvents(VehiclePropValues values) {
277        synchronized (this) {
278            for (VehiclePropValue v : values.getValuesList()) {
279                HalServiceBase service = mPropertyHandlers.get(v.getProp());
280                service.getDispatchList().add(v);
281                mServicesToDispatch.add(service);
282            }
283        }
284        for (HalServiceBase s : mServicesToDispatch) {
285            s.handleHalEvents(s.getDispatchList());
286            s.getDispatchList().clear();
287        }
288        mServicesToDispatch.clear();
289    }
290
291    @Override
292    public void onHalError(int errorCode, int property, int operation) {
293        Log.e(CarLog.TAG_HAL, "onHalError, errorCode:" + errorCode +
294                " property:0x" + Integer.toHexString(property) +
295                " operation:" + operation);
296        // TODO propagate per property error to HAL services and handle global error
297    }
298
299    @Override
300    public void onHalRestart(boolean inMocking) {
301        Log.e(CarLog.TAG_HAL, "onHalRestart, inMocking:" + inMocking);
302        // TODO restart things as other components started mocking. For now, ignore.
303    }
304
305    public void dump(PrintWriter writer) {
306        writer.println("**dump HAL services**");
307        for (HalServiceBase service: mAllServices) {
308            service.dump(writer);
309        }
310        writer.println("**All properties**");
311        for (VehiclePropConfig config : mAllProperties) {
312            StringBuilder builder = new StringBuilder();
313            builder.append("Property:" + Integer.toHexString(config.getProp()));
314            builder.append(",access:" + Integer.toHexString(config.getAccess()));
315            builder.append(",changeMode:" + Integer.toHexString(config.getChangeMode()));
316            builder.append(",valueType:" + Integer.toHexString(config.getValueType()));
317            builder.append(",permission:" + Integer.toHexString(config.getPermissionModel()));
318            builder.append(",config:" + Integer.toHexString(config.getConfigArray(0)));
319            builder.append(",fs min:" + config.getSampleRateMin());
320            builder.append(",fs max:" + config.getSampleRateMax());
321            for (int i = 0; i < config.getFloatMaxsCount(); i++) {
322                builder.append(",v min:" + config.getFloatMins(i));
323                builder.append(",v max:" + config.getFloatMaxs(i));
324            }
325            for (int i = 0; i < config.getInt32MaxsCount(); i++) {
326                builder.append(",v min:" + config.getInt32Mins(i));
327                builder.append(",v max:" + config.getInt32Maxs(i));
328            }
329            for (int i = 0; i < config.getInt64MaxsCount(); i++) {
330                builder.append(",v min:" + config.getInt64Mins(i));
331                builder.append(",v max:" + config.getInt64Maxs(i));
332            }
333            writer.println(builder.toString());
334        }
335    }
336}
337