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