1 /* 2 * Copyright (C) 2013 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 package android.hardware.cts.helpers; 17 18 import android.hardware.Sensor; 19 import java.io.File; 20 import java.io.IOException; 21 import java.util.ArrayList; 22 import java.util.Collection; 23 import java.util.Collections; 24 import java.util.List; 25 import java.util.concurrent.TimeUnit; 26 27 /** 28 * Set of static helper methods for CTS tests. 29 */ 30 //TODO: Refactor this class into several more well defined helper classes, look at StatisticsUtils 31 public class SensorCtsHelper { 32 33 private static final long NANOS_PER_MILLI = 1000000; 34 35 /** 36 * Private constructor for static class. 37 */ SensorCtsHelper()38 private SensorCtsHelper() {} 39 40 /** 41 * Get percentiles using nearest rank algorithm. 42 * 43 * @param percentiles List of percentiles interested. Its range is 0 to 1, instead of in %. 44 * The value will be internally bounded. 45 * 46 * @throws IllegalArgumentException if the collection or percentiles is null or empty 47 */ getPercentileValue( Collection<TValue> collection, float[] percentiles)48 public static <TValue extends Comparable<? super TValue>> List<TValue> getPercentileValue( 49 Collection<TValue> collection, float[] percentiles) { 50 validateCollection(collection); 51 if(percentiles == null || percentiles.length == 0) { 52 throw new IllegalStateException("percentiles cannot be null or empty"); 53 } 54 55 List<TValue> arrayCopy = new ArrayList<TValue>(collection); 56 Collections.sort(arrayCopy); 57 58 List<TValue> percentileValues = new ArrayList<TValue>(); 59 for (float p : percentiles) { 60 // zero-based array index 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); 64 percentileValues.add(arrayCopy.get(arrayIndex)); 65 } 66 return percentileValues; 67 } 68 69 /** 70 * Calculate the mean of a collection. 71 * 72 * @throws IllegalArgumentException if the collection is null or empty 73 */ getMean(Collection<TValue> collection)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 84 /** 85 * Calculate the bias-corrected sample variance of a collection. 86 * 87 * @throws IllegalArgumentException if the collection is null or empty 88 */ getVariance(Collection<TValue> collection)89 public static <TValue extends Number> double getVariance(Collection<TValue> collection) { 90 validateCollection(collection); 91 92 double mean = getMean(collection); 93 ArrayList<Double> squaredDiffs = new ArrayList<Double>(); 94 for(TValue value : collection) { 95 double difference = mean - value.doubleValue(); 96 squaredDiffs.add(Math.pow(difference, 2)); 97 } 98 99 double sum = 0.0; 100 for (Double value : squaredDiffs) { 101 sum += value; 102 } 103 return sum / (squaredDiffs.size() - 1); 104 } 105 106 /** 107 * @return The (measured) sampling rate of a collection of {@link TestSensorEvent}. 108 */ getSamplingPeriodNs(List<TestSensorEvent> collection)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 /** 120 * Calculate the bias-corrected standard deviation of a collection. 121 * 122 * @throws IllegalArgumentException if the collection is null or empty 123 */ getStandardDeviation( Collection<TValue> collection)124 public static <TValue extends Number> double getStandardDeviation( 125 Collection<TValue> collection) { 126 return Math.sqrt(getVariance(collection)); 127 } 128 129 /** 130 * Convert a period to frequency in Hz. 131 */ getFrequency(TValue period, TimeUnit unit)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 */ getPeriod(TValue frequency, TimeUnit unit)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 /** 144 * @return The magnitude (norm) represented by the given array of values. 145 */ getMagnitude(float[] values)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; 153 } 154 155 /** 156 * Helper method to sleep for a given duration. 157 */ sleep(long duration, TimeUnit timeUnit)158 public static void sleep(long duration, TimeUnit timeUnit) throws InterruptedException { 159 long durationNs = TimeUnit.NANOSECONDS.convert(duration, timeUnit); 160 Thread.sleep(durationNs / NANOS_PER_MILLI, (int) (durationNs % NANOS_PER_MILLI)); 161 } 162 163 /** 164 * Format an assertion message. 165 * 166 * @param label the verification name 167 * @param environment the environment of the test 168 * 169 * @return The formatted string 170 */ formatAssertionMessage(String label, TestSensorEnvironment environment)171 public static String formatAssertionMessage(String label, TestSensorEnvironment environment) { 172 return formatAssertionMessage(label, environment, "Failed"); 173 } 174 175 /** 176 * Format an assertion message with a custom message. 177 * 178 * @param label the verification name 179 * @param environment the environment of the test 180 * @param format the additional format string 181 * @param params the additional format params 182 * 183 * @return The formatted string 184 */ formatAssertionMessage( String label, TestSensorEnvironment environment, String format, Object ... params)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 */ formatAssertionMessage( String label, TestSensorEnvironment environment, String extras)202 public static String formatAssertionMessage( 203 String label, 204 TestSensorEnvironment environment, 205 String extras) { 206 return String.format( 207 "%s | sensor='%s', samplingPeriod=%dus, maxReportLatency=%dus | %s", 208 label, 209 environment.getSensor().getName(), 210 environment.getRequestedSamplingPeriodUs(), 211 environment.getMaxReportLatencyUs(), 212 extras); 213 } 214 215 /** 216 * @return A {@link File} representing a root directory to store sensor tests data. 217 */ getSensorTestDataDirectory()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 */ getSensorTestDataDirectory(String subdirectory)228 public static File getSensorTestDataDirectory(String subdirectory) throws IOException { 229 File subdirectoryFile = new File(getSensorTestDataDirectory(), subdirectory); 230 return createDirectoryStructure(subdirectoryFile); 231 } 232 233 /** 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 */ sanitizeStringForFileName(String value)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 /** 253 * Ensures that the directory structure represented by the given {@link File} is created. 254 */ createDirectoryStructure(File directoryStructure)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 /** 265 * Validate that a collection is not null or empty. 266 * 267 * @throws IllegalStateException if collection is null or empty. 268 */ validateCollection(Collection<T> collection)269 private static <T> void validateCollection(Collection<T> collection) { 270 if(collection == null || collection.size() == 0) { 271 throw new IllegalStateException("Collection cannot be null or empty"); 272 } 273 } 274 getUnitsForSensor(Sensor sensor)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 } 290 } 291