1 /* 2 * Copyright (C) 2014 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 17 package android.hardware.cts.helpers; 18 19 import junit.framework.Assert; 20 21 import android.hardware.Sensor; 22 import android.hardware.SensorEvent; 23 import android.hardware.SensorEventListener2; 24 import android.os.Handler; 25 import android.os.Looper; 26 import android.os.SystemClock; 27 28 import java.io.BufferedWriter; 29 import java.io.File; 30 import java.io.FileWriter; 31 import java.io.IOException; 32 import java.util.ArrayList; 33 import java.util.Arrays; 34 import java.util.Collections; 35 import java.util.List; 36 import java.util.concurrent.CountDownLatch; 37 import java.util.concurrent.TimeUnit; 38 import java.util.concurrent.atomic.AtomicInteger; 39 40 /** 41 * A {@link SensorEventListener2} which performs operations such as waiting for a specific number of 42 * events or for a specific time, or waiting for a flush to complete. This class performs 43 * verifications and will throw {@link AssertionError}s if there are any errors. It may also wrap 44 * another {@link SensorEventListener2}. 45 */ 46 public class TestSensorEventListener implements SensorEventListener2 { 47 public static final String LOG_TAG = "TestSensorEventListener"; 48 49 private static final long EVENT_TIMEOUT_US = TimeUnit.SECONDS.toMicros(5); 50 private static final long FLUSH_TIMEOUT_US = TimeUnit.SECONDS.toMicros(10); 51 52 private final List<TestSensorEvent> mCollectedEvents = new ArrayList<>(); 53 private final List<CountDownLatch> mEventLatches = new ArrayList<>(); 54 private final List<CountDownLatch> mFlushLatches = new ArrayList<>(); 55 private final AtomicInteger mEventsReceivedOutsideHandler = new AtomicInteger(); 56 57 private final Handler mHandler; 58 private final TestSensorEnvironment mEnvironment; 59 60 /** 61 * @deprecated Use {@link TestSensorEventListener(TestSensorEnvironment)}. 62 */ 63 @Deprecated 64 public TestSensorEventListener() { 65 this(null /* environment */); 66 } 67 68 /** 69 * Construct a {@link TestSensorEventListener}. 70 */ 71 public TestSensorEventListener(TestSensorEnvironment environment) { 72 this(environment, null /* handler */); 73 } 74 75 /** 76 * Construct a {@link TestSensorEventListener}. 77 */ 78 public TestSensorEventListener(TestSensorEnvironment environment, Handler handler) { 79 mEnvironment = environment; 80 mHandler = handler; 81 } 82 83 /** 84 * {@inheritDoc} 85 */ 86 @Override 87 public void onSensorChanged(SensorEvent event) { 88 long timestampNs = SystemClock.elapsedRealtimeNanos(); 89 checkHandler(); 90 synchronized (mCollectedEvents) { 91 mCollectedEvents.add(new TestSensorEvent(event, timestampNs)); 92 } 93 synchronized (mEventLatches) { 94 for (CountDownLatch latch : mEventLatches) { 95 latch.countDown(); 96 } 97 } 98 } 99 100 /** 101 * {@inheritDoc} 102 */ 103 @Override 104 public void onAccuracyChanged(Sensor sensor, int accuracy) { 105 checkHandler(); 106 } 107 108 /** 109 * @param eventCount 110 * @return A CountDownLatch initialzed with eventCount and decremented as sensor events arrive 111 * for this listerner. 112 */ 113 public CountDownLatch getLatchForSensorEvents(int eventCount) { 114 CountDownLatch latch = new CountDownLatch(eventCount); 115 synchronized (mEventLatches) { 116 mEventLatches.add(latch); 117 } 118 return latch; 119 } 120 121 /** 122 * @return A CountDownLatch initialzed with 1 and decremented as a flush complete arrives 123 * for this listerner. 124 */ 125 public CountDownLatch getLatchForFlushCompleteEvent() { 126 CountDownLatch latch = new CountDownLatch(1); 127 synchronized (mFlushLatches) { 128 mFlushLatches.add(latch); 129 } 130 return latch; 131 } 132 133 /** 134 * {@inheritDoc} 135 */ 136 @Override 137 public void onFlushCompleted(Sensor sensor) { 138 checkHandler(); 139 synchronized (mFlushLatches) { 140 for (CountDownLatch latch : mFlushLatches) { 141 latch.countDown(); 142 } 143 } 144 } 145 146 /** 147 * @return The handler (if any) associated with the instance. 148 */ 149 public Handler getHandler() { 150 return mHandler; 151 } 152 153 /** 154 * @return A list of {@link TestSensorEvent}s collected by the listener. 155 */ 156 public List<TestSensorEvent> getCollectedEvents() { 157 synchronized (mCollectedEvents){ 158 return Collections.unmodifiableList(mCollectedEvents); 159 } 160 } 161 162 /** 163 * Clears the internal list of collected {@link TestSensorEvent}s. 164 */ 165 public void clearEvents() { 166 synchronized (mCollectedEvents) { 167 mCollectedEvents.clear(); 168 } 169 } 170 171 172 /** 173 * Utility method to log the collected events to a file. 174 * It will overwrite the file if it already exists, the file is created in a relative directory 175 * named 'events' under the sensor test directory (part of external storage). 176 */ 177 public void logCollectedEventsToFile(String fileName) throws IOException { 178 StringBuilder builder = new StringBuilder(); 179 builder.append("Sensor='").append(mEnvironment.getSensor()).append("', "); 180 builder.append("SamplingRateOverloaded=") 181 .append(mEnvironment.isSensorSamplingRateOverloaded()).append(", "); 182 builder.append("RequestedSamplingPeriod=") 183 .append(mEnvironment.getRequestedSamplingPeriodUs()).append("us, "); 184 builder.append("MaxReportLatency=") 185 .append(mEnvironment.getMaxReportLatencyUs()).append("us"); 186 187 synchronized (mCollectedEvents) { 188 for (TestSensorEvent event : mCollectedEvents) { 189 builder.append("\n"); 190 builder.append("Timestamp=").append(event.timestamp).append("ns, "); 191 builder.append("ReceivedTimestamp=").append(event.receivedTimestamp).append("ns, "); 192 builder.append("Accuracy=").append(event.accuracy).append(", "); 193 builder.append("Values=").append(Arrays.toString(event.values)); 194 } 195 } 196 197 File eventsDirectory = SensorCtsHelper.getSensorTestDataDirectory("events/"); 198 File logFile = new File(eventsDirectory, fileName); 199 FileWriter fileWriter = new FileWriter(logFile, false /* append */); 200 try (BufferedWriter writer = new BufferedWriter(fileWriter)) { 201 writer.write(builder.toString()); 202 } 203 } 204 205 /** 206 * Wait for {@link #onFlushCompleted(Sensor)} to be called. 207 * 208 * @throws AssertionError if there was a timeout after {@link #FLUSH_TIMEOUT_US} µs 209 */ 210 public void waitForFlushComplete(CountDownLatch latch) throws InterruptedException { 211 clearEvents(); 212 try { 213 String message = SensorCtsHelper.formatAssertionMessage( 214 "WaitForFlush", 215 mEnvironment, 216 "timeout=%dus", 217 FLUSH_TIMEOUT_US); 218 Assert.assertTrue(message, latch.await(FLUSH_TIMEOUT_US, TimeUnit.MICROSECONDS)); 219 } finally { 220 synchronized (mFlushLatches) { 221 mFlushLatches.remove(latch); 222 } 223 } 224 } 225 226 /** 227 * Collect a specific number of {@link TestSensorEvent}s. 228 * 229 * @throws AssertionError if there was a timeout after {@link #FLUSH_TIMEOUT_US} µs 230 */ 231 public void waitForEvents(CountDownLatch latch, int eventCount) throws InterruptedException { 232 clearEvents(); 233 try { 234 long samplingPeriodUs = mEnvironment.getMaximumExpectedSamplingPeriodUs(); 235 // timeout is 2 * event count * expected period + batch timeout + default wait 236 // we multiply by two as not to raise an error in this function even if the events are 237 // streaming at a lower rate than expected, as long as it's not streaming twice as slow 238 // as expected 239 long timeoutUs = (2 * eventCount * samplingPeriodUs) 240 + mEnvironment.getMaxReportLatencyUs() 241 + EVENT_TIMEOUT_US; 242 boolean success = latch.await(timeoutUs, TimeUnit.MICROSECONDS); 243 if (!success) { 244 String message = SensorCtsHelper.formatAssertionMessage( 245 "WaitForEvents", 246 mEnvironment, 247 "requested=%d, received=%d, timeout=%dus", 248 eventCount, 249 eventCount - latch.getCount(), 250 timeoutUs); 251 Assert.fail(message); 252 } 253 } finally { 254 synchronized (mEventLatches) { 255 mEventLatches.remove(latch); 256 } 257 } 258 } 259 260 /** 261 * Collect {@link TestSensorEvent} for a specific duration. 262 */ 263 public void waitForEvents(long duration, TimeUnit timeUnit) throws InterruptedException { 264 SensorCtsHelper.sleep(duration, timeUnit); 265 } 266 267 /** 268 * Asserts that sensor events arrived in the proper thread if a {@link Handler} was associated 269 * with the current instance. 270 * 271 * If no events were received this assertion will be evaluated to {@code true}. 272 */ 273 public void assertEventsReceivedInHandler() { 274 int eventsOutsideHandler = mEventsReceivedOutsideHandler.get(); 275 String message = String.format( 276 "Events arrived outside the associated Looper. Expected=0, Found=%d", 277 eventsOutsideHandler); 278 Assert.assertEquals(message, 0 /* expected */, eventsOutsideHandler); 279 } 280 281 /** 282 * Keeps track of the number of events that arrived in a different {@link Looper} than the one 283 * associated with the {@link TestSensorEventListener}. 284 */ 285 private void checkHandler() { 286 if (mHandler != null && mHandler.getLooper() != Looper.myLooper()) { 287 mEventsReceivedOutsideHandler.incrementAndGet(); 288 } 289 } 290 } 291