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