• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.cts.utils;
18 
19 import android.car.VehicleAreaType;
20 import android.car.VehiclePropertyIds;
21 import android.car.hardware.CarPropertyConfig;
22 import android.util.ArrayMap;
23 import android.util.SparseArray;
24 
25 import androidx.annotation.Nullable;
26 
27 import com.google.common.collect.ImmutableList;
28 import com.google.common.collect.ImmutableSet;
29 
30 import org.json.JSONArray;
31 import org.json.JSONException;
32 import org.json.JSONObject;
33 
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.util.ArrayList;
37 import java.util.Iterator;
38 import java.util.List;
39 import java.util.Map;
40 
41 /** A parser for CarSvcProps.json which is the config file for car property service. */
42 public final class CarSvcPropsParser {
43     private static final String CONFIG_RESOURCE_NAME = "CarSvcProps.json";
44     private static final String JSON_FIELD_NAME_PROPERTIES = "properties";
45 
46     private static final String PERM_TYPE_SINGLE = "single";
47     private static final String PERM_TYPE_ANYOF = "anyOf";
48     private static final String PERM_TYPE_ALLOF = "allOf";
49     private static final String JSON_FIELD_VALUE = "value";
50     private static final String JSON_FIELD_TYPE = "type";
51 
52     // The CarSvcProps.json file version that we can parse.
53     private static final int CAR_SVC_PROP_JSON_VERSION = 1;
54 
55     private final SparseArray<VehiclePropertyIdInfo> mVehiclePropertyIdInfoByPropertyId =
56             new SparseArray<>();
57     private final List<Integer> mAllPropertyIds = new ArrayList<>();
58     private final Map<String, List<Integer>> mSystemPropertyIdsByFlag = new ArrayMap<>();
59 
60     public static class VehiclePropertyIdInfo {
61         // This is one of VehiclePropertyIds.
62         public int propertyId;
63         public String propertyName;
64         // The element should be one of VehiclePropertyAccessType
65         public ImmutableSet<Integer> allowedAccessModes;
66         // This is one of VehicleAreaTypeValue.
67         public int areaType;
68         // This is one of VehiclePropertyChangeModeType.
69         public int changeMode;
70         public Class<?> propertyType;
71         // Any one of the read permission in this list is required.
72         public ImmutableSet<String> readPermissions;
73         // Any one of the condition in this list is required. For each condition, all of the
74         // permissions are required.
75         public ImmutableList<ImmutableSet<String>> writePermissions;
76     }
77 
parseAllowedAccessModes( JSONArray jsonArray, String propertyName)78     private static ImmutableSet<Integer> parseAllowedAccessModes(
79             JSONArray jsonArray, String propertyName) throws JSONException {
80         ImmutableSet.Builder<Integer> allowedAccessModes = ImmutableSet.builder();
81         for (int i = 0; i < jsonArray.length(); i++) {
82             String accessModeStr = jsonArray.getString(i);
83             switch (accessModeStr) {
84                 case "READ":
85                     allowedAccessModes.add(CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ);
86                     break;
87                 case "WRITE":
88                     allowedAccessModes.add(CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE);
89                     break;
90                 case "READ_WRITE":
91                     allowedAccessModes.add(CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE);
92                     break;
93                 default:
94                     throw new IllegalArgumentException(
95                             "Invalid access mode: "
96                                     + accessModeStr
97                                     + " for property: "
98                                     + propertyName);
99             }
100         }
101         return allowedAccessModes.build();
102     }
103 
parseChangeMode(String changeModeStr, String propertyName)104     private static int parseChangeMode(String changeModeStr, String propertyName) {
105         switch (changeModeStr) {
106             case "STATIC":
107                 return CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_STATIC;
108             case "ONCHANGE":
109                 return CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE;
110             case "CONTINUOUS":
111                 return CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS;
112             default:
113                 throw new IllegalArgumentException(
114                         "Invalid change mode: " + changeModeStr + " for property: " + propertyName);
115         }
116     }
117 
parseAreaType(String areaTypeStr, String propertyName)118     private static int parseAreaType(String areaTypeStr, String propertyName) {
119         switch (areaTypeStr) {
120             case "GLOBAL":
121                 return VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL;
122             case "WINDOW":
123                 return VehicleAreaType.VEHICLE_AREA_TYPE_WINDOW;
124             case "SEAT":
125                 return VehicleAreaType.VEHICLE_AREA_TYPE_SEAT;
126             case "DOOR":
127                 return VehicleAreaType.VEHICLE_AREA_TYPE_DOOR;
128             case "MIRROR":
129                 return VehicleAreaType.VEHICLE_AREA_TYPE_MIRROR;
130             case "WHEEL":
131                 return VehicleAreaType.VEHICLE_AREA_TYPE_WHEEL;
132             case "VENDOR":
133                 return VehicleAreaType.VEHICLE_AREA_TYPE_VENDOR;
134             default:
135                 throw new IllegalArgumentException(
136                         "Invalid area type: " + areaTypeStr + " for property: " + propertyName);
137         }
138     }
139 
parsePropertyType(String propertyTypeStr, String propertyName)140     private static Class<?> parsePropertyType(String propertyTypeStr, String propertyName) {
141         switch (propertyTypeStr) {
142             case "Integer":
143                 return Integer.class;
144             case "Integer[]":
145                 return Integer[].class;
146             case "Long":
147                 return Long.class;
148             case "Long[]":
149                 return Long[].class;
150             case "Float":
151                 return Float.class;
152             case "Float[]":
153                 return Float[].class;
154             case "String":
155                 return String.class;
156             case "Boolean":
157                 return Boolean.class;
158             case "byte[]":
159                 return byte[].class;
160             case "Object[]":
161                 return Object[].class;
162             default:
163                 throw new IllegalArgumentException(
164                         "Invalid property type: "
165                                 + propertyTypeStr
166                                 + " for property: "
167                                 + propertyName);
168         }
169     }
170 
getSubPermissions(JSONObject permissionObj, int propertyId)171     private static ImmutableSet<String> getSubPermissions(JSONObject permissionObj, int propertyId)
172             throws JSONException {
173         ImmutableSet.Builder<String> subPermissions = ImmutableSet.builder();
174         var subFields = permissionObj.getJSONArray(JSON_FIELD_VALUE);
175         for (int i = 0; i < subFields.length(); i++) {
176             var subPermissionObj = subFields.getJSONObject(i);
177             if (!subPermissionObj.getString(JSON_FIELD_TYPE).equals(PERM_TYPE_SINGLE)) {
178                 throw new IllegalStateException(
179                         "sub permission type must be single for property: "
180                                 + VehiclePropertyIds.toString(propertyId));
181             }
182             subPermissions.add(subPermissionObj.getString(JSON_FIELD_VALUE));
183         }
184         return subPermissions.build();
185     }
186 
parseReadPermission( @ullable JSONObject permissionObj, int propertyId)187     private static ImmutableSet<String> parseReadPermission(
188             @Nullable JSONObject permissionObj, int propertyId) throws JSONException {
189         if (permissionObj == null) {
190             return ImmutableSet.of();
191         }
192         var type = permissionObj.getString(JSON_FIELD_TYPE);
193         switch (type) {
194             case PERM_TYPE_SINGLE:
195                 return ImmutableSet.of(permissionObj.getString(JSON_FIELD_VALUE));
196             case PERM_TYPE_ANYOF:
197                 return getSubPermissions(permissionObj, propertyId);
198                 // "allOf" is not supported for read.
199             default:
200                 throw new IllegalStateException(
201                         "Invalid read permission type: "
202                                 + type
203                                 + " for property: "
204                                 + VehiclePropertyIds.toString(propertyId));
205         }
206     }
207 
parseWritePermission( @ullable JSONObject permissionObj, int propertyId)208     private static ImmutableList<ImmutableSet<String>> parseWritePermission(
209             @Nullable JSONObject permissionObj, int propertyId) throws JSONException {
210         if (permissionObj == null) {
211             return ImmutableList.of();
212         }
213         var type = permissionObj.getString(JSON_FIELD_TYPE);
214         var permBuilder = ImmutableList.<ImmutableSet<String>>builder();
215         switch (type) {
216             case PERM_TYPE_SINGLE:
217                 return ImmutableList.of(ImmutableSet.of(permissionObj.getString(JSON_FIELD_VALUE)));
218             case PERM_TYPE_ANYOF:
219                 for (String subPermission : getSubPermissions(permissionObj, propertyId)) {
220                     permBuilder.add(ImmutableSet.of(subPermission));
221                 }
222                 return permBuilder.build();
223             case PERM_TYPE_ALLOF:
224                 return permBuilder.add(getSubPermissions(permissionObj, propertyId)).build();
225             default:
226                 throw new IllegalStateException(
227                         "Invalid write permission type: "
228                                 + type
229                                 + " for property: "
230                                 + VehiclePropertyIds.toString(propertyId));
231         }
232     }
233 
CarSvcPropsParser()234     public CarSvcPropsParser() {
235         String configString;
236         try (InputStream configFile =
237                 this.getClass().getClassLoader().getResourceAsStream(CONFIG_RESOURCE_NAME)) {
238             try {
239                 configString = new String(configFile.readAllBytes());
240             } catch (IOException e) {
241                 throw new IllegalStateException(
242                         "Cannot read from config file: " + CONFIG_RESOURCE_NAME, e);
243             }
244         } catch (IOException e) {
245             throw new IllegalStateException("Failed to close config resource stream", e);
246         }
247 
248         JSONObject configJsonObject;
249         try {
250             configJsonObject = new JSONObject(configString);
251         } catch (JSONException e) {
252             throw new IllegalStateException(
253                     "Config file: "
254                             + CONFIG_RESOURCE_NAME
255                             + " does not contain a valid JSONObject.",
256                     e);
257         }
258 
259         int version = configJsonObject.optInt("version");
260         if (version > CAR_SVC_PROP_JSON_VERSION) {
261             throw new IllegalStateException(
262                     "Incompatible Car service property config JSON file version, only support: "
263                             + CAR_SVC_PROP_JSON_VERSION
264                             + ", actually: "
265                             + version);
266         }
267         try {
268             JSONObject properties = configJsonObject.getJSONObject(JSON_FIELD_NAME_PROPERTIES);
269             Iterator<String> keysIt = properties.keys();
270             while (keysIt.hasNext()) {
271                 String propertyName = keysIt.next();
272                 JSONObject propertyObj = properties.getJSONObject(propertyName);
273                 int propertyId = propertyObj.getInt("propertyId");
274                 // TURN_SIGNAL_STATE is deprecated but still exposed through CarPropertyManager.
275                 if (propertyId != VehiclePropertyIds.TURN_SIGNAL_STATE
276                         && propertyObj.optBoolean("deprecated")) {
277                     continue;
278                 }
279 
280                 String featureFlag = propertyObj.optString("featureFlag");
281                 if (!featureFlag.isEmpty()) {
282                     if (mSystemPropertyIdsByFlag.get(featureFlag) == null) {
283                         mSystemPropertyIdsByFlag.put(featureFlag, new ArrayList<>());
284                     }
285                     mSystemPropertyIdsByFlag.get(featureFlag).add(propertyId);
286                 }
287 
288                 VehiclePropertyIdInfo vehiclePropertyIdInfo = new VehiclePropertyIdInfo();
289                 vehiclePropertyIdInfo.propertyId = propertyId;
290                 vehiclePropertyIdInfo.propertyName = propertyName;
291                 vehiclePropertyIdInfo.allowedAccessModes =
292                         parseAllowedAccessModes(
293                                 propertyObj.getJSONArray("allowedAccessModes"), propertyName);
294                 vehiclePropertyIdInfo.changeMode =
295                         parseChangeMode(propertyObj.getString("changeMode"), propertyName);
296                 vehiclePropertyIdInfo.propertyType =
297                         parsePropertyType(propertyObj.getString("propertyType"), propertyName);
298                 vehiclePropertyIdInfo.areaType =
299                         parseAreaType(propertyObj.getString("areaType"), propertyName);
300                 vehiclePropertyIdInfo.readPermissions =
301                         parseReadPermission(
302                                 propertyObj.optJSONObject("readPermission"), propertyId);
303                 vehiclePropertyIdInfo.writePermissions =
304                         parseWritePermission(
305                                 propertyObj.optJSONObject("writePermission"), propertyId);
306                 mVehiclePropertyIdInfoByPropertyId.put(propertyId, vehiclePropertyIdInfo);
307                 mAllPropertyIds.add(propertyId);
308             }
309         } catch (JSONException e) {
310             throw new IllegalStateException(
311                     "Config file: " + CONFIG_RESOURCE_NAME + " has invalid JSON format.", e);
312         }
313     }
314 
315     /** Gets the VehiclePropertyId information. */
getVehiclePropertyIdInfo(int propertyId)316     public @Nullable VehiclePropertyIdInfo getVehiclePropertyIdInfo(int propertyId) {
317         return mVehiclePropertyIdInfoByPropertyId.get(propertyId);
318     }
319 
320     /** Gets all the defined system property IDs. */
getAllSystemPropertyIds()321     public List<Integer> getAllSystemPropertyIds() {
322         return new ArrayList<>(mAllPropertyIds);
323     }
324 
325     /** Gets the defined system property IDs under the given flag. */
getSystemPropertyIdsForFlag(String flag)326     public List<Integer> getSystemPropertyIdsForFlag(String flag) {
327         List<Integer> ids = mSystemPropertyIdsByFlag.get(flag);
328         if (ids == null) {
329             return new ArrayList<Integer>();
330         }
331         return new ArrayList<Integer>(ids);
332     }
333 }
334