1 /*
2  * Copyright (C) 2022 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 com.google.common.truth.Truth.assertThat;
20 import static com.google.common.truth.Truth.assertWithMessage;
21 
22 import static junit.framework.Assert.fail;
23 
24 import static java.lang.Integer.toHexString;
25 
26 import android.hardware.automotive.vehicle.GetValueRequest;
27 import android.hardware.automotive.vehicle.GetValueRequests;
28 import android.hardware.automotive.vehicle.GetValueResult;
29 import android.hardware.automotive.vehicle.GetValueResults;
30 import android.hardware.automotive.vehicle.IVehicle;
31 import android.hardware.automotive.vehicle.IVehicleCallback;
32 import android.hardware.automotive.vehicle.SetValueRequest;
33 import android.hardware.automotive.vehicle.SetValueRequests;
34 import android.hardware.automotive.vehicle.SetValueResult;
35 import android.hardware.automotive.vehicle.SetValueResults;
36 import android.hardware.automotive.vehicle.StatusCode;
37 import android.hardware.automotive.vehicle.SubscribeOptions;
38 import android.hardware.automotive.vehicle.VehiclePropConfig;
39 import android.hardware.automotive.vehicle.VehiclePropConfigs;
40 import android.hardware.automotive.vehicle.VehiclePropError;
41 import android.hardware.automotive.vehicle.VehiclePropErrors;
42 import android.hardware.automotive.vehicle.VehiclePropValue;
43 import android.hardware.automotive.vehicle.VehiclePropValues;
44 import android.hardware.automotive.vehicle.VehiclePropertyAccess;
45 import android.os.RemoteException;
46 import android.os.ServiceSpecificException;
47 import android.os.SystemClock;
48 import android.util.ArrayMap;
49 import android.util.Log;
50 import android.util.SparseArray;
51 
52 import com.android.internal.annotations.GuardedBy;
53 
54 import java.util.ArrayList;
55 import java.util.List;
56 import java.util.Map;
57 
58 import javax.annotation.concurrent.NotThreadSafe;
59 import javax.annotation.concurrent.ThreadSafe;
60 
61 public class AidlMockedVehicleHal extends IVehicle.Stub {
62 
63     private static final String TAG = AidlMockedVehicleHal.class.getSimpleName();
64 
65     /**
66      * Interface for handler of each property.
67      */
68     public interface VehicleHalPropertyHandler {
onPropertySet(VehiclePropValue value)69         default void onPropertySet(VehiclePropValue value) {}
70 
71         // Same as onPropertySet, except that it returns whether to generate property change event
72         // for the new value. By default, this will return true.
73         // Caller can override this to control whether to generate property change event.
onPropertySet2(VehiclePropValue value)74         default boolean onPropertySet2(VehiclePropValue value) {
75             onPropertySet(value);
76             return true;
77         }
78 
onPropertyGet(VehiclePropValue value)79         default VehiclePropValue onPropertyGet(VehiclePropValue value) {
80             return null;
81         }
82 
onPropertySubscribe(int property, float sampleRate)83         default void onPropertySubscribe(int property, float sampleRate) {}
84 
85         /**
86          * Called when a property is subscribed.
87          */
onPropertySubscribe(int property, int[] areaIds, float sampleRate)88         default void onPropertySubscribe(int property, int[] areaIds, float sampleRate) {}
89 
onPropertyUnsubscribe(int property)90         default void onPropertyUnsubscribe(int property) {}
91 
92         VehicleHalPropertyHandler NOP = new VehicleHalPropertyHandler() {};
93     }
94 
95     private final Object mLock = new Object();
96     @GuardedBy("mLock")
97     private final SparseArray<VehicleHalPropertyHandler> mPropertyHandlerMap = new SparseArray<>();
98     @GuardedBy("mLock")
99     private final SparseArray<VehiclePropConfig> mConfigs = new SparseArray<>();
100     @GuardedBy("mLock")
101     private final SparseArray<List<IVehicleCallback>> mSubscribers = new SparseArray<>();
102 
addProperties(VehiclePropConfig... configs)103     public void addProperties(VehiclePropConfig... configs) {
104         for (VehiclePropConfig config : configs) {
105             addProperty(config, new DefaultPropertyHandler(config, /* initialValue= */ null));
106         }
107     }
108 
addProperty(VehiclePropConfig config, VehicleHalPropertyHandler handler)109     public void addProperty(VehiclePropConfig config, VehicleHalPropertyHandler handler) {
110         synchronized (mLock) {
111             mPropertyHandlerMap.put(config.prop, handler);
112             mConfigs.put(config.prop, config);
113         }
114     }
115 
addStaticProperty(VehiclePropConfig config, VehiclePropValue value)116     public void addStaticProperty(VehiclePropConfig config, VehiclePropValue value) {
117         addProperty(config, new StaticPropertyHandler(value));
118     }
119 
waitForSubscriber(int propId, long timeoutMillis)120     public boolean waitForSubscriber(int propId, long timeoutMillis) {
121         long startTime = SystemClock.elapsedRealtime();
122         try {
123             synchronized (mLock) {
124                 while (mSubscribers.get(propId) == null) {
125                     long waitMillis = startTime - SystemClock.elapsedRealtime() + timeoutMillis;
126                     if (waitMillis < 0) break;
127                     mLock.wait(waitMillis);
128                 }
129 
130                 return mSubscribers.get(propId) != null;
131             }
132         } catch (InterruptedException e) {
133             return false;
134         }
135     }
136 
injectEvent(VehiclePropValue value, boolean setProperty)137     public void injectEvent(VehiclePropValue value, boolean setProperty) {
138         synchronized (mLock) {
139             List<IVehicleCallback> callbacks = mSubscribers.get(value.prop);
140             assertWithMessage("Injecting event failed for property: " + value.prop
141                     + ". No listeners found").that(callbacks).isNotNull();
142 
143             if (setProperty) {
144                 // Update property if requested
145                 VehicleHalPropertyHandler handler = mPropertyHandlerMap.get(value.prop);
146                 if (handler != null) {
147                     handler.onPropertySet2(value);
148                 }
149             }
150 
151             for (int i = 0; i < callbacks.size(); i++) {
152                 IVehicleCallback callback = callbacks.get(i);
153                 try {
154                     VehiclePropValues propValues = new VehiclePropValues();
155                     propValues.payloads = new VehiclePropValue[1];
156                     propValues.payloads[0] = value;
157                     callback.onPropertyEvent(propValues, /* sharedMemoryCount= */ 0);
158                 } catch (RemoteException e) {
159                     Log.e(TAG, "Failed invoking callback", e);
160                     fail("Remote exception while injecting events.");
161                 }
162             }
163         }
164     }
165 
injectEvent(VehiclePropValue value)166     public void injectEvent(VehiclePropValue value) {
167         injectEvent(value, /* setProperty= */ false);
168     }
169 
injectError(int errorCode, int propertyId, int areaId)170     public void injectError(int errorCode, int propertyId, int areaId) {
171         synchronized (mLock) {
172             List<IVehicleCallback> callbacks = mSubscribers.get(propertyId);
173             assertWithMessage("Injecting error failed for property: " + propertyId
174                     + ". No listeners found").that(callbacks).isNotNull();
175             for (int i = 0; i < callbacks.size(); i++) {
176                 IVehicleCallback callback = callbacks.get(i);
177                 try {
178                     VehiclePropError error = new VehiclePropError();
179                     error.propId = propertyId;
180                     error.areaId = areaId;
181                     error.errorCode = errorCode;
182                     VehiclePropErrors propErrors = new VehiclePropErrors();
183                     propErrors.payloads = new VehiclePropError[]{error};
184                     callback.onPropertySetError(propErrors);
185                 } catch (RemoteException e) {
186                     Log.e(TAG, "Failed invoking callback", e);
187                     fail("Remote exception while injecting errors.");
188                 }
189             }
190         }
191     }
192 
193     @Override
getAllPropConfigs()194     public VehiclePropConfigs getAllPropConfigs() throws RemoteException {
195         synchronized (mLock) {
196             VehiclePropConfigs propConfigs = new VehiclePropConfigs();
197             propConfigs.payloads = new VehiclePropConfig[mConfigs.size()];
198             for (int i = 0; i < mConfigs.size(); i++) {
199                 // Make a copy of the config.
200                 propConfigs.payloads[i] = AidlVehiclePropConfigBuilder.newBuilder(
201                         mConfigs.valueAt(i)).build();
202             }
203             return propConfigs;
204         }
205     }
206 
207     @Override
getPropConfigs(int[] props)208     public VehiclePropConfigs getPropConfigs(int[] props) throws RemoteException {
209         synchronized (mLock) {
210             int count = 0;
211             for (int prop : props) {
212                 if (mConfigs.contains(prop)) {
213                     count++;
214                 }
215             }
216 
217             VehiclePropConfigs propConfigs = new VehiclePropConfigs();
218             propConfigs.payloads = new VehiclePropConfig[count];
219 
220             int i = 0;
221             for (int prop : props) {
222                 if (mConfigs.contains(prop)) {
223                     // Make a copy of the config.
224                     propConfigs.payloads[i] = AidlVehiclePropConfigBuilder.newBuilder(
225                             mConfigs.get(prop)).build();
226                     i++;
227                 }
228             }
229 
230             return propConfigs;
231         }
232     }
233 
234     @Override
getValues(IVehicleCallback callback, GetValueRequests requests)235     public void getValues(IVehicleCallback callback, GetValueRequests requests)
236             throws RemoteException {
237         synchronized (mLock) {
238             assertWithMessage("AidlMockedVehicleHal does not support large parcelable").that(
239                     requests.sharedMemoryFd).isNull();
240             GetValueResults results = new GetValueResults();
241             results.payloads = new GetValueResult[requests.payloads.length];
242 
243             for (int i = 0; i < requests.payloads.length; i++) {
244                 GetValueRequest request = requests.payloads[i];
245                 GetValueResult result = new GetValueResult();
246                 result.requestId = request.requestId;
247                 VehiclePropValue requestedPropValue = request.prop;
248                 VehicleHalPropertyHandler handler = mPropertyHandlerMap.get(
249                         requestedPropValue.prop);
250                 if (handler == null) {
251                     result.status = StatusCode.INVALID_ARG;
252                 } else {
253                     try {
254                         VehiclePropValue prop = handler.onPropertyGet(requestedPropValue);
255                         result.status = StatusCode.OK;
256                         if (prop == null) {
257                             result.prop = null;
258                         } else {
259                             // Make a copy of prop.
260                             result.prop = AidlVehiclePropValueBuilder.newBuilder(prop).build();
261                         }
262                     } catch (ServiceSpecificException e) {
263                         result.status = e.errorCode;
264                     }
265                 }
266                 results.payloads[i] = result;
267             }
268 
269             callback.onGetValues(results);
270         }
271     }
272 
273     @Override
setValues(IVehicleCallback callback, SetValueRequests requests)274     public void setValues(IVehicleCallback callback, SetValueRequests requests)
275             throws RemoteException {
276         SetValueResults results = new SetValueResults();
277         Map<IVehicleCallback, List<VehiclePropValue>> subCallbackToValues = new ArrayMap<>();
278         synchronized (mLock) {
279             assertWithMessage("AidlMockedVehicleHal does not support large parcelable").that(
280                     requests.sharedMemoryFd).isNull();
281             results.payloads = new SetValueResult[requests.payloads.length];
282             for (int i = 0; i < requests.payloads.length; i++) {
283                 SetValueRequest request = requests.payloads[i];
284                 SetValueResult result = new SetValueResult();
285                 result.requestId = request.requestId;
286                 VehiclePropValue requestedPropValue = request.value;
287                 VehicleHalPropertyHandler handler = mPropertyHandlerMap.get(
288                         requestedPropValue.prop);
289                 if (handler == null) {
290                     result.status = StatusCode.INVALID_ARG;
291                 } else {
292                     try {
293                         requestedPropValue.timestamp = SystemClock.elapsedRealtimeNanos();
294                         boolean generateEvent = handler.onPropertySet2(requestedPropValue);
295                         result.status = StatusCode.OK;
296                         int propId = requestedPropValue.prop;
297                         // VMS has special logic.
298                         if (generateEvent && mSubscribers.get(propId) != null) {
299                             for (IVehicleCallback subCallback: mSubscribers.get(propId)) {
300                                 if (subCallbackToValues.get(subCallback) == null) {
301                                     subCallbackToValues.put(subCallback, new ArrayList<>());
302                                 }
303                                 subCallbackToValues.get(subCallback).add(requestedPropValue);
304                             }
305                         }
306                     } catch (ServiceSpecificException e) {
307                         result.status = e.errorCode;
308                     }
309                 }
310                 results.payloads[i] = result;
311             }
312         }
313         callback.onSetValues(results);
314 
315         for (IVehicleCallback subCallback : subCallbackToValues.keySet()) {
316             VehiclePropValues propValues = new VehiclePropValues();
317             List<VehiclePropValue> updatedValues = subCallbackToValues.get(subCallback);
318             propValues.payloads = new VehiclePropValue[updatedValues.size()];
319             for (int i = 0; i < updatedValues.size(); i++) {
320                 propValues.payloads[i] = updatedValues.get(i);
321             }
322             try {
323                 subCallback.onPropertyEvent(propValues, /* sharedMemoryCount= */ 0);
324             } catch (RemoteException e) {
325                 Log.e(TAG, "Failed invoking callback", e);
326                 fail("Remote exception while injecting events.");
327             }
328         }
329 
330     }
331 
332     @Override
subscribe(IVehicleCallback callback, SubscribeOptions[] options, int maxSharedMemoryFileCount)333     public void subscribe(IVehicleCallback callback, SubscribeOptions[] options,
334             int maxSharedMemoryFileCount) throws RemoteException {
335         synchronized (mLock) {
336             for (SubscribeOptions opt : options) {
337                 VehicleHalPropertyHandler handler = mPropertyHandlerMap.get(opt.propId);
338                 if (handler == null) {
339                     throw new ServiceSpecificException(StatusCode.INVALID_ARG,
340                             "no registered handler");
341                 }
342 
343                 handler.onPropertySubscribe(opt.propId, opt.sampleRate);
344                 handler.onPropertySubscribe(opt.propId, opt.areaIds, opt.sampleRate);
345                 List<IVehicleCallback> subscribers = mSubscribers.get(opt.propId);
346                 if (subscribers == null) {
347                     subscribers = new ArrayList<>();
348                     mSubscribers.put(opt.propId, subscribers);
349                     mLock.notifyAll();
350                 } else {
351                     for (int i = 0; i < subscribers.size(); i++) {
352                         IVehicleCallback s = subscribers.get(i);
353                         if (callback.asBinder() == s.asBinder()) {
354                             // Remove callback that was registered previously for this property
355                             subscribers.remove(callback);
356                             break;
357                         }
358                     }
359                 }
360                 subscribers.add(callback);
361             }
362         }
363     }
364 
365     @Override
unsubscribe(IVehicleCallback callback, int[] propIds)366     public void unsubscribe(IVehicleCallback callback, int[] propIds)
367             throws RemoteException {
368         synchronized (mLock) {
369             for (int propId : propIds) {
370                 VehicleHalPropertyHandler handler = mPropertyHandlerMap.get(propId);
371                 if (handler == null) {
372                     throw new ServiceSpecificException(StatusCode.INVALID_ARG,
373                             "no registered handler");
374                 }
375 
376                 handler.onPropertyUnsubscribe(propId);
377                 List<IVehicleCallback> subscribers = mSubscribers.get(propId);
378                 if (subscribers != null) {
379                     subscribers.remove(callback);
380                     if (subscribers.size() == 0) {
381                         mSubscribers.remove(propId);
382                     }
383                 }
384             }
385         }
386     }
387 
388     @Override
returnSharedMemory(IVehicleCallback callback, long sharedMemoryId)389     public void returnSharedMemory(IVehicleCallback callback, long sharedMemoryId)
390             throws RemoteException {
391         // Do nothing.
392     }
393 
394     @Override
getInterfaceHash()395     public String getInterfaceHash() {
396         return IVehicle.HASH;
397     }
398 
399     @Override
getInterfaceVersion()400     public int getInterfaceVersion() {
401         return IVehicle.VERSION;
402     }
403 
404     public static class FailingPropertyHandler implements VehicleHalPropertyHandler {
405         @Override
onPropertySet(VehiclePropValue value)406         public void onPropertySet(VehiclePropValue value) {
407             fail("Unexpected onPropertySet call");
408         }
409 
410         @Override
onPropertyGet(VehiclePropValue value)411         public VehiclePropValue onPropertyGet(VehiclePropValue value) {
412             fail("Unexpected onPropertyGet call");
413             return null;
414         }
415 
416         @Override
onPropertySubscribe(int property, float sampleRate)417         public void onPropertySubscribe(int property, float sampleRate) {
418             fail("Unexpected onPropertySubscribe call");
419         }
420 
421         @Override
onPropertySubscribe(int property, int[] areaIds, float sampleRate)422         public void onPropertySubscribe(int property, int[] areaIds, float sampleRate) {
423             fail("Unexpected onPropertySubscribe call");
424         }
425 
426         @Override
onPropertyUnsubscribe(int property)427         public void onPropertyUnsubscribe(int property) {
428             fail("Unexpected onPropertyUnsubscribe call");
429         }
430     }
431 
432     @NotThreadSafe
433     public static final class StaticPropertyHandler extends FailingPropertyHandler {
434 
435         private final VehiclePropValue mValue;
436 
StaticPropertyHandler(VehiclePropValue value)437         public StaticPropertyHandler(VehiclePropValue value) {
438             mValue = value;
439         }
440 
441         @Override
onPropertyGet(VehiclePropValue value)442         public VehiclePropValue onPropertyGet(VehiclePropValue value) {
443             return mValue;
444         }
445     }
446 
447     @ThreadSafe
448     public static final class ErrorCodeHandler extends FailingPropertyHandler {
449         private final Object mLock = new Object();
450 
451         @GuardedBy("mLock")
452         private int mStatus;
453 
setStatus(int status)454         public void setStatus(int status) {
455             synchronized (mLock) {
456                 mStatus = status;
457             }
458         }
459 
460         @Override
onPropertyGet(VehiclePropValue value)461         public VehiclePropValue onPropertyGet(VehiclePropValue value) {
462             synchronized (mLock) {
463                 throw new ServiceSpecificException(mStatus);
464             }
465         }
466 
467         @Override
onPropertySet(VehiclePropValue value)468         public void onPropertySet(VehiclePropValue value) {
469             synchronized (mLock) {
470                 throw new ServiceSpecificException(mStatus);
471             }
472         }
473     }
474 
475     @NotThreadSafe
476     public static final class DefaultPropertyHandler implements VehicleHalPropertyHandler {
477 
478         private final VehiclePropConfig mConfig;
479 
480         private boolean mSubscribed;
481 
482         private VehiclePropValue mValue;
483 
DefaultPropertyHandler(VehiclePropConfig config, VehiclePropValue initialValue)484         public DefaultPropertyHandler(VehiclePropConfig config, VehiclePropValue initialValue) {
485             mConfig = config;
486             mValue = initialValue;
487         }
488 
onPropertySet(VehiclePropValue value)489         public void onPropertySet(VehiclePropValue value) {
490             assertThat(mConfig.prop).isEqualTo(value.prop);
491             assertThat(mConfig.access & VehiclePropertyAccess.WRITE).isEqualTo(
492                     VehiclePropertyAccess.WRITE);
493             mValue = value;
494         }
495 
496         @Override
onPropertyGet(VehiclePropValue value)497         public VehiclePropValue onPropertyGet(VehiclePropValue value) {
498             assertThat(mConfig.prop).isEqualTo(value.prop);
499             assertThat(mConfig.access & VehiclePropertyAccess.READ).isEqualTo(
500                     VehiclePropertyAccess.READ);
501             return mValue;
502         }
503 
504         @Override
onPropertySubscribe(int property, float sampleRate)505         public void onPropertySubscribe(int property, float sampleRate) {
506             assertThat(mConfig.prop).isEqualTo(property);
507             mSubscribed = true;
508         }
509 
510         @Override
onPropertySubscribe(int property, int[] areaIds, float sampleRate)511         public void onPropertySubscribe(int property, int[] areaIds, float sampleRate) {
512             assertThat(mConfig.prop).isEqualTo(property);
513             mSubscribed = true;
514         }
515 
516         @Override
onPropertyUnsubscribe(int property)517         public void onPropertyUnsubscribe(int property) {
518             assertThat(mConfig.prop).isEqualTo(property);
519             if (!mSubscribed) {
520                 throw new IllegalArgumentException("Property was not subscribed 0x"
521                         + toHexString(property));
522             }
523             mSubscribed = false;
524         }
525     }
526 }
527