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