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 the value of the 95th percentile using nearest rank algorithm. 42 * 43 * @throws IllegalArgumentException if the collection is null or empty 44 */ get95PercentileValue( Collection<TValue> collection)45 public static <TValue extends Comparable<? super TValue>> TValue get95PercentileValue( 46 Collection<TValue> collection) { 47 validateCollection(collection); 48 49 List<TValue> arrayCopy = new ArrayList<TValue>(collection); 50 Collections.sort(arrayCopy); 51 52 // zero-based array index 53 int arrayIndex = (int) Math.round(arrayCopy.size() * 0.95 + .5) - 1; 54 55 return arrayCopy.get(arrayIndex); 56 } 57 58 /** 59 * Calculate the mean of a collection. 60 * 61 * @throws IllegalArgumentException if the collection is null or empty 62 */ getMean(Collection<TValue> collection)63 public static <TValue extends Number> double getMean(Collection<TValue> collection) { 64 validateCollection(collection); 65 66 double sum = 0.0; 67 for(TValue value : collection) { 68 sum += value.doubleValue(); 69 } 70 return sum / collection.size(); 71 } 72 73 /** 74 * Calculate the bias-corrected sample variance of a collection. 75 * 76 * @throws IllegalArgumentException if the collection is null or empty 77 */ getVariance(Collection<TValue> collection)78 public static <TValue extends Number> double getVariance(Collection<TValue> collection) { 79 validateCollection(collection); 80 81 double mean = getMean(collection); 82 ArrayList<Double> squaredDiffs = new ArrayList<Double>(); 83 for(TValue value : collection) { 84 double difference = mean - value.doubleValue(); 85 squaredDiffs.add(Math.pow(difference, 2)); 86 } 87 88 double sum = 0.0; 89 for (Double value : squaredDiffs) { 90 sum += value; 91 } 92 return sum / (squaredDiffs.size() - 1); 93 } 94 95 /** 96 * @return The (measured) sampling rate of a collection of {@link TestSensorEvent}. 97 */ getSamplingPeriodNs(List<TestSensorEvent> collection)98 public static long getSamplingPeriodNs(List<TestSensorEvent> collection) { 99 int collectionSize = collection.size(); 100 if (collectionSize < 2) { 101 return 0; 102 } 103 TestSensorEvent firstEvent = collection.get(0); 104 TestSensorEvent lastEvent = collection.get(collectionSize - 1); 105 return (lastEvent.timestamp - firstEvent.timestamp) / (collectionSize - 1); 106 } 107 108 /** 109 * Calculate the bias-corrected standard deviation of a collection. 110 * 111 * @throws IllegalArgumentException if the collection is null or empty 112 */ getStandardDeviation( Collection<TValue> collection)113 public static <TValue extends Number> double getStandardDeviation( 114 Collection<TValue> collection) { 115 return Math.sqrt(getVariance(collection)); 116 } 117 118 /** 119 * Convert a period to frequency in Hz. 120 */ getFrequency(TValue period, TimeUnit unit)121 public static <TValue extends Number> double getFrequency(TValue period, TimeUnit unit) { 122 return 1000000000 / (TimeUnit.NANOSECONDS.convert(1, unit) * period.doubleValue()); 123 } 124 125 /** 126 * Convert a frequency in Hz into a period. 127 */ getPeriod(TValue frequency, TimeUnit unit)128 public static <TValue extends Number> double getPeriod(TValue frequency, TimeUnit unit) { 129 return 1000000000 / (TimeUnit.NANOSECONDS.convert(1, unit) * frequency.doubleValue()); 130 } 131 132 /** 133 * @return The magnitude (norm) represented by the given array of values. 134 */ getMagnitude(float[] values)135 public static double getMagnitude(float[] values) { 136 float sumOfSquares = 0.0f; 137 for (float value : values) { 138 sumOfSquares += value * value; 139 } 140 double magnitude = Math.sqrt(sumOfSquares); 141 return magnitude; 142 } 143 144 /** 145 * Helper method to sleep for a given duration. 146 */ sleep(long duration, TimeUnit timeUnit)147 public static void sleep(long duration, TimeUnit timeUnit) throws InterruptedException { 148 long durationNs = TimeUnit.NANOSECONDS.convert(duration, timeUnit); 149 Thread.sleep(durationNs / NANOS_PER_MILLI, (int) (durationNs % NANOS_PER_MILLI)); 150 } 151 152 /** 153 * Format an assertion message. 154 * 155 * @param label the verification name 156 * @param environment the environment of the test 157 * 158 * @return The formatted string 159 */ formatAssertionMessage(String label, TestSensorEnvironment environment)160 public static String formatAssertionMessage(String label, TestSensorEnvironment environment) { 161 return formatAssertionMessage(label, environment, "Failed"); 162 } 163 164 /** 165 * Format an assertion message with a custom message. 166 * 167 * @param label the verification name 168 * @param environment the environment of the test 169 * @param format the additional format string 170 * @param params the additional format params 171 * 172 * @return The formatted string 173 */ formatAssertionMessage( String label, TestSensorEnvironment environment, String format, Object ... params)174 public static String formatAssertionMessage( 175 String label, 176 TestSensorEnvironment environment, 177 String format, 178 Object ... params) { 179 return formatAssertionMessage(label, environment, String.format(format, params)); 180 } 181 182 /** 183 * Format an assertion message. 184 * 185 * @param label the verification name 186 * @param environment the environment of the test 187 * @param extras the additional information for the assertion 188 * 189 * @return The formatted string 190 */ formatAssertionMessage( String label, TestSensorEnvironment environment, String extras)191 public static String formatAssertionMessage( 192 String label, 193 TestSensorEnvironment environment, 194 String extras) { 195 return String.format( 196 "%s | sensor='%s', samplingPeriod=%dus, maxReportLatency=%dus | %s", 197 label, 198 environment.getSensor().getName(), 199 environment.getRequestedSamplingPeriodUs(), 200 environment.getMaxReportLatencyUs(), 201 extras); 202 } 203 204 /** 205 * @return A {@link File} representing a root directory to store sensor tests data. 206 */ getSensorTestDataDirectory()207 public static File getSensorTestDataDirectory() throws IOException { 208 File dataDirectory = new File(System.getenv("EXTERNAL_STORAGE"), "sensorTests/"); 209 return createDirectoryStructure(dataDirectory); 210 } 211 212 /** 213 * Creates the directory structure for the given sensor test data sub-directory. 214 * 215 * @param subdirectory The sub-directory's name. 216 */ getSensorTestDataDirectory(String subdirectory)217 public static File getSensorTestDataDirectory(String subdirectory) throws IOException { 218 File subdirectoryFile = new File(getSensorTestDataDirectory(), subdirectory); 219 return createDirectoryStructure(subdirectoryFile); 220 } 221 222 /** 223 * Sanitizes a string so it can be used in file names. 224 * 225 * @param value The string to sanitize. 226 * @return The sanitized string. 227 * 228 * @throws SensorTestPlatformException If the string cannot be sanitized. 229 */ sanitizeStringForFileName(String value)230 public static String sanitizeStringForFileName(String value) 231 throws SensorTestPlatformException { 232 String sanitizedValue = value.replaceAll("[^a-zA-Z0-9_\\-]", "_"); 233 if (sanitizedValue.matches("_*")) { 234 throw new SensorTestPlatformException( 235 "Unable to sanitize string '%s' for file name.", 236 value); 237 } 238 return sanitizedValue; 239 } 240 241 /** 242 * Ensures that the directory structure represented by the given {@link File} is created. 243 */ createDirectoryStructure(File directoryStructure)244 private static File createDirectoryStructure(File directoryStructure) throws IOException { 245 directoryStructure.mkdirs(); 246 if (!directoryStructure.isDirectory()) { 247 throw new IOException("Unable to create directory structure for " 248 + directoryStructure.getAbsolutePath()); 249 } 250 return directoryStructure; 251 } 252 253 /** 254 * Validate that a collection is not null or empty. 255 * 256 * @throws IllegalStateException if collection is null or empty. 257 */ validateCollection(Collection<T> collection)258 private static <T> void validateCollection(Collection<T> collection) { 259 if(collection == null || collection.size() == 0) { 260 throw new IllegalStateException("Collection cannot be null or empty"); 261 } 262 } 263 getUnitsForSensor(Sensor sensor)264 public static String getUnitsForSensor(Sensor sensor) { 265 switch(sensor.getType()) { 266 case Sensor.TYPE_ACCELEROMETER: 267 return "m/s^2"; 268 case Sensor.TYPE_MAGNETIC_FIELD: 269 case Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED: 270 return "uT"; 271 case Sensor.TYPE_GYROSCOPE: 272 case Sensor.TYPE_GYROSCOPE_UNCALIBRATED: 273 return "radians/sec"; 274 case Sensor.TYPE_PRESSURE: 275 return "hPa"; 276 }; 277 return ""; 278 } 279 } 280