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.feature.Flags;
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.PropertyPermissionMapping;
36 import com.android.car.internal.property.AsyncPropertyServiceRequest;
37 import com.android.car.internal.property.AsyncPropertyServiceRequestList;
38 import com.android.car.internal.property.CarPropertyConfigList;
39 import com.android.car.internal.property.CarSubscription;
40 import com.android.car.internal.property.GetPropertyConfigListResult;
41 import com.android.car.internal.property.GetSetValueResult;
42 import com.android.car.internal.property.GetSetValueResultList;
43 import com.android.car.internal.property.IAsyncPropertyResultCallback;
44 import com.android.car.internal.util.PairSparseArray;
45 import com.android.internal.annotations.GuardedBy;
46 
47 import java.util.ArrayList;
48 import java.util.HashMap;
49 import java.util.List;
50 import java.util.Map;
51 import java.util.Objects;
52 import java.util.Set;
53 
54 /**
55  * This is fake implementation of the service which is used in
56  * {@link android.car.hardware.property.CarPropertyManager}.
57  *
58  * @hide
59  */
60 class FakeCarPropertyService extends ICarProperty.Stub implements CarPropertyController {
61     private final Map<Integer, CarPropertyConfig> mConfigs = new HashMap<>();
62     private final Map<PropKey, CarPropertyValue> mValues = new HashMap<>();
63 
64     private final PropertyPermissionMapping mPermissions = new PropertyPermissionMapping();
65 
66     // Contains a list of values that were set from the manager.
67     private final ArrayList<CarPropertyValue<?>> mValuesSet = new ArrayList<>();
68 
69     private final Object mLock = new Object();
70 
71     // Mapping between [propId, areaId] and a set of listeners.
72     @GuardedBy("mLock")
73     private final PairSparseArray<Set<ListenerInfo>> mListenersByPropIdAreaId =
74             new PairSparseArray<>();
75 
76     @Override
registerListener(List<CarSubscription> subscriptions, ICarPropertyEventListener listener)77     public void registerListener(List<CarSubscription> subscriptions,
78             ICarPropertyEventListener listener) {
79         synchronized (mLock) {
80             for (int i = 0; i < subscriptions.size(); i++) {
81                 int propId = subscriptions.get(i).propertyId;
82                 for (int areaId : subscriptions.get(i).areaIds) {
83                     Set<ListenerInfo> propListeners = mListenersByPropIdAreaId.get(propId, areaId);
84                     if (propListeners == null) {
85                         propListeners = new ArraySet<>();
86                         mListenersByPropIdAreaId.put(propId, areaId, propListeners);
87                     }
88 
89                     propListeners.add(new ListenerInfo(listener));
90                 }
91             }
92         }
93     }
94 
95     @Override
unregisterListener(int propId, ICarPropertyEventListener listener)96     public void unregisterListener(int propId, ICarPropertyEventListener listener)
97             throws RemoteException {
98         synchronized (mLock) {
99             for (int areaId : mListenersByPropIdAreaId.getSecondKeysForFirstKey(propId)) {
100                 Set<ListenerInfo> propListeners = mListenersByPropIdAreaId.get(propId, areaId);
101                 if (propListeners.remove(new ListenerInfo(listener)) && propListeners.isEmpty()) {
102                     mListenersByPropIdAreaId.remove(propId, areaId);
103                 }
104             }
105         }
106     }
107 
108     @Override
getPropertyList()109     public CarPropertyConfigList getPropertyList() throws RemoteException {
110         return new CarPropertyConfigList(new ArrayList<>(mConfigs.values()));
111     }
112 
113     @Override
getPropertyConfigList(int[] propIds)114     public GetPropertyConfigListResult getPropertyConfigList(int[] propIds) {
115         List<CarPropertyConfig> configs = new ArrayList<>(propIds.length);
116         for (int prop : propIds) {
117             CarPropertyConfig cfg = mConfigs.get(prop);
118             if (cfg != null) {
119                 configs.add(cfg);
120             }
121         }
122         GetPropertyConfigListResult result = new GetPropertyConfigListResult();
123         result.unsupportedPropIds = new int[0];
124         result.missingPermissionPropIds = new int[0];
125         result.carPropertyConfigList = new CarPropertyConfigList(configs);
126         return result;
127     }
128 
129     @Override
getPropertiesAsync(AsyncPropertyServiceRequestList asyncPropertyServiceRequests, IAsyncPropertyResultCallback asyncPropertyResultCallback, long timeoutInMs)130     public void getPropertiesAsync(AsyncPropertyServiceRequestList asyncPropertyServiceRequests,
131             IAsyncPropertyResultCallback asyncPropertyResultCallback, long timeoutInMs)
132             throws RemoteException {
133         List<AsyncPropertyServiceRequest> asyncPropertyServiceRequestList =
134                 asyncPropertyServiceRequests.getList();
135         List<GetSetValueResult> getValueResults = new ArrayList<>();
136         for (int i = 0; i < asyncPropertyServiceRequestList.size(); i++) {
137             AsyncPropertyServiceRequest asyncPropertyServiceRequest =
138                     asyncPropertyServiceRequestList.get(i);
139             getValueResults.add(GetSetValueResult.newGetValueResult(
140                     asyncPropertyServiceRequest.getRequestId(),
141                     getProperty(asyncPropertyServiceRequest.getPropertyId(),
142                             asyncPropertyServiceRequest.getAreaId())));
143         }
144         asyncPropertyResultCallback.onGetValueResults(new GetSetValueResultList(getValueResults));
145     }
146 
147     @Override
setPropertiesAsync(AsyncPropertyServiceRequestList asyncPropertyServiceRequests, IAsyncPropertyResultCallback asyncPropertyResultCallback, long timeoutInMs)148     public void setPropertiesAsync(AsyncPropertyServiceRequestList asyncPropertyServiceRequests,
149             IAsyncPropertyResultCallback asyncPropertyResultCallback, long timeoutInMs)
150             throws RemoteException {
151         List<AsyncPropertyServiceRequest> asyncPropertyServiceRequestList =
152                 asyncPropertyServiceRequests.getList();
153         List<GetSetValueResult> setValueResults = new ArrayList<>();
154         for (int i = 0; i < asyncPropertyServiceRequestList.size(); i++) {
155             AsyncPropertyServiceRequest asyncPropertyServiceRequest =
156                     asyncPropertyServiceRequestList.get(i);
157             setProperty(asyncPropertyServiceRequest.getCarPropertyValue(), /* listener= */ null);
158             setValueResults.add(GetSetValueResult.newSetValueResult(
159                     asyncPropertyServiceRequest.getRequestId(), /* updateTimestampNanos= */ 0));
160         }
161         asyncPropertyResultCallback.onSetValueResults(new GetSetValueResultList(setValueResults));
162     }
163 
164     @Override
getProperty(int prop, int zone)165     public CarPropertyValue getProperty(int prop, int zone) throws RemoteException {
166         return mValues.get(PropKey.of(prop, zone));
167     }
168 
169     @Override
setProperty(CarPropertyValue prop, ICarPropertyEventListener listener)170     public void setProperty(CarPropertyValue prop, ICarPropertyEventListener listener)
171             throws RemoteException {
172         mValues.put(PropKey.of(prop), prop);
173         mValuesSet.add(prop);
174         sendEvent(prop);
175     }
176 
177     @Override
cancelRequests(int[] serviceRequestIds)178     public void cancelRequests(int[] serviceRequestIds) {
179         // Do nothing.
180     }
181 
182     @Override
getReadPermission(int propId)183     public String getReadPermission(int propId) throws RemoteException {
184         return mConfigs.containsKey(propId) ? mPermissions.getReadPermission(propId) : null;
185     }
186 
187     @Override
getWritePermission(int propId)188     public String getWritePermission(int propId) throws RemoteException {
189         return mConfigs.containsKey(propId) ? mPermissions.getWritePermission(propId) : null;
190     }
191 
192     @Override
getSupportedNoReadPermPropIds(int[] propertyids)193     public int[] getSupportedNoReadPermPropIds(int[] propertyids) {
194         return new int[0];
195     }
196 
197     @Override
isSupportedAndHasWritePermissionOnly(int propertyId)198     public boolean isSupportedAndHasWritePermissionOnly(int propertyId) {
199         return false;
200     }
201 
202     @Override
addProperty(Integer propId, Object value)203     public CarPropertyController addProperty(Integer propId, Object value) {
204         int areaType = getVehicleAreaType(propId);
205         Class<?> type = getPropertyType(propId);
206         CarPropertyConfig.Builder<?> builder = CarPropertyConfig
207                 .newBuilder(type, propId, areaType);
208         mConfigs.put(propId, builder.build());
209         if (value != null) {
210             updateValues(false, new CarPropertyValue<>(propId, 0, value));
211         }
212 
213         return this;
214     }
215 
216     @Override
addProperty(CarPropertyConfig<?> config, @Nullable CarPropertyValue<?> value)217     public CarPropertyController addProperty(CarPropertyConfig<?> config,
218             @Nullable CarPropertyValue<?> value) {
219         mConfigs.put(config.getPropertyId(), config);
220         if (value != null) {
221             updateValues(false, value);
222         }
223         return this;
224     }
225 
226     @Override
updateValues(boolean triggerListeners, CarPropertyValue<?>... propValues)227     public void updateValues(boolean triggerListeners, CarPropertyValue<?>... propValues) {
228         for (CarPropertyValue v : propValues) {
229             mValues.put(PropKey.of(v), v);
230             if (triggerListeners) {
231                 sendEvent(v);
232             }
233         }
234     }
235 
sendEvent(CarPropertyValue v)236     private void sendEvent(CarPropertyValue v) {
237         synchronized (mLock) {
238             Set<ListenerInfo> listeners = mListenersByPropIdAreaId.get(v.getPropertyId(),
239                     v.getAreaId());
240             if (listeners != null) {
241                 for (ListenerInfo listenerInfo : listeners) {
242                     List<CarPropertyEvent> events = new ArrayList<>();
243                     events.add(new CarPropertyEvent(PROPERTY_EVENT_PROPERTY_CHANGE, v));
244                     try {
245                         listenerInfo.mListener.onEvent(events);
246                     } catch (RemoteException e) {
247                         // This is impossible as the code runs within the same process in test.
248                         throw new RuntimeException(e);
249                     }
250                 }
251             }
252         }
253     }
254 
255     @Override
getSetValues()256     public List<CarPropertyValue<?>> getSetValues() {
257         // Explicitly return the instance of this object rather than copying it such that test code
258         // will have a chance to clear this list if needed.
259         return mValuesSet;
260     }
261 
262     /** Consists of property id and area */
263     private static class PropKey {
264         final int mPropId;
265         final int mAreaId;
266 
PropKey(int propId, int areaId)267         private PropKey(int propId, int areaId) {
268             this.mPropId = propId;
269             this.mAreaId = areaId;
270         }
271 
of(int propId, int areaId)272         static PropKey of(int propId, int areaId) {
273             return new PropKey(propId, areaId);
274         }
275 
of(CarPropertyValue carPropertyValue)276         static PropKey of(CarPropertyValue carPropertyValue) {
277             return of(carPropertyValue.getPropertyId(), carPropertyValue.getAreaId());
278         }
279 
280         @Override
281 
equals(Object o)282         public boolean equals(Object o) {
283             if (this == o) {
284                 return true;
285             }
286             if (!(o instanceof PropKey)) {
287                 return false;
288             }
289             PropKey propKey = (PropKey) o;
290             return mPropId == propKey.mPropId && mAreaId == propKey.mAreaId;
291         }
292 
293         @Override
hashCode()294         public int hashCode() {
295             return Objects.hash(mPropId, mAreaId);
296         }
297     }
298 
299     private static class ListenerInfo {
300         private final ICarPropertyEventListener mListener;
301 
ListenerInfo(ICarPropertyEventListener listener)302         ListenerInfo(ICarPropertyEventListener listener) {
303             this.mListener = listener;
304         }
305 
306         @Override
equals(Object o)307         public boolean equals(Object o) {
308             if (this == o) {
309                 return true;
310             }
311             if (!(o instanceof ListenerInfo)) {
312                 return false;
313             }
314             ListenerInfo that = (ListenerInfo) o;
315             return Objects.equals(mListener, that.mListener);
316         }
317 
318         @Override
hashCode()319         public int hashCode() {
320             return Objects.hash(mListener);
321         }
322     }
323 
getPropertyType(int propId)324     private static Class<?> getPropertyType(int propId) {
325         int type = propId & VehiclePropertyType.MASK;
326         switch (type) {
327             case VehiclePropertyType.BOOLEAN:
328                 return Boolean.class;
329             case VehiclePropertyType.FLOAT:
330                 return Float.class;
331             case VehiclePropertyType.INT32:
332                 return Integer.class;
333             case VehiclePropertyType.INT64:
334                 return Long.class;
335             case VehiclePropertyType.FLOAT_VEC:
336                 return Float[].class;
337             case VehiclePropertyType.INT32_VEC:
338                 return Integer[].class;
339             case VehiclePropertyType.INT64_VEC:
340                 return Long[].class;
341             case VehiclePropertyType.STRING:
342                 return String.class;
343             case VehiclePropertyType.BYTES:
344                 return byte[].class;
345             case VehiclePropertyType.MIXED:
346                 return Object.class;
347             default:
348                 throw new IllegalArgumentException("Unexpected type: " + toHexString(type));
349         }
350     }
351 
getVehicleAreaType(int propId)352     private static int getVehicleAreaType(int propId) {
353         int halArea = propId & VehicleArea.MASK;
354         switch (halArea) {
355             case VehicleArea.GLOBAL:
356                 return VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL;
357             case VehicleArea.SEAT:
358                 return VehicleAreaType.VEHICLE_AREA_TYPE_SEAT;
359             case VehicleArea.DOOR:
360                 return VehicleAreaType.VEHICLE_AREA_TYPE_DOOR;
361             case VehicleArea.WINDOW:
362                 return VehicleAreaType.VEHICLE_AREA_TYPE_WINDOW;
363             case VehicleArea.MIRROR:
364                 return VehicleAreaType.VEHICLE_AREA_TYPE_MIRROR;
365             case VehicleArea.WHEEL:
366                 return VehicleAreaType.VEHICLE_AREA_TYPE_WHEEL;
367             default:
368                 if (Flags.androidVicVehicleProperties()) {
369                     if (halArea == VehicleArea.VENDOR) {
370                         return VehicleAreaType.VEHICLE_AREA_TYPE_VENDOR;
371                     }
372                 }
373                 throw new RuntimeException("Unsupported area type " + halArea);
374         }
375     }
376 
377     /** Copy from VHAL generated file VehicleArea.java */
378     private static final class VehicleArea {
379         static final int GLOBAL = 0x01000000;
380         static final int WINDOW = 0x03000000;
381         static final int MIRROR = 0x04000000;
382         static final int SEAT = 0x05000000;
383         static final int DOOR = 0x06000000;
384         static final int WHEEL = 0x07000000;
385         static final int VENDOR = 0x08000000;
386         static final int MASK = 0x0f000000;
387     }
388 }
389