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