1 /* 2 * Copyright (C) 2019 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 android.car.testapi; 18 19 import static android.car.hardware.property.CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE; 20 21 import static java.lang.Integer.toHexString; 22 23 import android.annotation.Nullable; 24 import android.car.Car; 25 import android.car.VehicleAreaType; 26 import android.car.VehiclePropertyType; 27 import android.car.hardware.CarPropertyConfig; 28 import android.car.hardware.CarPropertyValue; 29 import android.car.hardware.property.CarPropertyEvent; 30 import android.car.hardware.property.ICarProperty; 31 import android.car.hardware.property.ICarPropertyEventListener; 32 import android.os.RemoteException; 33 import android.util.ArraySet; 34 35 import com.android.car.internal.property.AsyncPropertyServiceRequest; 36 import com.android.car.internal.property.AsyncPropertyServiceRequestList; 37 import com.android.car.internal.property.CarPropertyConfigList; 38 import com.android.car.internal.property.CarSubscription; 39 import com.android.car.internal.property.GetPropertyConfigListResult; 40 import com.android.car.internal.property.GetSetValueResult; 41 import com.android.car.internal.property.GetSetValueResultList; 42 import com.android.car.internal.property.IAsyncPropertyResultCallback; 43 import com.android.car.internal.property.ISupportedValuesChangeCallback; 44 import com.android.car.internal.property.MinMaxSupportedPropertyValue; 45 import com.android.car.internal.property.PropIdAreaId; 46 import com.android.car.internal.property.RawPropertyValue; 47 import com.android.car.internal.util.PairSparseArray; 48 import com.android.internal.annotations.GuardedBy; 49 50 import java.util.ArrayList; 51 import java.util.HashMap; 52 import java.util.List; 53 import java.util.Map; 54 import java.util.Objects; 55 import java.util.Set; 56 57 /** 58 * This is fake implementation of the service which is used in 59 * {@link android.car.hardware.property.CarPropertyManager}. 60 * 61 * @hide 62 */ 63 class FakeCarPropertyService extends ICarProperty.Stub implements CarPropertyController { 64 private final Map<Integer, CarPropertyConfig> mConfigs = new HashMap<>(); 65 private final Map<PropKey, CarPropertyValue> mValues = new HashMap<>(); 66 67 // Contains a list of values that were set from the manager. 68 private final ArrayList<CarPropertyValue<?>> mValuesSet = new ArrayList<>(); 69 70 private final Object mLock = new Object(); 71 72 // Mapping between [propId, areaId] and a set of listeners. 73 @GuardedBy("mLock") 74 private final PairSparseArray<Set<ListenerInfo>> mListenersByPropIdAreaId = 75 new PairSparseArray<>(); 76 77 @Override registerListener(List<CarSubscription> subscriptions, ICarPropertyEventListener listener)78 public void registerListener(List<CarSubscription> subscriptions, 79 ICarPropertyEventListener listener) { 80 synchronized (mLock) { 81 for (int i = 0; i < subscriptions.size(); i++) { 82 int propId = subscriptions.get(i).propertyId; 83 for (int areaId : subscriptions.get(i).areaIds) { 84 Set<ListenerInfo> propListeners = mListenersByPropIdAreaId.get(propId, areaId); 85 if (propListeners == null) { 86 propListeners = new ArraySet<>(); 87 mListenersByPropIdAreaId.put(propId, areaId, propListeners); 88 } 89 90 propListeners.add(new ListenerInfo(listener)); 91 } 92 } 93 } 94 } 95 96 @Override unregisterListener(int propId, ICarPropertyEventListener listener)97 public void unregisterListener(int propId, ICarPropertyEventListener listener) 98 throws RemoteException { 99 synchronized (mLock) { 100 for (int areaId : mListenersByPropIdAreaId.getSecondKeysForFirstKey(propId)) { 101 Set<ListenerInfo> propListeners = mListenersByPropIdAreaId.get(propId, areaId); 102 if (propListeners.remove(new ListenerInfo(listener)) && propListeners.isEmpty()) { 103 mListenersByPropIdAreaId.remove(propId, areaId); 104 } 105 } 106 } 107 } 108 109 @Override getPropertyList()110 public CarPropertyConfigList getPropertyList() throws RemoteException { 111 return new CarPropertyConfigList(new ArrayList<>(mConfigs.values())); 112 } 113 114 @Override getPropertyConfigList(int[] propIds)115 public GetPropertyConfigListResult getPropertyConfigList(int[] propIds) { 116 List<CarPropertyConfig> configs = new ArrayList<>(propIds.length); 117 for (int prop : propIds) { 118 CarPropertyConfig cfg = mConfigs.get(prop); 119 if (cfg != null) { 120 configs.add(cfg); 121 } 122 } 123 GetPropertyConfigListResult result = new GetPropertyConfigListResult(); 124 result.unsupportedPropIds = new int[0]; 125 result.missingPermissionPropIds = new int[0]; 126 result.carPropertyConfigList = new CarPropertyConfigList(configs); 127 return result; 128 } 129 130 @Override getPropertiesAsync(AsyncPropertyServiceRequestList asyncPropertyServiceRequests, IAsyncPropertyResultCallback asyncPropertyResultCallback, long timeoutInMs)131 public void getPropertiesAsync(AsyncPropertyServiceRequestList asyncPropertyServiceRequests, 132 IAsyncPropertyResultCallback asyncPropertyResultCallback, long timeoutInMs) 133 throws RemoteException { 134 List<AsyncPropertyServiceRequest> asyncPropertyServiceRequestList = 135 asyncPropertyServiceRequests.getList(); 136 List<GetSetValueResult> getValueResults = new ArrayList<>(); 137 for (int i = 0; i < asyncPropertyServiceRequestList.size(); i++) { 138 AsyncPropertyServiceRequest asyncPropertyServiceRequest = 139 asyncPropertyServiceRequestList.get(i); 140 getValueResults.add(GetSetValueResult.newGetValueResult( 141 asyncPropertyServiceRequest.getRequestId(), 142 getProperty(asyncPropertyServiceRequest.getPropertyId(), 143 asyncPropertyServiceRequest.getAreaId()))); 144 } 145 asyncPropertyResultCallback.onGetValueResults(new GetSetValueResultList(getValueResults)); 146 } 147 148 @Override setPropertiesAsync(AsyncPropertyServiceRequestList asyncPropertyServiceRequests, IAsyncPropertyResultCallback asyncPropertyResultCallback, long timeoutInMs)149 public void setPropertiesAsync(AsyncPropertyServiceRequestList asyncPropertyServiceRequests, 150 IAsyncPropertyResultCallback asyncPropertyResultCallback, long timeoutInMs) 151 throws RemoteException { 152 List<AsyncPropertyServiceRequest> asyncPropertyServiceRequestList = 153 asyncPropertyServiceRequests.getList(); 154 List<GetSetValueResult> setValueResults = new ArrayList<>(); 155 for (int i = 0; i < asyncPropertyServiceRequestList.size(); i++) { 156 AsyncPropertyServiceRequest asyncPropertyServiceRequest = 157 asyncPropertyServiceRequestList.get(i); 158 setProperty(asyncPropertyServiceRequest.getCarPropertyValue(), /* listener= */ null); 159 setValueResults.add(GetSetValueResult.newSetValueResult( 160 asyncPropertyServiceRequest.getRequestId(), /* updateTimestampNanos= */ 0)); 161 } 162 asyncPropertyResultCallback.onSetValueResults(new GetSetValueResultList(setValueResults)); 163 } 164 165 @Override getProperty(int prop, int areaId)166 public CarPropertyValue getProperty(int prop, int areaId) throws RemoteException { 167 return mValues.get(PropKey.of(prop, areaId)); 168 } 169 170 @Override getAndDispatchInitialValue(List<PropIdAreaId> propIdAreaIds, ICarPropertyEventListener carPropertyEventListener)171 public void getAndDispatchInitialValue(List<PropIdAreaId> propIdAreaIds, 172 ICarPropertyEventListener carPropertyEventListener) throws RemoteException { 173 List<CarPropertyEvent> events = new ArrayList<>(); 174 for (var propIdAreaId : propIdAreaIds) { 175 CarPropertyEvent event = new CarPropertyEvent( 176 CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, 177 getProperty(propIdAreaId.propId, propIdAreaId.areaId)); 178 events.add(event); 179 } 180 181 carPropertyEventListener.onEvent(events); 182 } 183 184 @Override setProperty(CarPropertyValue prop, ICarPropertyEventListener listener)185 public void setProperty(CarPropertyValue prop, ICarPropertyEventListener listener) 186 throws RemoteException { 187 mValues.put(PropKey.of(prop), prop); 188 mValuesSet.add(prop); 189 sendEvent(prop); 190 } 191 192 @Override cancelRequests(int[] serviceRequestIds)193 public void cancelRequests(int[] serviceRequestIds) { 194 // Do nothing. 195 } 196 197 @Override registerRecordingListener(ICarPropertyEventListener callback)198 public CarPropertyConfigList registerRecordingListener(ICarPropertyEventListener callback) { 199 return new CarPropertyConfigList(new ArrayList<>()); 200 } 201 202 @Override isRecordingVehicleProperties()203 public boolean isRecordingVehicleProperties() { 204 return false; 205 } 206 207 @Override stopRecordingVehicleProperties(ICarPropertyEventListener callback)208 public void stopRecordingVehicleProperties(ICarPropertyEventListener callback) { 209 // no-op 210 } 211 212 @Override enableInjectionMode(int[] propertyIdsFromRealHardware)213 public long enableInjectionMode(int[] propertyIdsFromRealHardware) { 214 return -1; 215 } 216 217 @Override disableInjectionMode()218 public void disableInjectionMode() { 219 // no-op 220 } 221 222 @Override isVehiclePropertyInjectionModeEnabled()223 public boolean isVehiclePropertyInjectionModeEnabled() { 224 return false; 225 } 226 227 @Override getLastInjectedVehicleProperty(int propertyId)228 public CarPropertyValue getLastInjectedVehicleProperty(int propertyId) { 229 return null; 230 } 231 232 @Override injectVehicleProperties(List<CarPropertyValue> carPropertyValues)233 public void injectVehicleProperties(List<CarPropertyValue> carPropertyValues) { 234 // no-op 235 } 236 237 @Override getReadPermission(int propId)238 public String getReadPermission(int propId) throws RemoteException { 239 // Return an arbitrary permission if the propId is supported. 240 return mConfigs.containsKey(propId) ? Car.PERMISSION_SPEED : null; 241 } 242 243 @Override getWritePermission(int propId)244 public String getWritePermission(int propId) throws RemoteException { 245 // Return an arbitrary permission if the propId is supported. 246 return mConfigs.containsKey(propId) ? Car.PERMISSION_SPEED : null; 247 } 248 249 @Override getSupportedNoReadPermPropIds(int[] propertyids)250 public int[] getSupportedNoReadPermPropIds(int[] propertyids) { 251 return new int[0]; 252 } 253 254 @Override isSupportedAndHasWritePermissionOnly(int propertyId)255 public boolean isSupportedAndHasWritePermissionOnly(int propertyId) { 256 return false; 257 } 258 259 @Override addProperty(Integer propId, Object value)260 public CarPropertyController addProperty(Integer propId, Object value) { 261 int areaType = getVehicleAreaType(propId); 262 Class<?> type = getPropertyType(propId); 263 CarPropertyConfig.Builder<?> builder = CarPropertyConfig 264 .newBuilder(type, propId, areaType); 265 mConfigs.put(propId, builder.build()); 266 if (value != null) { 267 updateValues(false, new CarPropertyValue<>(propId, 0, value)); 268 } 269 270 return this; 271 } 272 273 @Override addProperty(CarPropertyConfig<?> config, @Nullable CarPropertyValue<?> value)274 public CarPropertyController addProperty(CarPropertyConfig<?> config, 275 @Nullable CarPropertyValue<?> value) { 276 mConfigs.put(config.getPropertyId(), config); 277 if (value != null) { 278 updateValues(false, value); 279 } 280 return this; 281 } 282 283 @Override updateValues(boolean triggerListeners, CarPropertyValue<?>... propValues)284 public void updateValues(boolean triggerListeners, CarPropertyValue<?>... propValues) { 285 for (CarPropertyValue v : propValues) { 286 mValues.put(PropKey.of(v), v); 287 if (triggerListeners) { 288 sendEvent(v); 289 } 290 } 291 } 292 293 @Override getMinMaxSupportedValue(int propertyId, int areaId)294 public MinMaxSupportedPropertyValue getMinMaxSupportedValue(int propertyId, int areaId) { 295 // This is currently unused, so just return a fake result here that doesn't support 296 // min or max value. 297 return new MinMaxSupportedPropertyValue(); 298 } 299 300 @Override 301 @Nullable getSupportedValuesList(int propertyId, int areaId)302 public List<RawPropertyValue> getSupportedValuesList(int propertyId, int areaId) { 303 // This is currently unused, so just return null indicating the hardware does not specify 304 // supported values list. 305 return null; 306 } 307 308 @Override registerSupportedValuesChangeCallback(List<PropIdAreaId> propIdAreaIds, ISupportedValuesChangeCallback callback)309 public void registerSupportedValuesChangeCallback(List<PropIdAreaId> propIdAreaIds, 310 ISupportedValuesChangeCallback callback) { 311 // This is currently unused, do nothing. 312 } 313 314 @Override unregisterSupportedValuesChangeCallback(List<PropIdAreaId> propIdAreaIds, ISupportedValuesChangeCallback callback)315 public void unregisterSupportedValuesChangeCallback(List<PropIdAreaId> propIdAreaIds, 316 ISupportedValuesChangeCallback callback) { 317 // This is currently unused, do nothing. 318 } 319 sendEvent(CarPropertyValue v)320 private void sendEvent(CarPropertyValue v) { 321 synchronized (mLock) { 322 Set<ListenerInfo> listeners = mListenersByPropIdAreaId.get(v.getPropertyId(), 323 v.getAreaId()); 324 if (listeners != null) { 325 for (ListenerInfo listenerInfo : listeners) { 326 List<CarPropertyEvent> events = new ArrayList<>(); 327 events.add(new CarPropertyEvent(PROPERTY_EVENT_PROPERTY_CHANGE, v)); 328 try { 329 listenerInfo.mListener.onEvent(events); 330 } catch (RemoteException e) { 331 // This is impossible as the code runs within the same process in test. 332 throw new RuntimeException(e); 333 } 334 } 335 } 336 } 337 } 338 339 @Override getSetValues()340 public List<CarPropertyValue<?>> getSetValues() { 341 // Explicitly return the instance of this object rather than copying it such that test code 342 // will have a chance to clear this list if needed. 343 return mValuesSet; 344 } 345 346 /** Consists of property id and area */ 347 private static class PropKey { 348 final int mPropId; 349 final int mAreaId; 350 PropKey(int propId, int areaId)351 private PropKey(int propId, int areaId) { 352 this.mPropId = propId; 353 this.mAreaId = areaId; 354 } 355 of(int propId, int areaId)356 static PropKey of(int propId, int areaId) { 357 return new PropKey(propId, areaId); 358 } 359 of(CarPropertyValue carPropertyValue)360 static PropKey of(CarPropertyValue carPropertyValue) { 361 return of(carPropertyValue.getPropertyId(), carPropertyValue.getAreaId()); 362 } 363 364 @Override 365 equals(Object o)366 public boolean equals(Object o) { 367 if (this == o) { 368 return true; 369 } 370 if (!(o instanceof PropKey)) { 371 return false; 372 } 373 PropKey propKey = (PropKey) o; 374 return mPropId == propKey.mPropId && mAreaId == propKey.mAreaId; 375 } 376 377 @Override hashCode()378 public int hashCode() { 379 return Objects.hash(mPropId, mAreaId); 380 } 381 } 382 383 private static class ListenerInfo { 384 private final ICarPropertyEventListener mListener; 385 ListenerInfo(ICarPropertyEventListener listener)386 ListenerInfo(ICarPropertyEventListener listener) { 387 this.mListener = listener; 388 } 389 390 @Override equals(Object o)391 public boolean equals(Object o) { 392 if (this == o) { 393 return true; 394 } 395 if (!(o instanceof ListenerInfo)) { 396 return false; 397 } 398 ListenerInfo that = (ListenerInfo) o; 399 return Objects.equals(mListener, that.mListener); 400 } 401 402 @Override hashCode()403 public int hashCode() { 404 return Objects.hash(mListener); 405 } 406 } 407 getPropertyType(int propId)408 private static Class<?> getPropertyType(int propId) { 409 int type = propId & VehiclePropertyType.MASK; 410 switch (type) { 411 case VehiclePropertyType.BOOLEAN: 412 return Boolean.class; 413 case VehiclePropertyType.FLOAT: 414 return Float.class; 415 case VehiclePropertyType.INT32: 416 return Integer.class; 417 case VehiclePropertyType.INT64: 418 return Long.class; 419 case VehiclePropertyType.FLOAT_VEC: 420 return Float[].class; 421 case VehiclePropertyType.INT32_VEC: 422 return Integer[].class; 423 case VehiclePropertyType.INT64_VEC: 424 return Long[].class; 425 case VehiclePropertyType.STRING: 426 return String.class; 427 case VehiclePropertyType.BYTES: 428 return byte[].class; 429 case VehiclePropertyType.MIXED: 430 return Object.class; 431 default: 432 throw new IllegalArgumentException("Unexpected type: " + toHexString(type)); 433 } 434 } 435 getVehicleAreaType(int propId)436 private static int getVehicleAreaType(int propId) { 437 int halArea = propId & VehicleArea.MASK; 438 switch (halArea) { 439 case VehicleArea.GLOBAL: 440 return VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL; 441 case VehicleArea.SEAT: 442 return VehicleAreaType.VEHICLE_AREA_TYPE_SEAT; 443 case VehicleArea.DOOR: 444 return VehicleAreaType.VEHICLE_AREA_TYPE_DOOR; 445 case VehicleArea.WINDOW: 446 return VehicleAreaType.VEHICLE_AREA_TYPE_WINDOW; 447 case VehicleArea.MIRROR: 448 return VehicleAreaType.VEHICLE_AREA_TYPE_MIRROR; 449 case VehicleArea.WHEEL: 450 return VehicleAreaType.VEHICLE_AREA_TYPE_WHEEL; 451 case VehicleArea.VENDOR: 452 return VehicleAreaType.VEHICLE_AREA_TYPE_VENDOR; 453 default: 454 throw new RuntimeException("Unsupported area type " + halArea); 455 } 456 } 457 458 /** Copy from VHAL generated file VehicleArea.java */ 459 private static final class VehicleArea { 460 static final int GLOBAL = 0x01000000; 461 static final int WINDOW = 0x03000000; 462 static final int MIRROR = 0x04000000; 463 static final int SEAT = 0x05000000; 464 static final int DOOR = 0x06000000; 465 static final int WHEEL = 0x07000000; 466 static final int VENDOR = 0x08000000; 467 static final int MASK = 0x0f000000; 468 } 469 } 470