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} &micro;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} &micro;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