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 */
16package android.car.test;
17
18import android.car.Car;
19import android.car.CarNotConnectedException;
20import android.util.Log;
21
22import com.android.car.vehiclenetwork.VehicleNetwork.VehicleNetworkHalMock;
23import com.android.car.vehiclenetwork.VehicleNetworkConsts;
24import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehiclePermissionModel;
25import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehiclePropAccess;
26import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehiclePropChangeMode;
27import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleValueType;
28import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropConfig;
29import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropConfigs;
30import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropValue;
31import com.android.car.vehiclenetwork.VehiclePropValueUtil;
32
33import java.lang.reflect.Field;
34import java.util.HashMap;
35
36/**
37 * This is for mocking vehicle HAL and testing system's internal behavior.
38 * By default, emulated vehicle HAL will have all properties defined with default values
39 * returned for get call. For interested properties, each test can replace default behavior with
40 * {@link #addProperty(VehiclePropConfig, VehicleHalPropertyHandler)} or
41 * {@link #addStaticProperty(VehiclePropConfig, VehiclePropValue)}.
42 * To test a case where specific property should not be present, test can call
43 * {@link #removeProperty(int)}.
44 *
45 * Adding / removing properties should be done before calling {@link #start()} as the call will
46 * start emulating with properties added / removed up to now.
47 * @hide
48 */
49public class VehicleHalEmulator {
50    private static final String TAG = VehicleHalEmulator.class.getSimpleName();
51    /**
52     * Interface for handler of each property.
53     */
54    public interface VehicleHalPropertyHandler {
55        void onPropertySet(VehiclePropValue value);
56        VehiclePropValue onPropertyGet(VehiclePropValue value);
57        void onPropertySubscribe(int property, float sampleRate, int zones);
58        void onPropertyUnsubscribe(int property);
59    }
60
61    private final HashMap<Integer, VehicleHalProperty> mProperties =
62            new HashMap<>();
63
64    private final CarTestManager mCarTestManager;
65    private final HalMock mMock = new HalMock();
66    private boolean mDefaultPropertiesPopulated = false;
67    private boolean mStarted = false;
68
69    /**
70     * Constructor. Car instance passed should be already connected to car service.
71     * @param car
72     */
73    public VehicleHalEmulator(Car car) {
74        try {
75            mCarTestManager = new CarTestManager(
76                    (CarTestManagerBinderWrapper) car.getCarManager(Car.TEST_SERVICE));
77        } catch (CarNotConnectedException e) {
78            throw new RuntimeException(e);
79        }
80    }
81
82    /**
83     * Add property to mocked vehicle hal.
84     * @param config
85     * @param handler
86     */
87    public synchronized void addProperty(VehiclePropConfig config,
88            VehicleHalPropertyHandler handler) {
89        populateDefaultPropertiesIfNecessary();
90        VehicleHalProperty halProp = new VehicleHalProperty(config, handler);
91        mProperties.put(config.getProp(), halProp);
92    }
93
94    /**
95     * Add static property to mocked vehicle hal.
96     * @param config
97     * @param value
98     */
99    public synchronized void addStaticProperty(VehiclePropConfig config, VehiclePropValue value) {
100        populateDefaultPropertiesIfNecessary();
101        DefaultPropertyHandler handler = new DefaultPropertyHandler(config, value);
102        VehicleHalProperty halProp = new VehicleHalProperty(config, handler);
103        mProperties.put(config.getProp(), halProp);
104    }
105
106    /**
107     * Remove this property from vehicle HAL properties. Emulated vehicle HAL will not have this
108     * property. This is useful to test the case where specific property is not present.
109     * @param property
110     */
111    public synchronized void removeProperty(int property) {
112        populateDefaultPropertiesIfNecessary();
113        mProperties.remove(property);
114    }
115
116    /**
117     * Start emulation. All necessary properties should have been added / removed before this.
118     */
119    public void start() {
120        mCarTestManager.startMocking(mMock, CarTestManager.FLAG_MOCKING_NONE);
121        synchronized (this) {
122            mStarted = true;
123        }
124    }
125
126    /** Whether emulation is started or not. */
127    public synchronized boolean isStarted() {
128        return mStarted;
129    }
130
131    /**
132     * Stop emulation. should be done before finishing test.
133     */
134    public void stop() {
135        mCarTestManager.stopMocking();
136        synchronized (this) {
137            mStarted = false;
138        }
139    }
140
141    /**
142     * Inject given value to VNS which ultimately delivered as HAL event to clients.
143     * This can be used to emulate H/W side change.
144     * @param value
145     */
146    public void injectEvent(VehiclePropValue value) {
147        mCarTestManager.injectEvent(value);
148    }
149
150    public static void assertPropertyForGet(VehiclePropConfig config, int property) {
151        assertProperty(config, property);
152        if ((config.getAccess() & VehiclePropAccess.VEHICLE_PROP_ACCESS_READ) == 0) {
153            throw new IllegalArgumentException("cannot set write-only property 0x" +
154                    Integer.toHexString(config.getProp()));
155        }
156    }
157
158    public static void assertPropertyForSet(VehiclePropConfig config, VehiclePropValue value) {
159        assertProperty(config, value.getProp());
160        if ((config.getAccess() & VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE) == 0) {
161            throw new IllegalArgumentException("cannot set read-only property 0x" +
162                    Integer.toHexString(config.getProp()));
163        }
164    }
165
166    public static void assertPropertyForSubscribe(VehiclePropConfig config, int property,
167            float sampleRate, int zones) {
168        assertPropertyForGet(config, property);
169        if (config.getChangeMode() == VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_STATIC) {
170            throw new IllegalStateException("cannot subscribe static property 0x" +
171                    Integer.toHexString(config.getProp()));
172        }
173    }
174
175    public static void assertProperty(VehiclePropConfig config, int property) {
176        if (config.getProp() != property) {
177            throw new IllegalStateException("Wrong prop, expecting 0x" +
178                    Integer.toHexString(config.getProp()) + " while got 0x" +
179                    Integer.toHexString(property));
180        }
181    }
182
183    private synchronized void populateDefaultPropertiesIfNecessary() {
184        if (mDefaultPropertiesPopulated) {
185            return;
186        }
187        for (Field f : VehicleNetworkConsts.class.getDeclaredFields()) {
188            if (f.getType() == int.class) {
189                int property = 0;
190                try {
191                    property = f.getInt(null);
192                } catch (IllegalAccessException e) {
193                    continue;
194                }
195                int valueType = VehicleNetworkConsts.getVehicleValueType(property);
196                if (valueType == VehicleValueType.VEHICLE_VALUE_TYPE_SHOUD_NOT_USE) {
197                    // invalid property or not a property
198                    continue;
199                }
200                int changeMode = VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_STATIC;
201                int[] changeModes = VehicleNetworkConsts.getVehicleChangeMode(property);
202                if (changeModes != null) {
203                    changeMode = changeModes[0];
204                }
205                int[] accesses = VehicleNetworkConsts.getVehicleAccess(property);
206                if (accesses == null) { // invalid
207                    continue;
208                }
209                VehiclePropConfig config = VehiclePropConfig.newBuilder().
210                        setProp(property).
211                        setAccess(accesses[0]).
212                        setChangeMode(changeMode).
213                        setValueType(valueType).
214                        setPermissionModel(
215                                VehiclePermissionModel.VEHICLE_PERMISSION_NO_RESTRICTION).
216                        addConfigArray(0).
217                        setSampleRateMax(0).
218                        setSampleRateMin(0).
219                        build();
220                VehiclePropValue initialValue = VehiclePropValueUtil.createDummyValue(property,
221                        valueType);
222                DefaultPropertyHandler handler = new DefaultPropertyHandler(config, initialValue);
223                VehicleHalProperty halProp = new VehicleHalProperty(config, handler);
224                mProperties.put(property, halProp);
225            }
226        }
227        mDefaultPropertiesPopulated = true;
228    }
229
230    private synchronized VehiclePropConfigs handleListProperties() {
231        VehiclePropConfigs.Builder builder = VehiclePropConfigs.newBuilder();
232        for (VehicleHalProperty halProp : mProperties.values()) {
233            builder.addConfigs(halProp.config);
234        }
235        return builder.build();
236    }
237
238    private synchronized void handlePropertySet(VehiclePropValue value) {
239        getHalPropertyLocked(value.getProp()).handler.onPropertySet(value);
240    }
241
242    private synchronized VehiclePropValue handlePropertyGet(VehiclePropValue value) {
243        return getHalPropertyLocked(value.getProp()).handler.onPropertyGet(value);
244    }
245
246    private synchronized void handlePropertySubscribe(int property, float sampleRate, int zones) {
247        getHalPropertyLocked(property).handler.onPropertySubscribe(property, sampleRate, zones);
248    }
249
250    private synchronized void handlePropertyUnsubscribe(int property) {
251        getHalPropertyLocked(property).handler.onPropertyUnsubscribe(property);
252    }
253
254    private VehicleHalProperty getHalPropertyLocked(int property) {
255        VehicleHalProperty halProp = mProperties.get(property);
256        if (halProp == null) {
257            IllegalArgumentException e = new IllegalArgumentException();
258            Log.i(TAG, "property not supported:" + Integer.toHexString(property), e);
259            throw e;
260        }
261        return halProp;
262    }
263
264    private static class VehicleHalProperty {
265        public final VehiclePropConfig config;
266        public final VehicleHalPropertyHandler handler;
267
268        public VehicleHalProperty(VehiclePropConfig config, VehicleHalPropertyHandler handler) {
269            this.config = config;
270            this.handler = handler;
271        }
272    }
273
274    private static class DefaultPropertyHandler implements VehicleHalPropertyHandler {
275        private final VehiclePropConfig mConfig;
276        private VehiclePropValue mValue;
277        private boolean mSubscribed = false;
278
279        public DefaultPropertyHandler(VehiclePropConfig config, VehiclePropValue initialValue) {
280            mConfig = config;
281            mValue = initialValue;
282        }
283
284        @Override
285        public synchronized void onPropertySet(VehiclePropValue value) {
286            assertPropertyForSet(mConfig, value);
287            mValue = value;
288        }
289
290        @Override
291        public synchronized VehiclePropValue onPropertyGet(VehiclePropValue value) {
292            assertPropertyForGet(mConfig, value.getProp());
293            return mValue;
294        }
295
296        @Override
297        public synchronized void onPropertySubscribe(int property, float sampleRate, int zones) {
298            assertPropertyForSubscribe(mConfig, property, sampleRate, zones);
299            mSubscribed = true;
300        }
301
302        @Override
303        public synchronized void onPropertyUnsubscribe(int property) {
304            assertProperty(mConfig, property);
305            if (!mSubscribed) {
306                throw new IllegalArgumentException("unsubscibe for not subscribed property 0x" +
307                        Integer.toHexString(property));
308            }
309            mSubscribed = false;
310        }
311
312    }
313
314    private class HalMock implements VehicleNetworkHalMock {
315
316        @Override
317        public VehiclePropConfigs onListProperties() {
318            return handleListProperties();
319        }
320
321        @Override
322        public void onPropertySet(VehiclePropValue value) {
323            handlePropertySet(value);
324        }
325
326        @Override
327        public VehiclePropValue onPropertyGet(VehiclePropValue value) {
328            return handlePropertyGet(value);
329        }
330
331        @Override
332        public void onPropertySubscribe(int property, float sampleRate, int zones) {
333            handlePropertySubscribe(property, sampleRate, zones);
334        }
335
336        @Override
337        public void onPropertyUnsubscribe(int property) {
338            handlePropertyUnsubscribe(property);
339        }
340    }
341}
342