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/1000).append("us, ");
226                     builder.append("ReceivedTimestamp=").append(event.receivedTimestamp/1000).
227                         append("us, ");
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)/1000)
235                     .append("us 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/1000).append("us, ");
249                 builder.append("ReceivedTimestamp=").append(event.receivedTimestamp/1000).
250                     append("us, ");
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)/1000)
258                     .append("us 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} &micro;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} &micro;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