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.VehicleAreaType; 25 import android.car.VehiclePropertyType; 26 import android.car.hardware.CarPropertyConfig; 27 import android.car.hardware.CarPropertyValue; 28 import android.car.hardware.property.CarPropertyEvent; 29 import android.car.hardware.property.ICarProperty; 30 import android.car.hardware.property.ICarPropertyEventListener; 31 import android.os.RemoteException; 32 33 import com.android.car.internal.PropertyPermissionMapping; 34 35 import java.util.ArrayList; 36 import java.util.HashMap; 37 import java.util.HashSet; 38 import java.util.List; 39 import java.util.Map; 40 import java.util.Objects; 41 import java.util.Set; 42 43 /** 44 * This is fake implementation of the service which is used in 45 * {@link android.car.hardware.property.CarPropertyManager}. 46 * 47 * @hide 48 */ 49 class FakeCarPropertyService extends ICarProperty.Stub implements CarPropertyController { 50 private final Map<Integer, CarPropertyConfig> mConfigs = new HashMap<>(); 51 private final Map<PropKey, CarPropertyValue> mValues = new HashMap<>(); 52 53 private final PropertyPermissionMapping mPermissions = new PropertyPermissionMapping(); 54 55 // Contains a list of values that were set from the manager. 56 private final ArrayList<CarPropertyValue<?>> mValuesSet = new ArrayList<>(); 57 58 // Mapping between propertyId and a set of listeners. 59 private final Map<Integer, Set<ListenerInfo>> mListeners = new HashMap<>(); 60 61 @Override registerListener(int propId, float rate, ICarPropertyEventListener listener)62 public void registerListener(int propId, float rate, ICarPropertyEventListener listener) 63 throws RemoteException { 64 Set<ListenerInfo> propListeners = mListeners.get(propId); 65 if (propListeners == null) { 66 propListeners = new HashSet<>(); 67 mListeners.put(propId, propListeners); 68 } 69 70 propListeners.add(new ListenerInfo(listener)); 71 } 72 73 @Override unregisterListener(int propId, ICarPropertyEventListener listener)74 public void unregisterListener(int propId, ICarPropertyEventListener listener) 75 throws RemoteException { 76 Set<ListenerInfo> propListeners = mListeners.get(propId); 77 if (propListeners != null && propListeners.remove(new ListenerInfo(listener))) { 78 if (propListeners.isEmpty()) { 79 mListeners.remove(propId); 80 } 81 } 82 } 83 84 @Override getPropertyList()85 public List<CarPropertyConfig> getPropertyList() throws RemoteException { 86 return new ArrayList<>(mConfigs.values()); 87 } 88 89 @Override getProperty(int prop, int zone)90 public CarPropertyValue getProperty(int prop, int zone) throws RemoteException { 91 return mValues.get(PropKey.of(prop, zone)); 92 } 93 94 @Override setProperty(CarPropertyValue prop)95 public void setProperty(CarPropertyValue prop) throws RemoteException { 96 mValues.put(PropKey.of(prop), prop); 97 mValuesSet.add(prop); 98 sendEvent(prop); 99 } 100 101 @Override getReadPermission(int propId)102 public String getReadPermission(int propId) throws RemoteException { 103 return mConfigs.containsKey(propId) ? mPermissions.getReadPermission(propId) : null; 104 } 105 106 @Override getWritePermission(int propId)107 public String getWritePermission(int propId) throws RemoteException { 108 return mConfigs.containsKey(propId) ? mPermissions.getWritePermission(propId) : null; 109 } 110 111 @Override addProperty(Integer propId, Object value)112 public CarPropertyController addProperty(Integer propId, Object value) { 113 int areaType = getVehicleAreaType(propId); 114 Class<?> type = getPropertyType(propId); 115 CarPropertyConfig.Builder<?> builder = CarPropertyConfig 116 .newBuilder(type, propId, areaType); 117 mConfigs.put(propId, builder.build()); 118 if (value != null) { 119 updateValues(false, new CarPropertyValue<>(propId, 0, value)); 120 } 121 122 return this; 123 } 124 125 @Override addProperty(CarPropertyConfig<?> config, @Nullable CarPropertyValue<?> value)126 public CarPropertyController addProperty(CarPropertyConfig<?> config, 127 @Nullable CarPropertyValue<?> value) { 128 mConfigs.put(config.getPropertyId(), config); 129 if (value != null) { 130 updateValues(false, value); 131 } 132 return this; 133 } 134 135 @Override updateValues(boolean triggerListeners, CarPropertyValue<?>... propValues)136 public void updateValues(boolean triggerListeners, CarPropertyValue<?>... propValues) { 137 for (CarPropertyValue v : propValues) { 138 mValues.put(PropKey.of(v), v); 139 if (triggerListeners) { 140 sendEvent(v); 141 } 142 } 143 } 144 sendEvent(CarPropertyValue v)145 private void sendEvent(CarPropertyValue v) { 146 Set<ListenerInfo> listeners = mListeners.get(v.getPropertyId()); 147 if (listeners != null) { 148 for (ListenerInfo listenerInfo : listeners) { 149 List<CarPropertyEvent> events = new ArrayList<>(); 150 events.add(new CarPropertyEvent(PROPERTY_EVENT_PROPERTY_CHANGE, v)); 151 try { 152 listenerInfo.mListener.onEvent(events); 153 } catch (RemoteException e) { 154 // This is impossible as the code runs within the same process in test. 155 throw new RuntimeException(e); 156 } 157 } 158 } 159 } 160 161 @Override getSetValues()162 public List<CarPropertyValue<?>> getSetValues() { 163 // Explicitly return the instance of this object rather than copying it such that test code 164 // will have a chance to clear this list if needed. 165 return mValuesSet; 166 } 167 168 /** Consists of property id and area */ 169 private static class PropKey { 170 final int mPropId; 171 final int mAreaId; 172 PropKey(int propId, int areaId)173 private PropKey(int propId, int areaId) { 174 this.mPropId = propId; 175 this.mAreaId = areaId; 176 } 177 of(int propId, int areaId)178 static PropKey of(int propId, int areaId) { 179 return new PropKey(propId, areaId); 180 } 181 of(CarPropertyValue carPropertyValue)182 static PropKey of(CarPropertyValue carPropertyValue) { 183 return of(carPropertyValue.getPropertyId(), carPropertyValue.getAreaId()); 184 } 185 186 @Override 187 equals(Object o)188 public boolean equals(Object o) { 189 if (this == o) { 190 return true; 191 } 192 if (!(o instanceof PropKey)) { 193 return false; 194 } 195 PropKey propKey = (PropKey) o; 196 return mPropId == propKey.mPropId && mAreaId == propKey.mAreaId; 197 } 198 199 @Override hashCode()200 public int hashCode() { 201 return Objects.hash(mPropId, mAreaId); 202 } 203 } 204 205 private static class ListenerInfo { 206 private final ICarPropertyEventListener mListener; 207 ListenerInfo(ICarPropertyEventListener listener)208 ListenerInfo(ICarPropertyEventListener listener) { 209 this.mListener = listener; 210 } 211 212 @Override equals(Object o)213 public boolean equals(Object o) { 214 if (this == o) { 215 return true; 216 } 217 if (!(o instanceof ListenerInfo)) { 218 return false; 219 } 220 ListenerInfo that = (ListenerInfo) o; 221 return Objects.equals(mListener, that.mListener); 222 } 223 224 @Override hashCode()225 public int hashCode() { 226 return Objects.hash(mListener); 227 } 228 } 229 getPropertyType(int propId)230 private static Class<?> getPropertyType(int propId) { 231 int type = propId & VehiclePropertyType.MASK; 232 switch (type) { 233 case VehiclePropertyType.BOOLEAN: 234 return Boolean.class; 235 case VehiclePropertyType.FLOAT: 236 return Float.class; 237 case VehiclePropertyType.INT32: 238 return Integer.class; 239 case VehiclePropertyType.INT64: 240 return Long.class; 241 case VehiclePropertyType.FLOAT_VEC: 242 return Float[].class; 243 case VehiclePropertyType.INT32_VEC: 244 return Integer[].class; 245 case VehiclePropertyType.INT64_VEC: 246 return Long[].class; 247 case VehiclePropertyType.STRING: 248 return String.class; 249 case VehiclePropertyType.BYTES: 250 return byte[].class; 251 case VehiclePropertyType.MIXED: 252 return Object.class; 253 default: 254 throw new IllegalArgumentException("Unexpected type: " + toHexString(type)); 255 } 256 } 257 getVehicleAreaType(int propId)258 private static int getVehicleAreaType(int propId) { 259 int halArea = propId & VehicleArea.MASK; 260 switch (halArea) { 261 case VehicleArea.GLOBAL: 262 return VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL; 263 case VehicleArea.SEAT: 264 return VehicleAreaType.VEHICLE_AREA_TYPE_SEAT; 265 case VehicleArea.DOOR: 266 return VehicleAreaType.VEHICLE_AREA_TYPE_DOOR; 267 case VehicleArea.WINDOW: 268 return VehicleAreaType.VEHICLE_AREA_TYPE_WINDOW; 269 case VehicleArea.MIRROR: 270 return VehicleAreaType.VEHICLE_AREA_TYPE_MIRROR; 271 case VehicleArea.WHEEL: 272 return VehicleAreaType.VEHICLE_AREA_TYPE_WHEEL; 273 default: 274 throw new RuntimeException("Unsupported area type " + halArea); 275 } 276 } 277 278 /** Copy from VHAL generated file VehicleArea.java */ 279 private static final class VehicleArea { 280 static final int GLOBAL = 16777216 /* 0x01000000 */; 281 static final int WINDOW = 50331648 /* 0x03000000 */; 282 static final int MIRROR = 67108864 /* 0x04000000 */; 283 static final int SEAT = 83886080 /* 0x05000000 */; 284 static final int DOOR = 100663296 /* 0x06000000 */; 285 static final int WHEEL = 117440512 /* 0x07000000 */; 286 static final int MASK = 251658240 /* 0x0f000000 */; 287 } 288 } 289