1 /*
2  * Copyright (C) 2024 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 com.android.systemui.ambient.touch;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.mockito.ArgumentMatchers.any;
22 import static org.mockito.ArgumentMatchers.anyBoolean;
23 import static org.mockito.ArgumentMatchers.anyInt;
24 import static org.mockito.ArgumentMatchers.eq;
25 import static org.mockito.Mockito.atLeast;
26 import static org.mockito.Mockito.clearInvocations;
27 import static org.mockito.Mockito.doAnswer;
28 import static org.mockito.Mockito.never;
29 import static org.mockito.Mockito.spy;
30 import static org.mockito.Mockito.verify;
31 import static org.mockito.Mockito.when;
32 
33 import android.content.res.Configuration;
34 import android.graphics.Rect;
35 import android.graphics.Region;
36 import android.hardware.display.DisplayManager;
37 import android.platform.test.annotations.DisableFlags;
38 import android.platform.test.annotations.EnableFlags;
39 import android.testing.TestableLooper;
40 import android.view.GestureDetector;
41 import android.view.IWindowManager;
42 import android.view.InputEvent;
43 import android.view.MotionEvent;
44 import android.view.WindowManager;
45 import android.view.WindowMetrics;
46 
47 import androidx.annotation.NonNull;
48 import androidx.lifecycle.Lifecycle;
49 import androidx.lifecycle.LifecycleObserver;
50 import androidx.lifecycle.LifecycleOwner;
51 import androidx.lifecycle.LifecycleRegistry;
52 import androidx.test.ext.junit.runners.AndroidJUnit4;
53 import androidx.test.filters.SmallTest;
54 
55 import com.android.systemui.Flags;
56 import com.android.systemui.SysuiTestCase;
57 import com.android.systemui.ambient.touch.dagger.InputSessionComponent;
58 import com.android.systemui.kosmos.KosmosJavaAdapter;
59 import com.android.systemui.shared.system.InputChannelCompat;
60 import com.android.systemui.util.concurrency.FakeExecutor;
61 import com.android.systemui.util.display.DisplayHelper;
62 import com.android.systemui.util.time.FakeSystemClock;
63 
64 import com.google.common.util.concurrent.ListenableFuture;
65 
66 import org.junit.Before;
67 import org.junit.Test;
68 import org.junit.runner.RunWith;
69 import org.mockito.ArgumentCaptor;
70 import org.mockito.Mockito;
71 import org.mockito.MockitoAnnotations;
72 
73 import java.util.ArrayList;
74 import java.util.HashSet;
75 import java.util.Set;
76 import java.util.concurrent.ExecutionException;
77 import java.util.function.Consumer;
78 import java.util.stream.Collectors;
79 import java.util.stream.Stream;
80 
81 @SmallTest
82 @RunWith(AndroidJUnit4.class)
83 @TestableLooper.RunWithLooper(setAsMainLooper = true)
84 public class TouchMonitorTest extends SysuiTestCase {
85     private KosmosJavaAdapter mKosmos;
86     @Before
setup()87     public void setup() {
88         MockitoAnnotations.initMocks(this);
89         mKosmos = new KosmosJavaAdapter(this);
90     }
91 
92     private static class SimpleLifecycleOwner implements LifecycleOwner {
93         LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
94         @NonNull
95         @Override
getLifecycle()96         public Lifecycle getLifecycle() {
97             return mLifecycle;
98         }
99 
setState(Lifecycle.State state)100         public void setState(Lifecycle.State state) {
101             mLifecycle.setCurrentState(state);
102         }
103     }
104 
105     private static class Environment {
106         private final InputSessionComponent.Factory mInputFactory;
107         private final InputSession mInputSession;
108         private final SimpleLifecycleOwner mLifecycleOwner;
109 
110         private final LifecycleRegistry mLifecycleRegistry;
111         private final TouchMonitor mMonitor;
112         private final InputChannelCompat.InputEventListener mEventListener;
113         private final GestureDetector.OnGestureListener mGestureListener;
114         private final DisplayHelper mDisplayHelper;
115         private final DisplayManager mDisplayManager;
116         private final WindowManager mWindowManager;
117         private final WindowMetrics mWindowMetrics;
118         private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
119         private final FakeExecutor mBackgroundExecutor = new FakeExecutor(new FakeSystemClock());
120 
121         private final Rect mDisplayBounds = Mockito.mock(Rect.class);
122         private final IWindowManager mIWindowManager;
123 
124         private final KosmosJavaAdapter mKosmos;
125 
126         private ArrayList<LifecycleObserver> mLifecycleObservers = new ArrayList<>();
127 
128 
Environment(Set<TouchHandler> handlers, KosmosJavaAdapter kosmos)129         Environment(Set<TouchHandler> handlers, KosmosJavaAdapter kosmos) {
130             mLifecycleOwner = new SimpleLifecycleOwner();
131             mLifecycleRegistry = spy(new LifecycleRegistry(mLifecycleOwner));
132 
133             mIWindowManager = Mockito.mock(IWindowManager.class);
134             mDisplayManager = Mockito.mock(DisplayManager.class);
135             mWindowManager = Mockito.mock(WindowManager.class);
136             mKosmos = kosmos;
137 
138             mInputFactory = Mockito.mock(InputSessionComponent.Factory.class);
139             final InputSessionComponent inputComponent = Mockito.mock(InputSessionComponent.class);
140             mInputSession = Mockito.mock(InputSession.class);
141 
142             when(mInputFactory.create(any(), any(), any(), anyBoolean()))
143                     .thenReturn(inputComponent);
144             when(inputComponent.getInputSession()).thenReturn(mInputSession);
145 
146             mDisplayHelper = Mockito.mock(DisplayHelper.class);
147             when(mDisplayHelper.getMaxBounds(anyInt(), anyInt()))
148                     .thenReturn(mDisplayBounds);
149 
150             mWindowMetrics = Mockito.mock(WindowMetrics.class);
151             when(mWindowMetrics.getBounds()).thenReturn(mDisplayBounds);
152             when(mWindowManager.getMaximumWindowMetrics()).thenReturn(mWindowMetrics);
153             mMonitor = new TouchMonitor(mExecutor, mBackgroundExecutor, mLifecycleRegistry,
154                     mInputFactory, mDisplayHelper, mKosmos.getConfigurationInteractor(),
155                     handlers, mIWindowManager,  0);
156             clearInvocations(mLifecycleRegistry);
157             mMonitor.init();
158 
159             ArgumentCaptor<LifecycleObserver> observerCaptor =
160                     ArgumentCaptor.forClass(LifecycleObserver.class);
161             verify(mLifecycleRegistry, atLeast(1)).addObserver(observerCaptor.capture());
162             mLifecycleObservers.addAll(observerCaptor.getAllValues());
163 
164             updateLifecycle(Lifecycle.State.RESUMED);
165 
166             // Capture creation request.
167             final ArgumentCaptor<InputChannelCompat.InputEventListener> inputEventListenerCaptor =
168                     ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
169             final ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
170                     ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
171             verify(mInputFactory).create(any(), inputEventListenerCaptor.capture(),
172                     gestureListenerCaptor.capture(),
173                     eq(true));
174             mEventListener = inputEventListenerCaptor.getValue();
175             mGestureListener = gestureListenerCaptor.getValue();
176         }
177 
getDisplayBounds()178         public Rect getDisplayBounds() {
179             return mDisplayBounds;
180         }
181 
executeAll()182         void executeAll() {
183             mExecutor.runAllReady();
184         }
185 
publishInputEvent(InputEvent event)186         void publishInputEvent(InputEvent event) {
187             mEventListener.onInputEvent(event);
188         }
189 
publishGestureEvent(Consumer<GestureDetector.OnGestureListener> listenerConsumer)190         void publishGestureEvent(Consumer<GestureDetector.OnGestureListener> listenerConsumer) {
191             listenerConsumer.accept(mGestureListener);
192         }
193 
updateLifecycle(Lifecycle.State state)194         void updateLifecycle(Lifecycle.State state) {
195             mLifecycleRegistry.setCurrentState(state);
196         }
197 
verifyInputSessionDispose()198         void verifyInputSessionDispose() {
199             verify(mInputSession).dispose();
200             Mockito.clearInvocations(mInputSession);
201         }
202 
destroyMonitor()203         void destroyMonitor() {
204             mMonitor.destroy();
205         }
206 
verifyLifecycleObserversUnregistered()207         void verifyLifecycleObserversUnregistered() {
208             for (LifecycleObserver observer : mLifecycleObservers) {
209                 verify(mLifecycleRegistry).removeObserver(observer);
210             }
211         }
212     }
213 
214     @Test
215     @EnableFlags(Flags.FLAG_AMBIENT_TOUCH_MONITOR_LISTEN_TO_DISPLAY_CHANGES)
testConfigurationListenerUpdatesBounds()216     public void testConfigurationListenerUpdatesBounds() {
217         final TouchHandler touchHandler = createTouchHandler();
218         final Environment environment = new Environment(Stream.of(touchHandler)
219                 .collect(Collectors.toCollection(HashSet::new)), mKosmos);
220         ArgumentCaptor<DisplayManager.DisplayListener> listenerCaptor =
221                 ArgumentCaptor.forClass(DisplayManager.DisplayListener.class);
222         final Rect testRect = new Rect(0, 0, 2, 2);
223         final Configuration configuration = new Configuration();
224         configuration.windowConfiguration.setMaxBounds(testRect);
225 
226         mKosmos.getConfigurationRepository().onConfigurationChange(configuration);
227         final MotionEvent initialEvent = Mockito.mock(MotionEvent.class);
228         when(initialEvent.getX()).thenReturn(0.0f);
229         when(initialEvent.getY()).thenReturn(0.0f);
230         environment.publishInputEvent(initialEvent);
231 
232         // Verify display bounds passed into TouchHandler#getTouchInitiationRegion
233         verify(touchHandler).getTouchInitiationRegion(eq(testRect), any(), any());
234     }
235 
236     @Test
237     @DisableFlags(Flags.FLAG_AMBIENT_TOUCH_MONITOR_LISTEN_TO_DISPLAY_CHANGES)
testReportedDisplayBounds()238     public void testReportedDisplayBounds() {
239         final TouchHandler touchHandler = createTouchHandler();
240         final Environment environment = new Environment(Stream.of(touchHandler)
241                 .collect(Collectors.toCollection(HashSet::new)), mKosmos);
242 
243         final MotionEvent initialEvent = Mockito.mock(MotionEvent.class);
244         when(initialEvent.getX()).thenReturn(0.0f);
245         when(initialEvent.getY()).thenReturn(0.0f);
246         environment.publishInputEvent(initialEvent);
247 
248         // Verify display bounds passed into TouchHandler#getTouchInitiationRegion
249         verify(touchHandler).getTouchInitiationRegion(
250                 eq(environment.getDisplayBounds()), any(), any());
251         final ArgumentCaptor<TouchHandler.TouchSession> touchSessionArgumentCaptor =
252                 ArgumentCaptor.forClass(TouchHandler.TouchSession.class);
253         verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture());
254 
255         // Verify that display bounds provided from TouchSession#getBounds
256         assertThat(touchSessionArgumentCaptor.getValue().getBounds())
257                 .isEqualTo(environment.getDisplayBounds());
258     }
259 
260     @Test
testEntryTouchZone()261     public void testEntryTouchZone() {
262         final TouchHandler touchHandler = createTouchHandler();
263         final Rect touchArea = new Rect(4, 4, 8 , 8);
264 
265         doAnswer(invocation -> {
266             final Region region = (Region) invocation.getArguments()[1];
267             region.set(touchArea);
268             return null;
269         }).when(touchHandler).getTouchInitiationRegion(any(), any(), any());
270 
271         final Environment environment = new Environment(Stream.of(touchHandler)
272                 .collect(Collectors.toCollection(HashSet::new)), mKosmos);
273 
274         // Ensure touch outside specified region is not delivered.
275         final MotionEvent initialEvent = Mockito.mock(MotionEvent.class);
276         when(initialEvent.getX()).thenReturn(0.0f);
277         when(initialEvent.getY()).thenReturn(1.0f);
278         environment.publishInputEvent(initialEvent);
279         verify(touchHandler, never()).onSessionStart(any());
280 
281         // Make sure touch inside region causes session start.
282         when(initialEvent.getX()).thenReturn(5.0f);
283         when(initialEvent.getY()).thenReturn(5.0f);
284         environment.publishInputEvent(initialEvent);
285         verify(touchHandler).onSessionStart(any());
286     }
287 
288     @Test
testSessionCount()289     public void testSessionCount() {
290         final TouchHandler touchHandler = createTouchHandler();
291         final Rect touchArea = new Rect(4, 4, 8 , 8);
292 
293         final TouchHandler unzonedTouchHandler = createTouchHandler();
294         doAnswer(invocation -> {
295             final Region region = (Region) invocation.getArguments()[1];
296             region.set(touchArea);
297             return null;
298         }).when(touchHandler).getTouchInitiationRegion(any(), any(), any());
299 
300         final Environment environment = new Environment(Stream.of(touchHandler, unzonedTouchHandler)
301                 .collect(Collectors.toCollection(HashSet::new)), mKosmos);
302 
303         // Ensure touch outside specified region is delivered to unzoned touch handler.
304         final MotionEvent initialEvent = Mockito.mock(MotionEvent.class);
305         when(initialEvent.getX()).thenReturn(0.0f);
306         when(initialEvent.getY()).thenReturn(1.0f);
307         environment.publishInputEvent(initialEvent);
308 
309         ArgumentCaptor<TouchHandler.TouchSession> touchSessionCaptor = ArgumentCaptor.forClass(
310                 TouchHandler.TouchSession.class);
311 
312         // Make sure only one active session.
313         {
314             verify(unzonedTouchHandler).onSessionStart(touchSessionCaptor.capture());
315             final TouchHandler.TouchSession touchSession = touchSessionCaptor.getValue();
316             assertThat(touchSession.getActiveSessionCount()).isEqualTo(1);
317             touchSession.pop();
318             environment.executeAll();
319         }
320 
321         // Make sure touch inside the touch region.
322         when(initialEvent.getX()).thenReturn(5.0f);
323         when(initialEvent.getY()).thenReturn(5.0f);
324         environment.publishInputEvent(initialEvent);
325 
326         // Make sure there are two active sessions.
327         {
328             verify(touchHandler).onSessionStart(touchSessionCaptor.capture());
329             final TouchHandler.TouchSession touchSession = touchSessionCaptor.getValue();
330             assertThat(touchSession.getActiveSessionCount()).isEqualTo(2);
331             touchSession.pop();
332         }
333     }
334 
335 
336     @Test
testNoActiveSessionWhenHandlerDisabled()337     public void testNoActiveSessionWhenHandlerDisabled() {
338         final TouchHandler touchHandler = Mockito.mock(TouchHandler.class);
339         // disable the handler
340         when(touchHandler.isEnabled()).thenReturn(false);
341 
342         final Environment environment = new Environment(Stream.of(touchHandler)
343                 .collect(Collectors.toCollection(HashSet::new)), mKosmos);
344         final MotionEvent initialEvent = Mockito.mock(MotionEvent.class);
345         when(initialEvent.getX()).thenReturn(5.0f);
346         when(initialEvent.getY()).thenReturn(5.0f);
347         environment.publishInputEvent(initialEvent);
348 
349         // Make sure there is no active session.
350         verify(touchHandler, never()).onSessionStart(any());
351         verify(touchHandler, never()).getTouchInitiationRegion(any(), any(), any());
352     }
353 
354     @Test
testInputEventPropagation()355     public void testInputEventPropagation() {
356         final TouchHandler touchHandler = createTouchHandler();
357 
358         final Environment environment = new Environment(Stream.of(touchHandler)
359                 .collect(Collectors.toCollection(HashSet::new)), mKosmos);
360 
361         final InputEvent initialEvent = Mockito.mock(InputEvent.class);
362         environment.publishInputEvent(initialEvent);
363 
364         // Ensure session started
365         final InputChannelCompat.InputEventListener eventListener =
366                 registerInputEventListener(touchHandler);
367 
368         // First event will be missed since we register after the execution loop,
369         final InputEvent event = Mockito.mock(InputEvent.class);
370         environment.publishInputEvent(event);
371         verify(eventListener).onInputEvent(eq(event));
372     }
373 
374     @Test
testInputEventPropagationAfterRemoval()375     public void testInputEventPropagationAfterRemoval() {
376         final TouchHandler touchHandler = createTouchHandler();
377 
378         final Environment environment = new Environment(Stream.of(touchHandler)
379                 .collect(Collectors.toCollection(HashSet::new)), mKosmos);
380 
381         final InputEvent initialEvent = Mockito.mock(InputEvent.class);
382         environment.publishInputEvent(initialEvent);
383 
384         // Ensure session started
385         final TouchHandler.TouchSession session = captureSession(touchHandler);
386         final InputChannelCompat.InputEventListener eventListener =
387                 registerInputEventListener(session);
388 
389         session.pop();
390         environment.executeAll();
391 
392         final InputEvent event = Mockito.mock(InputEvent.class);
393         environment.publishInputEvent(event);
394 
395         verify(eventListener, never()).onInputEvent(eq(event));
396     }
397 
398     @Test
testInputGesturePropagation()399     public void testInputGesturePropagation() {
400         final TouchHandler touchHandler = createTouchHandler();
401 
402         final Environment environment = new Environment(Stream.of(touchHandler)
403                 .collect(Collectors.toCollection(HashSet::new)), mKosmos);
404 
405         final InputEvent initialEvent = Mockito.mock(InputEvent.class);
406         environment.publishInputEvent(initialEvent);
407 
408         // Ensure session started
409         final GestureDetector.OnGestureListener gestureListener =
410                 registerGestureListener(touchHandler);
411 
412         final MotionEvent event = Mockito.mock(MotionEvent.class);
413         environment.publishGestureEvent(onGestureListener -> onGestureListener.onShowPress(event));
414         verify(gestureListener).onShowPress(eq(event));
415     }
416 
417     @Test
testGestureConsumption()418     public void testGestureConsumption() {
419         final TouchHandler touchHandler = createTouchHandler();
420 
421         final Environment environment = new Environment(Stream.of(touchHandler)
422                 .collect(Collectors.toCollection(HashSet::new)), mKosmos);
423 
424         final InputEvent initialEvent = Mockito.mock(InputEvent.class);
425         environment.publishInputEvent(initialEvent);
426 
427         // Ensure session started
428         final GestureDetector.OnGestureListener gestureListener =
429                 registerGestureListener(touchHandler);
430 
431         when(gestureListener.onDown(any())).thenReturn(true);
432         final MotionEvent event = Mockito.mock(MotionEvent.class);
433         environment.publishGestureEvent(onGestureListener -> {
434             assertThat(onGestureListener.onDown(event)).isTrue();
435         });
436 
437         verify(gestureListener).onDown(eq(event));
438     }
439 
440     @Test
testBroadcast()441     public void testBroadcast() {
442         final TouchHandler touchHandler = createTouchHandler();
443         final TouchHandler touchHandler2 = createTouchHandler();
444         when(touchHandler2.isEnabled()).thenReturn(true);
445 
446         final Environment environment = new Environment(Stream.of(touchHandler, touchHandler2)
447                 .collect(Collectors.toCollection(HashSet::new)), mKosmos);
448 
449         final InputEvent initialEvent = Mockito.mock(InputEvent.class);
450         environment.publishInputEvent(initialEvent);
451 
452         final HashSet<InputChannelCompat.InputEventListener> inputListeners = new HashSet<>();
453 
454         inputListeners.add(registerInputEventListener(touchHandler));
455         inputListeners.add(registerInputEventListener(touchHandler));
456         inputListeners.add(registerInputEventListener(touchHandler2));
457 
458         final MotionEvent event = Mockito.mock(MotionEvent.class);
459         environment.publishInputEvent(event);
460 
461         inputListeners
462                 .stream()
463                 .forEach(inputEventListener -> verify(inputEventListener).onInputEvent(event));
464     }
465 
466     @Test
testPush()467     public void testPush() throws InterruptedException, ExecutionException {
468         final TouchHandler touchHandler = createTouchHandler();
469 
470         final Environment environment = new Environment(Stream.of(touchHandler)
471                 .collect(Collectors.toCollection(HashSet::new)), mKosmos);
472 
473         final InputEvent initialEvent = Mockito.mock(InputEvent.class);
474         environment.publishInputEvent(initialEvent);
475 
476         final TouchHandler.TouchSession session = captureSession(touchHandler);
477         final InputChannelCompat.InputEventListener eventListener =
478                 registerInputEventListener(session);
479 
480         final ListenableFuture<TouchHandler.TouchSession> frontSessionFuture = session.push();
481         environment.executeAll();
482         final TouchHandler.TouchSession frontSession = frontSessionFuture.get();
483         final InputChannelCompat.InputEventListener frontEventListener =
484                 registerInputEventListener(frontSession);
485 
486         final MotionEvent event = Mockito.mock(MotionEvent.class);
487         environment.publishInputEvent(event);
488 
489         verify(frontEventListener).onInputEvent(eq(event));
490         verify(eventListener, never()).onInputEvent(any());
491 
492         Mockito.clearInvocations(eventListener, frontEventListener);
493 
494         ListenableFuture<TouchHandler.TouchSession> sessionFuture = frontSession.pop();
495         environment.executeAll();
496 
497         TouchHandler.TouchSession returnedSession = sessionFuture.get();
498         assertThat(session == returnedSession).isTrue();
499 
500         environment.executeAll();
501 
502         final MotionEvent followupEvent = Mockito.mock(MotionEvent.class);
503         environment.publishInputEvent(followupEvent);
504 
505         verify(eventListener).onInputEvent(eq(followupEvent));
506         verify(frontEventListener, never()).onInputEvent(any());
507     }
508 
509     @Test
testPop()510     public void testPop() {
511         final TouchHandler touchHandler = createTouchHandler();
512 
513         final TouchHandler.TouchSession.Callback callback =
514                 Mockito.mock(TouchHandler.TouchSession.Callback.class);
515 
516         final Environment environment = new Environment(Stream.of(touchHandler)
517                 .collect(Collectors.toCollection(HashSet::new)), mKosmos);
518 
519         final InputEvent initialEvent = Mockito.mock(InputEvent.class);
520         environment.publishInputEvent(initialEvent);
521 
522         final TouchHandler.TouchSession session = captureSession(touchHandler);
523         session.registerCallback(callback);
524         session.pop();
525         environment.executeAll();
526 
527         verify(callback).onRemoved();
528     }
529 
530     @Test
testPauseWithNoActiveSessions()531     public void testPauseWithNoActiveSessions() {
532         final TouchHandler touchHandler = createTouchHandler();
533 
534         final Environment environment = new Environment(Stream.of(touchHandler)
535                 .collect(Collectors.toCollection(HashSet::new)), mKosmos);
536 
537         environment.updateLifecycle(Lifecycle.State.STARTED);
538 
539         environment.verifyInputSessionDispose();
540     }
541 
542     @Test
testDeferredPauseWithActiveSessions()543     public void testDeferredPauseWithActiveSessions() {
544         final TouchHandler touchHandler = createTouchHandler();
545 
546         final Environment environment = new Environment(Stream.of(touchHandler)
547                 .collect(Collectors.toCollection(HashSet::new)), mKosmos);
548 
549         final InputEvent initialEvent = Mockito.mock(InputEvent.class);
550         environment.publishInputEvent(initialEvent);
551 
552         // Ensure session started
553         final InputChannelCompat.InputEventListener eventListener =
554                 registerInputEventListener(touchHandler);
555 
556         // First event will be missed since we register after the execution loop,
557         final InputEvent event = Mockito.mock(InputEvent.class);
558         environment.publishInputEvent(event);
559         verify(eventListener).onInputEvent(eq(event));
560 
561         final ArgumentCaptor<TouchHandler.TouchSession> touchSessionArgumentCaptor =
562                 ArgumentCaptor.forClass(TouchHandler.TouchSession.class);
563 
564         verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture());
565 
566         environment.updateLifecycle(Lifecycle.State.STARTED);
567 
568         verify(environment.mInputSession, never()).dispose();
569 
570         // End session
571         touchSessionArgumentCaptor.getValue().pop();
572         environment.executeAll();
573 
574         // Check to make sure the input session is now disposed.
575         environment.verifyInputSessionDispose();
576     }
577 
578     @Test
testDestroyWithActiveSessions()579     public void testDestroyWithActiveSessions() {
580         final TouchHandler touchHandler = createTouchHandler();
581 
582         final Environment environment = new Environment(Stream.of(touchHandler)
583                 .collect(Collectors.toCollection(HashSet::new)), mKosmos);
584 
585         final InputEvent initialEvent = Mockito.mock(InputEvent.class);
586         environment.publishInputEvent(initialEvent);
587 
588         // Ensure session started
589         final InputChannelCompat.InputEventListener eventListener =
590                 registerInputEventListener(touchHandler);
591 
592         // First event will be missed since we register after the execution loop,
593         final InputEvent event = Mockito.mock(InputEvent.class);
594         environment.publishInputEvent(event);
595         verify(eventListener).onInputEvent(eq(event));
596 
597         final ArgumentCaptor<TouchHandler.TouchSession> touchSessionArgumentCaptor =
598                 ArgumentCaptor.forClass(TouchHandler.TouchSession.class);
599 
600         verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture());
601 
602         environment.updateLifecycle(Lifecycle.State.DESTROYED);
603 
604         // Check to make sure the input session is now disposed.
605         environment.verifyInputSessionDispose();
606     }
607 
608 
609     @Test
testPilfering()610     public void testPilfering() {
611         final TouchHandler touchHandler1 = createTouchHandler();
612         final TouchHandler touchHandler2 = createTouchHandler();
613         final Environment environment = new Environment(Stream.of(touchHandler1, touchHandler2)
614                 .collect(Collectors.toCollection(HashSet::new)), mKosmos);
615 
616         final InputEvent initialEvent = Mockito.mock(InputEvent.class);
617         environment.publishInputEvent(initialEvent);
618 
619         final TouchHandler.TouchSession session1 = captureSession(touchHandler1);
620         final GestureDetector.OnGestureListener gestureListener1 =
621                 registerGestureListener(session1);
622 
623         final TouchHandler.TouchSession session2 = captureSession(touchHandler2);
624         final GestureDetector.OnGestureListener gestureListener2 =
625                 registerGestureListener(session2);
626         when(gestureListener2.onDown(any())).thenReturn(true);
627 
628         final MotionEvent gestureEvent = Mockito.mock(MotionEvent.class);
629         environment.publishGestureEvent(
630                 onGestureListener -> onGestureListener.onDown(gestureEvent));
631 
632         Mockito.clearInvocations(gestureListener1, gestureListener2);
633 
634         final MotionEvent followupEvent = Mockito.mock(MotionEvent.class);
635         environment.publishGestureEvent(
636                 onGestureListener -> onGestureListener.onDown(followupEvent));
637 
638         verify(gestureListener1, never()).onDown(any());
639         verify(gestureListener2).onDown(eq(followupEvent));
640     }
641 
642     @Test
testOnRemovedCallbackOnStopMonitoring()643     public void testOnRemovedCallbackOnStopMonitoring() {
644         final TouchHandler touchHandler = createTouchHandler();
645 
646         final TouchHandler.TouchSession.Callback callback =
647                 Mockito.mock(TouchHandler.TouchSession.Callback.class);
648 
649         final Environment environment = new Environment(Stream.of(touchHandler)
650                 .collect(Collectors.toCollection(HashSet::new)), mKosmos);
651 
652         final InputEvent initialEvent = Mockito.mock(InputEvent.class);
653         environment.publishInputEvent(initialEvent);
654 
655         final TouchHandler.TouchSession session = captureSession(touchHandler);
656         session.registerCallback(callback);
657 
658         environment.executeAll();
659 
660         environment.updateLifecycle(Lifecycle.State.DESTROYED);
661 
662         environment.executeAll();
663 
664         verify(callback).onRemoved();
665     }
666 
667     @Test
testDestroy_cleansUpLifecycleObserver()668     public void testDestroy_cleansUpLifecycleObserver() {
669         final TouchHandler touchHandler = createTouchHandler();
670 
671         final Environment environment = new Environment(Stream.of(touchHandler)
672                 .collect(Collectors.toCollection(HashSet::new)), mKosmos);
673         environment.destroyMonitor();
674         environment.verifyLifecycleObserversUnregistered();
675     }
676 
registerGestureListener(TouchHandler handler)677     private GestureDetector.OnGestureListener registerGestureListener(TouchHandler handler) {
678         final GestureDetector.OnGestureListener gestureListener = Mockito.mock(
679                 GestureDetector.OnGestureListener.class);
680         final ArgumentCaptor<TouchHandler.TouchSession> sessionCaptor =
681                 ArgumentCaptor.forClass(TouchHandler.TouchSession.class);
682         verify(handler).onSessionStart(sessionCaptor.capture());
683         sessionCaptor.getValue().registerGestureListener(gestureListener);
684 
685         return gestureListener;
686     }
687 
registerGestureListener( TouchHandler.TouchSession session)688     private GestureDetector.OnGestureListener registerGestureListener(
689             TouchHandler.TouchSession session) {
690         final GestureDetector.OnGestureListener gestureListener = Mockito.mock(
691                 GestureDetector.OnGestureListener.class);
692         session.registerGestureListener(gestureListener);
693 
694         return gestureListener;
695     }
696 
registerInputEventListener( TouchHandler.TouchSession session)697     private InputChannelCompat.InputEventListener registerInputEventListener(
698             TouchHandler.TouchSession session) {
699         final InputChannelCompat.InputEventListener eventListener = Mockito.mock(
700                 InputChannelCompat.InputEventListener.class);
701         session.registerInputListener(eventListener);
702 
703         return eventListener;
704     }
705 
captureSession(TouchHandler handler)706     private TouchHandler.TouchSession captureSession(TouchHandler handler) {
707         final ArgumentCaptor<TouchHandler.TouchSession> sessionCaptor =
708                 ArgumentCaptor.forClass(TouchHandler.TouchSession.class);
709         verify(handler).onSessionStart(sessionCaptor.capture());
710         return sessionCaptor.getValue();
711     }
712 
registerInputEventListener( TouchHandler handler)713     private InputChannelCompat.InputEventListener registerInputEventListener(
714             TouchHandler handler) {
715         return registerInputEventListener(captureSession(handler));
716     }
717 
createTouchHandler()718     private TouchHandler createTouchHandler() {
719         final TouchHandler touchHandler = Mockito.mock(TouchHandler.class);
720         // enable the handler by default
721         when(touchHandler.isEnabled()).thenReturn(true);
722         return touchHandler;
723     }
724 }
725