• 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 com.android.car.hal.property;
18 
19 import static android.car.Car.PERMISSION_VENDOR_EXTENSION;
20 
21 import static java.util.Objects.requireNonNull;
22 
23 import android.annotation.Nullable;
24 import android.car.VehiclePropertyIds;
25 import android.car.builtin.os.TraceHelper;
26 import android.car.builtin.util.Slogf;
27 import android.car.feature.FeatureFlags;
28 import android.car.feature.FeatureFlagsImpl;
29 import android.content.Context;
30 import android.hardware.automotive.vehicle.VehiclePropertyStatus;
31 import android.hardware.automotive.vehicle.VehiclePropertyType;
32 import android.os.Trace;
33 import android.util.ArraySet;
34 import android.util.JsonReader;
35 import android.util.SparseArray;
36 
37 import com.android.car.CarLog;
38 import com.android.car.hal.BidirectionalSparseIntArray;
39 import com.android.car.hal.HalPropValue;
40 import com.android.car.hal.property.PropertyPermissionInfo.AllOfPermissions;
41 import com.android.car.hal.property.PropertyPermissionInfo.AnyOfPermissions;
42 import com.android.car.hal.property.PropertyPermissionInfo.PermissionCondition;
43 import com.android.car.hal.property.PropertyPermissionInfo.PropertyPermissions;
44 import com.android.car.hal.property.PropertyPermissionInfo.PropertyPermissionsBuilder;
45 import com.android.car.hal.property.PropertyPermissionInfo.SinglePermission;
46 import com.android.car.internal.property.CarPropertyHelper;
47 import com.android.internal.annotations.GuardedBy;
48 import com.android.internal.annotations.VisibleForTesting;
49 
50 import java.io.IOException;
51 import java.io.InputStream;
52 import java.io.InputStreamReader;
53 import java.util.ArrayList;
54 import java.util.Collections;
55 import java.util.List;
56 import java.util.Set;
57 
58 /**
59  * A singleton class to define which AIDL HAL property IDs are used by PropertyHalService.
60  * This class binds the read and write permissions to the property ID.
61  */
62 public class PropertyHalServiceConfigs {
63     private static final long TRACE_TAG = TraceHelper.TRACE_TAG_CAR_SERVICE;
64     private static final Object sLock = new Object();
65     @GuardedBy("sLock")
66     private static PropertyHalServiceConfigs sPropertyHalServiceConfigs;
67     private static final PermissionCondition SINGLE_PERMISSION_VENDOR_EXTENSION =
68             new SinglePermission(PERMISSION_VENDOR_EXTENSION);
69     /**
70      * Represents one VHAL property that is exposed through
71      * {@link android.car.hardware.property.CarPropertyManager}.
72      *
73      * Note that the property ID is defined in {@link android.car.VehiclePropertyIds} and it might
74      * be different than the property ID used by VHAL, defined in {@link VehicleProperty}.
75      * The latter is represented by {@code halPropId}.
76      *
77      * If the property is an {@code Integer} enum property, its supported enum values are listed
78      * in {@code dataEnums}. If the property is a flag-combination property, its valid bit flag is
79      * listed in {@code validBitFlag}.
80      */
81     @VisibleForTesting
CarSvcPropertyConfig( int propertyId, int halPropId, String propertyName, String description, PropertyPermissions permissions, @Nullable Set<Integer> dataEnums, @Nullable Integer validBitFlag)82     /* package */ record CarSvcPropertyConfig(
83             int propertyId, int halPropId, String propertyName, String description,
84             PropertyPermissions permissions, @Nullable Set<Integer> dataEnums,
85             @Nullable Integer validBitFlag) {
86         public CarSvcPropertyConfig {
87             requireNonNull(propertyName);
88             requireNonNull(description);
89             requireNonNull(permissions);
90         }
91     }
92 
93     private static final String LATEST_CONFIG_RESOURCE_NAME = "CarSvcProps.json";
94     private static final String RELEASED_CONFIG_RESOURCE_NAME = "CarSvcProps-Released.json";
95     private static final String JSON_FIELD_NAME_PROPERTIES = "properties";
96 
97     private static final String REMOVE_SYSTEM_API_TAGS_FLAG_NAME =
98             "FLAG_VEHICLE_PROPERTY_REMOVE_SYSTEM_API_TAGS";
99     private static final String FLAG_25Q2_3P_PERMISSIONS =
100             "FLAG_VEHICLE_PROPERTY_25Q2_3P_PERMISSIONS";
101     private static final String B_FLAG_NAME = "FLAG_ANDROID_B_VEHICLE_PROPERTIES";
102 
103     private final FeatureFlags mFeatureFlags;
104 
105     private static final String TAG = CarLog.tagFor(PropertyHalServiceConfigs.class);
106 
107     private final SparseArray<Set<Integer>> mHalPropIdToEnumSet = new SparseArray<>();
108     private final SparseArray<CarSvcPropertyConfig> mHalPropIdToCarSvcConfig;
109     private final SparseArray<CarSvcPropertyConfig> mHalPropIdToReleasedCarSvcConfig;
110     private final BidirectionalSparseIntArray mMgrPropIdToHalPropId;
111 
112     private final Object mLock = new Object();
113     @GuardedBy("mLock")
114     private final SparseArray<PropertyPermissions> mVendorHalPropIdToPermissions =
115             new SparseArray<>();
116 
117     /**
118      * Should only be used in unit tests. Use {@link getInsance} instead.
119      */
120     @VisibleForTesting
PropertyHalServiceConfigs(@ullable FeatureFlags featureFlags)121     public PropertyHalServiceConfigs(@Nullable FeatureFlags featureFlags) {
122         Trace.traceBegin(TRACE_TAG, "initialize PropertyHalServiceConfigs");
123         if (featureFlags == null) {
124             mFeatureFlags = new FeatureFlagsImpl();
125         } else {
126             mFeatureFlags = featureFlags;
127         }
128 
129         // Read config from previous release.
130         mHalPropIdToReleasedCarSvcConfig = readJsonConfig(RELEASED_CONFIG_RESOURCE_NAME, null);
131         // Read latest generated config.
132         mHalPropIdToCarSvcConfig = readJsonConfig(
133                 LATEST_CONFIG_RESOURCE_NAME, mHalPropIdToReleasedCarSvcConfig);
134 
135         List<Integer> halPropIdMgrIds = new ArrayList<>();
136         for (int i = 0; i < mHalPropIdToCarSvcConfig.size(); i++) {
137             CarSvcPropertyConfig config = mHalPropIdToCarSvcConfig.valueAt(i);
138             if (config.halPropId() != config.propertyId()) {
139                 halPropIdMgrIds.add(config.propertyId());
140                 halPropIdMgrIds.add(config.halPropId());
141             }
142         }
143         int[] halPropIdMgrIdArray = new int[halPropIdMgrIds.size()];
144         for (int i = 0; i < halPropIdMgrIds.size(); i++) {
145             halPropIdMgrIdArray[i] = halPropIdMgrIds.get(i);
146         }
147         mMgrPropIdToHalPropId = BidirectionalSparseIntArray.create(halPropIdMgrIdArray);
148         Trace.traceEnd(TRACE_TAG);
149     }
150 
151     /**
152      * Common helper function for reading a car svc json config in an input stream, and the parsing
153      * the json for the car property config information.
154      *
155      * @throws IllegalStateException if failed to open/close resource input stream.
156      */
readJsonConfig(String resourceName, SparseArray<CarSvcPropertyConfig> halPropIdToReleasedCarSvcConfig)157     private SparseArray<CarSvcPropertyConfig> readJsonConfig(String resourceName,
158             SparseArray<CarSvcPropertyConfig> halPropIdToReleasedCarSvcConfig) {
159         try (InputStream defaultConfigInputStream =
160                 this.getClass().getClassLoader().getResourceAsStream(resourceName)) {
161             return parseJsonConfig(defaultConfigInputStream,
162                     "defaultResource", halPropIdToReleasedCarSvcConfig);
163         } catch (IOException e) {
164             String errorMsg = "Failed to open/close resource input stream for: " + resourceName;
165             Slogf.e(TAG, errorMsg, e);
166             throw new IllegalStateException(errorMsg, e);
167         }
168     }
169 
170     /**
171      * Gets the hard-coded HAL property ID to enum value set. For unit test only.
172      *
173      * TODO(b/293354967): Remove this once we migrate to runtime config.
174      */
175     @VisibleForTesting
getHalPropIdToEnumSet()176     /* package */ SparseArray<Set<Integer>> getHalPropIdToEnumSet() {
177         return mHalPropIdToEnumSet;
178     }
179 
180     /**
181      * Gets a list of all system VHAL property IDs. For unit test only.
182      */
183     @VisibleForTesting
getAllSystemHalPropIds()184     /* package */ List<Integer> getAllSystemHalPropIds() {
185         List<Integer> halPropIds = new ArrayList<>();
186         for (int i = 0; i < mHalPropIdToCarSvcConfig.size(); i++) {
187             halPropIds.add(mHalPropIdToCarSvcConfig.keyAt(i));
188         }
189         return halPropIds;
190     }
191 
192     /**
193      * Gets the singleton instance for {@link PropertyHalServiceConfigs}.
194      */
getInstance()195     public static PropertyHalServiceConfigs getInstance() {
196         synchronized (sLock) {
197             if (sPropertyHalServiceConfigs == null) {
198                 sPropertyHalServiceConfigs = new PropertyHalServiceConfigs(
199                         /* featureFlags= */ null);
200             }
201             return sPropertyHalServiceConfigs;
202         }
203     }
204 
205     /**
206      * Create a new instance for {@link PropertyHalServiceConfigs}.
207      */
208     @VisibleForTesting
newConfigs()209     public static PropertyHalServiceConfigs newConfigs() {
210         return new PropertyHalServiceConfigs(/* featureFlags= */ null);
211     }
212 
213     /**
214      * Returns all possible supported enum values for the {@code halPropId}. If property does not
215      * support an enum, then it returns {@code null}.
216      */
217     @Nullable
getAllPossibleSupportedEnumValues(int halPropId)218     public Set<Integer> getAllPossibleSupportedEnumValues(int halPropId) {
219         if (!mHalPropIdToCarSvcConfig.contains(halPropId)) {
220             return null;
221         }
222         Set<Integer> dataEnums = mHalPropIdToCarSvcConfig.get(halPropId).dataEnums;
223         return dataEnums != null ? Collections.unmodifiableSet(dataEnums) : null;
224     }
225 
226     /**
227      * Checks property value's format for all properties. Checks property value range if property
228      * has @data_enum flag in types.hal.
229      * @return true if property value's payload is valid.
230      */
checkPayload(HalPropValue propValue)231     public boolean checkPayload(HalPropValue propValue) {
232         int propId = propValue.getPropId();
233         // Mixed property uses config array to indicate the data format. Checked it when convert it
234         // to CarPropertyValue.
235         if ((propId & VehiclePropertyType.MASK) == VehiclePropertyType.MIXED) {
236             return true;
237         }
238         if (propValue.getStatus() != VehiclePropertyStatus.AVAILABLE) {
239             return true;
240         }
241         if (!checkFormatForAllProperties(propValue)) {
242             Slogf.e(TAG, "Property value: " + propValue + " has an invalid data format");
243             return false;
244         }
245         CarSvcPropertyConfig carSvcPropertyConfig = mHalPropIdToCarSvcConfig.get(propId);
246         if (carSvcPropertyConfig == null) {
247             // This is not a system property.
248             return true;
249         }
250         if (carSvcPropertyConfig.dataEnums() != null) {
251             return checkDataEnum(propValue, carSvcPropertyConfig.dataEnums());
252         }
253         if (carSvcPropertyConfig.validBitFlag() != null) {
254             return checkValidBitFlag(propValue, carSvcPropertyConfig.validBitFlag());
255         }
256         return true;
257     }
258 
259     /**
260      * Gets readPermission using a HAL-level propertyId.
261      *
262      * @param halPropId HAL-level propertyId
263      * @return PermissionCondition object that represents halPropId's readPermission
264      */
265     @Nullable
getReadPermission(int halPropId)266     public PermissionCondition getReadPermission(int halPropId) {
267         if (CarPropertyHelper.isVendorOrBackportedProperty(halPropId)) {
268             return getVendorReadPermission(halPropId);
269         }
270         CarSvcPropertyConfig carSvcPropertyConfig = mHalPropIdToCarSvcConfig.get(
271                 halPropId);
272         if (carSvcPropertyConfig == null) {
273             Slogf.w(TAG, "halPropId: "  + halPropIdToName(halPropId) + " is not supported,"
274                     + " no read permission");
275             return null;
276         }
277         return carSvcPropertyConfig.permissions().readPermission();
278     }
279 
280     @Nullable
getVendorReadPermission(int halPropId)281     private PermissionCondition getVendorReadPermission(int halPropId) {
282         String halPropIdName = halPropIdToName(halPropId);
283         synchronized (mLock) {
284             PropertyPermissions propertyPermissions = mVendorHalPropIdToPermissions.get(halPropId);
285             if (propertyPermissions == null) {
286                 Slogf.v(TAG, "no custom vendor read permission for: " + halPropIdName
287                         + ", default to PERMISSION_VENDOR_EXTENSION");
288                 return SINGLE_PERMISSION_VENDOR_EXTENSION;
289             }
290             PermissionCondition readPermission = propertyPermissions.readPermission();
291             if (readPermission == null) {
292                 Slogf.v(TAG, "vendor propId is not available for reading: " + halPropIdName);
293             }
294             return readPermission;
295         }
296     }
297 
298     /**
299      * Gets writePermission using a HAL-level propertyId.
300      *
301      * @param halPropId HAL-level propertyId
302      * @return PermissionCondition object that represents halPropId's writePermission
303      */
304     @Nullable
getWritePermission(int halPropId)305     public PermissionCondition getWritePermission(int halPropId) {
306         if (CarPropertyHelper.isVendorOrBackportedProperty(halPropId)) {
307             return getVendorWritePermission(halPropId);
308         }
309         CarSvcPropertyConfig carSvcPropertyConfig = mHalPropIdToCarSvcConfig.get(
310                 halPropId);
311         if (carSvcPropertyConfig == null) {
312             Slogf.w(TAG, "halPropId: "  + halPropIdToName(halPropId) + " is not supported,"
313                     + " no write permission");
314             return null;
315         }
316         return carSvcPropertyConfig.permissions().writePermission();
317     }
318 
319     @Nullable
getVendorWritePermission(int halPropId)320     private PermissionCondition getVendorWritePermission(int halPropId) {
321         String halPropIdName = halPropIdToName(halPropId);
322         synchronized (mLock) {
323             PropertyPermissions propertyPermissions = mVendorHalPropIdToPermissions.get(halPropId);
324             if (propertyPermissions == null) {
325                 Slogf.v(TAG, "no custom vendor write permission for: " + halPropIdName
326                         + ", default to PERMISSION_VENDOR_EXTENSION");
327                 return SINGLE_PERMISSION_VENDOR_EXTENSION;
328             }
329             PermissionCondition writePermission = propertyPermissions.writePermission();
330             if (writePermission == null) {
331                 Slogf.v(TAG, "vendor propId is not available for writing: " + halPropIdName);
332             }
333             return writePermission;
334         }
335     }
336 
337     /**
338      * Checks if readPermission is granted for a HAL-level propertyId in a given context.
339      *
340      * @param halPropId HAL-level propertyId
341      * @param context Context to check
342      * @return readPermission is granted or not.
343      */
isReadable(Context context, int halPropId)344     public boolean isReadable(Context context, int halPropId) {
345         PermissionCondition readPermission = getReadPermission(halPropId);
346         if (readPermission == null) {
347             Slogf.v(TAG, "propId is not readable: " + halPropIdToName(halPropId));
348             return false;
349         }
350         return readPermission.isMet(context);
351     }
352 
353     /**
354      * Checks if writePermission is granted for a HAL-level propertyId in a given context.
355      *
356      * @param halPropId HAL-level propertyId
357      * @param context Context to check
358      * @return writePermission is granted or not.
359      */
isWritable(Context context, int halPropId)360     public boolean isWritable(Context context, int halPropId) {
361         PermissionCondition writePermission = getWritePermission(halPropId);
362         if (writePermission == null) {
363             Slogf.v(TAG, "propId is not writable: " + halPropIdToName(halPropId));
364             return false;
365         }
366         return writePermission.isMet(context);
367     }
368 
369     /**
370      * Checks if property ID is in the list of known IDs that PropertyHalService is interested it.
371      */
isSupportedProperty(int propId)372     public boolean isSupportedProperty(int propId) {
373         return mHalPropIdToCarSvcConfig.get(propId) != null
374                 || CarPropertyHelper.isVendorOrBackportedProperty(propId);
375     }
376 
377     /**
378      * Overrides the permission map for vendor properties
379      *
380      * @param configArray the configArray for
381      * {@link VehicleProperty#SUPPORT_CUSTOMIZE_VENDOR_PERMISSION}
382      */
customizeVendorPermission(int[] configArray)383     public void customizeVendorPermission(int[] configArray) {
384         if (configArray == null || configArray.length % 3 != 0) {
385             throw new IllegalArgumentException(
386                     "ConfigArray for SUPPORT_CUSTOMIZE_VENDOR_PERMISSION is wrong");
387         }
388         int index = 0;
389         while (index < configArray.length) {
390             int propId = configArray[index++];
391             if (!CarPropertyHelper.isVendorOrBackportedProperty(propId)) {
392                 throw new IllegalArgumentException("Property Id: " + propId
393                         + " is not in vendor range");
394             }
395             int readPermission = configArray[index++];
396             int writePermission = configArray[index++];
397             String readPermissionStr = PropertyPermissionInfo.toPermissionString(
398                     readPermission, propId);
399             String writePermissionStr = PropertyPermissionInfo.toPermissionString(
400                     writePermission, propId);
401             synchronized (mLock) {
402                 if (mVendorHalPropIdToPermissions.get(propId) != null) {
403                     Slogf.e(TAG, "propId is a vendor property that already exists in "
404                             + "mVendorHalPropIdToPermissions and thus cannot have its "
405                             + "permissions overwritten: " + halPropIdToName(propId));
406                     continue;
407                 }
408 
409                 PropertyPermissionsBuilder propertyPermissionBuilder =
410                         new PropertyPermissionsBuilder();
411                 if (readPermissionStr != null) {
412                     propertyPermissionBuilder.setReadPermission(
413                             new SinglePermission(readPermissionStr));
414                 }
415                 if (writePermissionStr != null) {
416                     propertyPermissionBuilder.setWritePermission(
417                             new SinglePermission(writePermissionStr));
418                 }
419 
420                 mVendorHalPropIdToPermissions.put(propId, propertyPermissionBuilder.build());
421             }
422         }
423     }
424 
425     /**
426      * Converts manager property ID to Vehicle HAL property ID.
427      */
managerToHalPropId(int mgrPropId)428     public int managerToHalPropId(int mgrPropId) {
429         return mMgrPropIdToHalPropId.getValue(mgrPropId, mgrPropId);
430     }
431 
432     /**
433      * Converts Vehicle HAL property ID to manager property ID.
434      */
halToManagerPropId(int halPropId)435     public int halToManagerPropId(int halPropId) {
436         return mMgrPropIdToHalPropId.getKey(halPropId, halPropId);
437     }
438 
439     /**
440      * Print out the name for a VHAL property Id.
441      *
442      * For debugging only.
443      */
halPropIdToName(int halPropId)444     public String halPropIdToName(int halPropId) {
445         return VehiclePropertyIds.toString(halToManagerPropId(halPropId));
446     }
447 
checkValidBitFlag(HalPropValue propValue, int flagCombination)448     private static boolean checkValidBitFlag(HalPropValue propValue, int flagCombination) {
449         for (int i = 0; i < propValue.getInt32ValuesSize(); i++) {
450             int value = propValue.getInt32Value(i);
451             if ((value & flagCombination) != value) {
452                 return false;
453             }
454         }
455         return true;
456     }
457 
458     /**
459      * Parses the latest car service JSON config file with an already parsed released-version
460      * of the car service JSON config file. Only exposed for testing.
461      */
462     @VisibleForTesting
parseJsonConfig( InputStream configFile, String path, @Nullable SparseArray<CarSvcPropertyConfig> halPropIdToReleasedCarSvcConfig)463     /* package */ SparseArray<CarSvcPropertyConfig> parseJsonConfig(
464             InputStream configFile,
465             String path,
466             @Nullable SparseArray<CarSvcPropertyConfig> halPropIdToReleasedCarSvcConfig) {
467         try {
468             SparseArray<CarSvcPropertyConfig> configs = new SparseArray<>();
469             try (var reader = new JsonReader(new InputStreamReader(configFile, "UTF-8"))) {
470                 reader.setLenient(true);
471                 parseObjectEntry(reader, () -> {
472                     if (!reader.nextName().equals(JSON_FIELD_NAME_PROPERTIES)) {
473                         reader.skipValue();
474                         return;
475                     }
476                     parseObjectEntry(reader, () -> {
477                         String propertyName = reader.nextName();
478                         CarSvcPropertyConfig config;
479                         try {
480                             config = readPropertyObject(
481                                 propertyName, reader, halPropIdToReleasedCarSvcConfig);
482                         } catch (IllegalArgumentException e) {
483                             throw new IllegalArgumentException("Invalid json config for property: "
484                                      + propertyName + ", error: " + e);
485                         }
486                         if (config == null) {
487                             return;
488                         }
489                         configs.put(config.halPropId(), config);
490                     });
491                 });
492             }
493             return configs;
494         } catch (IllegalStateException | IOException e) {
495             throw new IllegalArgumentException("Config file: " + path
496                     + " does not contain a valid JSON object.", e);
497         }
498     }
499 
readPropertyObject( String propertyName, JsonReader reader, @Nullable SparseArray<CarSvcPropertyConfig> halPropIdToReleasedCarSvcConfig)500     private @Nullable CarSvcPropertyConfig readPropertyObject(
501             String propertyName, JsonReader reader,
502             @Nullable SparseArray<CarSvcPropertyConfig> halPropIdToReleasedCarSvcConfig)
503                     throws IOException {
504         String featureFlag = null;
505         boolean deprecated = false;
506         int propertyId = 0;
507         int vhalPropertyId = 0;
508         String description = null;
509         ArraySet<Integer> dataEnums = new ArraySet<Integer>();
510         List<Integer> dataFlags = new ArrayList<>();
511         PermissionCondition readPermission = null;
512         PermissionCondition writePermission = null;
513         // Starts parsing each field.
514         reader.beginObject();
515         while (reader.hasNext()) {
516             String name = reader.nextName();
517             switch (name) {
518                 case "featureFlag":
519                     featureFlag = reader.nextString();
520                     break;
521                 case "deprecated":
522                     deprecated = reader.nextBoolean();
523                     break;
524                 case "propertyId":
525                     propertyId = reader.nextInt();
526                     break;
527                 case "vhalPropertyId":
528                     vhalPropertyId = reader.nextInt();
529                     break;
530                 case "description":
531                     description = reader.nextString();
532                     break;
533                 case "dataEnums":
534                     reader.beginArray();
535                     while (reader.hasNext()) {
536                         dataEnums.add(reader.nextInt());
537                     }
538                     reader.endArray();
539                     break;
540                 case "dataFlags":
541                     reader.beginArray();
542                     while (reader.hasNext()) {
543                         dataFlags.add(reader.nextInt());
544                     }
545                     reader.endArray();
546                     break;
547                 case "readPermission":
548                     try {
549                         readPermission = parsePermissionCondition(reader);
550                     } catch (IllegalArgumentException e) {
551                         throw new IllegalArgumentException(
552                                 "Failed to parse read permissions for property: " + propertyName
553                                 + ", error: " + e);
554                     }
555                     break;
556                 case "writePermission":
557                     try {
558                         writePermission = parsePermissionCondition(reader);
559                     } catch (IllegalArgumentException e) {
560                         throw new IllegalArgumentException(
561                                 "Failed to parse write permissions for property: " + propertyName
562                                 + ", error: " + e);
563                     }
564                     break;
565                 default:
566                     reader.skipValue();
567             }
568         }
569         reader.endObject();
570 
571         // Finished parsing each field, now check whether the required fields are present and
572         // assign them to config.
573         if (deprecated) {
574             return null;
575         }
576         if (featureFlag != null) {
577             switch (featureFlag) {
578                 case REMOVE_SYSTEM_API_TAGS_FLAG_NAME:
579                     // do nothing as no behavior change
580                     break;
581                 case FLAG_25Q2_3P_PERMISSIONS:
582                     // Parsing the older released config. Skip the flag specific logic and continue.
583                     if (halPropIdToReleasedCarSvcConfig == null) {
584                         break;
585                     }
586                     // If flag is disabled, use vehicle property entry from old json file.
587                     if (!mFeatureFlags.vehicleProperty25q23pPermissions()) {
588                         return getReleasedCarSvcPropertyConfig(propertyId, featureFlag,
589                                 propertyName, halPropIdToReleasedCarSvcConfig);
590                     }
591                     break;
592                 case B_FLAG_NAME:
593                     // Parsing the older released config. Skip the flag specific logic and continue.
594                     if (halPropIdToReleasedCarSvcConfig == null) {
595                         break;
596                     }
597                     if (!mFeatureFlags.androidBVehicleProperties()
598                             && propertyId == VehiclePropertyIds.PERF_ODOMETER) {
599                         return getReleasedCarSvcPropertyConfig(
600                                 propertyId,
601                                 featureFlag,
602                                 propertyName,
603                                 halPropIdToReleasedCarSvcConfig);
604                     }
605                     if (!mFeatureFlags.androidBVehicleProperties()) {
606                         Slogf.w(TAG, "The required feature flag for property: %s is not enabled, "
607                                 + "so its config is ignored", propertyName);
608                         return null;
609                     }
610                     break;
611                 default:
612                     throw new IllegalArgumentException("Unknown feature flag: "
613                             + featureFlag + " for property: " + propertyName);
614             }
615         }
616         if (description == null) {
617             throw new IllegalArgumentException("Missing required description field for property: "
618                     + propertyName);
619         }
620         if (propertyId == 0) {
621             throw new IllegalArgumentException("Missing required propertyId field for property: "
622                     + propertyName);
623         }
624         int halPropId;
625         if (vhalPropertyId != 0) {
626             halPropId = vhalPropertyId;
627         } else {
628             halPropId = propertyId;
629         }
630         if (dataEnums.isEmpty()) {
631             dataEnums = null;
632         }
633         Integer validBitFlag = null;
634         if (!dataFlags.isEmpty()) {
635             validBitFlag = generateAllCombination(dataFlags);
636         }
637         if (readPermission == null && writePermission == null) {
638             throw new IllegalArgumentException(
639                     "No read or write permission specified for: " + propertyName);
640         }
641         var builder = new PropertyPermissionsBuilder();
642         if (readPermission != null) {
643             builder.setReadPermission(readPermission);
644         }
645         if (writePermission != null) {
646             builder.setWritePermission(writePermission);
647         }
648         PropertyPermissions permissions = builder.build();
649         return new CarSvcPropertyConfig(propertyId, halPropId, propertyName, description,
650                 permissions, dataEnums, validBitFlag);
651     }
652 
getReleasedCarSvcPropertyConfig( int propertyId, String featureFlag, String propertyName, @Nullable SparseArray<CarSvcPropertyConfig> halPropIdToReleasedCarSvcConfig)653     private CarSvcPropertyConfig getReleasedCarSvcPropertyConfig(
654             int propertyId,
655             String featureFlag,
656             String propertyName,
657             @Nullable SparseArray<CarSvcPropertyConfig> halPropIdToReleasedCarSvcConfig) {
658         for (int i = 0; i < halPropIdToReleasedCarSvcConfig.size(); i++) {
659             CarSvcPropertyConfig config =
660                     halPropIdToReleasedCarSvcConfig.valueAt(i);
661             if (config.propertyId() == propertyId) {
662                 return config;
663             }
664         }
665         // If no matching property id, then something is wrong. Every
666         // property changed in this feature flag should have been in the
667         // previous release.
668         throw new IllegalArgumentException("Unknown flag config: "
669                 + featureFlag + " for property: " + propertyName);
670     }
671 
672     private interface RunanbleWithException {
run()673         void run() throws IOException;
674     }
675 
parseObjectEntry(JsonReader reader, RunanbleWithException forEachEntry)676     private static void parseObjectEntry(JsonReader reader, RunanbleWithException forEachEntry)
677             throws IOException {
678         reader.beginObject();
679         while (reader.hasNext()) {
680             forEachEntry.run();
681         }
682         reader.endObject();
683     }
684 
parsePermissionCondition(JsonReader reader)685     private static PermissionCondition parsePermissionCondition(JsonReader reader)
686             throws IOException {
687         // we only have one type, use a list to be effective-final.
688         List<String> types = new ArrayList<>();
689         List<PermissionCondition> permissions = new ArrayList<>();
690         parseObjectEntry(reader, () -> {
691             String name = reader.nextName();
692             switch (name) {
693                 case "type":
694                     types.add(reader.nextString());
695                     break;
696                 case "value":
697                     try {
698                         permissions.add(new SinglePermission(reader.nextString()));
699                     } catch (IllegalStateException e) {
700                         // The value field is not a string, then it must be an array.
701                         reader.beginArray();
702                         while (reader.hasNext()) {
703                             permissions.add(parsePermissionCondition(reader));
704                         }
705                         reader.endArray();
706                     }
707                     break;
708                 default:
709                     reader.skipValue();
710             }
711         });
712         if (types.size() == 0) {
713             throw new IllegalArgumentException("Missing type field for permission");
714         }
715         String type = types.get(0);
716         if (permissions.size() < 1) {
717             throw new IllegalArgumentException("Missing valid value field for permission");
718         }
719         if (type.equals("single")) {
720             return permissions.get(0);
721         }
722         if (type.equals("anyOf")) {
723             return new AnyOfPermissions(permissions.toArray(new PermissionCondition[0]));
724         }
725         if (type.equals("allOf")) {
726             return new AllOfPermissions(permissions.toArray(new PermissionCondition[0]));
727         }
728         throw new IllegalArgumentException("Invalid permission type: " + type);
729     }
730 
checkFormatForAllProperties(HalPropValue propValue)731     private static boolean checkFormatForAllProperties(HalPropValue propValue) {
732         int propId = propValue.getPropId();
733         int vehiclePropertyType = propId & VehiclePropertyType.MASK;
734 
735         // Records sum size of int32values, floatValue, int64Values, bytes, String
736         int sizeOfAllValue = propValue.getInt32ValuesSize() + propValue.getFloatValuesSize()
737                 + propValue.getInt64ValuesSize() + propValue.getByteValuesSize()
738                 + propValue.getStringValue().length();
739         if (sizeOfAllValue == 0
740                 && vehiclePropertyType != VehiclePropertyType.FLOAT_VEC
741                 && vehiclePropertyType != VehiclePropertyType.INT64_VEC
742                 && vehiclePropertyType != VehiclePropertyType.INT32_VEC) {
743             Slogf.e(TAG, "Property value is empty: " + propValue);
744             return false;
745         }
746 
747         switch (vehiclePropertyType) {
748             case VehiclePropertyType.BOOLEAN:
749             case VehiclePropertyType.INT32:
750                 return sizeOfAllValue == 1 && propValue.getInt32ValuesSize() == 1;
751             case VehiclePropertyType.FLOAT:
752                 return sizeOfAllValue == 1 && propValue.getFloatValuesSize() == 1;
753             case VehiclePropertyType.INT64:
754                 return sizeOfAllValue == 1 && propValue.getInt64ValuesSize() == 1;
755             case VehiclePropertyType.FLOAT_VEC:
756                 return sizeOfAllValue == propValue.getFloatValuesSize();
757             case VehiclePropertyType.INT64_VEC:
758                 return sizeOfAllValue == propValue.getInt64ValuesSize();
759             case VehiclePropertyType.INT32_VEC:
760                 return sizeOfAllValue == propValue.getInt32ValuesSize();
761             case VehiclePropertyType.BYTES:
762                 return sizeOfAllValue == propValue.getByteValuesSize();
763             case VehiclePropertyType.STRING:
764                 return sizeOfAllValue == propValue.getStringValue().length();
765             default:
766                 throw new IllegalArgumentException("Unexpected property type for propId: "
767                         + Integer.toHexString(propId));
768         }
769     }
770 
checkDataEnum(HalPropValue propValue, Set<Integer> enums)771     private static boolean checkDataEnum(HalPropValue propValue, Set<Integer> enums) {
772         for (int i = 0; i < propValue.getInt32ValuesSize(); i++) {
773             if (!enums.contains(propValue.getInt32Value(i))) {
774                 return false;
775             }
776         }
777         return true;
778     }
779 
generateAllCombination(List<Integer> bitFlags)780     private static int generateAllCombination(List<Integer> bitFlags) {
781         int combination = bitFlags.get(0);
782         for (int i = 1; i < bitFlags.size(); i++) {
783             combination |= bitFlags.get(i);
784         }
785         return combination;
786     }
787 }
788