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 * If value lies outside the boundary limit, then return the nearer bound value. 145 * Otherwise, return the value unchanged. 146 */ clamp(TValue val, TValue min, TValue max)147 public static <TValue extends Number> double clamp(TValue val, TValue min, TValue max) { 148 return Math.min(max.doubleValue(), Math.max(min.doubleValue(), val.doubleValue())); 149 } 150 151 /** 152 * @return The magnitude (norm) represented by the given array of values. 153 */ getMagnitude(float[] values)154 public static double getMagnitude(float[] values) { 155 float sumOfSquares = 0.0f; 156 for (float value : values) { 157 sumOfSquares += value * value; 158 } 159 double magnitude = Math.sqrt(sumOfSquares); 160 return magnitude; 161 } 162 163 /** 164 * Helper method to sleep for a given duration. 165 */ sleep(long duration, TimeUnit timeUnit)166 public static void sleep(long duration, TimeUnit timeUnit) throws InterruptedException { 167 long durationNs = TimeUnit.NANOSECONDS.convert(duration, timeUnit); 168 Thread.sleep(durationNs / NANOS_PER_MILLI, (int) (durationNs % NANOS_PER_MILLI)); 169 } 170 171 /** 172 * Format an assertion message. 173 * 174 * @param label the verification name 175 * @param environment the environment of the test 176 * 177 * @return The formatted string 178 */ formatAssertionMessage(String label, TestSensorEnvironment environment)179 public static String formatAssertionMessage(String label, TestSensorEnvironment environment) { 180 return formatAssertionMessage(label, environment, "Failed"); 181 } 182 183 /** 184 * Format an assertion message with a custom message. 185 * 186 * @param label the verification name 187 * @param environment the environment of the test 188 * @param format the additional format string 189 * @param params the additional format params 190 * 191 * @return The formatted string 192 */ formatAssertionMessage( String label, TestSensorEnvironment environment, String format, Object ... params)193 public static String formatAssertionMessage( 194 String label, 195 TestSensorEnvironment environment, 196 String format, 197 Object ... params) { 198 return formatAssertionMessage(label, environment, String.format(format, params)); 199 } 200 201 /** 202 * Format an assertion message. 203 * 204 * @param label the verification name 205 * @param environment the environment of the test 206 * @param extras the additional information for the assertion 207 * 208 * @return The formatted string 209 */ formatAssertionMessage( String label, TestSensorEnvironment environment, String extras)210 public static String formatAssertionMessage( 211 String label, 212 TestSensorEnvironment environment, 213 String extras) { 214 return String.format( 215 "%s | sensor='%s', samplingPeriod=%dus, maxReportLatency=%dus | %s", 216 label, 217 environment.getSensor().getName(), 218 environment.getRequestedSamplingPeriodUs(), 219 environment.getMaxReportLatencyUs(), 220 extras); 221 } 222 223 /** 224 * @return A {@link File} representing a root directory to store sensor tests data. 225 */ getSensorTestDataDirectory()226 public static File getSensorTestDataDirectory() throws IOException { 227 File dataDirectory = new File(System.getenv("EXTERNAL_STORAGE"), "sensorTests/"); 228 return createDirectoryStructure(dataDirectory); 229 } 230 231 /** 232 * Creates the directory structure for the given sensor test data sub-directory. 233 * 234 * @param subdirectory The sub-directory's name. 235 */ getSensorTestDataDirectory(String subdirectory)236 public static File getSensorTestDataDirectory(String subdirectory) throws IOException { 237 File subdirectoryFile = new File(getSensorTestDataDirectory(), subdirectory); 238 return createDirectoryStructure(subdirectoryFile); 239 } 240 241 /** 242 * Sanitizes a string so it can be used in file names. 243 * 244 * @param value The string to sanitize. 245 * @return The sanitized string. 246 * 247 * @throws SensorTestPlatformException If the string cannot be sanitized. 248 */ sanitizeStringForFileName(String value)249 public static String sanitizeStringForFileName(String value) 250 throws SensorTestPlatformException { 251 String sanitizedValue = value.replaceAll("[^a-zA-Z0-9_\\-]", "_"); 252 if (sanitizedValue.matches("_*")) { 253 throw new SensorTestPlatformException( 254 "Unable to sanitize string '%s' for file name.", 255 value); 256 } 257 return sanitizedValue; 258 } 259 260 /** 261 * Ensures that the directory structure represented by the given {@link File} is created. 262 */ createDirectoryStructure(File directoryStructure)263 private static File createDirectoryStructure(File directoryStructure) throws IOException { 264 directoryStructure.mkdirs(); 265 if (!directoryStructure.isDirectory()) { 266 throw new IOException("Unable to create directory structure for " 267 + directoryStructure.getAbsolutePath()); 268 } 269 return directoryStructure; 270 } 271 272 /** 273 * Validate that a collection is not null or empty. 274 * 275 * @throws IllegalStateException if collection is null or empty. 276 */ validateCollection(Collection<T> collection)277 private static <T> void validateCollection(Collection<T> collection) { 278 if(collection == null || collection.size() == 0) { 279 throw new IllegalStateException("Collection cannot be null or empty"); 280 } 281 } 282 getUnitsForSensor(Sensor sensor)283 public static String getUnitsForSensor(Sensor sensor) { 284 switch(sensor.getType()) { 285 case Sensor.TYPE_ACCELEROMETER: 286 return "m/s^2"; 287 case Sensor.TYPE_MAGNETIC_FIELD: 288 case Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED: 289 return "uT"; 290 case Sensor.TYPE_GYROSCOPE: 291 case Sensor.TYPE_GYROSCOPE_UNCALIBRATED: 292 return "radians/sec"; 293 case Sensor.TYPE_PRESSURE: 294 return "hPa"; 295 }; 296 return ""; 297 } 298 } 299