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 
17 package com.android.car.hal.test;
18 
19 import static junit.framework.Assert.assertEquals;
20 import static junit.framework.Assert.assertNotNull;
21 import static junit.framework.Assert.fail;
22 
23 import static java.lang.Integer.toHexString;
24 
25 import android.hardware.automotive.vehicle.V2_0.IVehicle;
26 import android.hardware.automotive.vehicle.V2_0.IVehicleCallback;
27 import android.hardware.automotive.vehicle.V2_0.StatusCode;
28 import android.hardware.automotive.vehicle.V2_0.SubscribeOptions;
29 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
30 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
31 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyAccess;
32 import android.os.RemoteException;
33 import android.os.ServiceSpecificException;
34 import android.os.SystemClock;
35 import android.util.Log;
36 
37 import com.android.internal.annotations.GuardedBy;
38 
39 import java.util.ArrayList;
40 import java.util.HashMap;
41 import java.util.List;
42 import java.util.Map;
43 
44 import javax.annotation.concurrent.NotThreadSafe;
45 
46 /**
47  * Mocked implementation of {@link IVehicle}.
48  */
49 public class HidlMockedVehicleHal extends IVehicle.Stub {
50     private static final String TAG = HidlMockedVehicleHal.class.getSimpleName();
51 
52     private final Object mLock = new Object();
53 
54     /**
55      * Interface for handler of each property.
56      */
57     public interface VehicleHalPropertyHandler {
onPropertySet(VehiclePropValue value)58         default void onPropertySet(VehiclePropValue value) {}
59 
onPropertyGet(VehiclePropValue value)60         default VehiclePropValue onPropertyGet(VehiclePropValue value) {
61             return null;
62         }
63 
onPropertySubscribe(int property, float sampleRate)64         default void onPropertySubscribe(int property, float sampleRate) {}
65 
onPropertyUnsubscribe(int property)66         default void onPropertyUnsubscribe(int property) {}
67 
68         VehicleHalPropertyHandler NOP = new VehicleHalPropertyHandler() {};
69     }
70 
71     @GuardedBy("mLock")
72     private final Map<Integer, VehicleHalPropertyHandler> mPropertyHandlerMap = new HashMap<>();
73 
74     @GuardedBy("mLock")
75     private final Map<Integer, VehiclePropConfig> mConfigs = new HashMap<>();
76 
77     @GuardedBy("mLock")
78     private final Map<Integer, List<IVehicleCallback>> mSubscribers = new HashMap<>();
79 
addProperties(VehiclePropConfig... configs)80     public void addProperties(VehiclePropConfig... configs) {
81         synchronized (mLock) {
82             for (VehiclePropConfig config : configs) {
83                 addProperty(config, new DefaultPropertyHandler(config, null));
84             }
85         }
86     }
87 
addProperty(VehiclePropConfig config, VehicleHalPropertyHandler handler)88     public void addProperty(VehiclePropConfig config, VehicleHalPropertyHandler handler) {
89         synchronized (mLock) {
90             mPropertyHandlerMap.put(config.prop, handler);
91             mConfigs.put(config.prop, config);
92         }
93     }
94 
addStaticProperty(VehiclePropConfig config, VehiclePropValue value)95     public void addStaticProperty(VehiclePropConfig config, VehiclePropValue value) {
96         synchronized (mLock) {
97             addProperty(config, new StaticPropertyHandler(value));
98         }
99     }
100 
waitForSubscriber(int propId, long timeoutMillis)101     public boolean waitForSubscriber(int propId, long timeoutMillis) {
102         long startTime = SystemClock.elapsedRealtime();
103         try {
104             synchronized (mLock) {
105                 while (mSubscribers.get(propId) == null) {
106                     long waitMillis = startTime - SystemClock.elapsedRealtime() + timeoutMillis;
107                     if (waitMillis < 0) break;
108                     wait(waitMillis);
109                 }
110                 return mSubscribers.get(propId) != null;
111             }
112         } catch (InterruptedException e) {
113             Thread.currentThread().interrupt();
114             return false;
115         }
116     }
117 
injectEvent(VehiclePropValue value, boolean setProperty)118     public void injectEvent(VehiclePropValue value, boolean setProperty) {
119         synchronized (mLock) {
120             List<IVehicleCallback> callbacks = mSubscribers.get(value.prop);
121             assertNotNull("Injecting event failed for property: " + value.prop
122                     + ". No listeners found", callbacks);
123 
124             if (setProperty) {
125                 // Update property if requested
126                 VehicleHalPropertyHandler handler = mPropertyHandlerMap.get(value.prop);
127                 if (handler != null) {
128                     handler.onPropertySet(value);
129                 }
130             }
131 
132             for (int i = 0; i < callbacks.size(); i++) {
133                 IVehicleCallback callback = callbacks.get(i);
134                 try {
135                     ArrayList<VehiclePropValue> values = new ArrayList<>(1);
136                     values.add(value);
137                     callback.onPropertyEvent(values);
138                 } catch (RemoteException e) {
139                     Log.e(TAG, "Failed invoking callback", e);
140                     fail("Remote exception while injecting events.");
141                 }
142             }
143         }
144     }
145 
injectEvent(VehiclePropValue value)146     public void injectEvent(VehiclePropValue value) {
147         injectEvent(value, false);
148     }
149 
injectError(int errorCode, int propertyId, int areaId)150     public void injectError(int errorCode, int propertyId, int areaId) {
151         synchronized (mLock) {
152             List<IVehicleCallback> callbacks = mSubscribers.get(propertyId);
153             assertNotNull("Injecting error failed for property: " + propertyId
154                     + ". No listeners found", callbacks);
155             for (int i = 0; i < callbacks.size(); i++) {
156                 IVehicleCallback callback = callbacks.get(i);
157                 try {
158                     callback.onPropertySetError(errorCode, propertyId, areaId);
159                 } catch (RemoteException e) {
160                     Log.e(TAG, "Failed invoking callback", e);
161                     fail("Remote exception while injecting errors.");
162                 }
163             }
164         }
165     }
166 
167     @Override
getAllPropConfigs()168     public ArrayList<VehiclePropConfig> getAllPropConfigs() {
169         synchronized (mLock) {
170             return new ArrayList<>(mConfigs.values());
171         }
172     }
173 
174     @Override
getPropConfigs(ArrayList<Integer> props, getPropConfigsCallback cb)175     public void getPropConfigs(ArrayList<Integer> props, getPropConfigsCallback cb) {
176         synchronized (mLock) {
177             ArrayList<VehiclePropConfig> res = new ArrayList<>();
178             for (Integer prop : props) {
179                 VehiclePropConfig config = mConfigs.get(prop);
180                 if (config == null) {
181                     cb.onValues(StatusCode.INVALID_ARG, new ArrayList<>());
182                     return;
183                 }
184                 res.add(config);
185             }
186             cb.onValues(StatusCode.OK, res);
187         }
188     }
189 
190     @Override
get(VehiclePropValue requestedPropValue, getCallback cb)191     public void get(VehiclePropValue requestedPropValue, getCallback cb) {
192         synchronized (mLock) {
193             VehicleHalPropertyHandler handler = mPropertyHandlerMap.get(requestedPropValue.prop);
194             if (handler == null) {
195                 cb.onValues(StatusCode.INVALID_ARG, null);
196             } else {
197                 try {
198                     VehiclePropValue prop = handler.onPropertyGet(requestedPropValue);
199                     cb.onValues(StatusCode.OK, prop);
200                 } catch (ServiceSpecificException e) {
201                     // Don't directly pass ServiceSpecificException through binder to client, pass
202                     // status code similar to how the c++ server does.
203                     cb.onValues(e.errorCode, null);
204                 }
205             }
206         }
207     }
208 
209     @Override
set(VehiclePropValue propValue)210     public int set(VehiclePropValue propValue) {
211         synchronized (mLock) {
212             VehicleHalPropertyHandler handler = mPropertyHandlerMap.get(propValue.prop);
213             if (handler == null) {
214                 return StatusCode.INVALID_ARG;
215             } else {
216                 try {
217                     handler.onPropertySet(propValue);
218                     return StatusCode.OK;
219                 } catch (ServiceSpecificException e) {
220                     // Don't directly pass ServiceSpecificException through binder to client, pass
221                     // status code similar to how the c++ server does.
222                     return e.errorCode;
223                 }
224             }
225         }
226     }
227 
228     @Override
subscribe(IVehicleCallback callback, ArrayList<SubscribeOptions> options)229     public int subscribe(IVehicleCallback callback, ArrayList<SubscribeOptions> options) {
230         synchronized (mLock) {
231             for (SubscribeOptions opt : options) {
232                 VehicleHalPropertyHandler handler = mPropertyHandlerMap.get(opt.propId);
233                 if (handler == null) {
234                     return StatusCode.INVALID_ARG;
235                 }
236 
237                 handler.onPropertySubscribe(opt.propId, opt.sampleRate);
238                 List<IVehicleCallback> subscribers = mSubscribers.get(opt.propId);
239                 if (subscribers == null) {
240                     subscribers = new ArrayList<>();
241                     mSubscribers.put(opt.propId, subscribers);
242                     notifyAll();
243                 } else {
244                     for (int i = 0; i < subscribers.size(); i++) {
245                         IVehicleCallback s = subscribers.get(i);
246                         if (callback.asBinder() == s.asBinder()) {
247                             // Remove callback that was registered previously for this property
248                             subscribers.remove(callback);
249                             break;
250                         }
251                     }
252                 }
253                 subscribers.add(callback);
254             }
255         }
256         return StatusCode.OK;
257     }
258 
259     @Override
unsubscribe(IVehicleCallback callback, int propId)260     public int unsubscribe(IVehicleCallback callback, int propId) {
261         synchronized (mLock) {
262             VehicleHalPropertyHandler handler = mPropertyHandlerMap.get(propId);
263             if (handler == null) {
264                 return StatusCode.INVALID_ARG;
265             }
266 
267             handler.onPropertyUnsubscribe(propId);
268             List<IVehicleCallback> subscribers = mSubscribers.get(propId);
269             if (subscribers != null) {
270                 subscribers.remove(callback);
271                 if (subscribers.size() == 0) {
272                     mSubscribers.remove(propId);
273                 }
274             }
275         }
276         return StatusCode.OK;
277     }
278 
279     @Override
debugDump()280     public String debugDump() {
281         return null;
282     }
283 
284     public static class FailingPropertyHandler implements VehicleHalPropertyHandler {
285         @Override
onPropertySet(VehiclePropValue value)286         public void onPropertySet(VehiclePropValue value) {
287             fail("Unexpected onPropertySet call");
288         }
289 
290         @Override
onPropertyGet(VehiclePropValue value)291         public VehiclePropValue onPropertyGet(VehiclePropValue value) {
292             fail("Unexpected onPropertyGet call");
293             return null;
294         }
295 
296         @Override
onPropertySubscribe(int property, float sampleRate)297         public void onPropertySubscribe(int property, float sampleRate) {
298             fail("Unexpected onPropertySubscribe call");
299         }
300 
301         @Override
onPropertyUnsubscribe(int property)302         public void onPropertyUnsubscribe(int property) {
303             fail("Unexpected onPropertyUnsubscribe call");
304         }
305     }
306 
307     @NotThreadSafe
308     public static final class StaticPropertyHandler extends FailingPropertyHandler {
309 
310         private final VehiclePropValue mValue;
311 
StaticPropertyHandler(VehiclePropValue value)312         public StaticPropertyHandler(VehiclePropValue value) {
313             mValue = value;
314         }
315 
316         @Override
onPropertyGet(VehiclePropValue value)317         public VehiclePropValue onPropertyGet(VehiclePropValue value) {
318             return mValue;
319         }
320     }
321 
322     @NotThreadSafe
323     public static final class ErrorCodeHandler extends FailingPropertyHandler {
324 
325         private final Object mLock = new Object();
326 
327         @GuardedBy("mLock")
328         private int mStatus;
329 
setStatus(int status)330         public void setStatus(int status) {
331             synchronized (mLock) {
332                 mStatus = status;
333             }
334         }
335 
336         @Override
onPropertyGet(VehiclePropValue value)337         public VehiclePropValue onPropertyGet(VehiclePropValue value) {
338             synchronized (mLock) {
339                 throw new ServiceSpecificException(mStatus);
340             }
341         }
342 
343         @Override
onPropertySet(VehiclePropValue value)344         public void onPropertySet(VehiclePropValue value) {
345             synchronized (mLock) {
346                 throw new ServiceSpecificException(mStatus);
347             }
348         }
349     }
350 
351     @NotThreadSafe
352     public static final class DefaultPropertyHandler implements VehicleHalPropertyHandler {
353 
354         private final VehiclePropConfig mConfig;
355         private VehiclePropValue mValue;
356         private boolean mSubscribed = false;
357 
DefaultPropertyHandler(VehiclePropConfig config, VehiclePropValue initialValue)358         public DefaultPropertyHandler(VehiclePropConfig config, VehiclePropValue initialValue) {
359             mConfig = config;
360             mValue = initialValue;
361         }
362 
363         @Override
onPropertySet(VehiclePropValue value)364         public void onPropertySet(VehiclePropValue value) {
365             assertEquals(mConfig.prop, value.prop);
366             assertEquals(VehiclePropertyAccess.WRITE,
367                     mConfig.access & VehiclePropertyAccess.WRITE);
368             mValue = value;
369         }
370 
371         @Override
onPropertyGet(VehiclePropValue value)372         public VehiclePropValue onPropertyGet(VehiclePropValue value) {
373             assertEquals(mConfig.prop, value.prop);
374             assertEquals(VehiclePropertyAccess.READ,
375                     mConfig.access & VehiclePropertyAccess.READ);
376             return mValue;
377         }
378 
379         @Override
onPropertySubscribe(int property, float sampleRate)380         public void onPropertySubscribe(int property, float sampleRate) {
381             assertEquals(mConfig.prop, property);
382             mSubscribed = true;
383         }
384 
385         @Override
onPropertyUnsubscribe(int property)386         public void onPropertyUnsubscribe(int property) {
387             assertEquals(mConfig.prop, property);
388             if (!mSubscribed) {
389                 throw new IllegalArgumentException("Property was not subscribed 0x"
390                         + toHexString(property));
391             }
392             mSubscribed = false;
393         }
394     }
395 }
396