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 com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.PRIVATE_CONSTRUCTOR;
20 import static com.android.car.internal.util.ConstantDebugUtils.toName;
21 
22 import static java.lang.Integer.toHexString;
23 
24 import android.annotation.Nullable;
25 import android.hardware.automotive.vehicle.EnumForVehicleProperty;
26 import android.hardware.automotive.vehicle.UnitsForVehicleProperty;
27 import android.hardware.automotive.vehicle.VehicleArea;
28 import android.hardware.automotive.vehicle.VehicleAreaDoor;
29 import android.hardware.automotive.vehicle.VehicleAreaMirror;
30 import android.hardware.automotive.vehicle.VehicleAreaSeat;
31 import android.hardware.automotive.vehicle.VehicleAreaWheel;
32 import android.hardware.automotive.vehicle.VehicleAreaWindow;
33 import android.hardware.automotive.vehicle.VehicleProperty;
34 import android.hardware.automotive.vehicle.VehiclePropertyAccess;
35 import android.hardware.automotive.vehicle.VehiclePropertyChangeMode;
36 import android.hardware.automotive.vehicle.VehiclePropertyGroup;
37 import android.hardware.automotive.vehicle.VehiclePropertyStatus;
38 import android.hardware.automotive.vehicle.VehiclePropertyType;
39 import android.hardware.automotive.vehicle.VehicleUnit;
40 import android.util.ArrayMap;
41 import android.util.Slog;
42 
43 import com.android.car.hal.HalPropValue;
44 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
45 import com.android.car.internal.property.CarPropertyHelper;
46 import com.android.car.internal.property.PropIdAreaId;
47 import com.android.car.internal.util.ConstantDebugUtils;
48 
49 import java.util.ArrayList;
50 import java.util.Arrays;
51 import java.util.Collections;
52 import java.util.List;
53 import java.util.Map;
54 import java.util.StringJoiner;
55 import java.util.concurrent.atomic.AtomicReference;
56 
57 /**
58  * Utility class for converting {@link VehicleProperty} related information to human-readable names.
59  */
60 public final class HalPropertyDebugUtils {
61     private static final String TAG = HalPropertyDebugUtils.class.getSimpleName();
62     private static final int MAX_BYTE_SIZE = 20;
63     private static final AtomicReference<Map<Class<?>, List<Integer>>> sClazzToAreaBitsHolder =
64             new AtomicReference<>();
65     private static final String NO_VALUE = "NO_VALUE";
66 
67 
68     /**
69      * HalPropertyDebugUtils only contains static fields and methods and must never be
70      * instantiated.
71      */
72     @ExcludeFromCodeCoverageGeneratedReport(reason = PRIVATE_CONSTRUCTOR)
HalPropertyDebugUtils()73     private HalPropertyDebugUtils() {
74         throw new UnsupportedOperationException("Must never be called");
75     }
76 
77     /**
78      * Gets a user-friendly representation string representation of a {@code propertyId}.
79      */
toPropertyIdString(int propertyId)80     public static String toPropertyIdString(int propertyId) {
81         String hexSuffix = "(0x" + toHexString(propertyId) + ")";
82         if (isSystemPropertyId(propertyId)) {
83             return toName(VehicleProperty.class, propertyId) + hexSuffix;
84         } else if (CarPropertyHelper.isVendorProperty(propertyId)) {
85             return "VENDOR_PROPERTY" + hexSuffix;
86         } else if (CarPropertyHelper.isBackportedProperty(propertyId)) {
87             return "BACKPORTED_PROPERTY" + hexSuffix;
88         }
89         return "INVALID_PROPERTY_ID" + hexSuffix;
90     }
91 
92     /**
93      * Gets the HAL property's ID based on the passed name.
94      */
95     @Nullable
toPropertyId(String propertyName)96     public static Integer toPropertyId(String propertyName) {
97         return ConstantDebugUtils.toValue(VehicleProperty.class, propertyName);
98     }
99 
100     /**
101      * Gets a user-friendly string representation of an {@code areaId} for the given
102      * {@code propertyId}.
103      */
toAreaIdString(int propertyId, int areaId)104     public static String toAreaIdString(int propertyId, int areaId) {
105         switch (propertyId & VehicleArea.MASK) {
106             case VehicleArea.GLOBAL -> {
107                 if (areaId == 0) {
108                     return "GLOBAL(0x0)";
109                 }
110                 return "INVALID_GLOBAL_AREA_ID(0x" + toHexString(areaId) + ")";
111             }
112             case VehicleArea.DOOR -> {
113                 return convertAreaIdToDebugString(VehicleAreaDoor.class, areaId);
114             }
115             case VehicleArea.SEAT -> {
116                 if (areaId == VehicleAreaSeat.UNKNOWN) {
117                     return toName(VehicleAreaSeat.class, areaId);
118                 }
119                 return convertAreaIdToDebugString(VehicleAreaSeat.class, areaId);
120             }
121             case VehicleArea.MIRROR -> {
122                 return convertAreaIdToDebugString(VehicleAreaMirror.class, areaId);
123             }
124             case VehicleArea.WHEEL -> {
125                 if (areaId == VehicleAreaWheel.UNKNOWN) {
126                     return toName(VehicleAreaWheel.class, areaId);
127                 }
128                 return convertAreaIdToDebugString(VehicleAreaWheel.class, areaId);
129             }
130             case VehicleArea.WINDOW -> {
131                 return convertAreaIdToDebugString(VehicleAreaWindow.class, areaId);
132             }
133             default -> {
134                 return "UNKNOWN_AREA_ID(0x" + toHexString(areaId) + ")";
135             }
136         }
137     }
138 
convertAreaIdToDebugString(Class<?> clazz, int areaId)139     private static String convertAreaIdToDebugString(Class<?> clazz, int areaId) {
140         String output = "";
141 
142         Map<Class<?>, List<Integer>> clazzToAreaBits = sClazzToAreaBitsHolder.get();
143         if (clazzToAreaBits == null || clazzToAreaBits.get(clazz) == null) {
144             clazzToAreaBits = getClazzToAreaBitsMapping(clazzToAreaBits, clazz);
145             sClazzToAreaBitsHolder.set(clazzToAreaBits);
146         }
147 
148         int areaBitMask = 0;
149         for (int i = 0; i < clazzToAreaBits.get(clazz).size(); i++) {
150             int areaBit = clazzToAreaBits.get(clazz).get(i).intValue();
151             if (areaBit == 0) {
152                 continue;
153             }
154             areaBitMask |= areaBit;
155             if ((areaId & areaBit) == areaBit) {
156                 if (!output.isEmpty()) {
157                     output += "|";
158                 }
159                 output += toName(clazz, areaBit);
160             }
161         }
162 
163         if ((areaId | areaBitMask) != areaBitMask || output.isEmpty()) {
164             output += "INVALID_" + clazz.getSimpleName() + "_AREA_ID";
165         }
166 
167         output += "(0x" + toHexString(areaId) + ")";
168         return output;
169     }
170 
getClazzToAreaBitsMapping( @ullable Map<Class<?>, List<Integer>> clazzToAreaBits, Class<?> clazz)171     private static Map<Class<?>, List<Integer>> getClazzToAreaBitsMapping(
172             @Nullable Map<Class<?>, List<Integer>> clazzToAreaBits, Class<?> clazz) {
173         Map<Class<?>, List<Integer>> outputClazzToAreaBits;
174         if (clazzToAreaBits == null) {
175             outputClazzToAreaBits = new ArrayMap<>();
176         } else {
177             outputClazzToAreaBits = new ArrayMap<>(clazzToAreaBits.size());
178             outputClazzToAreaBits.putAll(clazzToAreaBits);
179         }
180 
181         List<Integer> areaBits = new ArrayList<>(ConstantDebugUtils.getValues(clazz));
182         Collections.sort(areaBits, Collections.reverseOrder());
183 
184         outputClazzToAreaBits.put(clazz, areaBits);
185         return outputClazzToAreaBits;
186     }
187 
188     /**
189      * Gets a user-friendly representation string representation of the value of a
190      * {@link HalPropValue} instance.
191      */
toValueString(HalPropValue halPropValue)192     public static String toValueString(HalPropValue halPropValue) {
193         int propertyId = halPropValue.getPropId();
194         int valueType = propertyId & VehiclePropertyType.MASK;
195         String propertyUnits = getUnitsIfSupported(propertyId);
196         StringJoiner stringJoiner = new StringJoiner(", ", "[", "]");
197         switch (valueType) {
198             case VehiclePropertyType.BOOLEAN -> {
199                 if (halPropValue.getInt32ValuesSize() != 1) {
200                     return NO_VALUE;
201                 }
202                 return halPropValue.getInt32Value(0) == 0 ? "FALSE" : "TRUE";
203             }
204             case VehiclePropertyType.INT32 -> {
205                 if (halPropValue.getInt32ValuesSize() != 1) {
206                     return NO_VALUE;
207                 }
208                 return getIntValueName(propertyId, halPropValue.getInt32Value(0), propertyUnits);
209             }
210             case VehiclePropertyType.INT32_VEC -> {
211                 for (int i = 0; i < halPropValue.getInt32ValuesSize(); i++) {
212                     stringJoiner.add(getIntValueName(propertyId, halPropValue.getInt32Value(i),
213                             propertyUnits));
214                 }
215                 return stringJoiner.toString();
216             }
217             case VehiclePropertyType.FLOAT -> {
218                 if (halPropValue.getFloatValuesSize() != 1) {
219                     return NO_VALUE;
220                 }
221                 return halPropValue.getFloatValue(0) + propertyUnits;
222             }
223             case VehiclePropertyType.FLOAT_VEC -> {
224                 for (int i = 0; i < halPropValue.getFloatValuesSize(); i++) {
225                     stringJoiner.add(halPropValue.getFloatValue(i) + propertyUnits);
226                 }
227                 return stringJoiner.toString();
228             }
229             case VehiclePropertyType.INT64 -> {
230                 if (halPropValue.getInt64ValuesSize() != 1) {
231                     return NO_VALUE;
232                 }
233                 return halPropValue.getInt64Value(0) + propertyUnits;
234             }
235             case VehiclePropertyType.INT64_VEC -> {
236                 for (int i = 0; i < halPropValue.getInt64ValuesSize(); i++) {
237                     stringJoiner.add(halPropValue.getInt64Value(i) + propertyUnits);
238                 }
239                 return stringJoiner.toString();
240             }
241             case VehiclePropertyType.STRING -> {
242                 return halPropValue.getStringValue();
243             }
244             case VehiclePropertyType.BYTES -> {
245                 String bytesString = "";
246                 byte[] byteValues = halPropValue.getByteArray();
247                 if (byteValues.length > MAX_BYTE_SIZE) {
248                     byte[] bytes = Arrays.copyOf(byteValues, MAX_BYTE_SIZE);
249                     bytesString = Arrays.toString(bytes);
250                 } else {
251                     bytesString = Arrays.toString(byteValues);
252                 }
253                 return bytesString;
254             }
255         }
256         String bytesString = "";
257         byte[] byteValues = halPropValue.getByteArray();
258         if (byteValues.length > MAX_BYTE_SIZE) {
259             byte[] bytes = Arrays.copyOf(byteValues, MAX_BYTE_SIZE);
260             bytesString = Arrays.toString(bytes);
261         } else {
262             bytesString = Arrays.toString(byteValues);
263         }
264         return "floatValues: " + halPropValue.dumpFloatValues() + ", int32Values: "
265                 + halPropValue.dumpInt32Values() + ", int64Values: "
266                 + halPropValue.dumpInt64Values() + ", bytes: " + bytesString + ", string: "
267                 + halPropValue.getStringValue();
268     }
269 
getIntValueName(int propertyId, int value, String propertyUnits)270     private static String getIntValueName(int propertyId, int value, String propertyUnits) {
271         if (EnumForVehicleProperty.values.containsKey(propertyId)) {
272             for (int i = 0; i < EnumForVehicleProperty.values.get(propertyId).size(); i++) {
273                 Class<?> enumClazz = EnumForVehicleProperty.values.get(propertyId).get(i);
274                 String valueName = ConstantDebugUtils.toName(enumClazz, value);
275                 if (valueName != null) {
276                     return valueName + "(0x" + toHexString(value) + ")";
277                 }
278             }
279             Slog.w(TAG,
280                     "Failed to find enum name for property ID: " + toPropertyIdString(propertyId)
281                             + " value: " + value);
282         }
283         return value + propertyUnits;
284     }
285 
getUnitsIfSupported(int propertyId)286     private static String getUnitsIfSupported(int propertyId) {
287         if (!UnitsForVehicleProperty.values.containsKey(propertyId)) {
288             return "";
289         }
290         Integer units = UnitsForVehicleProperty.values.get(propertyId);
291         String unitsString = ConstantDebugUtils.toName(VehicleUnit.class, units);
292         if (unitsString == null) {
293             return "";
294         }
295         return " " + unitsString;
296     }
297 
298     /**
299      * Gets a user-friendly representation string representation of {@link VehicleArea}
300      * constant for the passed {@code propertyId}.
301      */
toAreaTypeString(int propertyId)302     public static String toAreaTypeString(int propertyId) {
303         int areaType = propertyId & VehicleArea.MASK;
304         return toDebugString(VehicleArea.class, areaType);
305     }
306 
307     /**
308      * Gets a user-friendly representation string representation of {@link VehiclePropertyGroup}
309      * constant for the passed {@code propertyId}.
310      */
toGroupString(int propertyId)311     public static String toGroupString(int propertyId) {
312         int group = propertyId & VehiclePropertyGroup.MASK;
313         return toDebugString(VehiclePropertyGroup.class, group);
314     }
315 
316     /**
317      * Gets a user-friendly representation string representation of {@link VehiclePropertyType}
318      * constant for the passed {@code propertyId}.
319      */
toValueTypeString(int propertyId)320     public static String toValueTypeString(int propertyId) {
321         int valueType = propertyId & VehiclePropertyType.MASK;
322         return toDebugString(VehiclePropertyType.class, valueType);
323     }
324 
325     /**
326      * Gets a user-friendly representation string representation of
327      * {@link VehiclePropertyAccess} constant.
328      */
toAccessString(int access)329     public static String toAccessString(int access) {
330         return toDebugString(VehiclePropertyAccess.class, access);
331     }
332 
333     /**
334      * Gets a user-friendly representation string representation of
335      * {@link VehiclePropertyChangeMode} constant.
336      */
toChangeModeString(int changeMode)337     public static String toChangeModeString(int changeMode) {
338         return toDebugString(VehiclePropertyChangeMode.class, changeMode);
339     }
340 
341     /**
342      * Gets a user-friendly representation string representation of
343      * {@link VehiclePropertyStatus} constant.
344      */
toStatusString(int status)345     public static String toStatusString(int status) {
346         return toDebugString(VehiclePropertyStatus.class, status);
347     }
348 
toDebugString(Class<?> clazz, int constantValue)349     private static String toDebugString(Class<?> clazz, int constantValue) {
350         String hexSuffix = "(0x" + toHexString(constantValue) + ")";
351         if (toName(clazz, constantValue) == null) {
352             String invalidConstantValue = "INVALID_" + clazz.getSimpleName() + hexSuffix;
353             Slog.e(TAG, invalidConstantValue);
354             return invalidConstantValue;
355         }
356         return toName(clazz, constantValue) + hexSuffix;
357     }
358 
359     /**
360      * Gets human-readable representation of a {@code PropIdAreaId} structure.
361      *
362      * Note that the property ID is the VHAL property ID, not the CarPropertyManager property ID.
363      */
toHalPropIdAreaIdString(PropIdAreaId propIdAreaId)364     public static String toHalPropIdAreaIdString(PropIdAreaId propIdAreaId) {
365         return "PropIdAreaId{propId=" + toPropertyIdString(propIdAreaId.propId)
366             + ", areaId=" + toAreaIdString(propIdAreaId.propId, propIdAreaId.areaId) + "}";
367     }
368 
369     /**
370      * Gets human-readable representation of a list of {@code PropIdAreaId}.
371      *
372      * Note that the property ID is the VHAL property ID, not the CarPropertyManager property ID.
373      */
toHalPropIdAreaIdsString(Iterable<PropIdAreaId> propIdAreaIds)374     public static String toHalPropIdAreaIdsString(Iterable<PropIdAreaId> propIdAreaIds) {
375         StringBuilder sb = new StringBuilder();
376         sb.append("[");
377         boolean first = true;
378         for (PropIdAreaId propIdAreaId : propIdAreaIds) {
379             if (first) {
380                 first = false;
381             } else {
382                 sb.append(", ");
383             }
384             sb.append(toHalPropIdAreaIdString(propIdAreaId));
385         }
386         sb.append("]");
387         return sb.toString();
388     }
389 
390     /**
391      * Returns {@code true} if {@code propertyId} is defined in {@link VehicleProperty}.
392      * {@code false} otherwise.
393      */
isSystemPropertyId(int propertyId)394     private static boolean isSystemPropertyId(int propertyId) {
395         return toName(VehicleProperty.class, propertyId) != null;
396     }
397 }
398