1 /*
2  * Copyright (C) 2014 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 android.hardware.cts.helpers;
18 
19 import android.content.Context;
20 import android.hardware.Sensor;
21 import android.hardware.cts.helpers.sensoroperations.SensorOperation;
22 import android.os.Environment;
23 import android.util.Log;
24 
25 import java.io.BufferedWriter;
26 import java.io.File;
27 import java.io.FileWriter;
28 import java.io.IOException;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.Collections;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Map.Entry;
36 import java.util.Set;
37 
38 /**
39  * Class used to store stats related to {@link SensorOperation}s. Sensor stats may be linked
40  * together so that they form a tree.
41  */
42 public class SensorStats {
43     public static final String DELIMITER = "__";
44 
45     public static final String ERROR = "error";
46     public static final String EVENT_FIFO_LENGTH = "event_fifo_length_observed";
47     public static final String EVENT_GAP_COUNT_KEY = "event_gap_count";
48     public static final String EVENT_GAP_POSITIONS_KEY = "event_gap_positions";
49     public static final String EVENT_OUT_OF_ORDER_COUNT_KEY = "event_out_of_order_count";
50     public static final String EVENT_OUT_OF_ORDER_POSITIONS_KEY = "event_out_of_order_positions";
51     public static final String EVENT_TIME_SYNCHRONIZATION_COUNT_KEY =
52             "event_time_synchronization_count";
53     public static final String EVENT_TIME_SYNCHRONIZATION_POSITIONS_KEY =
54             "event_time_synchronization_positions";
55     public static final String EVENT_TIME_WRONG_CLOCKSOURCE_COUNT_KEY =
56             "event_time_wrong_clocksource_count";
57     public static final String EVENT_TIME_WRONG_CLOCKSOURCE_POSITIONS_KEY =
58             "event_time_wrong_clocksource_positions";
59     public static final String EVENT_COUNT_KEY = "event_count";
60     public static final String EVENT_COUNT_EXPECTED_KEY = "event_count_expected";
61     public static final String EVENT_NOT_SANITIZED_KEY = "event_not_sanitized";
62     public static final String EVENT_LOG_FILENAME = "event_log_filename";
63     public static final String WRONG_SENSOR_KEY = "wrong_sensor_observed";
64     public static final String FREQUENCY_KEY = "frequency";
65     public static final String JITTER_95_PERCENTILE_PERCENT_KEY = "jitter_95_percentile_percent";
66     public static final String MEAN_KEY = "mean";
67     public static final String STANDARD_DEVIATION_KEY = "standard_deviation";
68     public static final String MAGNITUDE_KEY = "magnitude";
69     public static final String DELAYED_BATCH_DELIVERY = "delayed_batch_delivery";
70     public static final String INITIAL_MEAN_KEY = "initial_mean";
71     public static final String LATER_MEAN_KEY = "later_mean";
72 
73     private final Map<String, Object> mValues = new HashMap<>();
74     private final Map<String, SensorStats> mSensorStats = new HashMap<>();
75 
76     /**
77      * Add a value.
78      *
79      * @param key the key.
80      * @param value the value as an {@link Object}.
81      */
addValue(String key, Object value)82     public synchronized void addValue(String key, Object value) {
83         if (value == null) {
84             return;
85         }
86         mValues.put(key, value);
87     }
88 
89     /**
90      * Add a nested {@link SensorStats}. This is useful for keeping track of stats in a
91      * {@link SensorOperation} tree.
92      *
93      * @param key the key
94      * @param stats the sub {@link SensorStats} object.
95      */
addSensorStats(String key, SensorStats stats)96     public synchronized void addSensorStats(String key, SensorStats stats) {
97         if (stats == null) {
98             return;
99         }
100         mSensorStats.put(key, stats);
101     }
102 
103     /**
104      * Get the keys from the values table. Will not get the keys from the nested
105      * {@link SensorStats}.
106      */
getKeys()107     public synchronized Set<String> getKeys() {
108         return mValues.keySet();
109     }
110 
111     /**
112      * Get a value from the values table. Will not attempt to get values from nested
113      * {@link SensorStats}.
114      */
getValue(String key)115     public synchronized Object getValue(String key) {
116         return mValues.get(key);
117     }
118 
119     /**
120      * Flattens the map and all sub {@link SensorStats} objects. Keys will be flattened using
121      * {@value #DELIMITER}. For example, if a sub {@link SensorStats} is added with key
122      * {@code "key1"} containing the key value pair {@code \("key2", "value"\)}, the flattened map
123      * will contain the entry {@code \("key1__key2", "value"\)}.
124      *
125      * @return a {@link Map} containing all stats from the value and sub {@link SensorStats}.
126      */
flatten()127     public synchronized Map<String, Object> flatten() {
128         final Map<String, Object> flattenedMap = new HashMap<>(mValues);
129         for (Entry<String, SensorStats> statsEntry : mSensorStats.entrySet()) {
130             for (Entry<String, Object> valueEntry : statsEntry.getValue().flatten().entrySet()) {
131                 String key = statsEntry.getKey() + DELIMITER + valueEntry.getKey();
132                 flattenedMap.put(key, valueEntry.getValue());
133             }
134         }
135         return flattenedMap;
136     }
137 
138     /**
139      * Utility method to log the stats to the logcat.
140      */
log(String tag)141     public void log(String tag) {
142         final Map<String, Object> flattened = flatten();
143         for (String key : getSortedKeys(flattened)) {
144             Object value = flattened.get(key);
145             Log.v(tag, String.format("%s: %s", key, getValueString(value)));
146         }
147     }
148 
149     /**
150      * Utility method to log the stats to a file. Will overwrite the file if it already exists.
151      */
logToFile(Context context, String fileName)152     public void logToFile(Context context, String fileName) throws IOException {
153         // Only log to file if currently not an Instant App since Instant Apps do not have access to
154         // external storage.
155         if (!context.getPackageManager().isInstantApp()) {
156             File statsDirectory = SensorCtsHelper.getSensorTestDataDirectory("stats/");
157             File logFile = new File(statsDirectory, fileName);
158             final Map<String, Object> flattened = flatten();
159             FileWriter fileWriter = new FileWriter(logFile, false /* append */);
160             try (BufferedWriter writer = new BufferedWriter(fileWriter)) {
161                 for (String key : getSortedKeys(flattened)) {
162                     Object value = flattened.get(key);
163                     writer.write(String.format("%s: %s\n", key, getValueString(value)));
164                 }
165             }
166         }
167     }
168 
169     /**
170      * Provides a sanitized sensor name, that can be used in file names.
171      * See {@link #logToFile(String)}.
172      */
getSanitizedSensorName(Sensor sensor)173     public static String getSanitizedSensorName(Sensor sensor) throws SensorTestPlatformException {
174         return SensorCtsHelper.sanitizeStringForFileName(sensor.getStringType());
175     }
176 
getSortedKeys(Map<String, Object> flattenedStats)177     private static List<String> getSortedKeys(Map<String, Object> flattenedStats) {
178         List<String> keys = new ArrayList<>(flattenedStats.keySet());
179         Collections.sort(keys);
180         return keys;
181     }
182 
getValueString(Object value)183     private static String getValueString(Object value) {
184         if (value == null) {
185             return "";
186         } else if (value instanceof boolean[]) {
187             return Arrays.toString((boolean[]) value);
188         } else if (value instanceof byte[]) {
189             return Arrays.toString((byte[]) value);
190         } else if (value instanceof char[]) {
191             return Arrays.toString((char[]) value);
192         } else if (value instanceof double[]) {
193             return Arrays.toString((double[]) value);
194         } else if (value instanceof float[]) {
195             return Arrays.toString((float[]) value);
196         } else if (value instanceof int[]) {
197             return Arrays.toString((int[]) value);
198         } else if (value instanceof long[]) {
199             return Arrays.toString((long[]) value);
200         } else if (value instanceof short[]) {
201             return Arrays.toString((short[]) value);
202         } else if (value instanceof Object[]) {
203             return Arrays.toString((Object[]) value);
204         } else {
205             return value.toString();
206         }
207     }
208 }
209