destradaa | e69403f | 2013-10-07 17:37:49 -0700 | [diff] [blame] | 1 | /* |
destradaa | 228d1b5 | 2013-10-16 18:43:32 -0700 | [diff] [blame] | 2 | * Copyright (C) 2013 The Android Open Source Project |
destradaa | e69403f | 2013-10-07 17:37:49 -0700 | [diff] [blame] | 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 | package android.hardware.cts.helpers; |
| 17 | |
Tarandeep Singh | e219b9a | 2015-06-11 19:58:15 +0530 | [diff] [blame] | 18 | import android.hardware.Sensor; |
destradaa | 37286b4 | 2014-10-02 13:46:05 -0700 | [diff] [blame] | 19 | import java.io.File; |
| 20 | import java.io.IOException; |
destradaa | e69403f | 2013-10-07 17:37:49 -0700 | [diff] [blame] | 21 | import java.util.ArrayList; |
| 22 | import java.util.Collection; |
| 23 | import java.util.Collections; |
Eric Rowe | 8a92e11 | 2014-02-11 15:17:46 -0800 | [diff] [blame] | 24 | import java.util.List; |
destradaa | e69403f | 2013-10-07 17:37:49 -0700 | [diff] [blame] | 25 | import java.util.concurrent.TimeUnit; |
| 26 | |
| 27 | /** |
| 28 | * Set of static helper methods for CTS tests. |
| 29 | */ |
destradaa | fd031b9 | 2014-10-10 10:45:51 -0700 | [diff] [blame] | 30 | //TODO: Refactor this class into several more well defined helper classes, look at StatisticsUtils |
destradaa | e69403f | 2013-10-07 17:37:49 -0700 | [diff] [blame] | 31 | public class SensorCtsHelper { |
Eric Rowe | 8a92e11 | 2014-02-11 15:17:46 -0800 | [diff] [blame] | 32 | |
destradaa | 139500e | 2014-08-22 16:01:52 -0700 | [diff] [blame] | 33 | private static final long NANOS_PER_MILLI = 1000000; |
Eric Rowe | 7be0b5f | 2014-03-12 17:52:55 -0700 | [diff] [blame] | 34 | |
destradaa | e69403f | 2013-10-07 17:37:49 -0700 | [diff] [blame] | 35 | /** |
Eric Rowe | 8a92e11 | 2014-02-11 15:17:46 -0800 | [diff] [blame] | 36 | * Private constructor for static class. |
destradaa | e69403f | 2013-10-07 17:37:49 -0700 | [diff] [blame] | 37 | */ |
| 38 | private SensorCtsHelper() {} |
| 39 | |
Eric Rowe | 8a92e11 | 2014-02-11 15:17:46 -0800 | [diff] [blame] | 40 | /** |
Peng Xu | d1ade0d | 2015-09-15 13:11:58 -0700 | [diff] [blame] | 41 | * Get percentiles using nearest rank algorithm. |
Eric Rowe | 8a92e11 | 2014-02-11 15:17:46 -0800 | [diff] [blame] | 42 | * |
Peng Xu | 40b86b7 | 2015-09-15 13:11:58 -0700 | [diff] [blame] | 43 | * @param percentiles List of percentiles interested. Its range is 0 to 1, instead of in %. |
| 44 | * The value will be internally bounded. |
| 45 | * |
Peng Xu | d1ade0d | 2015-09-15 13:11:58 -0700 | [diff] [blame] | 46 | * @throws IllegalArgumentException if the collection or percentiles is null or empty |
Eric Rowe | 8a92e11 | 2014-02-11 15:17:46 -0800 | [diff] [blame] | 47 | */ |
Peng Xu | d1ade0d | 2015-09-15 13:11:58 -0700 | [diff] [blame] | 48 | public static <TValue extends Comparable<? super TValue>> List<TValue> getPercentileValue( |
| 49 | Collection<TValue> collection, float[] percentiles) { |
destradaa | e69403f | 2013-10-07 17:37:49 -0700 | [diff] [blame] | 50 | validateCollection(collection); |
Peng Xu | d1ade0d | 2015-09-15 13:11:58 -0700 | [diff] [blame] | 51 | if(percentiles == null || percentiles.length == 0) { |
| 52 | throw new IllegalStateException("percentiles cannot be null or empty"); |
| 53 | } |
destradaa | e69403f | 2013-10-07 17:37:49 -0700 | [diff] [blame] | 54 | |
Eric Rowe | 8a92e11 | 2014-02-11 15:17:46 -0800 | [diff] [blame] | 55 | List<TValue> arrayCopy = new ArrayList<TValue>(collection); |
destradaa | e69403f | 2013-10-07 17:37:49 -0700 | [diff] [blame] | 56 | Collections.sort(arrayCopy); |
| 57 | |
Peng Xu | d1ade0d | 2015-09-15 13:11:58 -0700 | [diff] [blame] | 58 | List<TValue> percentileValues = new ArrayList<TValue>(); |
| 59 | for (float p : percentiles) { |
| 60 | // zero-based array index |
Peng Xu | 40b86b7 | 2015-09-15 13:11:58 -0700 | [diff] [blame] | 61 | int arrayIndex = (int) Math.round(arrayCopy.size() * p - .5f); |
| 62 | // bound the index to avoid out of range error |
| 63 | arrayIndex = Math.min(Math.max(arrayIndex, 0), arrayCopy.size() - 1); |
Peng Xu | d1ade0d | 2015-09-15 13:11:58 -0700 | [diff] [blame] | 64 | percentileValues.add(arrayCopy.get(arrayIndex)); |
| 65 | } |
| 66 | return percentileValues; |
destradaa | e69403f | 2013-10-07 17:37:49 -0700 | [diff] [blame] | 67 | } |
| 68 | |
destradaa | 65c7cdb | 2013-10-28 16:36:12 -0700 | [diff] [blame] | 69 | /** |
Eric Rowe | 8a92e11 | 2014-02-11 15:17:46 -0800 | [diff] [blame] | 70 | * Calculate the mean of a collection. |
| 71 | * |
| 72 | * @throws IllegalArgumentException if the collection is null or empty |
| 73 | */ |
destradaa | e69403f | 2013-10-07 17:37:49 -0700 | [diff] [blame] | 74 | public static <TValue extends Number> double getMean(Collection<TValue> collection) { |
| 75 | validateCollection(collection); |
| 76 | |
| 77 | double sum = 0.0; |
| 78 | for(TValue value : collection) { |
| 79 | sum += value.doubleValue(); |
| 80 | } |
| 81 | return sum / collection.size(); |
| 82 | } |
| 83 | |
Eric Rowe | 8a92e11 | 2014-02-11 15:17:46 -0800 | [diff] [blame] | 84 | /** |
Eric Rowe | 3194ad3 | 2014-02-21 17:30:21 -0800 | [diff] [blame] | 85 | * Calculate the bias-corrected sample variance of a collection. |
Eric Rowe | 8a92e11 | 2014-02-11 15:17:46 -0800 | [diff] [blame] | 86 | * |
| 87 | * @throws IllegalArgumentException if the collection is null or empty |
| 88 | */ |
destradaa | e69403f | 2013-10-07 17:37:49 -0700 | [diff] [blame] | 89 | public static <TValue extends Number> double getVariance(Collection<TValue> collection) { |
| 90 | validateCollection(collection); |
| 91 | |
| 92 | double mean = getMean(collection); |
Eric Rowe | 3194ad3 | 2014-02-21 17:30:21 -0800 | [diff] [blame] | 93 | ArrayList<Double> squaredDiffs = new ArrayList<Double>(); |
destradaa | e69403f | 2013-10-07 17:37:49 -0700 | [diff] [blame] | 94 | for(TValue value : collection) { |
| 95 | double difference = mean - value.doubleValue(); |
Eric Rowe | 3194ad3 | 2014-02-21 17:30:21 -0800 | [diff] [blame] | 96 | squaredDiffs.add(Math.pow(difference, 2)); |
destradaa | e69403f | 2013-10-07 17:37:49 -0700 | [diff] [blame] | 97 | } |
| 98 | |
Eric Rowe | 3194ad3 | 2014-02-21 17:30:21 -0800 | [diff] [blame] | 99 | double sum = 0.0; |
| 100 | for (Double value : squaredDiffs) { |
| 101 | sum += value; |
| 102 | } |
| 103 | return sum / (squaredDiffs.size() - 1); |
destradaa | e69403f | 2013-10-07 17:37:49 -0700 | [diff] [blame] | 104 | } |
| 105 | |
| 106 | /** |
destradaa | 37286b4 | 2014-10-02 13:46:05 -0700 | [diff] [blame] | 107 | * @return The (measured) sampling rate of a collection of {@link TestSensorEvent}. |
| 108 | */ |
| 109 | public static long getSamplingPeriodNs(List<TestSensorEvent> collection) { |
| 110 | int collectionSize = collection.size(); |
| 111 | if (collectionSize < 2) { |
| 112 | return 0; |
| 113 | } |
| 114 | TestSensorEvent firstEvent = collection.get(0); |
| 115 | TestSensorEvent lastEvent = collection.get(collectionSize - 1); |
| 116 | return (lastEvent.timestamp - firstEvent.timestamp) / (collectionSize - 1); |
| 117 | } |
| 118 | |
| 119 | /** |
Eric Rowe | 3194ad3 | 2014-02-21 17:30:21 -0800 | [diff] [blame] | 120 | * Calculate the bias-corrected standard deviation of a collection. |
destradaa | e69403f | 2013-10-07 17:37:49 -0700 | [diff] [blame] | 121 | * |
Eric Rowe | 8a92e11 | 2014-02-11 15:17:46 -0800 | [diff] [blame] | 122 | * @throws IllegalArgumentException if the collection is null or empty |
destradaa | e69403f | 2013-10-07 17:37:49 -0700 | [diff] [blame] | 123 | */ |
Eric Rowe | 8a92e11 | 2014-02-11 15:17:46 -0800 | [diff] [blame] | 124 | public static <TValue extends Number> double getStandardDeviation( |
| 125 | Collection<TValue> collection) { |
| 126 | return Math.sqrt(getVariance(collection)); |
destradaa | e69403f | 2013-10-07 17:37:49 -0700 | [diff] [blame] | 127 | } |
| 128 | |
| 129 | /** |
Eric Rowe | 8a92e11 | 2014-02-11 15:17:46 -0800 | [diff] [blame] | 130 | * Convert a period to frequency in Hz. |
| 131 | */ |
| 132 | public static <TValue extends Number> double getFrequency(TValue period, TimeUnit unit) { |
| 133 | return 1000000000 / (TimeUnit.NANOSECONDS.convert(1, unit) * period.doubleValue()); |
| 134 | } |
| 135 | |
| 136 | /** |
| 137 | * Convert a frequency in Hz into a period. |
| 138 | */ |
| 139 | public static <TValue extends Number> double getPeriod(TValue frequency, TimeUnit unit) { |
| 140 | return 1000000000 / (TimeUnit.NANOSECONDS.convert(1, unit) * frequency.doubleValue()); |
| 141 | } |
| 142 | |
| 143 | /** |
destradaa | 37286b4 | 2014-10-02 13:46:05 -0700 | [diff] [blame] | 144 | * @return The magnitude (norm) represented by the given array of values. |
Eric Rowe | 8a92e11 | 2014-02-11 15:17:46 -0800 | [diff] [blame] | 145 | */ |
destradaa | 37286b4 | 2014-10-02 13:46:05 -0700 | [diff] [blame] | 146 | public static double getMagnitude(float[] values) { |
| 147 | float sumOfSquares = 0.0f; |
| 148 | for (float value : values) { |
| 149 | sumOfSquares += value * value; |
| 150 | } |
| 151 | double magnitude = Math.sqrt(sumOfSquares); |
| 152 | return magnitude; |
Eric Rowe | 8a92e11 | 2014-02-11 15:17:46 -0800 | [diff] [blame] | 153 | } |
| 154 | |
| 155 | /** |
Eric Rowe | 7be0b5f | 2014-03-12 17:52:55 -0700 | [diff] [blame] | 156 | * Helper method to sleep for a given duration. |
Eric Rowe | 8a92e11 | 2014-02-11 15:17:46 -0800 | [diff] [blame] | 157 | */ |
destradaa | fd031b9 | 2014-10-10 10:45:51 -0700 | [diff] [blame] | 158 | public static void sleep(long duration, TimeUnit timeUnit) throws InterruptedException { |
Eric Rowe | 7be0b5f | 2014-03-12 17:52:55 -0700 | [diff] [blame] | 159 | long durationNs = TimeUnit.NANOSECONDS.convert(duration, timeUnit); |
destradaa | fd031b9 | 2014-10-10 10:45:51 -0700 | [diff] [blame] | 160 | Thread.sleep(durationNs / NANOS_PER_MILLI, (int) (durationNs % NANOS_PER_MILLI)); |
destradaa | e69403f | 2013-10-07 17:37:49 -0700 | [diff] [blame] | 161 | } |
| 162 | |
| 163 | /** |
Eric Rowe | 7b44fee | 2014-03-18 18:16:50 -0700 | [diff] [blame] | 164 | * Format an assertion message. |
| 165 | * |
Eric Rowe | 7b44fee | 2014-03-18 18:16:50 -0700 | [diff] [blame] | 166 | * @param label the verification name |
destradaa | b7f8949 | 2014-09-19 14:23:04 -0700 | [diff] [blame] | 167 | * @param environment the environment of the test |
| 168 | * |
Eric Rowe | 7b44fee | 2014-03-18 18:16:50 -0700 | [diff] [blame] | 169 | * @return The formatted string |
| 170 | */ |
destradaa | b7f8949 | 2014-09-19 14:23:04 -0700 | [diff] [blame] | 171 | public static String formatAssertionMessage(String label, TestSensorEnvironment environment) { |
destradaa | 37286b4 | 2014-10-02 13:46:05 -0700 | [diff] [blame] | 172 | return formatAssertionMessage(label, environment, "Failed"); |
Eric Rowe | 7b44fee | 2014-03-18 18:16:50 -0700 | [diff] [blame] | 173 | } |
| 174 | |
| 175 | /** |
| 176 | * Format an assertion message with a custom message. |
| 177 | * |
Eric Rowe | 7b44fee | 2014-03-18 18:16:50 -0700 | [diff] [blame] | 178 | * @param label the verification name |
destradaa | b7f8949 | 2014-09-19 14:23:04 -0700 | [diff] [blame] | 179 | * @param environment the environment of the test |
Eric Rowe | 7b44fee | 2014-03-18 18:16:50 -0700 | [diff] [blame] | 180 | * @param format the additional format string |
| 181 | * @param params the additional format params |
destradaa | b7f8949 | 2014-09-19 14:23:04 -0700 | [diff] [blame] | 182 | * |
Eric Rowe | 7b44fee | 2014-03-18 18:16:50 -0700 | [diff] [blame] | 183 | * @return The formatted string |
| 184 | */ |
destradaa | b7f8949 | 2014-09-19 14:23:04 -0700 | [diff] [blame] | 185 | public static String formatAssertionMessage( |
| 186 | String label, |
| 187 | TestSensorEnvironment environment, |
| 188 | String format, |
| 189 | Object ... params) { |
| 190 | return formatAssertionMessage(label, environment, String.format(format, params)); |
| 191 | } |
| 192 | |
| 193 | /** |
| 194 | * Format an assertion message. |
| 195 | * |
| 196 | * @param label the verification name |
| 197 | * @param environment the environment of the test |
| 198 | * @param extras the additional information for the assertion |
| 199 | * |
| 200 | * @return The formatted string |
| 201 | */ |
| 202 | public static String formatAssertionMessage( |
| 203 | String label, |
| 204 | TestSensorEnvironment environment, |
| 205 | String extras) { |
| 206 | return String.format( |
destradaa | 4458c69 | 2014-10-15 12:42:29 -0700 | [diff] [blame] | 207 | "%s | sensor='%s', samplingPeriod=%dus, maxReportLatency=%dus | %s", |
destradaa | b7f8949 | 2014-09-19 14:23:04 -0700 | [diff] [blame] | 208 | label, |
destradaa | 37286b4 | 2014-10-02 13:46:05 -0700 | [diff] [blame] | 209 | environment.getSensor().getName(), |
destradaa | b7f8949 | 2014-09-19 14:23:04 -0700 | [diff] [blame] | 210 | environment.getRequestedSamplingPeriodUs(), |
| 211 | environment.getMaxReportLatencyUs(), |
| 212 | extras); |
Eric Rowe | 7b44fee | 2014-03-18 18:16:50 -0700 | [diff] [blame] | 213 | } |
| 214 | |
| 215 | /** |
destradaa | 37286b4 | 2014-10-02 13:46:05 -0700 | [diff] [blame] | 216 | * @return A {@link File} representing a root directory to store sensor tests data. |
| 217 | */ |
| 218 | public static File getSensorTestDataDirectory() throws IOException { |
| 219 | File dataDirectory = new File(System.getenv("EXTERNAL_STORAGE"), "sensorTests/"); |
| 220 | return createDirectoryStructure(dataDirectory); |
| 221 | } |
| 222 | |
| 223 | /** |
| 224 | * Creates the directory structure for the given sensor test data sub-directory. |
| 225 | * |
| 226 | * @param subdirectory The sub-directory's name. |
| 227 | */ |
| 228 | public static File getSensorTestDataDirectory(String subdirectory) throws IOException { |
| 229 | File subdirectoryFile = new File(getSensorTestDataDirectory(), subdirectory); |
| 230 | return createDirectoryStructure(subdirectoryFile); |
| 231 | } |
| 232 | |
| 233 | /** |
destradaa | b058a7a | 2014-12-18 15:41:24 -0800 | [diff] [blame] | 234 | * Sanitizes a string so it can be used in file names. |
| 235 | * |
| 236 | * @param value The string to sanitize. |
| 237 | * @return The sanitized string. |
| 238 | * |
| 239 | * @throws SensorTestPlatformException If the string cannot be sanitized. |
| 240 | */ |
| 241 | public static String sanitizeStringForFileName(String value) |
| 242 | throws SensorTestPlatformException { |
| 243 | String sanitizedValue = value.replaceAll("[^a-zA-Z0-9_\\-]", "_"); |
| 244 | if (sanitizedValue.matches("_*")) { |
| 245 | throw new SensorTestPlatformException( |
| 246 | "Unable to sanitize string '%s' for file name.", |
| 247 | value); |
| 248 | } |
| 249 | return sanitizedValue; |
| 250 | } |
| 251 | |
| 252 | /** |
destradaa | 37286b4 | 2014-10-02 13:46:05 -0700 | [diff] [blame] | 253 | * Ensures that the directory structure represented by the given {@link File} is created. |
| 254 | */ |
| 255 | private static File createDirectoryStructure(File directoryStructure) throws IOException { |
| 256 | directoryStructure.mkdirs(); |
| 257 | if (!directoryStructure.isDirectory()) { |
| 258 | throw new IOException("Unable to create directory structure for " |
| 259 | + directoryStructure.getAbsolutePath()); |
| 260 | } |
| 261 | return directoryStructure; |
| 262 | } |
| 263 | |
| 264 | /** |
Eric Rowe | 8a92e11 | 2014-02-11 15:17:46 -0800 | [diff] [blame] | 265 | * Validate that a collection is not null or empty. |
| 266 | * |
| 267 | * @throws IllegalStateException if collection is null or empty. |
destradaa | e69403f | 2013-10-07 17:37:49 -0700 | [diff] [blame] | 268 | */ |
Eric Rowe | 8a92e11 | 2014-02-11 15:17:46 -0800 | [diff] [blame] | 269 | private static <T> void validateCollection(Collection<T> collection) { |
destradaa | e69403f | 2013-10-07 17:37:49 -0700 | [diff] [blame] | 270 | if(collection == null || collection.size() == 0) { |
| 271 | throw new IllegalStateException("Collection cannot be null or empty"); |
| 272 | } |
| 273 | } |
Tarandeep Singh | e219b9a | 2015-06-11 19:58:15 +0530 | [diff] [blame] | 274 | |
| 275 | public static String getUnitsForSensor(Sensor sensor) { |
| 276 | switch(sensor.getType()) { |
| 277 | case Sensor.TYPE_ACCELEROMETER: |
| 278 | return "m/s^2"; |
| 279 | case Sensor.TYPE_MAGNETIC_FIELD: |
| 280 | case Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED: |
| 281 | return "uT"; |
| 282 | case Sensor.TYPE_GYROSCOPE: |
| 283 | case Sensor.TYPE_GYROSCOPE_UNCALIBRATED: |
| 284 | return "radians/sec"; |
| 285 | case Sensor.TYPE_PRESSURE: |
| 286 | return "hPa"; |
| 287 | }; |
| 288 | return ""; |
| 289 | } |
destradaa | e69403f | 2013-10-07 17:37:49 -0700 | [diff] [blame] | 290 | } |