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, ICarPropertyEventListener listener)95 public void setProperty(CarPropertyValue prop, ICarPropertyEventListener listener) 96 throws RemoteException { 97 mValues.put(PropKey.of(prop), prop); 98 mValuesSet.add(prop); 99 sendEvent(prop); 100 } 101 102 @Override getReadPermission(int propId)103 public String getReadPermission(int propId) throws RemoteException { 104 return mConfigs.containsKey(propId) ? mPermissions.getReadPermission(propId) : null; 105 } 106 107 @Override getWritePermission(int propId)108 public String getWritePermission(int propId) throws RemoteException { 109 return mConfigs.containsKey(propId) ? mPermissions.getWritePermission(propId) : null; 110 } 111 112 @Override addProperty(Integer propId, Object value)113 public CarPropertyController addProperty(Integer propId, Object value) { 114 int areaType = getVehicleAreaType(propId); 115 Class<?> type = getPropertyType(propId); 116 CarPropertyConfig.Builder<?> builder = CarPropertyConfig 117 .newBuilder(type, propId, areaType); 118 mConfigs.put(propId, builder.build()); 119 if (value != null) { 120 updateValues(false, new CarPropertyValue<>(propId, 0, value)); 121 } 122 123 return this; 124 } 125 126 @Override addProperty(CarPropertyConfig<?> config, @Nullable CarPropertyValue<?> value)127 public CarPropertyController addProperty(CarPropertyConfig<?> config, 128 @Nullable CarPropertyValue<?> value) { 129 mConfigs.put(config.getPropertyId(), config); 130 if (value != null) { 131 updateValues(false, value); 132 } 133 return this; 134 } 135 136 @Override updateValues(boolean triggerListeners, CarPropertyValue<?>... propValues)137 public void updateValues(boolean triggerListeners, CarPropertyValue<?>... propValues) { 138 for (CarPropertyValue v : propValues) { 139 mValues.put(PropKey.of(v), v); 140 if (triggerListeners) { 141 sendEvent(v); 142 } 143 } 144 } 145 sendEvent(CarPropertyValue v)146 private void sendEvent(CarPropertyValue v) { 147 Set<ListenerInfo> listeners = mListeners.get(v.getPropertyId()); 148 if (listeners != null) { 149 for (ListenerInfo listenerInfo : listeners) { 150 List<CarPropertyEvent> events = new ArrayList<>(); 151 events.add(new CarPropertyEvent(PROPERTY_EVENT_PROPERTY_CHANGE, v)); 152 try { 153 listenerInfo.mListener.onEvent(events); 154 } catch (RemoteException e) { 155 // This is impossible as the code runs within the same process in test. 156 throw new RuntimeException(e); 157 } 158 } 159 } 160 } 161 162 @Override getSetValues()163 public List<CarPropertyValue<?>> getSetValues() { 164 // Explicitly return the instance of this object rather than copying it such that test code 165 // will have a chance to clear this list if needed. 166 return mValuesSet; 167 } 168 169 /** Consists of property id and area */ 170 private static class PropKey { 171 final int mPropId; 172 final int mAreaId; 173 PropKey(int propId, int areaId)174 private PropKey(int propId, int areaId) { 175 this.mPropId = propId; 176 this.mAreaId = areaId; 177 } 178 of(int propId, int areaId)179 static PropKey of(int propId, int areaId) { 180 return new PropKey(propId, areaId); 181 } 182 of(CarPropertyValue carPropertyValue)183 static PropKey of(CarPropertyValue carPropertyValue) { 184 return of(carPropertyValue.getPropertyId(), carPropertyValue.getAreaId()); 185 } 186 187 @Override 188 equals(Object o)189 public boolean equals(Object o) { 190 if (this == o) { 191 return true; 192 } 193 if (!(o instanceof PropKey)) { 194 return false; 195 } 196 PropKey propKey = (PropKey) o; 197 return mPropId == propKey.mPropId && mAreaId == propKey.mAreaId; 198 } 199 200 @Override hashCode()201 public int hashCode() { 202 return Objects.hash(mPropId, mAreaId); 203 } 204 } 205 206 private static class ListenerInfo { 207 private final ICarPropertyEventListener mListener; 208 ListenerInfo(ICarPropertyEventListener listener)209 ListenerInfo(ICarPropertyEventListener listener) { 210 this.mListener = listener; 211 } 212 213 @Override equals(Object o)214 public boolean equals(Object o) { 215 if (this == o) { 216 return true; 217 } 218 if (!(o instanceof ListenerInfo)) { 219 return false; 220 } 221 ListenerInfo that = (ListenerInfo) o; 222 return Objects.equals(mListener, that.mListener); 223 } 224 225 @Override hashCode()226 public int hashCode() { 227 return Objects.hash(mListener); 228 } 229 } 230 getPropertyType(int propId)231 private static Class<?> getPropertyType(int propId) { 232 int type = propId & VehiclePropertyType.MASK; 233 switch (type) { 234 case VehiclePropertyType.BOOLEAN: 235 return Boolean.class; 236 case VehiclePropertyType.FLOAT: 237 return Float.class; 238 case VehiclePropertyType.INT32: 239 return Integer.class; 240 case VehiclePropertyType.INT64: 241 return Long.class; 242 case VehiclePropertyType.FLOAT_VEC: 243 return Float[].class; 244 case VehiclePropertyType.INT32_VEC: 245 return Integer[].class; 246 case VehiclePropertyType.INT64_VEC: 247 return Long[].class; 248 case VehiclePropertyType.STRING: 249 return String.class; 250 case VehiclePropertyType.BYTES: 251 return byte[].class; 252 case VehiclePropertyType.MIXED: 253 return Object.class; 254 default: 255 throw new IllegalArgumentException("Unexpected type: " + toHexString(type)); 256 } 257 } 258 getVehicleAreaType(int propId)259 private static int getVehicleAreaType(int propId) { 260 int halArea = propId & VehicleArea.MASK; 261 switch (halArea) { 262 case VehicleArea.GLOBAL: 263 return VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL; 264 case VehicleArea.SEAT: 265 return VehicleAreaType.VEHICLE_AREA_TYPE_SEAT; 266 case VehicleArea.DOOR: 267 return VehicleAreaType.VEHICLE_AREA_TYPE_DOOR; 268 case VehicleArea.WINDOW: 269 return VehicleAreaType.VEHICLE_AREA_TYPE_WINDOW; 270 case VehicleArea.MIRROR: 271 return VehicleAreaType.VEHICLE_AREA_TYPE_MIRROR; 272 case VehicleArea.WHEEL: 273 return VehicleAreaType.VEHICLE_AREA_TYPE_WHEEL; 274 default: 275 throw new RuntimeException("Unsupported area type " + halArea); 276 } 277 } 278 279 /** Copy from VHAL generated file VehicleArea.java */ 280 private static final class VehicleArea { 281 static final int GLOBAL = 16777216 /* 0x01000000 */; 282 static final int WINDOW = 50331648 /* 0x03000000 */; 283 static final int MIRROR = 67108864 /* 0x04000000 */; 284 static final int SEAT = 83886080 /* 0x05000000 */; 285 static final int DOOR = 100663296 /* 0x06000000 */; 286 static final int WHEEL = 117440512 /* 0x07000000 */; 287 static final int MASK = 251658240 /* 0x0f000000 */; 288 } 289 } 290