1 /*
2  * Copyright (C) 2017 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.accessibilityservice.cts.utils;
18 
19 import static android.view.MotionEvent.ACTION_HOVER_MOVE;
20 import static android.view.MotionEvent.ACTION_MOVE;
21 
22 import static com.google.common.truth.Truth.assertThat;
23 import static com.google.common.truth.Truth.assertWithMessage;
24 
25 import static java.util.concurrent.TimeUnit.SECONDS;
26 
27 import android.view.MotionEvent;
28 import android.view.View;
29 
30 import java.util.ArrayList;
31 import java.util.List;
32 import java.util.concurrent.BlockingQueue;
33 import java.util.concurrent.LinkedBlockingQueue;
34 
35 public class EventCapturingMotionEventListener
36         implements View.OnTouchListener, View.OnHoverListener {
37     private static final long WAIT_TIME_SECONDS = 5;
38     private static final long MIN_WAIT_TIME_SECONDS = 2;
39     // whether or not to keep events from propagating to other listeners
40     private boolean mShouldConsumeEvents;
41     private final BlockingQueue<MotionEvent> mEvents = new LinkedBlockingQueue<>();
42 
EventCapturingMotionEventListener(boolean shouldConsumeEvents)43     public EventCapturingMotionEventListener(boolean shouldConsumeEvents) {
44         this.mShouldConsumeEvents = shouldConsumeEvents;
45     }
46 
EventCapturingMotionEventListener()47     public EventCapturingMotionEventListener() {
48         this.mShouldConsumeEvents = true;
49     }
50 
51     @Override
onTouch(View view, MotionEvent motionEvent)52     public boolean onTouch(View view, MotionEvent motionEvent) {
53         return onMotionEvent(motionEvent);
54     }
55 
56     @Override
onHover(View view, MotionEvent motionEvent)57     public boolean onHover(View view, MotionEvent motionEvent) {
58         return onMotionEvent(motionEvent);
59     }
60 
onMotionEvent(MotionEvent event)61     private boolean onMotionEvent(MotionEvent event) {
62         assertThat(mEvents.offer(MotionEvent.obtain(event))).isTrue();
63         return mShouldConsumeEvents;
64     }
65 
66     /** Insure that no motion events have been detected. */
assertNonePropagated()67     public void assertNonePropagated() {
68         try {
69             MotionEvent event = mEvents.poll(MIN_WAIT_TIME_SECONDS, SECONDS);
70             assertThat(event).isNull();
71         } catch (InterruptedException e) {
72             throw new RuntimeException(e);
73         }
74     }
75 
76     /**
77      * Check for the specified motion events. Note that specifying ACTION_MOVE or ACTION_HOVER_MOVE
78      * will match one or more consecutive events with the specified action because it is impossible
79      * to tell in advance how many move events will be generated by a gesture.
80      */
assertPropagated(int... eventTypes)81     public void assertPropagated(int... eventTypes) {
82         MotionEvent ev;
83         try {
84             List<String> expected = new ArrayList<>();
85             List<String> received = new ArrayList<>();
86             for (int e : eventTypes) {
87                 expected.add(MotionEvent.actionToString(e));
88             }
89             ev = mEvents.poll(WAIT_TIME_SECONDS, SECONDS);
90             assertWithMessage(
91                             "Expected "
92                                     + expected
93                                     + " but none present after "
94                                     + WAIT_TIME_SECONDS
95                                     + " seconds")
96                     .that(ev)
97                     .isNotNull();
98             // By this point there is at least one received event.
99             received.add(MotionEvent.actionToString(ev.getActionMasked()));
100             ev = mEvents.poll(WAIT_TIME_SECONDS, SECONDS);
101             while (ev != null) {
102                 int action = ev.getActionMasked();
103                 String current = MotionEvent.actionToString(action);
104                 String prev = received.get(received.size() - 1);
105                 if (action != ACTION_MOVE && action != ACTION_HOVER_MOVE) {
106                     // Add the current event if the previous received event was not ACTION_MOVE or
107                     // ACTION_HOVER_MOVE
108                     // There is no way to predict how many move events will be generated by a
109                     // gesture
110                     // so specifying a move event matches one or more.
111                     received.add(MotionEvent.actionToString(action));
112                 } else {
113                     // The previous event was a move event. Ignore subsequent events with the same
114                     // action.
115                     if (!prev.equals(current)) {
116                         received.add(current);
117                     }
118                 }
119                 if (expected.size() == received.size()) {
120                     // Avoid waiting an extra set of seconds just to confirm we are at the end of
121                     // the stream.
122                     break;
123                 }
124                 ev = mEvents.poll(WAIT_TIME_SECONDS, SECONDS);
125             }
126             assertThat(received).isEqualTo(expected);
127         } catch (InterruptedException e) {
128             throw new RuntimeException(e);
129         }
130     }
131 
getRawEvents()132     public List<MotionEvent> getRawEvents() {
133         List<MotionEvent> motionEvents = new ArrayList<>();
134         MotionEvent ev;
135         try {
136             ev = mEvents.poll(WAIT_TIME_SECONDS, SECONDS);
137             while (ev != null) {
138                 motionEvents.add(ev);
139                 ev = mEvents.poll(WAIT_TIME_SECONDS, SECONDS);
140             }
141         } catch (InterruptedException e) {
142             throw new RuntimeException(e);
143         }
144         assertThat(motionEvents).isNotEmpty();
145         return motionEvents;
146     }
147 
peek()148     public MotionEvent peek() {
149         return mEvents.peek();
150     }
151 
152     /** Clears all received motions events. */
clear()153     public void clear() {
154         mEvents.clear();
155     }
156 }
157