<lambda>null1 package com.android.systemui.keyguard
2
3 import android.app.ActivityManager
4 import android.app.WallpaperManager
5 import android.app.WindowConfiguration
6 import android.graphics.Point
7 import android.graphics.Rect
8 import android.os.PowerManager
9 import android.platform.test.annotations.DisableFlags
10 import android.testing.TestableLooper.RunWithLooper
11 import android.view.RemoteAnimationTarget
12 import android.view.SurfaceControl
13 import android.view.SyncRtSurfaceTransactionApplier
14 import android.view.View
15 import android.view.ViewRootImpl
16 import android.view.WindowManager
17 import androidx.test.ext.junit.runners.AndroidJUnit4
18 import androidx.test.filters.SmallTest
19 import com.android.keyguard.KeyguardViewController
20 import com.android.systemui.Flags
21 import com.android.systemui.SysuiTestCase
22 import com.android.systemui.flags.FeatureFlags
23 import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController
24 import com.android.systemui.statusbar.NotificationShadeWindowController
25 import com.android.systemui.statusbar.SysuiStatusBarStateController
26 import com.android.systemui.statusbar.phone.BiometricUnlockController
27 import com.android.systemui.statusbar.policy.KeyguardStateController
28 import com.android.systemui.util.mockito.any
29 import com.android.systemui.util.mockito.argThat
30 import com.android.systemui.util.mockito.whenever
31 import junit.framework.Assert.assertEquals
32 import junit.framework.Assert.assertFalse
33 import junit.framework.Assert.assertTrue
34 import org.junit.After
35 import org.junit.Before
36 import org.junit.Test
37 import org.junit.runner.RunWith
38 import org.mockito.Mock
39 import org.mockito.Mockito.atLeastOnce
40 import org.mockito.Mockito.eq
41 import org.mockito.Mockito.mock
42 import org.mockito.Mockito.never
43 import org.mockito.Mockito.times
44 import org.mockito.Mockito.verify
45 import org.mockito.Mockito.verifyNoMoreInteractions
46 import org.mockito.MockitoAnnotations
47 import org.mockito.kotlin.clearInvocations
48 import java.util.function.Predicate
49
50 @RunWith(AndroidJUnit4::class)
51 @RunWithLooper
52 @SmallTest
53 class KeyguardUnlockAnimationControllerTest : SysuiTestCase() {
54 private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
55
56 @Mock
57 private lateinit var windowManager: WindowManager
58 @Mock
59 private lateinit var keyguardViewMediator: KeyguardViewMediator
60 @Mock
61 private lateinit var keyguardStateController: KeyguardStateController
62 @Mock
63 private lateinit var keyguardViewController: KeyguardViewController
64 @Mock
65 private lateinit var featureFlags: FeatureFlags
66 @Mock
67 private lateinit var biometricUnlockController: BiometricUnlockController
68 @Mock
69 private lateinit var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier
70 @Mock
71 private lateinit var statusBarStateController: SysuiStatusBarStateController
72 @Mock
73 private lateinit var notificationShadeWindowController: NotificationShadeWindowController
74 @Mock
75 private lateinit var powerManager: PowerManager
76 @Mock
77 private lateinit var wallpaperManager: WallpaperManager
78
79 @Mock
80 private lateinit var launcherUnlockAnimationController: ILauncherUnlockAnimationController.Stub
81
82 private var surfaceControl1 = mock(SurfaceControl::class.java)
83 private var remoteTarget1 = RemoteAnimationTarget(
84 0 /* taskId */, 0, surfaceControl1, false, Rect(), Rect(), 0, Point(), Rect(), Rect(),
85 mock(WindowConfiguration::class.java), false, surfaceControl1, Rect(),
86 mock(ActivityManager.RunningTaskInfo::class.java), false)
87
88 private var surfaceControl2 = mock(SurfaceControl::class.java)
89 private var remoteTarget2 = RemoteAnimationTarget(
90 1 /* taskId */, 0, surfaceControl2, false, Rect(), Rect(), 0, Point(), Rect(), Rect(),
91 mock(WindowConfiguration::class.java), false, surfaceControl2, Rect(),
92 mock(ActivityManager.RunningTaskInfo::class.java), false)
93 private lateinit var remoteAnimationTargets: Array<RemoteAnimationTarget>
94
95 private var surfaceControlWp = mock(SurfaceControl::class.java)
96 private var wallpaperTarget = RemoteAnimationTarget(
97 2 /* taskId */, 0, surfaceControlWp, false, Rect(), Rect(), 0, Point(), Rect(), Rect(),
98 mock(WindowConfiguration::class.java), false, surfaceControlWp, Rect(),
99 mock(ActivityManager.RunningTaskInfo::class.java), false)
100 private lateinit var wallpaperTargets: Array<RemoteAnimationTarget>
101
102 @Before
103 fun setUp() {
104 MockitoAnnotations.initMocks(this)
105 keyguardUnlockAnimationController = KeyguardUnlockAnimationController(
106 windowManager, context.resources,
107 keyguardStateController, { keyguardViewMediator }, keyguardViewController,
108 featureFlags, { biometricUnlockController }, statusBarStateController,
109 notificationShadeWindowController, powerManager, wallpaperManager
110 )
111 keyguardUnlockAnimationController.setLauncherUnlockController(
112 "", launcherUnlockAnimationController)
113
114 whenever(keyguardViewController.viewRootImpl).thenReturn(mock(ViewRootImpl::class.java))
115 whenever(powerManager.isInteractive).thenReturn(true)
116
117 // All of these fields are final, so we can't mock them, but are needed so that the surface
118 // appear amount setter doesn't short circuit.
119 remoteAnimationTargets = arrayOf(remoteTarget1)
120 wallpaperTargets = arrayOf(wallpaperTarget)
121
122 // Set the surface applier to our mock so that we can verify the arguments passed to it.
123 // This applier does not have any side effects within the unlock animation controller, so
124 // this is a reasonable way to test.
125 keyguardUnlockAnimationController.surfaceTransactionApplier = surfaceTransactionApplier
126 }
127
128 @After
129 fun tearDown() {
130 keyguardUnlockAnimationController.notifyFinishedKeyguardExitAnimation(true)
131 }
132
133 /**
134 * If we're wake and unlocking, we are animating from the black/AOD screen to the app/launcher
135 * underneath. The LightRevealScrim will animate circularly from the fingerprint reader,
136 * revealing the app/launcher below. In this case, we want to make sure we are not animating the
137 * surface, or the user will see the wallpaper briefly as the app animates in.
138 */
139 @Test
140 @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
141 fun noSurfaceAnimation_ifWakeAndUnlocking() {
142 whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
143
144 keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
145 remoteAnimationTargets,
146 arrayOf(),
147 0 /* startTime */,
148 false /* requestedShowSurfaceBehindKeyguard */
149 )
150
151 val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
152 verify(surfaceTransactionApplier, times(1)).scheduleApply(
153 captorSb.capture { sp -> sp.surface == surfaceControl1 })
154
155 val params = captorSb.getLastValue()
156
157 // We expect that we've instantly set the surface behind to alpha = 1f, and have no
158 // transforms (translate, scale) on its matrix.
159 assertEquals(1f, params.alpha)
160 assertTrue(params.matrix.isIdentity)
161
162 // Also expect we've immediately asked the keyguard view mediator to finish the remote
163 // animation.
164 verify(keyguardViewMediator, times(1)).exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
165 false /* cancelled */)
166
167 verifyNoMoreInteractions(surfaceTransactionApplier)
168 }
169
170 /**
171 * If we are not wake and unlocking, we expect the unlock animation to play normally.
172 */
173 @Test
174 fun surfaceAnimation_ifNotWakeAndUnlocking() {
175 whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(false)
176
177 keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
178 remoteAnimationTargets,
179 wallpaperTargets,
180 0 /* startTime */,
181 false /* requestedShowSurfaceBehindKeyguard */
182 )
183
184 // Since the animation is running, we should not have finished the remote animation.
185 verify(keyguardViewMediator, times(0)).exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
186 false /* cancelled */)
187 }
188
189 @Test
190 fun onWakeAndUnlock_notifiesListenerWithTrue() {
191 whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
192 whenever(biometricUnlockController.mode).thenReturn(
193 BiometricUnlockController.MODE_WAKE_AND_UNLOCK)
194
195 val listener = mock(
196 KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener::class.java)
197 keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(listener)
198
199 keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
200 remoteAnimationTargets,
201 wallpaperTargets,
202 0 /* startTime */,
203 false /* requestedShowSurfaceBehindKeyguard */
204 )
205
206 verify(listener).onUnlockAnimationStarted(any(), eq(true), any(), any())
207 }
208
209 @Test
210 fun onWakeAndUnlockFromDream_notifiesListenerWithFalse() {
211 whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
212 whenever(biometricUnlockController.mode).thenReturn(
213 BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
214
215 val listener = mock(
216 KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener::class.java)
217 keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(listener)
218
219 keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
220 remoteAnimationTargets,
221 wallpaperTargets,
222 0 /* startTime */,
223 false /* requestedShowSurfaceBehindKeyguard */
224 )
225
226 verify(listener).onUnlockAnimationStarted(any(), eq(false), any(), any())
227 }
228
229 /**
230 * If we requested that the surface behind be made visible, and we're not flinging away the
231 * keyguard, it means that we're swiping to unlock and want the surface visible so it can follow
232 * the user's touch event as they swipe to unlock.
233 *
234 * In this case, we should verify that the surface was made visible via the alpha fade in
235 * animator, and verify that we did not start the canned animation to animate the surface in
236 * (since it's supposed to be following the touch events).
237 */
238 @Test
239 fun fadeInSurfaceBehind_ifRequestedShowSurface_butNotFlinging() {
240 whenever(keyguardStateController.isFlingingToDismissKeyguard).thenReturn(false)
241
242 keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
243 remoteAnimationTargets,
244 wallpaperTargets,
245 0 /* startTime */,
246 true /* requestedShowSurfaceBehindKeyguard */
247 )
248
249 assertTrue(keyguardUnlockAnimationController.surfaceBehindAlphaAnimator.isRunning)
250 assertFalse(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation())
251 }
252
253 /**
254 * We requested the surface behind to be made visible, but we're now flinging to dismiss the
255 * keyguard. This means this was a swipe to dismiss gesture but the user flung the keyguard and
256 * lifted their finger while we were requesting the surface be made visible.
257 *
258 * In this case, we should verify that we are playing the canned unlock animation and not
259 * simply fading in the surface.
260 */
261 @Test
262 fun playCannedUnlockAnimation_ifRequestedShowSurface_andFlinging() {
263 whenever(keyguardStateController.isFlingingToDismissKeyguard).thenReturn(true)
264
265 keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
266 remoteAnimationTargets,
267 wallpaperTargets,
268 0 /* startTime */,
269 true /* requestedShowSurfaceBehindKeyguard */
270 )
271
272 assertTrue(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation())
273 assertFalse(keyguardUnlockAnimationController.surfaceBehindAlphaAnimator.isRunning)
274 }
275
276 /**
277 * We never requested the surface behind to be made visible, which means no swiping to unlock
278 * ever happened and we're just playing the simple canned animation (happens via UDFPS unlock,
279 * long press on the lock icon, etc).
280 *
281 * In this case, we should verify that we are playing the canned unlock animation and not
282 * simply fading in the surface.
283 */
284 @Test
285 fun playCannedUnlockAnimation_ifDidNotRequestShowSurface() {
286 keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
287 remoteAnimationTargets,
288 wallpaperTargets,
289 0 /* startTime */,
290 false /* requestedShowSurfaceBehindKeyguard */
291 )
292
293 assertTrue(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation())
294 assertFalse(keyguardUnlockAnimationController.surfaceBehindAlphaAnimator.isRunning)
295 }
296
297 @Test
298 fun doNotPlayCannedUnlockAnimation_ifLaunchingApp() {
299 whenever(notificationShadeWindowController.isLaunchingActivity).thenReturn(true)
300
301 keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
302 remoteAnimationTargets,
303 wallpaperTargets,
304 0 /* startTime */,
305 true /* requestedShowSurfaceBehindKeyguard */
306 )
307
308 assertFalse(keyguardUnlockAnimationController.canPerformInWindowLauncherAnimations())
309 assertFalse(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation())
310 }
311
312 @Test
313 fun playCannedUnlockAnimation_nullSmartspaceView_doesNotThrowExecption() {
314 keyguardUnlockAnimationController.lockscreenSmartspace = null
315 keyguardUnlockAnimationController.willUnlockWithInWindowLauncherAnimations = true
316
317 keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
318 remoteAnimationTargets,
319 wallpaperTargets,
320 0 /* startTime */,
321 false /* requestedShowSurfaceBehindKeyguard */
322 )
323
324 assertTrue(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation())
325 }
326
327 /**
328 * If we are not wake and unlocking, we expect the unlock animation to play normally.
329 */
330 @Test
331 @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
332 fun surfaceAnimation_multipleTargets() {
333 keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
334 arrayOf(remoteTarget1, remoteTarget2),
335 wallpaperTargets,
336 0 /* startTime */,
337 false /* requestedShowSurfaceBehindKeyguard */
338 )
339
340 // Cancel the animator so we can verify only the setSurfaceBehind call below.
341 keyguardUnlockAnimationController.surfaceBehindAlphaAnimator.end()
342 clearInvocations(surfaceTransactionApplier)
343
344 // Set appear to 50%, we'll just verify that we're not applying the identity matrix which
345 // means an animation is in progress.
346 keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(0.5f)
347
348 val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
349 verify(surfaceTransactionApplier, times(2)).scheduleApply(captorSb
350 .capture { sp -> sp.surface == surfaceControl1 || sp.surface == surfaceControl2 })
351 val captorWp = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
352 verify(surfaceTransactionApplier, times(1).description(
353 "WallpaperSurface was expected to receive scheduleApply once"
354 )).scheduleApply(captorWp.capture { sp -> sp.surface == surfaceControlWp})
355
356 val allParams = captorSb.getAllValues()
357
358 val remainingTargets = mutableListOf(surfaceControl1, surfaceControl2)
359 allParams.forEach { params ->
360 assertTrue(!params.matrix.isIdentity)
361 remainingTargets.remove(params.surface)
362 }
363
364 // Make sure we called applyParams with each of the surface controls once. The order does
365 // not matter, so don't explicitly check for that.
366 assertTrue(remainingTargets.isEmpty())
367
368 // Since the animation is running, we should not have finished the remote animation.
369 verify(keyguardViewMediator, times(0)).exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
370 false /* cancelled */)
371 }
372
373 @Test
374 @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
375 fun surfaceBehindAlphaOverriddenTo0_ifNotInteractive() {
376 whenever(powerManager.isInteractive).thenReturn(false)
377
378 keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
379 remoteAnimationTargets,
380 wallpaperTargets,
381 0 /* startTime */,
382 false /* requestedShowSurfaceBehindKeyguard */
383 )
384
385 // Cancel the animator so we can verify only the setSurfaceBehind call below.
386 keyguardUnlockAnimationController.surfaceBehindAlphaAnimator.end()
387 clearInvocations(surfaceTransactionApplier)
388
389 keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(1f)
390 keyguardUnlockAnimationController.setWallpaperAppearAmount(1f)
391
392 val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
393 verify(surfaceTransactionApplier, times(1)).scheduleApply(
394 captorSb.capture { sp -> sp.surface == surfaceControl1})
395 val captorWp = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
396 verify(surfaceTransactionApplier, atLeastOnce().description("Wallpaper surface has not " +
397 "received scheduleApply")).scheduleApply(
398 captorWp.capture { sp -> sp.surface == surfaceControlWp })
399
400 val params = captorSb.getLastValue()
401
402 // We expect that we've set the surface behind to alpha = 0f since we're not interactive.
403 assertEquals(0f, params.alpha)
404 assertTrue(params.matrix.isIdentity)
405
406 verifyNoMoreInteractions(surfaceTransactionApplier)
407 }
408
409 @Test
410 @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
411 fun surfaceBehindAlphaNotOverriddenTo0_ifInteractive() {
412 whenever(powerManager.isInteractive).thenReturn(true)
413
414 keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
415 remoteAnimationTargets,
416 wallpaperTargets,
417 0 /* startTime */,
418 false /* requestedShowSurfaceBehindKeyguard */
419 )
420
421 // Stop the animator - we just want to test whether the override is not applied.
422 keyguardUnlockAnimationController.surfaceBehindAlphaAnimator.end()
423 clearInvocations(surfaceTransactionApplier)
424
425 keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(1f)
426 keyguardUnlockAnimationController.setWallpaperAppearAmount(1f)
427
428 val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
429 verify(surfaceTransactionApplier, times(1)).scheduleApply(
430 captorSb.capture { sp -> sp.surface == surfaceControl1 })
431 val captorWp = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
432 verify(surfaceTransactionApplier, atLeastOnce().description("Wallpaper surface has not " +
433 "received scheduleApply")).scheduleApply(
434 captorWp.capture { sp -> sp.surface == surfaceControlWp })
435
436 val params = captorSb.getLastValue()
437 assertEquals(1f, params.alpha)
438 assertTrue(params.matrix.isIdentity)
439 assertEquals("Wallpaper surface was expected to have opacity 1",
440 1f, captorWp.getLastValue().alpha)
441
442 verifyNoMoreInteractions(surfaceTransactionApplier)
443 }
444
445 @Test
446 fun unlockToLauncherWithInWindowAnimations_ssViewIsVisible() {
447 val mockLockscreenSmartspaceView = mock(View::class.java)
448 whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.VISIBLE)
449 keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView
450
451 keyguardUnlockAnimationController.unlockToLauncherWithInWindowAnimations()
452
453 verify(mockLockscreenSmartspaceView).visibility = View.INVISIBLE
454 }
455
456 @Test
457 fun unlockToLauncherWithInWindowAnimations_ssViewIsInvisible() {
458 val mockLockscreenSmartspaceView = mock(View::class.java)
459 whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.INVISIBLE)
460 keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView
461
462 keyguardUnlockAnimationController.unlockToLauncherWithInWindowAnimations()
463
464 verify(mockLockscreenSmartspaceView, never()).visibility = View.INVISIBLE
465 }
466
467 @Test
468 fun unlockToLauncherWithInWindowAnimations_ssViewIsGone() {
469 val mockLockscreenSmartspaceView = mock(View::class.java)
470 whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.GONE)
471 keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView
472
473 keyguardUnlockAnimationController.unlockToLauncherWithInWindowAnimations()
474
475 verify(mockLockscreenSmartspaceView, never()).visibility = View.INVISIBLE
476 }
477
478 @Test
479 fun notifyFinishedKeyguardExitAnimation_ssViewIsInvisibleAndCancelledIsTrue() {
480 val mockLockscreenSmartspaceView = mock(View::class.java)
481 whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.INVISIBLE)
482 keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView
483
484 keyguardUnlockAnimationController.notifyFinishedKeyguardExitAnimation(true)
485
486 verify(mockLockscreenSmartspaceView).visibility = View.VISIBLE
487 }
488
489 @Test
490 fun notifyFinishedKeyguardExitAnimation_ssViewIsGoneAndCancelledIsTrue() {
491 val mockLockscreenSmartspaceView = mock(View::class.java)
492 whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.GONE)
493 keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView
494
495 keyguardUnlockAnimationController.notifyFinishedKeyguardExitAnimation(true)
496
497 verify(mockLockscreenSmartspaceView, never()).visibility = View.VISIBLE
498 }
499
500 @Test
501 fun notifyFinishedKeyguardExitAnimation_ssViewIsInvisibleAndCancelledIsFalse() {
502 val mockLockscreenSmartspaceView = mock(View::class.java)
503 whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.INVISIBLE)
504 keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView
505
506 keyguardUnlockAnimationController.notifyFinishedKeyguardExitAnimation(false)
507
508 verify(mockLockscreenSmartspaceView).visibility = View.VISIBLE
509 }
510
511 @Test
512 fun notifyFinishedKeyguardExitAnimation_ssViewIsGoneAndCancelledIsFalse() {
513 val mockLockscreenSmartspaceView = mock(View::class.java)
514 whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.GONE)
515 keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView
516
517 keyguardUnlockAnimationController.notifyFinishedKeyguardExitAnimation(false)
518
519 verify(mockLockscreenSmartspaceView, never()).visibility = View.VISIBLE
520 }
521
522 private class ArgThatCaptor<T> {
523 private var allArgs: MutableList<T> = mutableListOf()
524
525 fun capture(predicate: Predicate<T>): T {
526 return argThat{x: T ->
527 if (predicate.test(x)) {
528 allArgs.add(x)
529 return@argThat true
530 }
531 return@argThat false
532 }
533 }
534
535 fun getLastValue(): T {
536 return allArgs.last()
537 }
538
539 fun getAllValues(): List<T> {
540 return allArgs
541 }
542 }
543 }
544