blob: 9cb371030c054fb0d83e3e493ba1af6b5cfd4c86 [file] [log] [blame]
destradaae69403f2013-10-07 17:37:49 -07001/*
destradaa228d1b52013-10-16 18:43:32 -07002 * Copyright (C) 2013 The Android Open Source Project
destradaae69403f2013-10-07 17:37:49 -07003 *
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 */
16package android.hardware.cts.helpers;
17
Tarandeep Singhe219b9a2015-06-11 19:58:15 +053018import android.hardware.Sensor;
destradaa37286b42014-10-02 13:46:05 -070019import java.io.File;
20import java.io.IOException;
destradaae69403f2013-10-07 17:37:49 -070021import java.util.ArrayList;
22import java.util.Collection;
23import java.util.Collections;
Eric Rowe8a92e112014-02-11 15:17:46 -080024import java.util.List;
destradaae69403f2013-10-07 17:37:49 -070025import java.util.concurrent.TimeUnit;
26
27/**
28 * Set of static helper methods for CTS tests.
29 */
destradaafd031b92014-10-10 10:45:51 -070030//TODO: Refactor this class into several more well defined helper classes, look at StatisticsUtils
destradaae69403f2013-10-07 17:37:49 -070031public class SensorCtsHelper {
Eric Rowe8a92e112014-02-11 15:17:46 -080032
destradaa139500e2014-08-22 16:01:52 -070033 private static final long NANOS_PER_MILLI = 1000000;
Eric Rowe7be0b5f2014-03-12 17:52:55 -070034
destradaae69403f2013-10-07 17:37:49 -070035 /**
Eric Rowe8a92e112014-02-11 15:17:46 -080036 * Private constructor for static class.
destradaae69403f2013-10-07 17:37:49 -070037 */
38 private SensorCtsHelper() {}
39
Eric Rowe8a92e112014-02-11 15:17:46 -080040 /**
Peng Xud1ade0d2015-09-15 13:11:58 -070041 * Get percentiles using nearest rank algorithm.
Eric Rowe8a92e112014-02-11 15:17:46 -080042 *
Peng Xu40b86b72015-09-15 13:11:58 -070043 * @param percentiles List of percentiles interested. Its range is 0 to 1, instead of in %.
44 * The value will be internally bounded.
45 *
Peng Xud1ade0d2015-09-15 13:11:58 -070046 * @throws IllegalArgumentException if the collection or percentiles is null or empty
Eric Rowe8a92e112014-02-11 15:17:46 -080047 */
Peng Xud1ade0d2015-09-15 13:11:58 -070048 public static <TValue extends Comparable<? super TValue>> List<TValue> getPercentileValue(
49 Collection<TValue> collection, float[] percentiles) {
destradaae69403f2013-10-07 17:37:49 -070050 validateCollection(collection);
Peng Xud1ade0d2015-09-15 13:11:58 -070051 if(percentiles == null || percentiles.length == 0) {
52 throw new IllegalStateException("percentiles cannot be null or empty");
53 }
destradaae69403f2013-10-07 17:37:49 -070054
Eric Rowe8a92e112014-02-11 15:17:46 -080055 List<TValue> arrayCopy = new ArrayList<TValue>(collection);
destradaae69403f2013-10-07 17:37:49 -070056 Collections.sort(arrayCopy);
57
Peng Xud1ade0d2015-09-15 13:11:58 -070058 List<TValue> percentileValues = new ArrayList<TValue>();
59 for (float p : percentiles) {
60 // zero-based array index
Peng Xu40b86b72015-09-15 13:11:58 -070061 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);
Peng Xud1ade0d2015-09-15 13:11:58 -070064 percentileValues.add(arrayCopy.get(arrayIndex));
65 }
66 return percentileValues;
destradaae69403f2013-10-07 17:37:49 -070067 }
68
destradaa65c7cdb2013-10-28 16:36:12 -070069 /**
Eric Rowe8a92e112014-02-11 15:17:46 -080070 * Calculate the mean of a collection.
71 *
72 * @throws IllegalArgumentException if the collection is null or empty
73 */
destradaae69403f2013-10-07 17:37:49 -070074 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
Eric Rowe8a92e112014-02-11 15:17:46 -080084 /**
Eric Rowe3194ad32014-02-21 17:30:21 -080085 * Calculate the bias-corrected sample variance of a collection.
Eric Rowe8a92e112014-02-11 15:17:46 -080086 *
87 * @throws IllegalArgumentException if the collection is null or empty
88 */
destradaae69403f2013-10-07 17:37:49 -070089 public static <TValue extends Number> double getVariance(Collection<TValue> collection) {
90 validateCollection(collection);
91
92 double mean = getMean(collection);
Eric Rowe3194ad32014-02-21 17:30:21 -080093 ArrayList<Double> squaredDiffs = new ArrayList<Double>();
destradaae69403f2013-10-07 17:37:49 -070094 for(TValue value : collection) {
95 double difference = mean - value.doubleValue();
Eric Rowe3194ad32014-02-21 17:30:21 -080096 squaredDiffs.add(Math.pow(difference, 2));
destradaae69403f2013-10-07 17:37:49 -070097 }
98
Eric Rowe3194ad32014-02-21 17:30:21 -080099 double sum = 0.0;
100 for (Double value : squaredDiffs) {
101 sum += value;
102 }
103 return sum / (squaredDiffs.size() - 1);
destradaae69403f2013-10-07 17:37:49 -0700104 }
105
106 /**
destradaa37286b42014-10-02 13:46:05 -0700107 * @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 /**
Eric Rowe3194ad32014-02-21 17:30:21 -0800120 * Calculate the bias-corrected standard deviation of a collection.
destradaae69403f2013-10-07 17:37:49 -0700121 *
Eric Rowe8a92e112014-02-11 15:17:46 -0800122 * @throws IllegalArgumentException if the collection is null or empty
destradaae69403f2013-10-07 17:37:49 -0700123 */
Eric Rowe8a92e112014-02-11 15:17:46 -0800124 public static <TValue extends Number> double getStandardDeviation(
125 Collection<TValue> collection) {
126 return Math.sqrt(getVariance(collection));
destradaae69403f2013-10-07 17:37:49 -0700127 }
128
129 /**
Eric Rowe8a92e112014-02-11 15:17:46 -0800130 * 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 /**
destradaa37286b42014-10-02 13:46:05 -0700144 * @return The magnitude (norm) represented by the given array of values.
Eric Rowe8a92e112014-02-11 15:17:46 -0800145 */
destradaa37286b42014-10-02 13:46:05 -0700146 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;
Eric Rowe8a92e112014-02-11 15:17:46 -0800153 }
154
155 /**
Eric Rowe7be0b5f2014-03-12 17:52:55 -0700156 * Helper method to sleep for a given duration.
Eric Rowe8a92e112014-02-11 15:17:46 -0800157 */
destradaafd031b92014-10-10 10:45:51 -0700158 public static void sleep(long duration, TimeUnit timeUnit) throws InterruptedException {
Eric Rowe7be0b5f2014-03-12 17:52:55 -0700159 long durationNs = TimeUnit.NANOSECONDS.convert(duration, timeUnit);
destradaafd031b92014-10-10 10:45:51 -0700160 Thread.sleep(durationNs / NANOS_PER_MILLI, (int) (durationNs % NANOS_PER_MILLI));
destradaae69403f2013-10-07 17:37:49 -0700161 }
162
163 /**
Eric Rowe7b44fee2014-03-18 18:16:50 -0700164 * Format an assertion message.
165 *
Eric Rowe7b44fee2014-03-18 18:16:50 -0700166 * @param label the verification name
destradaab7f89492014-09-19 14:23:04 -0700167 * @param environment the environment of the test
168 *
Eric Rowe7b44fee2014-03-18 18:16:50 -0700169 * @return The formatted string
170 */
destradaab7f89492014-09-19 14:23:04 -0700171 public static String formatAssertionMessage(String label, TestSensorEnvironment environment) {
destradaa37286b42014-10-02 13:46:05 -0700172 return formatAssertionMessage(label, environment, "Failed");
Eric Rowe7b44fee2014-03-18 18:16:50 -0700173 }
174
175 /**
176 * Format an assertion message with a custom message.
177 *
Eric Rowe7b44fee2014-03-18 18:16:50 -0700178 * @param label the verification name
destradaab7f89492014-09-19 14:23:04 -0700179 * @param environment the environment of the test
Eric Rowe7b44fee2014-03-18 18:16:50 -0700180 * @param format the additional format string
181 * @param params the additional format params
destradaab7f89492014-09-19 14:23:04 -0700182 *
Eric Rowe7b44fee2014-03-18 18:16:50 -0700183 * @return The formatted string
184 */
destradaab7f89492014-09-19 14:23:04 -0700185 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 */
202 public static String formatAssertionMessage(
203 String label,
204 TestSensorEnvironment environment,
205 String extras) {
206 return String.format(
destradaa4458c692014-10-15 12:42:29 -0700207 "%s | sensor='%s', samplingPeriod=%dus, maxReportLatency=%dus | %s",
destradaab7f89492014-09-19 14:23:04 -0700208 label,
destradaa37286b42014-10-02 13:46:05 -0700209 environment.getSensor().getName(),
destradaab7f89492014-09-19 14:23:04 -0700210 environment.getRequestedSamplingPeriodUs(),
211 environment.getMaxReportLatencyUs(),
212 extras);
Eric Rowe7b44fee2014-03-18 18:16:50 -0700213 }
214
215 /**
destradaa37286b42014-10-02 13:46:05 -0700216 * @return A {@link File} representing a root directory to store sensor tests data.
217 */
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 */
228 public static File getSensorTestDataDirectory(String subdirectory) throws IOException {
229 File subdirectoryFile = new File(getSensorTestDataDirectory(), subdirectory);
230 return createDirectoryStructure(subdirectoryFile);
231 }
232
233 /**
destradaab058a7a2014-12-18 15:41:24 -0800234 * 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 */
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 /**
destradaa37286b42014-10-02 13:46:05 -0700253 * Ensures that the directory structure represented by the given {@link File} is created.
254 */
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 /**
Eric Rowe8a92e112014-02-11 15:17:46 -0800265 * Validate that a collection is not null or empty.
266 *
267 * @throws IllegalStateException if collection is null or empty.
destradaae69403f2013-10-07 17:37:49 -0700268 */
Eric Rowe8a92e112014-02-11 15:17:46 -0800269 private static <T> void validateCollection(Collection<T> collection) {
destradaae69403f2013-10-07 17:37:49 -0700270 if(collection == null || collection.size() == 0) {
271 throw new IllegalStateException("Collection cannot be null or empty");
272 }
273 }
Tarandeep Singhe219b9a2015-06-11 19:58:15 +0530274
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 }
destradaae69403f2013-10-07 17:37:49 -0700290}