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