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