1/*
2 * Copyright (C) 2016 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.vehiclehal.test;
18
19import static java.lang.Integer.toHexString;
20import static junit.framework.Assert.assertEquals;
21import static junit.framework.Assert.assertNotNull;
22import static junit.framework.Assert.fail;
23
24import android.hardware.automotive.vehicle.V2_0.IVehicle;
25import android.hardware.automotive.vehicle.V2_0.IVehicleCallback;
26import android.hardware.automotive.vehicle.V2_0.StatusCode;
27import android.hardware.automotive.vehicle.V2_0.SubscribeOptions;
28import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
29import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
30import android.hardware.automotive.vehicle.V2_0.VehiclePropertyAccess;
31import android.os.RemoteException;
32import android.os.SystemClock;
33
34import com.google.android.collect.Lists;
35
36import java.util.ArrayList;
37import java.util.HashMap;
38import java.util.List;
39import java.util.Map;
40
41/**
42 * Mocked implementation of {@link IVehicle}.
43 */
44public class MockedVehicleHal extends IVehicle.Stub {
45    /**
46     * Interface for handler of each property.
47     */
48    public interface VehicleHalPropertyHandler {
49        default void onPropertySet(VehiclePropValue value) {}
50        default VehiclePropValue onPropertyGet(VehiclePropValue value) { return null; }
51        default void onPropertySubscribe(int property, int zones, float sampleRate) {}
52        default void onPropertyUnsubscribe(int property) {}
53
54        VehicleHalPropertyHandler NOP = new VehicleHalPropertyHandler() {};
55    }
56
57    private final Map<Integer, VehicleHalPropertyHandler> mPropertyHandlerMap = new HashMap<>();
58    private final Map<Integer, VehiclePropConfig> mConfigs = new HashMap<>();
59    private final Map<Integer, List<IVehicleCallback>> mSubscribers = new HashMap<>();
60
61    public synchronized void addProperties(VehiclePropConfig... configs) {
62        for (VehiclePropConfig config : configs) {
63            addProperty(config, new DefaultPropertyHandler(config, null));
64        }
65    }
66
67    public synchronized void addProperty(VehiclePropConfig config,
68            VehicleHalPropertyHandler handler) {
69        mPropertyHandlerMap.put(config.prop, handler);
70        mConfigs.put(config.prop, config);
71    }
72
73    public synchronized void addStaticProperty(VehiclePropConfig config,
74            VehiclePropValue value) {
75        addProperty(config, new StaticPropertyHandler(value));
76    }
77
78    public boolean waitForSubscriber(int propId, long timeoutMillis) {
79        long startTime = SystemClock.elapsedRealtime();
80        try {
81            synchronized (this) {
82                while (mSubscribers.get(propId) == null) {
83                    long waitMillis = startTime - SystemClock.elapsedRealtime() + timeoutMillis;
84                    if (waitMillis < 0) break;
85                    wait(waitMillis);
86                }
87
88                return mSubscribers.get(propId) != null;
89            }
90        } catch (InterruptedException e) {
91            return false;
92        }
93    }
94
95    public synchronized void injectEvent(VehiclePropValue value) {
96        List<IVehicleCallback> callbacks = mSubscribers.get(value.prop);
97        assertNotNull("Injecting event failed for property: " + value.prop
98                        + ". No listeners found", callbacks);
99        for (IVehicleCallback callback : callbacks) {
100            try {
101                callback.onPropertyEvent(Lists.newArrayList(value));
102            } catch (RemoteException e) {
103                e.printStackTrace();
104                fail("Remote exception while injecting events.");
105            }
106        }
107    }
108
109    public synchronized void injectError(int errorCode, int propertyId, int areaId) {
110        List<IVehicleCallback> callbacks = mSubscribers.get(propertyId);
111        assertNotNull("Injecting error failed for property: " + propertyId
112                        + ". No listeners found", callbacks);
113        for (IVehicleCallback callback : callbacks) {
114            try {
115                callback.onPropertySetError(errorCode, propertyId, areaId);
116            } catch (RemoteException e) {
117                e.printStackTrace();
118                fail("Remote exception while injecting errors.");
119            }
120        }
121    }
122
123    @Override
124    public synchronized ArrayList<VehiclePropConfig> getAllPropConfigs() {
125        return new ArrayList<>(mConfigs.values());
126    }
127
128    @Override
129    public synchronized void getPropConfigs(ArrayList<Integer> props, getPropConfigsCallback cb) {
130        ArrayList<VehiclePropConfig> res = new ArrayList<>();
131        for (Integer prop : props) {
132            VehiclePropConfig config = mConfigs.get(prop);
133            if (config == null) {
134                cb.onValues(StatusCode.INVALID_ARG, new ArrayList<>());
135                return;
136            }
137            res.add(config);
138        }
139        cb.onValues(StatusCode.OK, res);
140    }
141
142    @Override
143    public synchronized void get(VehiclePropValue requestedPropValue, getCallback cb) {
144        VehicleHalPropertyHandler handler = mPropertyHandlerMap.get(requestedPropValue.prop);
145        if (handler == null) {
146            cb.onValues(StatusCode.INVALID_ARG, null);
147        } else {
148            cb.onValues(StatusCode.OK, handler.onPropertyGet(requestedPropValue));
149        }
150    }
151
152    @Override
153    public synchronized int set(VehiclePropValue propValue) {
154        VehicleHalPropertyHandler handler = mPropertyHandlerMap.get(propValue.prop);
155        if (handler == null) {
156            return StatusCode.INVALID_ARG;
157        } else {
158            handler.onPropertySet(propValue);
159            return StatusCode.OK;
160        }
161    }
162
163    @Override
164    public synchronized int subscribe(IVehicleCallback callback,
165            ArrayList<SubscribeOptions> options) {
166        for (SubscribeOptions opt : options) {
167            VehicleHalPropertyHandler handler = mPropertyHandlerMap.get(opt.propId);
168            if (handler == null) {
169                return StatusCode.INVALID_ARG;
170            }
171
172            handler.onPropertySubscribe(opt.propId, opt.vehicleAreas, opt.sampleRate);
173            List<IVehicleCallback>  subscribers = mSubscribers.get(opt.propId);
174            if (subscribers == null) {
175                subscribers = new ArrayList<>();
176                mSubscribers.put(opt.propId, subscribers);
177                notifyAll();
178            }
179            subscribers.add(callback);
180        }
181        return StatusCode.OK;
182    }
183
184    @Override
185    public synchronized int unsubscribe(IVehicleCallback callback, int propId) {
186        VehicleHalPropertyHandler handler = mPropertyHandlerMap.get(propId);
187        if (handler == null) {
188            return StatusCode.INVALID_ARG;
189        }
190
191        handler.onPropertyUnsubscribe(propId);
192        List<IVehicleCallback>  subscribers = mSubscribers.get(propId);
193        if (subscribers != null) {
194            subscribers.remove(callback);
195            if (subscribers.size() == 0) {
196                mSubscribers.remove(propId);
197            }
198        }
199        return StatusCode.OK;
200    }
201
202    @Override
203    public String debugDump() {
204        return null;
205    }
206
207    public static class FailingPropertyHandler implements VehicleHalPropertyHandler {
208        @Override
209        public void onPropertySet(VehiclePropValue value) {
210            fail("Unexpected onPropertySet call");
211        }
212
213        @Override
214        public VehiclePropValue onPropertyGet(VehiclePropValue value) {
215            fail("Unexpected onPropertyGet call");
216            return null;
217        }
218
219        @Override
220        public void onPropertySubscribe(int property, int zones, float sampleRate) {
221            fail("Unexpected onPropertySubscribe call");
222        }
223
224        @Override
225        public void onPropertyUnsubscribe(int property) {
226            fail("Unexpected onPropertyUnsubscribe call");
227        }
228    }
229
230    public static class StaticPropertyHandler extends FailingPropertyHandler {
231        private final VehiclePropValue mValue;
232
233        public StaticPropertyHandler(VehiclePropValue value) {
234            mValue = value;
235        }
236
237        @Override
238        public synchronized VehiclePropValue onPropertyGet(VehiclePropValue value) {
239            return mValue;
240        }
241    }
242
243    public static class DefaultPropertyHandler implements VehicleHalPropertyHandler {
244        private final VehiclePropConfig mConfig;
245        private VehiclePropValue mValue;
246        private boolean mSubscribed = false;
247
248        public DefaultPropertyHandler(VehiclePropConfig config, VehiclePropValue initialValue) {
249            mConfig = config;
250            mValue = initialValue;
251        }
252
253        @Override
254        public synchronized void onPropertySet(VehiclePropValue value) {
255            assertEquals(mConfig.prop, value.prop);
256            assertEquals(VehiclePropertyAccess.WRITE, mConfig.access & VehiclePropertyAccess.WRITE);
257            mValue = value;
258        }
259
260        @Override
261        public synchronized VehiclePropValue onPropertyGet(VehiclePropValue value) {
262            assertEquals(mConfig.prop, value.prop);
263            assertEquals(VehiclePropertyAccess.READ, mConfig.access & VehiclePropertyAccess.READ);
264            return mValue;
265        }
266
267        @Override
268        public synchronized void onPropertySubscribe(int property, int zones, float sampleRate) {
269            assertEquals(mConfig.prop, property);
270            mSubscribed = true;
271        }
272
273        @Override
274        public synchronized void onPropertyUnsubscribe(int property) {
275            assertEquals(mConfig.prop, property);
276            if (!mSubscribed) {
277                throw new IllegalArgumentException("Property was not subscribed 0x"
278                        + toHexString( property));
279            }
280            mSubscribed = false;
281        }
282    }
283}
284