1 /*
2  * Copyright (C) 2022 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.dreams.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.doAnswer;
26 import static org.mockito.Mockito.never;
27 import static org.mockito.Mockito.verify;
28 import static org.mockito.Mockito.when;
29 
30 import android.graphics.Rect;
31 import android.graphics.Region;
32 import android.testing.AndroidTestingRunner;
33 import android.util.Pair;
34 import android.view.GestureDetector;
35 import android.view.InputEvent;
36 import android.view.MotionEvent;
37 
38 import androidx.lifecycle.DefaultLifecycleObserver;
39 import androidx.lifecycle.Lifecycle;
40 import androidx.lifecycle.LifecycleObserver;
41 import androidx.lifecycle.LifecycleOwner;
42 import androidx.test.filters.SmallTest;
43 
44 import com.android.systemui.SysuiTestCase;
45 import com.android.systemui.dreams.touch.dagger.InputSessionComponent;
46 import com.android.systemui.shared.system.InputChannelCompat;
47 import com.android.systemui.util.concurrency.FakeExecutor;
48 import com.android.systemui.util.display.DisplayHelper;
49 import com.android.systemui.util.time.FakeSystemClock;
50 
51 import com.google.common.util.concurrent.ListenableFuture;
52 
53 import org.junit.Before;
54 import org.junit.Test;
55 import org.junit.runner.RunWith;
56 import org.mockito.ArgumentCaptor;
57 import org.mockito.Mockito;
58 import org.mockito.MockitoAnnotations;
59 
60 import java.util.HashSet;
61 import java.util.Set;
62 import java.util.concurrent.ExecutionException;
63 import java.util.function.Consumer;
64 import java.util.stream.Collectors;
65 import java.util.stream.Stream;
66 
67 @SmallTest
68 @RunWith(AndroidTestingRunner.class)
69 public class DreamOverlayTouchMonitorTest extends SysuiTestCase {
70     @Before
setup()71     public void setup() {
72         MockitoAnnotations.initMocks(this);
73     }
74 
75     private static class Environment {
76         private final InputSessionComponent.Factory mInputFactory;
77         private final InputSession mInputSession;
78         private final Lifecycle mLifecycle;
79         private final LifecycleOwner mLifecycleOwner;
80         private final DreamOverlayTouchMonitor mMonitor;
81         private final DefaultLifecycleObserver mLifecycleObserver;
82         private final InputChannelCompat.InputEventListener mEventListener;
83         private final GestureDetector.OnGestureListener mGestureListener;
84         private final DisplayHelper mDisplayHelper;
85         private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
86         private final Rect mDisplayBounds = Mockito.mock(Rect.class);
87 
Environment(Set<DreamTouchHandler> handlers)88         Environment(Set<DreamTouchHandler> handlers) {
89             mLifecycle = Mockito.mock(Lifecycle.class);
90             mLifecycleOwner = Mockito.mock(LifecycleOwner.class);
91 
92             mInputFactory = Mockito.mock(InputSessionComponent.Factory.class);
93             final InputSessionComponent inputComponent = Mockito.mock(InputSessionComponent.class);
94             mInputSession = Mockito.mock(InputSession.class);
95 
96             when(mInputFactory.create(any(), any(), any(), anyBoolean()))
97                     .thenReturn(inputComponent);
98             when(inputComponent.getInputSession()).thenReturn(mInputSession);
99 
100             mDisplayHelper = Mockito.mock(DisplayHelper.class);
101             when(mDisplayHelper.getMaxBounds(anyInt(), anyInt()))
102                     .thenReturn(mDisplayBounds);
103             mMonitor = new DreamOverlayTouchMonitor(mExecutor, mLifecycle, mInputFactory,
104                     mDisplayHelper, handlers);
105             mMonitor.init();
106 
107             final ArgumentCaptor<LifecycleObserver> lifecycleObserverCaptor =
108                     ArgumentCaptor.forClass(LifecycleObserver.class);
109             verify(mLifecycle).addObserver(lifecycleObserverCaptor.capture());
110             assertThat(lifecycleObserverCaptor.getValue() instanceof DefaultLifecycleObserver)
111                     .isTrue();
112             mLifecycleObserver = (DefaultLifecycleObserver) lifecycleObserverCaptor.getValue();
113 
114             updateLifecycle(observer -> observer.first.onResume(observer.second));
115 
116             // Capture creation request.
117             final ArgumentCaptor<InputChannelCompat.InputEventListener> inputEventListenerCaptor =
118                     ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
119             final ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
120                     ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
121             verify(mInputFactory).create(any(), inputEventListenerCaptor.capture(),
122                     gestureListenerCaptor.capture(),
123                     eq(true));
124             mEventListener = inputEventListenerCaptor.getValue();
125             mGestureListener = gestureListenerCaptor.getValue();
126         }
127 
getDisplayBounds()128         public Rect getDisplayBounds() {
129             return mDisplayBounds;
130         }
131 
executeAll()132         void executeAll() {
133             mExecutor.runAllReady();
134         }
135 
publishInputEvent(InputEvent event)136         void publishInputEvent(InputEvent event) {
137             mEventListener.onInputEvent(event);
138         }
139 
publishGestureEvent(Consumer<GestureDetector.OnGestureListener> listenerConsumer)140         void publishGestureEvent(Consumer<GestureDetector.OnGestureListener> listenerConsumer) {
141             listenerConsumer.accept(mGestureListener);
142         }
143 
updateLifecycle(Consumer<Pair<DefaultLifecycleObserver, LifecycleOwner>> consumer)144         void updateLifecycle(Consumer<Pair<DefaultLifecycleObserver, LifecycleOwner>> consumer) {
145             consumer.accept(Pair.create(mLifecycleObserver, mLifecycleOwner));
146         }
147 
verifyInputSessionDispose()148         void verifyInputSessionDispose() {
149             verify(mInputSession).dispose();
150             Mockito.clearInvocations(mInputSession);
151         }
152     }
153 
154     @Test
testReportedDisplayBounds()155     public void testReportedDisplayBounds() {
156         final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
157         final Environment environment = new Environment(Stream.of(touchHandler)
158                 .collect(Collectors.toCollection(HashSet::new)));
159 
160         final MotionEvent initialEvent = Mockito.mock(MotionEvent.class);
161         when(initialEvent.getX()).thenReturn(0.0f);
162         when(initialEvent.getY()).thenReturn(0.0f);
163         environment.publishInputEvent(initialEvent);
164 
165         // Verify display bounds passed into TouchHandler#getTouchInitiationRegion
166         verify(touchHandler).getTouchInitiationRegion(eq(environment.getDisplayBounds()), any());
167         final ArgumentCaptor<DreamTouchHandler.TouchSession> touchSessionArgumentCaptor =
168                 ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
169         verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture());
170 
171         // Verify that display bounds provided from TouchSession#getBounds
172         assertThat(touchSessionArgumentCaptor.getValue().getBounds())
173                 .isEqualTo(environment.getDisplayBounds());
174     }
175 
176     @Test
testEntryTouchZone()177     public void testEntryTouchZone() {
178         final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
179         final Rect touchArea = new Rect(4, 4, 8 , 8);
180 
181         doAnswer(invocation -> {
182             final Region region = (Region) invocation.getArguments()[1];
183             region.set(touchArea);
184             return null;
185         }).when(touchHandler).getTouchInitiationRegion(any(), any());
186 
187         final Environment environment = new Environment(Stream.of(touchHandler)
188                 .collect(Collectors.toCollection(HashSet::new)));
189 
190         // Ensure touch outside specified region is not delivered.
191         final MotionEvent initialEvent = Mockito.mock(MotionEvent.class);
192         when(initialEvent.getX()).thenReturn(0.0f);
193         when(initialEvent.getY()).thenReturn(1.0f);
194         environment.publishInputEvent(initialEvent);
195         verify(touchHandler, never()).onSessionStart(any());
196 
197         // Make sure touch inside region causes session start.
198         when(initialEvent.getX()).thenReturn(5.0f);
199         when(initialEvent.getY()).thenReturn(5.0f);
200         environment.publishInputEvent(initialEvent);
201         verify(touchHandler).onSessionStart(any());
202     }
203 
204     @Test
testSessionCount()205     public void testSessionCount() {
206         final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
207         final Rect touchArea = new Rect(4, 4, 8 , 8);
208 
209         final DreamTouchHandler unzonedTouchHandler = Mockito.mock(DreamTouchHandler.class);
210         doAnswer(invocation -> {
211             final Region region = (Region) invocation.getArguments()[1];
212             region.set(touchArea);
213             return null;
214         }).when(touchHandler).getTouchInitiationRegion(any(), any());
215 
216         final Environment environment = new Environment(Stream.of(touchHandler, unzonedTouchHandler)
217                 .collect(Collectors.toCollection(HashSet::new)));
218 
219         // Ensure touch outside specified region is delivered to unzoned touch handler.
220         final MotionEvent initialEvent = Mockito.mock(MotionEvent.class);
221         when(initialEvent.getX()).thenReturn(0.0f);
222         when(initialEvent.getY()).thenReturn(1.0f);
223         environment.publishInputEvent(initialEvent);
224 
225         ArgumentCaptor<DreamTouchHandler.TouchSession> touchSessionCaptor = ArgumentCaptor.forClass(
226                 DreamTouchHandler.TouchSession.class);
227 
228         // Make sure only one active session.
229         {
230             verify(unzonedTouchHandler).onSessionStart(touchSessionCaptor.capture());
231             final DreamTouchHandler.TouchSession touchSession = touchSessionCaptor.getValue();
232             assertThat(touchSession.getActiveSessionCount()).isEqualTo(1);
233             touchSession.pop();
234             environment.executeAll();
235         }
236 
237         // Make sure touch inside the touch region.
238         when(initialEvent.getX()).thenReturn(5.0f);
239         when(initialEvent.getY()).thenReturn(5.0f);
240         environment.publishInputEvent(initialEvent);
241 
242         // Make sure there are two active sessions.
243         {
244             verify(touchHandler).onSessionStart(touchSessionCaptor.capture());
245             final DreamTouchHandler.TouchSession touchSession = touchSessionCaptor.getValue();
246             assertThat(touchSession.getActiveSessionCount()).isEqualTo(2);
247             touchSession.pop();
248         }
249     }
250 
251     @Test
testInputEventPropagation()252     public void testInputEventPropagation() {
253         final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
254 
255         final Environment environment = new Environment(Stream.of(touchHandler)
256                 .collect(Collectors.toCollection(HashSet::new)));
257 
258         final InputEvent initialEvent = Mockito.mock(InputEvent.class);
259         environment.publishInputEvent(initialEvent);
260 
261         // Ensure session started
262         final InputChannelCompat.InputEventListener eventListener =
263                 registerInputEventListener(touchHandler);
264 
265         // First event will be missed since we register after the execution loop,
266         final InputEvent event = Mockito.mock(InputEvent.class);
267         environment.publishInputEvent(event);
268         verify(eventListener).onInputEvent(eq(event));
269     }
270 
271     @Test
testInputEventPropagationAfterRemoval()272     public void testInputEventPropagationAfterRemoval() {
273         final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
274 
275         final Environment environment = new Environment(Stream.of(touchHandler)
276                 .collect(Collectors.toCollection(HashSet::new)));
277 
278         final InputEvent initialEvent = Mockito.mock(InputEvent.class);
279         environment.publishInputEvent(initialEvent);
280 
281         // Ensure session started
282         final DreamTouchHandler.TouchSession session = captureSession(touchHandler);
283         final InputChannelCompat.InputEventListener eventListener =
284                 registerInputEventListener(session);
285 
286         session.pop();
287         environment.executeAll();
288 
289         final InputEvent event = Mockito.mock(InputEvent.class);
290         environment.publishInputEvent(event);
291 
292         verify(eventListener, never()).onInputEvent(eq(event));
293     }
294 
295     @Test
testInputGesturePropagation()296     public void testInputGesturePropagation() {
297         final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
298 
299         final Environment environment = new Environment(Stream.of(touchHandler)
300                 .collect(Collectors.toCollection(HashSet::new)));
301 
302         final InputEvent initialEvent = Mockito.mock(InputEvent.class);
303         environment.publishInputEvent(initialEvent);
304 
305         // Ensure session started
306         final GestureDetector.OnGestureListener gestureListener =
307                 registerGestureListener(touchHandler);
308 
309         final MotionEvent event = Mockito.mock(MotionEvent.class);
310         environment.publishGestureEvent(onGestureListener -> onGestureListener.onShowPress(event));
311         verify(gestureListener).onShowPress(eq(event));
312     }
313 
314     @Test
testGestureConsumption()315     public void testGestureConsumption() {
316         final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
317 
318         final Environment environment = new Environment(Stream.of(touchHandler)
319                 .collect(Collectors.toCollection(HashSet::new)));
320 
321         final InputEvent initialEvent = Mockito.mock(InputEvent.class);
322         environment.publishInputEvent(initialEvent);
323 
324         // Ensure session started
325         final GestureDetector.OnGestureListener gestureListener =
326                 registerGestureListener(touchHandler);
327 
328         when(gestureListener.onDown(any())).thenReturn(true);
329         final MotionEvent event = Mockito.mock(MotionEvent.class);
330         environment.publishGestureEvent(onGestureListener -> {
331             assertThat(onGestureListener.onDown(event)).isTrue();
332         });
333 
334         verify(gestureListener).onDown(eq(event));
335     }
336 
337     @Test
testBroadcast()338     public void testBroadcast() {
339         final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
340         final DreamTouchHandler touchHandler2 = Mockito.mock(DreamTouchHandler.class);
341 
342         final Environment environment = new Environment(Stream.of(touchHandler, touchHandler2)
343                 .collect(Collectors.toCollection(HashSet::new)));
344 
345         final InputEvent initialEvent = Mockito.mock(InputEvent.class);
346         environment.publishInputEvent(initialEvent);
347 
348         final HashSet<InputChannelCompat.InputEventListener> inputListeners = new HashSet<>();
349 
350         inputListeners.add(registerInputEventListener(touchHandler));
351         inputListeners.add(registerInputEventListener(touchHandler));
352         inputListeners.add(registerInputEventListener(touchHandler2));
353 
354         final MotionEvent event = Mockito.mock(MotionEvent.class);
355         environment.publishInputEvent(event);
356 
357         inputListeners
358                 .stream()
359                 .forEach(inputEventListener -> verify(inputEventListener).onInputEvent(event));
360     }
361 
362     @Test
testPush()363     public void testPush() throws InterruptedException, ExecutionException {
364         final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
365 
366         final Environment environment = new Environment(Stream.of(touchHandler)
367                 .collect(Collectors.toCollection(HashSet::new)));
368 
369         final InputEvent initialEvent = Mockito.mock(InputEvent.class);
370         environment.publishInputEvent(initialEvent);
371 
372         final DreamTouchHandler.TouchSession session = captureSession(touchHandler);
373         final InputChannelCompat.InputEventListener eventListener =
374                 registerInputEventListener(session);
375 
376         final ListenableFuture<DreamTouchHandler.TouchSession> frontSessionFuture = session.push();
377         environment.executeAll();
378         final DreamTouchHandler.TouchSession frontSession = frontSessionFuture.get();
379         final InputChannelCompat.InputEventListener frontEventListener =
380                 registerInputEventListener(frontSession);
381 
382         final MotionEvent event = Mockito.mock(MotionEvent.class);
383         environment.publishInputEvent(event);
384 
385         verify(frontEventListener).onInputEvent(eq(event));
386         verify(eventListener, never()).onInputEvent(any());
387 
388         Mockito.clearInvocations(eventListener, frontEventListener);
389 
390         ListenableFuture<DreamTouchHandler.TouchSession> sessionFuture = frontSession.pop();
391         environment.executeAll();
392 
393         DreamTouchHandler.TouchSession returnedSession = sessionFuture.get();
394         assertThat(session == returnedSession).isTrue();
395 
396         environment.executeAll();
397 
398         final MotionEvent followupEvent = Mockito.mock(MotionEvent.class);
399         environment.publishInputEvent(followupEvent);
400 
401         verify(eventListener).onInputEvent(eq(followupEvent));
402         verify(frontEventListener, never()).onInputEvent(any());
403     }
404 
405     @Test
testPop()406     public void testPop() {
407         final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
408         final DreamTouchHandler.TouchSession.Callback callback =
409                 Mockito.mock(DreamTouchHandler.TouchSession.Callback.class);
410 
411         final Environment environment = new Environment(Stream.of(touchHandler)
412                 .collect(Collectors.toCollection(HashSet::new)));
413 
414         final InputEvent initialEvent = Mockito.mock(InputEvent.class);
415         environment.publishInputEvent(initialEvent);
416 
417         final DreamTouchHandler.TouchSession session = captureSession(touchHandler);
418         session.registerCallback(callback);
419         session.pop();
420         environment.executeAll();
421 
422         verify(callback).onRemoved();
423     }
424 
425     @Test
testPauseWithNoActiveSessions()426     public void testPauseWithNoActiveSessions() {
427         final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
428 
429         final Environment environment = new Environment(Stream.of(touchHandler)
430                 .collect(Collectors.toCollection(HashSet::new)));
431 
432         environment.updateLifecycle(observerOwnerPair -> {
433             observerOwnerPair.first.onPause(observerOwnerPair.second);
434         });
435 
436         environment.verifyInputSessionDispose();
437     }
438 
439     @Test
testDeferredPauseWithActiveSessions()440     public void testDeferredPauseWithActiveSessions() {
441         final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
442 
443         final Environment environment = new Environment(Stream.of(touchHandler)
444                 .collect(Collectors.toCollection(HashSet::new)));
445 
446         final InputEvent initialEvent = Mockito.mock(InputEvent.class);
447         environment.publishInputEvent(initialEvent);
448 
449         // Ensure session started
450         final InputChannelCompat.InputEventListener eventListener =
451                 registerInputEventListener(touchHandler);
452 
453         // First event will be missed since we register after the execution loop,
454         final InputEvent event = Mockito.mock(InputEvent.class);
455         environment.publishInputEvent(event);
456         verify(eventListener).onInputEvent(eq(event));
457 
458         final ArgumentCaptor<DreamTouchHandler.TouchSession> touchSessionArgumentCaptor =
459                 ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
460 
461         verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture());
462 
463         environment.updateLifecycle(observerOwnerPair -> {
464             observerOwnerPair.first.onPause(observerOwnerPair.second);
465         });
466 
467         verify(environment.mInputSession, never()).dispose();
468 
469         // End session
470         touchSessionArgumentCaptor.getValue().pop();
471         environment.executeAll();
472 
473         // Check to make sure the input session is now disposed.
474         environment.verifyInputSessionDispose();
475     }
476 
477     @Test
testDestroyWithActiveSessions()478     public void testDestroyWithActiveSessions() {
479         final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
480 
481         final Environment environment = new Environment(Stream.of(touchHandler)
482                 .collect(Collectors.toCollection(HashSet::new)));
483 
484         final InputEvent initialEvent = Mockito.mock(InputEvent.class);
485         environment.publishInputEvent(initialEvent);
486 
487         // Ensure session started
488         final InputChannelCompat.InputEventListener eventListener =
489                 registerInputEventListener(touchHandler);
490 
491         // First event will be missed since we register after the execution loop,
492         final InputEvent event = Mockito.mock(InputEvent.class);
493         environment.publishInputEvent(event);
494         verify(eventListener).onInputEvent(eq(event));
495 
496         final ArgumentCaptor<DreamTouchHandler.TouchSession> touchSessionArgumentCaptor =
497                 ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
498 
499         verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture());
500 
501         environment.updateLifecycle(observerOwnerPair -> {
502             observerOwnerPair.first.onDestroy(observerOwnerPair.second);
503         });
504 
505         // Check to make sure the input session is now disposed.
506         environment.verifyInputSessionDispose();
507     }
508 
509 
510     @Test
testPilfering()511     public void testPilfering() {
512         final DreamTouchHandler touchHandler1 = Mockito.mock(DreamTouchHandler.class);
513         final DreamTouchHandler touchHandler2 = Mockito.mock(DreamTouchHandler.class);
514 
515         final Environment environment = new Environment(Stream.of(touchHandler1, touchHandler2)
516                 .collect(Collectors.toCollection(HashSet::new)));
517 
518         final InputEvent initialEvent = Mockito.mock(InputEvent.class);
519         environment.publishInputEvent(initialEvent);
520 
521         final DreamTouchHandler.TouchSession session1 = captureSession(touchHandler1);
522         final GestureDetector.OnGestureListener gestureListener1 =
523                 registerGestureListener(session1);
524 
525         final DreamTouchHandler.TouchSession session2 = captureSession(touchHandler2);
526         final GestureDetector.OnGestureListener gestureListener2 =
527                 registerGestureListener(session2);
528         when(gestureListener2.onDown(any())).thenReturn(true);
529 
530         final MotionEvent gestureEvent = Mockito.mock(MotionEvent.class);
531         environment.publishGestureEvent(
532                 onGestureListener -> onGestureListener.onDown(gestureEvent));
533 
534         Mockito.clearInvocations(gestureListener1, gestureListener2);
535 
536         final MotionEvent followupEvent = Mockito.mock(MotionEvent.class);
537         environment.publishGestureEvent(
538                 onGestureListener -> onGestureListener.onDown(followupEvent));
539 
540         verify(gestureListener1, never()).onDown(any());
541         verify(gestureListener2).onDown(eq(followupEvent));
542     }
543 
544     @Test
testOnRemovedCallbackOnStopMonitoring()545     public void testOnRemovedCallbackOnStopMonitoring() {
546         final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
547         final DreamTouchHandler.TouchSession.Callback callback =
548                 Mockito.mock(DreamTouchHandler.TouchSession.Callback.class);
549 
550         final Environment environment = new Environment(Stream.of(touchHandler)
551                 .collect(Collectors.toCollection(HashSet::new)));
552 
553         final InputEvent initialEvent = Mockito.mock(InputEvent.class);
554         environment.publishInputEvent(initialEvent);
555 
556         final DreamTouchHandler.TouchSession session = captureSession(touchHandler);
557         session.registerCallback(callback);
558 
559         environment.executeAll();
560 
561         environment.updateLifecycle(observerOwnerPair -> {
562             observerOwnerPair.first.onDestroy(observerOwnerPair.second);
563         });
564 
565         environment.executeAll();
566 
567         verify(callback).onRemoved();
568     }
569 
registerGestureListener(DreamTouchHandler handler)570     public GestureDetector.OnGestureListener registerGestureListener(DreamTouchHandler handler) {
571         final GestureDetector.OnGestureListener gestureListener = Mockito.mock(
572                 GestureDetector.OnGestureListener.class);
573         final ArgumentCaptor<DreamTouchHandler.TouchSession> sessionCaptor =
574                 ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
575         verify(handler).onSessionStart(sessionCaptor.capture());
576         sessionCaptor.getValue().registerGestureListener(gestureListener);
577 
578         return gestureListener;
579     }
580 
registerGestureListener( DreamTouchHandler.TouchSession session)581     public GestureDetector.OnGestureListener registerGestureListener(
582             DreamTouchHandler.TouchSession session) {
583         final GestureDetector.OnGestureListener gestureListener = Mockito.mock(
584                 GestureDetector.OnGestureListener.class);
585         session.registerGestureListener(gestureListener);
586 
587         return gestureListener;
588     }
589 
registerInputEventListener( DreamTouchHandler.TouchSession session)590     public InputChannelCompat.InputEventListener registerInputEventListener(
591             DreamTouchHandler.TouchSession session) {
592         final InputChannelCompat.InputEventListener eventListener = Mockito.mock(
593                 InputChannelCompat.InputEventListener.class);
594         session.registerInputListener(eventListener);
595 
596         return eventListener;
597     }
598 
captureSession(DreamTouchHandler handler)599     public DreamTouchHandler.TouchSession captureSession(DreamTouchHandler handler) {
600         final ArgumentCaptor<DreamTouchHandler.TouchSession> sessionCaptor =
601                 ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
602         verify(handler).onSessionStart(sessionCaptor.capture());
603         return sessionCaptor.getValue();
604     }
605 
registerInputEventListener( DreamTouchHandler handler)606     public InputChannelCompat.InputEventListener registerInputEventListener(
607             DreamTouchHandler handler) {
608         return registerInputEventListener(captureSession(handler));
609     }
610 }
611