Home | History | Annotate | Download | only in helpers
      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      */
     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      */
     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      */
     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      */
     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      */
    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      */
    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      */
    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     /**
    144      * If value lies outside the boundary limit, then return the nearer bound value.
    145      * Otherwise, return the value unchanged.
    146      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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