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 package android.car.test;
17 
18 import android.car.Car;
19 import android.car.CarNotConnectedException;
20 import android.util.Log;
21 
22 import com.android.car.vehiclenetwork.VehicleNetwork.VehicleNetworkHalMock;
23 import com.android.car.vehiclenetwork.VehicleNetworkConsts;
24 import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehiclePermissionModel;
25 import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehiclePropAccess;
26 import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehiclePropChangeMode;
27 import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleValueType;
28 import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropConfig;
29 import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropConfigs;
30 import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropValue;
31 import com.android.car.vehiclenetwork.VehiclePropValueUtil;
32 
33 import java.lang.reflect.Field;
34 import 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  */
49 public class VehicleHalEmulator {
50     private static final String TAG = VehicleHalEmulator.class.getSimpleName();
51     /**
52      * Interface for handler of each property.
53      */
54     public interface VehicleHalPropertyHandler {
onPropertySet(VehiclePropValue value)55         void onPropertySet(VehiclePropValue value);
onPropertyGet(VehiclePropValue value)56         VehiclePropValue onPropertyGet(VehiclePropValue value);
onPropertySubscribe(int property, float sampleRate, int zones)57         void onPropertySubscribe(int property, float sampleRate, int zones);
onPropertyUnsubscribe(int property)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      */
VehicleHalEmulator(Car car)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      */
addProperty(VehiclePropConfig config, VehicleHalPropertyHandler handler)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      */
addStaticProperty(VehiclePropConfig config, VehiclePropValue value)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      */
removeProperty(int property)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      */
start()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. */
isStarted()127     public synchronized boolean isStarted() {
128         return mStarted;
129     }
130 
131     /**
132      * Stop emulation. should be done before finishing test.
133      */
stop()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      */
injectEvent(VehiclePropValue value)146     public void injectEvent(VehiclePropValue value) {
147         mCarTestManager.injectEvent(value);
148     }
149 
assertPropertyForGet(VehiclePropConfig config, int property)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 
assertPropertyForSet(VehiclePropConfig config, VehiclePropValue value)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 
assertPropertyForSubscribe(VehiclePropConfig config, int property, float sampleRate, int zones)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 
assertProperty(VehiclePropConfig config, int property)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 
populateDefaultPropertiesIfNecessary()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 
handleListProperties()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 
handlePropertySet(VehiclePropValue value)238     private synchronized void handlePropertySet(VehiclePropValue value) {
239         getHalPropertyLocked(value.getProp()).handler.onPropertySet(value);
240     }
241 
handlePropertyGet(VehiclePropValue value)242     private synchronized VehiclePropValue handlePropertyGet(VehiclePropValue value) {
243         return getHalPropertyLocked(value.getProp()).handler.onPropertyGet(value);
244     }
245 
handlePropertySubscribe(int property, float sampleRate, int zones)246     private synchronized void handlePropertySubscribe(int property, float sampleRate, int zones) {
247         getHalPropertyLocked(property).handler.onPropertySubscribe(property, sampleRate, zones);
248     }
249 
handlePropertyUnsubscribe(int property)250     private synchronized void handlePropertyUnsubscribe(int property) {
251         getHalPropertyLocked(property).handler.onPropertyUnsubscribe(property);
252     }
253 
getHalPropertyLocked(int property)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 
VehicleHalProperty(VehiclePropConfig config, VehicleHalPropertyHandler handler)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 
DefaultPropertyHandler(VehiclePropConfig config, VehiclePropValue initialValue)279         public DefaultPropertyHandler(VehiclePropConfig config, VehiclePropValue initialValue) {
280             mConfig = config;
281             mValue = initialValue;
282         }
283 
284         @Override
onPropertySet(VehiclePropValue value)285         public synchronized void onPropertySet(VehiclePropValue value) {
286             assertPropertyForSet(mConfig, value);
287             mValue = value;
288         }
289 
290         @Override
onPropertyGet(VehiclePropValue value)291         public synchronized VehiclePropValue onPropertyGet(VehiclePropValue value) {
292             assertPropertyForGet(mConfig, value.getProp());
293             return mValue;
294         }
295 
296         @Override
onPropertySubscribe(int property, float sampleRate, int zones)297         public synchronized void onPropertySubscribe(int property, float sampleRate, int zones) {
298             assertPropertyForSubscribe(mConfig, property, sampleRate, zones);
299             mSubscribed = true;
300         }
301 
302         @Override
onPropertyUnsubscribe(int property)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
onListProperties()317         public VehiclePropConfigs onListProperties() {
318             return handleListProperties();
319         }
320 
321         @Override
onPropertySet(VehiclePropValue value)322         public void onPropertySet(VehiclePropValue value) {
323             handlePropertySet(value);
324         }
325 
326         @Override
onPropertyGet(VehiclePropValue value)327         public VehiclePropValue onPropertyGet(VehiclePropValue value) {
328             return handlePropertyGet(value);
329         }
330 
331         @Override
onPropertySubscribe(int property, float sampleRate, int zones)332         public void onPropertySubscribe(int property, float sampleRate, int zones) {
333             handlePropertySubscribe(property, sampleRate, zones);
334         }
335 
336         @Override
onPropertyUnsubscribe(int property)337         public void onPropertyUnsubscribe(int property) {
338             handlePropertyUnsubscribe(property);
339         }
340     }
341 }
342