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