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