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.server.display.utils;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.os.PowerManager;
22 import android.util.Slog;
23 
24 import com.android.internal.display.BrightnessSynchronizer;
25 import com.android.server.display.DisplayDeviceConfig;
26 
27 import java.util.ArrayList;
28 import java.util.HashMap;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.function.BiFunction;
32 import java.util.function.Function;
33 
34 /**
35  * Provides utility methods for DeviceConfig string parsing
36  */
37 public class DeviceConfigParsingUtils {
38     private static final String TAG = "DeviceConfigParsingUtils";
39 
40     /**
41      * Parses map from device config
42      * Data format:
43      * (displayId:String,numberOfPoints:Int,(state:T,value:Float){numberOfPoints},
44      * dataSetId:String)?;)+
45      * result : mapOf(displayId to mapOf(dataSetId to V))
46      */
47     @NonNull
parseDeviceConfigMap( @ullable String data, @NonNull BiFunction<String, String, T> dataPointMapper, @NonNull Function<List<T>, V> dataSetMapper)48     public static <T, V> Map<String, Map<String, V>> parseDeviceConfigMap(
49             @Nullable String data,
50             @NonNull BiFunction<String, String, T> dataPointMapper,
51             @NonNull Function<List<T>, V> dataSetMapper) {
52         if (data == null) {
53             return Map.of();
54         }
55         Map<String, Map<String, V>> result = new HashMap<>();
56         String[] dataSets = data.split(";"); // by displayId + dataSetId
57         for (String dataSet : dataSets) {
58             String[] items = dataSet.split(",");
59             int noOfItems = items.length;
60             // Validate number of items, at least: displayId,1,key1,value1
61             if (noOfItems < 4) {
62                 Slog.e(TAG, "Invalid dataSet(not enough items):" + dataSet, new Throwable());
63                 return Map.of();
64             }
65             int i = 0;
66             String uniqueDisplayId = items[i++];
67 
68             String numberOfPointsString = items[i++];
69             int numberOfPoints;
70             try {
71                 numberOfPoints = Integer.parseInt(numberOfPointsString);
72             } catch (NumberFormatException nfe) {
73                 Slog.e(TAG, "Invalid dataSet(invalid number of points):" + dataSet, nfe);
74                 return Map.of();
75             }
76             // Validate number of itmes based on numberOfPoints:
77             // displayId,numberOfPoints,(key,value) x numberOfPoints,dataSetId(optional)
78             int expectedMinItems = 2 + numberOfPoints * 2;
79             if (noOfItems < expectedMinItems || noOfItems > expectedMinItems + 1) {
80                 Slog.e(TAG, "Invalid dataSet(wrong number of points):" + dataSet, new Throwable());
81                 return Map.of();
82             }
83             // Construct data points
84             List<T> dataPoints = new ArrayList<>();
85             for (int j = 0; j < numberOfPoints; j++) {
86                 String key = items[i++];
87                 String value = items[i++];
88                 T dataPoint = dataPointMapper.apply(key, value);
89                 if (dataPoint == null) {
90                     Slog.e(TAG,
91                             "Invalid dataPoint ,key=" + key + ",value=" + value + ",dataSet="
92                                     + dataSet, new Throwable());
93                     return Map.of();
94                 }
95                 dataPoints.add(dataPoint);
96             }
97             // Construct dataSet
98             V dataSetMapped = dataSetMapper.apply(dataPoints);
99             if (dataSetMapped == null) {
100                 Slog.e(TAG, "Invalid dataSetMapped dataPoints=" + dataPoints + ",dataSet="
101                         + dataSet, new Throwable());
102                 return Map.of();
103             }
104             // Get dataSetId and dataSets map for displayId
105             String dataSetId = (i < items.length) ? items[i] : DisplayDeviceConfig.DEFAULT_ID;
106             Map<String, V> byDisplayId = result.computeIfAbsent(uniqueDisplayId,
107                     k -> new HashMap<>());
108 
109             // Try to store dataSet in datasets for display
110             if (byDisplayId.put(dataSetId, dataSetMapped) != null) {
111                 Slog.e(TAG, "Duplicate dataSetId=" + dataSetId + ",data=" + data, new Throwable());
112                 return Map.of();
113             }
114         }
115         return result;
116     }
117 
118     /**
119      * Parses thermal string value from device config
120      */
121     @PowerManager.ThermalStatus
parseThermalStatus(@onNull String value)122     public static int parseThermalStatus(@NonNull String value) throws IllegalArgumentException {
123         switch (value) {
124             case "none":
125                 return PowerManager.THERMAL_STATUS_NONE;
126             case "light":
127                 return PowerManager.THERMAL_STATUS_LIGHT;
128             case "moderate":
129                 return PowerManager.THERMAL_STATUS_MODERATE;
130             case "severe":
131                 return PowerManager.THERMAL_STATUS_SEVERE;
132             case "critical":
133                 return PowerManager.THERMAL_STATUS_CRITICAL;
134             case "emergency":
135                 return PowerManager.THERMAL_STATUS_EMERGENCY;
136             case "shutdown":
137                 return PowerManager.THERMAL_STATUS_SHUTDOWN;
138             default:
139                 throw new IllegalArgumentException("Invalid Thermal Status: " + value);
140         }
141     }
142 
143     /**
144      * Parses brightness value from device config
145      */
parseBrightness(String stringVal)146     public static float parseBrightness(String stringVal) throws IllegalArgumentException {
147         float value = Float.parseFloat(stringVal);
148         if (value < PowerManager.BRIGHTNESS_MIN || value > PowerManager.BRIGHTNESS_MAX) {
149             throw new IllegalArgumentException("Brightness value out of bounds: " + stringVal);
150         }
151         return value;
152     }
153 
154     /**
155      * Convert display brightness thresholds to a float array.
156      * @param thresholdsInt The int array of the thresholds in the range [0, 255]
157      * @return The float array of the thresholds
158      */
displayBrightnessThresholdsIntToFloat(int[] thresholdsInt)159     public static float[] displayBrightnessThresholdsIntToFloat(int[] thresholdsInt) {
160         if (thresholdsInt == null) {
161             return null;
162         }
163 
164         float[] thresholds = new float[thresholdsInt.length];
165         for (int i = 0; i < thresholds.length; i++) {
166             if (thresholdsInt[i] < 0) {
167                 // A negative value means that there's no threshold
168                 thresholds[i] = thresholdsInt[i];
169             } else {
170                 thresholds[i] = BrightnessSynchronizer.brightnessIntToFloat(thresholdsInt[i]);
171             }
172         }
173         return thresholds;
174     }
175 
176     /**
177      * Convert ambient brightness thresholds to a float array.
178      * @param thresholdsInt The int array of the thresholds
179      * @return The float array of the thresholds
180      */
ambientBrightnessThresholdsIntToFloat(int[] thresholdsInt)181     public static float[] ambientBrightnessThresholdsIntToFloat(int[] thresholdsInt) {
182         if (thresholdsInt == null) {
183             return null;
184         }
185 
186         float[] thresholds = new float[thresholdsInt.length];
187         for (int i = 0; i < thresholds.length; i++) {
188             thresholds[i] = thresholdsInt[i];
189         }
190         return thresholds;
191     }
192 }
193