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