1 /*
2  * Copyright (C) 2022 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.internal.property;
18 
19 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.PRIVATE_CONSTRUCTOR;
20 import static com.android.car.internal.util.ArrayUtils.convertToIntArray;
21 
22 import android.car.VehiclePropertyIds;
23 import android.car.feature.FeatureFlags;
24 import android.car.hardware.CarPropertyConfig;
25 import android.car.hardware.property.CarPropertyManager;
26 import android.util.Log;
27 import android.util.Slog;
28 
29 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
30 import com.android.internal.annotations.VisibleForTesting;
31 import com.android.internal.util.Preconditions;
32 
33 import java.util.ArrayList;
34 import java.util.List;
35 
36 /**
37  * Common utility functions used by {@link CarPropertyManager} stack to sanitize input arguments.
38  *
39  * @hide
40  */
41 public final class InputSanitizationUtils {
42 
43     private static final String TAG = InputSanitizationUtils.class.getSimpleName();
44     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
45 
46     @ExcludeFromCodeCoverageGeneratedReport(reason = PRIVATE_CONSTRUCTOR)
InputSanitizationUtils()47     private InputSanitizationUtils() {
48     }
49 
50     /**
51      * Sanitizes the {@code updateRateHz} passed to {@link
52      * CarPropertyManager#registerCallback(CarPropertyManager.CarPropertyEventCallback, int, float)}
53      * and similar functions.
54      */
sanitizeUpdateRateHz( CarPropertyConfig<?> carPropertyConfig, float updateRateHz)55     public static float sanitizeUpdateRateHz(
56             CarPropertyConfig<?> carPropertyConfig, float updateRateHz) {
57         float sanitizedUpdateRateHz = updateRateHz;
58         if (carPropertyConfig.getChangeMode()
59                 != CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS) {
60             sanitizedUpdateRateHz = CarPropertyManager.SENSOR_RATE_ONCHANGE;
61         } else if (sanitizedUpdateRateHz > carPropertyConfig.getMaxSampleRate()) {
62             sanitizedUpdateRateHz = carPropertyConfig.getMaxSampleRate();
63         } else if (sanitizedUpdateRateHz < carPropertyConfig.getMinSampleRate()) {
64             sanitizedUpdateRateHz = carPropertyConfig.getMinSampleRate();
65         }
66         return sanitizedUpdateRateHz;
67     }
68 
69     /**
70      * Sets resolution to 0 if the feature flag for resolution is not enabled. Also calls
71      * {@link #requireIntegerPowerOf10Resolution(float)} to determine if the incoming resolution
72      * value is an integer power of 10.
73      */
sanitizeResolution(FeatureFlags featureFlags, CarPropertyConfig<?> carPropertyConfig, float resolution)74     public static float sanitizeResolution(FeatureFlags featureFlags,
75             CarPropertyConfig<?> carPropertyConfig, float resolution) {
76         if (!featureFlags.subscriptionWithResolution() || carPropertyConfig.getChangeMode()
77                 != CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS) {
78             return 0.0f;
79         }
80         requireIntegerPowerOf10Resolution(resolution);
81         return resolution;
82     }
83 
84     /**
85      * Verifies that the incoming resolution value takes on only integer power of 10 values. Also
86      * sets resolution to 0 if the feature flag for resolution is not enabled.
87      */
requireIntegerPowerOf10Resolution(float resolution)88     public static void requireIntegerPowerOf10Resolution(float resolution) {
89         if (resolution == 0.0f) {
90             return;
91         }
92         double log = Math.log10(resolution);
93         Preconditions.checkArgument(Math.abs(log - Math.round(log)) < 0.0000001f,
94                 "resolution must be an integer power of 10. Instead, got resolution: " + resolution
95                         + ", whose log10 value is: " + log);
96     }
97 
98     /**
99      * Returns whether VUR feature is enabled and property is continuous.
100      *
101      * Need to be public even though InputSanitizationUtilsUnitTest is in the same package because
102      * in CarServiceUnitTest, it is loaded using a different class loader.
103      */
104     @VisibleForTesting
105     public static boolean isVurAllowed(FeatureFlags featureFlags,
106             CarPropertyConfig<?> carPropertyConfig) {
107         if (!featureFlags.variableUpdateRate()) {
108             if (DBG) {
109                 Slog.d(TAG, "VUR feature is not enabled, VUR is always off");
110             }
111             return false;
112         }
113         if (carPropertyConfig.getChangeMode()
114                 != CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS) {
115             if (DBG) {
116                 Slog.d(TAG, "VUR is always off for non-continuous property");
117             }
118             return false;
119         }
120         return true;
121     }
122 
123     /**
124      * Sanitizes the enableVur option in {@code CarSubscription}.
125      *
126      * <p>Overwrite it to false if:
127      *
128      * <ul>
129      * <li>Vur feature is disabled.</li>
130      * <li>Property is not continuous.</li>
131      * <li>Vur is not supported for the specific area.</li>
132      * </ul>
133      *
134      * @return a list of sanitized subscribe options. We may return more than one option because
135      * part of the areaIds support Vur, the rest does not.
136      */
137     public static List<CarSubscription> sanitizeEnableVariableUpdateRate(
138             FeatureFlags featureFlags, CarPropertyConfig<?> carPropertyConfig,
139             CarSubscription inputOption) throws IllegalArgumentException {
140         int[] areaIds = inputOption.areaIds;
141         Preconditions.checkArgument(areaIds != null && areaIds.length != 0,
142                 "areaIds must not be empty for property: "
143                 + VehiclePropertyIds.toString(inputOption.propertyId));
144         List<CarSubscription> sanitizedOptions = new ArrayList<>();
145         if (!inputOption.enableVariableUpdateRate) {
146             // We will only overwrite enableVur to off.
147             sanitizedOptions.add(inputOption);
148             return sanitizedOptions;
149         }
150         // If VUR feature is disabled, overwrite the VUR option to false.
151         if (!isVurAllowed(featureFlags, carPropertyConfig)) {
152             inputOption.enableVariableUpdateRate = false;
153             sanitizedOptions.add(inputOption);
154             return sanitizedOptions;
155         }
156         List<Integer> enabledAreaIds = new ArrayList<>();
157         List<Integer> disabledAreaIds = new ArrayList<>();
158         for (int areaId : areaIds) {
159             try {
160                 if (carPropertyConfig.getAreaIdConfig(areaId)
161                         .isVariableUpdateRateSupported()) {
162                     enabledAreaIds.add(areaId);
163                     continue;
164                 }
165             } catch (IllegalArgumentException e) {
166                 // Do nothing.
167             }
168             if (DBG) {
169                 Slog.d(TAG, "VUR is enabled but not supported for areaId: " + areaId);
170             }
171             disabledAreaIds.add(areaId);
172         }
173         CarSubscription disabledVurOption = new CarSubscription();
174         disabledVurOption.propertyId = inputOption.propertyId;
175         disabledVurOption.areaIds = convertToIntArray(disabledAreaIds);
176         disabledVurOption.updateRateHz = inputOption.updateRateHz;
177         disabledVurOption.enableVariableUpdateRate = false;
178         disabledVurOption.resolution = inputOption.resolution;
179 
180         CarSubscription enabledVurOption = new CarSubscription();
181         enabledVurOption.propertyId = inputOption.propertyId;
182         enabledVurOption.areaIds = convertToIntArray(enabledAreaIds);
183         enabledVurOption.updateRateHz = inputOption.updateRateHz;
184         enabledVurOption.enableVariableUpdateRate = true;
185         enabledVurOption.resolution = inputOption.resolution;
186 
187         sanitizedOptions.add(enabledVurOption);
188         sanitizedOptions.add(disabledVurOption);
189         return sanitizedOptions;
190     }
191 }
192