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.content.Context; 22 import android.hardware.Sensor; 23 import android.hardware.SensorEvent; 24 import android.hardware.SensorEventListener2; 25 import android.os.Handler; 26 import android.os.Looper; 27 import android.os.SystemClock; 28 import android.os.PowerManager; 29 import android.os.PowerManager.WakeLock; 30 import android.util.Log; 31 32 import java.io.BufferedWriter; 33 import java.io.File; 34 import java.io.FileWriter; 35 import java.io.IOException; 36 import java.util.ArrayList; 37 import java.util.Arrays; 38 import java.util.Collections; 39 import java.util.List; 40 import java.util.concurrent.CountDownLatch; 41 import java.util.concurrent.TimeUnit; 42 import java.util.concurrent.atomic.AtomicInteger; 43 44 /** 45 * A {@link SensorEventListener2} which performs operations such as waiting for a specific number of 46 * events or for a specific time, or waiting for a flush to complete. This class performs 47 * verifications and will throw {@link AssertionError}s if there are any errors. It may also wrap 48 * another {@link SensorEventListener2}. 49 */ 50 public class TestSensorEventListener implements SensorEventListener2 { 51 public static final String LOG_TAG = "TestSensorEventListener"; 52 53 private static final long EVENT_TIMEOUT_US = TimeUnit.SECONDS.toMicros(5); 54 private static final long FLUSH_TIMEOUT_US = TimeUnit.SECONDS.toMicros(10); 55 56 private final ArrayList<TestSensorEvent> mCollectedEvents = new ArrayList<>(); 57 private final ArrayList<Long> mTimeStampFlushCompleteEvents = new ArrayList<>(); 58 private final List<CountDownLatch> mEventLatches = new ArrayList<>(); 59 private final List<CountDownLatch> mFlushLatches = new ArrayList<>(); 60 private final AtomicInteger mEventsReceivedOutsideHandler = new AtomicInteger(); 61 62 private final Handler mHandler; 63 private final TestSensorEnvironment mEnvironment; 64 65 // Wakelock for keeping the system running after terminate criterion is met. 66 // Useful for CtsVerifier test cases in which cpu can sleep if usb is not connected. 67 private final PowerManager.WakeLock mTestSensorEventListenerWakeLock; 68 69 /** 70 * @deprecated Use {@link TestSensorEventListener(TestSensorEnvironment)}. 71 */ 72 @Deprecated TestSensorEventListener()73 public TestSensorEventListener() { 74 this(null /* environment */); 75 } 76 77 /** 78 * Construct a {@link TestSensorEventListener}. 79 */ TestSensorEventListener(TestSensorEnvironment environment)80 public TestSensorEventListener(TestSensorEnvironment environment) { 81 this(environment, null /* handler */); 82 } 83 84 /** 85 * Construct a {@link TestSensorEventListener}. 86 */ TestSensorEventListener(TestSensorEnvironment environment, Handler handler)87 public TestSensorEventListener(TestSensorEnvironment environment, Handler handler) { 88 mEnvironment = environment; 89 mHandler = handler; 90 PowerManager pm = (PowerManager) environment.getContext().getSystemService( 91 Context.POWER_SERVICE); 92 mTestSensorEventListenerWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 93 "TestSensorEventListenerWakeLock"); 94 } 95 96 /** 97 * {@inheritDoc} 98 */ 99 @Override onSensorChanged(SensorEvent event)100 public void onSensorChanged(SensorEvent event) { 101 long timestampNs = SystemClock.elapsedRealtimeNanos(); 102 checkHandler(); 103 synchronized (mCollectedEvents) { 104 mCollectedEvents.add(new TestSensorEvent(event, timestampNs)); 105 } 106 synchronized (mEventLatches) { 107 for (CountDownLatch latch : mEventLatches) { 108 latch.countDown(); 109 if (latch.getCount() == 0 && !mTestSensorEventListenerWakeLock.isHeld()) { 110 mTestSensorEventListenerWakeLock.acquire(); 111 } 112 } 113 } 114 } 115 116 /** 117 * {@inheritDoc} 118 */ 119 @Override onAccuracyChanged(Sensor sensor, int accuracy)120 public void onAccuracyChanged(Sensor sensor, int accuracy) { 121 checkHandler(); 122 } 123 124 /** 125 * @param eventCount 126 * @return A CountDownLatch initialzed with eventCount and decremented as sensor events arrive 127 * for this listerner. 128 */ getLatchForSensorEvents(int eventCount)129 public CountDownLatch getLatchForSensorEvents(int eventCount) { 130 CountDownLatch latch = new CountDownLatch(eventCount); 131 synchronized (mEventLatches) { 132 mEventLatches.add(latch); 133 } 134 return latch; 135 } 136 137 /** 138 * @return A CountDownLatch initialzed with 1 and decremented as a flush complete arrives 139 * for this listerner. 140 */ getLatchForFlushCompleteEvent()141 public CountDownLatch getLatchForFlushCompleteEvent() { 142 CountDownLatch latch = new CountDownLatch(1); 143 synchronized (mFlushLatches) { 144 mFlushLatches.add(latch); 145 } 146 return latch; 147 } 148 149 /** 150 * {@inheritDoc} 151 */ 152 @Override onFlushCompleted(Sensor sensor)153 public void onFlushCompleted(Sensor sensor) { 154 checkHandler(); 155 long timestampNs = SystemClock.elapsedRealtimeNanos(); 156 synchronized (mTimeStampFlushCompleteEvents) { 157 mTimeStampFlushCompleteEvents.add(timestampNs); 158 } 159 synchronized (mFlushLatches) { 160 for (CountDownLatch latch : mFlushLatches) { 161 latch.countDown(); 162 } 163 } 164 } 165 166 /** 167 * @return The handler (if any) associated with the instance. 168 */ getHandler()169 public Handler getHandler() { 170 return mHandler; 171 } 172 173 /** 174 * @return A list of {@link TestSensorEvent}s collected by the listener. 175 */ getCollectedEvents()176 public List<TestSensorEvent> getCollectedEvents() { 177 synchronized (mCollectedEvents){ 178 return Collections.unmodifiableList((List<TestSensorEvent>) mCollectedEvents.clone()); 179 } 180 } 181 182 /** 183 * Clears the internal list of collected {@link TestSensorEvent}s. 184 */ clearEvents()185 public void clearEvents() { 186 synchronized (mCollectedEvents) { 187 mCollectedEvents.clear(); 188 } 189 } 190 191 192 /** 193 * Utility method to log the collected events to a file. 194 * It will overwrite the file if it already exists, the file is created in a relative directory 195 * named 'events' under the sensor test directory (part of external storage). 196 */ logCollectedEventsToFile(String fileName, long deviceWakeUpTimeMs, long testStartTimeMs, long testStopTimeMs)197 public void logCollectedEventsToFile(String fileName, long deviceWakeUpTimeMs, 198 long testStartTimeMs, long testStopTimeMs) 199 throws IOException { 200 StringBuilder builder = new StringBuilder(); 201 builder.append("Sensor='").append(mEnvironment.getSensor()).append("', "); 202 builder.append("SamplingRateOverloaded=") 203 .append(mEnvironment.isSensorSamplingRateOverloaded()).append(", "); 204 builder.append("RequestedSamplingPeriod=") 205 .append(mEnvironment.getRequestedSamplingPeriodUs()).append("us, "); 206 builder.append("MaxReportLatency=") 207 .append(mEnvironment.getMaxReportLatencyUs()).append("us, "); 208 builder.append("StartedTimestamp=") 209 .append(testStartTimeMs).append("ms, "); 210 builder.append("StoppedTimestamp=") 211 .append(testStopTimeMs).append("ms"); 212 synchronized (mCollectedEvents) { 213 int i = 0, j = 0; 214 while (i < mCollectedEvents.size() && j < mTimeStampFlushCompleteEvents.size()) { 215 if (mCollectedEvents.get(i).receivedTimestamp < 216 mTimeStampFlushCompleteEvents.get(j)) { 217 TestSensorEvent event = mCollectedEvents.get(i); 218 if (deviceWakeUpTimeMs != -1 && deviceWakeUpTimeMs < 219 event.receivedTimestamp/1000000) { 220 builder.append("\n"); 221 builder.append("AP wake-up time=").append(deviceWakeUpTimeMs).append("ms"); 222 deviceWakeUpTimeMs = -1; 223 } 224 builder.append("\n"); 225 builder.append("Timestamp=").append(event.timestamp/1000000).append("ms, "); 226 builder.append("ReceivedTimestamp=").append(event.receivedTimestamp/1000000). 227 append("ms, "); 228 builder.append("Accuracy=").append(event.accuracy).append(", "); 229 builder.append("Values=").append(Arrays.toString(event.values)); 230 ++i; 231 } else { 232 builder.append("\n"); 233 builder.append("ReceivedTimestamp=") 234 .append(mTimeStampFlushCompleteEvents.get(j)/1000000) 235 .append("ms Flush complete Event"); 236 ++j; 237 } 238 } 239 for (;i < mCollectedEvents.size(); ++i) { 240 TestSensorEvent event = mCollectedEvents.get(i); 241 if (deviceWakeUpTimeMs != -1 && deviceWakeUpTimeMs < 242 event.receivedTimestamp/1000000) { 243 builder.append("\n"); 244 builder.append("AP wake-up time=").append(deviceWakeUpTimeMs).append("ms"); 245 deviceWakeUpTimeMs = -1; 246 } 247 builder.append("\n"); 248 builder.append("Timestamp=").append(event.timestamp/1000000).append("ms, "); 249 builder.append("ReceivedTimestamp=").append(event.receivedTimestamp/1000000). 250 append("ms, "); 251 builder.append("Accuracy=").append(event.accuracy).append(", "); 252 builder.append("Values=").append(Arrays.toString(event.values)); 253 } 254 for (;j < mTimeStampFlushCompleteEvents.size(); ++j) { 255 builder.append("\n"); 256 builder.append("ReceivedTimestamp=") 257 .append(mTimeStampFlushCompleteEvents.get(j)/1000000) 258 .append("ms Flush complete Event"); 259 } 260 } 261 262 File eventsDirectory = SensorCtsHelper.getSensorTestDataDirectory("events/"); 263 File logFile = new File(eventsDirectory, fileName); 264 FileWriter fileWriter = new FileWriter(logFile, false /* append */); 265 try (BufferedWriter writer = new BufferedWriter(fileWriter)) { 266 writer.write(builder.toString()); 267 } 268 } 269 270 /** 271 * Wait for {@link #onFlushCompleted(Sensor)} to be called. 272 * 273 * A wake lock may be acquired at the return if operation is successful. Do 274 * {@link releaseWakeLock()} if the wakelock is not necessary. 275 * 276 * @throws AssertionError if there was a timeout after {@link #FLUSH_TIMEOUT_US} µs 277 */ waitForFlushComplete(CountDownLatch latch, boolean clearCollectedEvents)278 public void waitForFlushComplete(CountDownLatch latch, 279 boolean clearCollectedEvents) throws InterruptedException { 280 if (clearCollectedEvents) { 281 clearEvents(); 282 } 283 try { 284 String message = SensorCtsHelper.formatAssertionMessage( 285 "WaitForFlush", 286 mEnvironment, 287 "timeout=%dus", 288 FLUSH_TIMEOUT_US); 289 Assert.assertTrue(message, latch.await(FLUSH_TIMEOUT_US, TimeUnit.MICROSECONDS)); 290 } finally { 291 synchronized (mFlushLatches) { 292 mFlushLatches.remove(latch); 293 } 294 } 295 } 296 297 /** 298 * Collect a specific number of {@link TestSensorEvent}s. 299 * 300 * A wake lock may be acquired at the return if operation is successful. Do 301 * {@link releaseWakeLock()} if the wakelock is not necessary. 302 * 303 * @throws AssertionError if there was a timeout after {@link #FLUSH_TIMEOUT_US} µs 304 */ waitForEvents(CountDownLatch latch, int eventCount, boolean clearCollectedEvents)305 public void waitForEvents(CountDownLatch latch, int eventCount, 306 boolean clearCollectedEvents) throws InterruptedException { 307 if (clearCollectedEvents) { 308 clearEvents(); 309 } 310 try { 311 long samplingPeriodUs = mEnvironment.getMaximumExpectedSamplingPeriodUs(); 312 // timeout is 2 * event count * expected period + batch timeout + default wait 313 // we multiply by two as not to raise an error in this function even if the events are 314 // streaming at a lower rate than expected, as long as it's not streaming twice as slow 315 // as expected 316 long timeoutUs = (2 * eventCount * samplingPeriodUs) 317 + mEnvironment.getMaxReportLatencyUs() 318 + EVENT_TIMEOUT_US; 319 boolean success = latch.await(timeoutUs, TimeUnit.MICROSECONDS); 320 if (!success) { 321 String message = SensorCtsHelper.formatAssertionMessage( 322 "WaitForEvents", 323 mEnvironment, 324 "requested=%d, received=%d, timeout=%dus", 325 eventCount, 326 eventCount - latch.getCount(), 327 timeoutUs); 328 Assert.fail(message); 329 } 330 } finally { 331 synchronized (mEventLatches) { 332 mEventLatches.remove(latch); 333 } 334 } 335 } 336 337 /** 338 * Collect {@link TestSensorEvent} for a specific duration. 339 */ waitForEvents(long duration, TimeUnit timeUnit)340 public void waitForEvents(long duration, TimeUnit timeUnit) throws InterruptedException { 341 SensorCtsHelper.sleep(duration, timeUnit); 342 } 343 344 /** 345 * Asserts that sensor events arrived in the proper thread if a {@link Handler} was associated 346 * with the current instance. 347 * 348 * If no events were received this assertion will be evaluated to {@code true}. 349 */ assertEventsReceivedInHandler()350 public void assertEventsReceivedInHandler() { 351 int eventsOutsideHandler = mEventsReceivedOutsideHandler.get(); 352 String message = String.format( 353 "Events arrived outside the associated Looper. Expected=0, Found=%d", 354 eventsOutsideHandler); 355 Assert.assertEquals(message, 0 /* expected */, eventsOutsideHandler); 356 } 357 releaseWakeLock()358 public void releaseWakeLock() { 359 if (mTestSensorEventListenerWakeLock.isHeld()) { 360 mTestSensorEventListenerWakeLock.release(); 361 } 362 } 363 364 /** 365 * Keeps track of the number of events that arrived in a different {@link Looper} than the one 366 * associated with the {@link TestSensorEventListener}. 367 */ checkHandler()368 private void checkHandler() { 369 if (mHandler != null && mHandler.getLooper() != Looper.myLooper()) { 370 mEventsReceivedOutsideHandler.incrementAndGet(); 371 } 372 } 373 } 374