<lambda>null1 package com.android.systemui.statusbar
2
3 import android.animation.Animator
4 import android.animation.AnimatorListenerAdapter
5 import android.animation.ObjectAnimator
6 import android.animation.ValueAnimator
7 import android.content.Context
8 import android.content.res.Configuration
9 import android.os.SystemClock
10 import android.util.DisplayMetrics
11 import android.util.MathUtils
12 import android.view.MotionEvent
13 import android.view.View
14 import android.view.ViewConfiguration
15 import androidx.annotation.VisibleForTesting
16 import com.android.internal.logging.nano.MetricsProto.MetricsEvent
17 import com.android.systemui.ExpandHelper
18 import com.android.systemui.Gefingerpoken
19 import com.android.systemui.R
20 import com.android.systemui.animation.Interpolators
21 import com.android.systemui.biometrics.UdfpsKeyguardViewController
22 import com.android.systemui.classifier.Classifier
23 import com.android.systemui.classifier.FalsingCollector
24 import com.android.systemui.dagger.SysUISingleton
25 import com.android.systemui.media.MediaHierarchyManager
26 import com.android.systemui.plugins.ActivityStarter.OnDismissAction
27 import com.android.systemui.plugins.FalsingManager
28 import com.android.systemui.plugins.qs.QS
29 import com.android.systemui.statusbar.notification.collection.NotificationEntry
30 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
31 import com.android.systemui.statusbar.notification.row.ExpandableView
32 import com.android.systemui.statusbar.notification.stack.AmbientState
33 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
34 import com.android.systemui.statusbar.phone.KeyguardBypassController
35 import com.android.systemui.statusbar.phone.LockscreenGestureLogger
36 import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent
37 import com.android.systemui.statusbar.phone.NotificationPanelViewController
38 import com.android.systemui.statusbar.phone.ScrimController
39 import com.android.systemui.statusbar.phone.StatusBar
40 import com.android.systemui.statusbar.policy.ConfigurationController
41 import com.android.systemui.util.Utils
42 import javax.inject.Inject
43
44 private const val SPRING_BACK_ANIMATION_LENGTH_MS = 375L
45 private const val RUBBERBAND_FACTOR_STATIC = 0.15f
46 private const val RUBBERBAND_FACTOR_EXPANDABLE = 0.5f
47
48 /**
49 * A class that controls the lockscreen to shade transition
50 */
51 @SysUISingleton
52 class LockscreenShadeTransitionController @Inject constructor(
53 private val statusBarStateController: SysuiStatusBarStateController,
54 private val lockscreenGestureLogger: LockscreenGestureLogger,
55 private val keyguardBypassController: KeyguardBypassController,
56 private val lockScreenUserManager: NotificationLockscreenUserManager,
57 private val falsingCollector: FalsingCollector,
58 private val ambientState: AmbientState,
59 private val displayMetrics: DisplayMetrics,
60 private val mediaHierarchyManager: MediaHierarchyManager,
61 private val scrimController: ScrimController,
62 private val depthController: NotificationShadeDepthController,
63 private val featureFlags: FeatureFlags,
64 private val context: Context,
65 configurationController: ConfigurationController,
66 falsingManager: FalsingManager
67 ) {
68 private var pulseHeight: Float = 0f
69 private var useSplitShade: Boolean = false
70 private lateinit var nsslController: NotificationStackScrollLayoutController
71 lateinit var notificationPanelController: NotificationPanelViewController
72 lateinit var statusbar: StatusBar
73 lateinit var qS: QS
74
75 /**
76 * A handler that handles the next keyguard dismiss animation.
77 */
78 private var animationHandlerOnKeyguardDismiss: ((Long) -> Unit)? = null
79
80 /**
81 * The entry that was just dragged down on.
82 */
83 private var draggedDownEntry: NotificationEntry? = null
84
85 /**
86 * The current animator if any
87 */
88 @VisibleForTesting
89 internal var dragDownAnimator: ValueAnimator? = null
90
91 /**
92 * The current pulse height animator if any
93 */
94 @VisibleForTesting
95 internal var pulseHeightAnimator: ValueAnimator? = null
96
97 /**
98 * Distance that the full shade transition takes in order for scrim to fully transition to
99 * the shade (in alpha)
100 */
101 private var scrimTransitionDistance = 0
102
103 /**
104 * Distance that the full transition takes in order for us to fully transition to the shade
105 */
106 private var fullTransitionDistance = 0
107
108 /**
109 * Flag to make sure that the dragDownAmount is applied to the listeners even when in the
110 * locked down shade.
111 */
112 private var forceApplyAmount = false
113
114 /**
115 * A flag to suppress the default animation when unlocking in the locked down shade.
116 */
117 private var nextHideKeyguardNeedsNoAnimation = false
118
119 /**
120 * The distance until we're showing the notifications when pulsing
121 */
122 val distanceUntilShowingPulsingNotifications
123 get() = scrimTransitionDistance
124
125 /**
126 * The udfpsKeyguardViewController if it exists.
127 */
128 var udfpsKeyguardViewController: UdfpsKeyguardViewController? = null
129
130 /**
131 * The touch helper responsible for the drag down animation.
132 */
133 val touchHelper = DragDownHelper(falsingManager, falsingCollector, this, context)
134
135 init {
136 updateResources()
137 configurationController.addCallback(object : ConfigurationController.ConfigurationListener {
138 override fun onConfigChanged(newConfig: Configuration?) {
139 updateResources()
140 touchHelper.updateResources(context)
141 }
142 })
143 }
144
145 private fun updateResources() {
146 scrimTransitionDistance = context.resources.getDimensionPixelSize(
147 R.dimen.lockscreen_shade_scrim_transition_distance)
148 fullTransitionDistance = context.resources.getDimensionPixelSize(
149 R.dimen.lockscreen_shade_qs_transition_distance)
150 useSplitShade = Utils.shouldUseSplitNotificationShade(featureFlags, context.resources)
151 }
152
153 fun setStackScroller(nsslController: NotificationStackScrollLayoutController) {
154 this.nsslController = nsslController
155 touchHelper.host = nsslController.view
156 touchHelper.expandCallback = nsslController.expandHelperCallback
157 }
158
159 /**
160 * Initialize the shelf controller such that clicks on it will expand the shade
161 */
162 fun bindController(notificationShelfController: NotificationShelfController) {
163 // Bind the click listener of the shelf to go to the full shade
164 notificationShelfController.setOnClickListener {
165 if (statusBarStateController.state == StatusBarState.KEYGUARD) {
166 statusbar.wakeUpIfDozing(SystemClock.uptimeMillis(), it, "SHADE_CLICK")
167 goToLockedShade(it)
168 }
169 }
170 }
171
172 /**
173 * @return true if the interaction is accepted, false if it should be cancelled
174 */
175 internal fun canDragDown(): Boolean {
176 return (statusBarStateController.state == StatusBarState.KEYGUARD ||
177 nsslController.isInLockedDownShade()) &&
178 qS.isFullyCollapsed
179 }
180
181 /**
182 * Called by the touch helper when when a gesture has completed all the way and released.
183 */
184 internal fun onDraggedDown(startingChild: View?, dragLengthY: Int) {
185 if (canDragDown()) {
186 if (nsslController.isInLockedDownShade()) {
187 statusBarStateController.setLeaveOpenOnKeyguardHide(true)
188 statusbar.dismissKeyguardThenExecute(OnDismissAction {
189 nextHideKeyguardNeedsNoAnimation = true
190 false
191 },
192 null /* cancelRunnable */, false /* afterKeyguardGone */)
193 } else {
194 lockscreenGestureLogger.write(
195 MetricsEvent.ACTION_LS_SHADE,
196 (dragLengthY / displayMetrics.density).toInt(),
197 0 /* velocityDp */)
198 lockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_PULL_SHADE_OPEN)
199 if (!ambientState.isDozing() || startingChild != null) {
200 // go to locked shade while animating the drag down amount from its current
201 // value
202 val animationHandler = { delay: Long ->
203 if (startingChild is ExpandableNotificationRow) {
204 startingChild.onExpandedByGesture(
205 true /* drag down is always an open */)
206 }
207 notificationPanelController.animateToFullShade(delay)
208 notificationPanelController.setTransitionToFullShadeAmount(0f,
209 true /* animated */, delay)
210
211 // Let's reset ourselves, ready for the next animation
212
213 // changing to shade locked will make isInLockDownShade true, so let's
214 // override that
215 forceApplyAmount = true
216 // Reset the behavior. At this point the animation is already started
217 dragDownAmount = 0f
218 forceApplyAmount = false
219 }
220 val cancelRunnable = Runnable { setDragDownAmountAnimated(0f) }
221 goToLockedShadeInternal(startingChild, animationHandler, cancelRunnable)
222 }
223 }
224 } else {
225 setDragDownAmountAnimated(0f)
226 }
227 }
228
229 /**
230 * Called by the touch helper when the drag down was aborted and should be reset.
231 */
232 internal fun onDragDownReset() {
233 nsslController.setDimmed(true /* dimmed */, true /* animated */)
234 nsslController.resetScrollPosition()
235 nsslController.resetCheckSnoozeLeavebehind()
236 setDragDownAmountAnimated(0f)
237 }
238
239 /**
240 * The user has dragged either above or below the threshold which changes the dimmed state.
241 * @param above whether they dragged above it
242 */
243 internal fun onCrossedThreshold(above: Boolean) {
244 nsslController.setDimmed(!above /* dimmed */, true /* animate */)
245 }
246
247 /**
248 * Called by the touch helper when the drag down was started
249 */
250 internal fun onDragDownStarted() {
251 nsslController.cancelLongPress()
252 nsslController.checkSnoozeLeavebehind()
253 dragDownAnimator?.cancel()
254 }
255
256 /**
257 * Do we need a falsing check currently?
258 */
259 internal val isFalsingCheckNeeded: Boolean
260 get() = statusBarStateController.state == StatusBarState.KEYGUARD
261
262 /**
263 * Is dragging down enabled on a given view
264 * @param view The view to check or `null` to check if it's enabled at all
265 */
266 internal fun isDragDownEnabledForView(view: ExpandableView?): Boolean {
267 if (isDragDownAnywhereEnabled) {
268 return true
269 }
270 if (nsslController.isInLockedDownShade()) {
271 if (view == null) {
272 // Dragging down is allowed in general
273 return true
274 }
275 if (view is ExpandableNotificationRow) {
276 // Only drag down on sensitive views, otherwise the ExpandHelper will take this
277 return view.entry.isSensitive
278 }
279 }
280 return false
281 }
282
283 /**
284 * @return if drag down is enabled anywhere, not just on selected views.
285 */
286 internal val isDragDownAnywhereEnabled: Boolean
287 get() = (statusBarStateController.getState() == StatusBarState.KEYGUARD &&
288 !keyguardBypassController.bypassEnabled &&
289 qS.isFullyCollapsed)
290
291 /**
292 * The amount in pixels that the user has dragged down.
293 */
294 internal var dragDownAmount = 0f
295 set(value) {
296 if (field != value || forceApplyAmount) {
297 field = value
298 if (!nsslController.isInLockedDownShade() || forceApplyAmount) {
299 nsslController.setTransitionToFullShadeAmount(field)
300 notificationPanelController.setTransitionToFullShadeAmount(field,
301 false /* animate */, 0 /* delay */)
302 // TODO: appear qs also in split shade
303 val qsAmount = if (useSplitShade) 0f else field
304 qS.setTransitionToFullShadeAmount(qsAmount, false /* animate */)
305 // TODO: appear media also in split shade
306 val mediaAmount = if (useSplitShade) 0f else field
307 mediaHierarchyManager.setTransitionToFullShadeAmount(mediaAmount)
308 transitionToShadeAmountCommon(field)
309 }
310 }
311 }
312
313 private fun transitionToShadeAmountCommon(dragDownAmount: Float) {
314 val scrimProgress = MathUtils.saturate(dragDownAmount / scrimTransitionDistance)
315 scrimController.setTransitionToFullShadeProgress(scrimProgress)
316 // Fade out all content only visible on the lockscreen
317 notificationPanelController.setKeyguardOnlyContentAlpha(1.0f - scrimProgress)
318 depthController.transitionToFullShadeProgress = scrimProgress
319 udfpsKeyguardViewController?.setTransitionToFullShadeProgress(scrimProgress)
320 }
321
322 private fun setDragDownAmountAnimated(
323 target: Float,
324 delay: Long = 0,
325 endlistener: (() -> Unit)? = null
326 ) {
327 val dragDownAnimator = ValueAnimator.ofFloat(dragDownAmount, target)
328 dragDownAnimator.interpolator = Interpolators.FAST_OUT_SLOW_IN
329 dragDownAnimator.duration = SPRING_BACK_ANIMATION_LENGTH_MS
330 dragDownAnimator.addUpdateListener { animation: ValueAnimator ->
331 dragDownAmount = animation.animatedValue as Float
332 }
333 if (delay > 0) {
334 dragDownAnimator.startDelay = delay
335 }
336 if (endlistener != null) {
337 dragDownAnimator.addListener(object : AnimatorListenerAdapter() {
338 override fun onAnimationEnd(animation: Animator?) {
339 endlistener.invoke()
340 }
341 })
342 }
343 dragDownAnimator.start()
344 this.dragDownAnimator = dragDownAnimator
345 }
346
347 /**
348 * Animate appear the drag down amount.
349 */
350 private fun animateAppear(delay: Long = 0) {
351 // changing to shade locked will make isInLockDownShade true, so let's override
352 // that
353 forceApplyAmount = true
354
355 // we set the value initially to 1 pixel, since that will make sure we're
356 // transitioning to the full shade. this is important to avoid flickering,
357 // as the below animation only starts once the shade is unlocked, which can
358 // be a couple of frames later. if we're setting it to 0, it will use the
359 // default inset and therefore flicker
360 dragDownAmount = 1f
361 setDragDownAmountAnimated(fullTransitionDistance.toFloat(), delay = delay) {
362 // End listener:
363 // Reset
364 dragDownAmount = 0f
365 forceApplyAmount = false
366 }
367 }
368
369 /**
370 * Ask this controller to go to the locked shade, changing the state change and doing
371 * an animation, where the qs appears from 0 from the top
372 *
373 * If secure with redaction: Show bouncer, go to unlocked shade.
374 * If secure without redaction or no security: Go to [StatusBarState.SHADE_LOCKED].
375 *
376 * @param expandView The view to expand after going to the shade
377 * @param needsQSAnimation if this needs the quick settings to slide in from the top or if
378 * that's already handled separately
379 */
380 @JvmOverloads
381 fun goToLockedShade(expandedView: View?, needsQSAnimation: Boolean = true) {
382 if (statusBarStateController.state == StatusBarState.KEYGUARD) {
383 val animationHandler: ((Long) -> Unit)?
384 if (needsQSAnimation) {
385 // Let's use the default animation
386 animationHandler = null
387 } else {
388 // Let's only animate notifications
389 animationHandler = { delay: Long ->
390 notificationPanelController.animateToFullShade(delay)
391 }
392 }
393 goToLockedShadeInternal(expandedView, animationHandler,
394 cancelAction = null)
395 }
396 }
397
398 /**
399 * If secure with redaction: Show bouncer, go to unlocked shade.
400 *
401 * If secure without redaction or no security: Go to [StatusBarState.SHADE_LOCKED].
402 *
403 * @param expandView The view to expand after going to the shade.
404 * @param animationHandler The handler which performs the go to full shade animation. If null,
405 * the default handler will do the animation, otherwise the caller is
406 * responsible for the animation. The input value is a Long for the
407 * delay for the animation.
408 * @param cancelAction The runnable to invoke when the transition is aborted. This happens if
409 * the user goes to the bouncer and goes back.
410 */
411 private fun goToLockedShadeInternal(
412 expandView: View?,
413 animationHandler: ((Long) -> Unit)? = null,
414 cancelAction: Runnable? = null
415 ) {
416 if (statusbar.isShadeDisabled) {
417 cancelAction?.run()
418 return
419 }
420 var userId: Int = lockScreenUserManager.getCurrentUserId()
421 var entry: NotificationEntry? = null
422 if (expandView is ExpandableNotificationRow) {
423 entry = expandView.entry
424 entry.setUserExpanded(true /* userExpanded */, true /* allowChildExpansion */)
425 // Indicate that the group expansion is changing at this time -- this way the group
426 // and children backgrounds / divider animations will look correct.
427 entry.setGroupExpansionChanging(true)
428 userId = entry.sbn.userId
429 }
430 var fullShadeNeedsBouncer = (!lockScreenUserManager.userAllowsPrivateNotificationsInPublic(
431 lockScreenUserManager.getCurrentUserId()) ||
432 !lockScreenUserManager.shouldShowLockscreenNotifications() ||
433 falsingCollector.shouldEnforceBouncer())
434 if (keyguardBypassController.bypassEnabled) {
435 fullShadeNeedsBouncer = false
436 }
437 if (lockScreenUserManager.isLockscreenPublicMode(userId) && fullShadeNeedsBouncer) {
438 statusBarStateController.setLeaveOpenOnKeyguardHide(true)
439 var onDismissAction: OnDismissAction? = null
440 if (animationHandler != null) {
441 onDismissAction = OnDismissAction {
442 // We're waiting on keyguard to hide before triggering the action,
443 // as that will make the animation work properly
444 animationHandlerOnKeyguardDismiss = animationHandler
445 false
446 }
447 }
448 val cancelHandler = Runnable {
449 draggedDownEntry?.apply {
450 setUserLocked(false)
451 notifyHeightChanged(false /* needsAnimation */)
452 draggedDownEntry = null
453 }
454 cancelAction?.run()
455 }
456 statusbar.showBouncerWithDimissAndCancelIfKeyguard(onDismissAction, cancelHandler)
457 draggedDownEntry = entry
458 } else {
459 statusBarStateController.setState(StatusBarState.SHADE_LOCKED)
460 // This call needs to be after updating the shade state since otherwise
461 // the scrimstate resets too early
462 if (animationHandler != null) {
463 animationHandler.invoke(0 /* delay */)
464 } else {
465 performDefaultGoToFullShadeAnimation(0)
466 }
467 }
468 }
469
470 /**
471 * Notify this handler that the keyguard was just dismissed and that a animation to
472 * the full shade should happen.
473 *
474 * @param delay the delay to do the animation with
475 * @param previousState which state were we in when we hid the keyguard?
476 */
477 fun onHideKeyguard(delay: Long, previousState: Int) {
478 if (animationHandlerOnKeyguardDismiss != null) {
479 animationHandlerOnKeyguardDismiss!!.invoke(delay)
480 animationHandlerOnKeyguardDismiss = null
481 } else {
482 if (nextHideKeyguardNeedsNoAnimation) {
483 nextHideKeyguardNeedsNoAnimation = false
484 } else if (previousState != StatusBarState.SHADE_LOCKED) {
485 // No animation necessary if we already were in the shade locked!
486 performDefaultGoToFullShadeAnimation(delay)
487 }
488 }
489 draggedDownEntry?.apply {
490 setUserLocked(false)
491 draggedDownEntry = null
492 }
493 }
494
495 /**
496 * Perform the default appear animation when going to the full shade. This is called when
497 * not triggered by gestures, e.g. when clicking on the shelf or expand button.
498 */
499 private fun performDefaultGoToFullShadeAnimation(delay: Long) {
500 notificationPanelController.animateToFullShade(delay)
501 animateAppear(delay)
502 }
503
504 //
505 // PULSE EXPANSION
506 //
507
508 /**
509 * Set the height how tall notifications are pulsing. This is only set whenever we are expanding
510 * from a pulse and determines how much the notifications are expanded.
511 */
512 fun setPulseHeight(height: Float, animate: Boolean = false) {
513 if (animate) {
514 val pulseHeightAnimator = ValueAnimator.ofFloat(pulseHeight, height)
515 pulseHeightAnimator.interpolator = Interpolators.FAST_OUT_SLOW_IN
516 pulseHeightAnimator.duration = SPRING_BACK_ANIMATION_LENGTH_MS
517 pulseHeightAnimator.addUpdateListener { animation: ValueAnimator ->
518 setPulseHeight(animation.animatedValue as Float)
519 }
520 pulseHeightAnimator.start()
521 this.pulseHeightAnimator = pulseHeightAnimator
522 } else {
523 pulseHeight = height
524 val overflow = nsslController.setPulseHeight(height)
525 notificationPanelController.setOverStrechAmount(overflow)
526 val transitionHeight = if (keyguardBypassController.bypassEnabled) height else 0.0f
527 transitionToShadeAmountCommon(transitionHeight)
528 }
529 }
530
531 /**
532 * Finish the pulse animation when the touch interaction finishes
533 * @param cancelled was the interaction cancelled and this is a reset?
534 */
535 fun finishPulseAnimation(cancelled: Boolean) {
536 if (cancelled) {
537 setPulseHeight(0f, animate = true)
538 } else {
539 notificationPanelController.onPulseExpansionFinished()
540 setPulseHeight(0f, animate = false)
541 }
542 }
543
544 /**
545 * Notify this class that a pulse expansion is starting
546 */
547 fun onPulseExpansionStarted() {
548 pulseHeightAnimator?.cancel()
549 }
550 }
551
552 /**
553 * A utility class to enable the downward swipe on the lockscreen to go to the full shade and expand
554 * the notification where the drag started.
555 */
556 class DragDownHelper(
557 private val falsingManager: FalsingManager,
558 private val falsingCollector: FalsingCollector,
559 private val dragDownCallback: LockscreenShadeTransitionController,
560 context: Context
561 ) : Gefingerpoken {
562
563 private var dragDownAmountOnStart = 0.0f
564 lateinit var expandCallback: ExpandHelper.Callback
565 lateinit var host: View
566
567 private var minDragDistance = 0
568 private var initialTouchX = 0f
569 private var initialTouchY = 0f
570 private var touchSlop = 0f
571 private var slopMultiplier = 0f
572 private val temp2 = IntArray(2)
573 private var draggedFarEnough = false
574 private var startingChild: ExpandableView? = null
575 private var lastHeight = 0f
576 var isDraggingDown = false
577 private set
578
579 private val isFalseTouch: Boolean
580 get() {
581 return if (!dragDownCallback.isFalsingCheckNeeded) {
582 false
583 } else {
584 falsingManager.isFalseTouch(Classifier.NOTIFICATION_DRAG_DOWN) || !draggedFarEnough
585 }
586 }
587
588 val isDragDownEnabled: Boolean
589 get() = dragDownCallback.isDragDownEnabledForView(null)
590
<lambda>null591 init {
592 updateResources(context)
593 }
594
updateResourcesnull595 fun updateResources(context: Context) {
596 minDragDistance = context.resources.getDimensionPixelSize(
597 R.dimen.keyguard_drag_down_min_distance)
598 val configuration = ViewConfiguration.get(context)
599 touchSlop = configuration.scaledTouchSlop.toFloat()
600 slopMultiplier = configuration.scaledAmbiguousGestureMultiplier
601 }
602
onInterceptTouchEventnull603 override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
604 val x = event.x
605 val y = event.y
606 when (event.actionMasked) {
607 MotionEvent.ACTION_DOWN -> {
608 draggedFarEnough = false
609 isDraggingDown = false
610 startingChild = null
611 initialTouchY = y
612 initialTouchX = x
613 }
614 MotionEvent.ACTION_MOVE -> {
615 val h = y - initialTouchY
616 // Adjust the touch slop if another gesture may be being performed.
617 val touchSlop = if (event.classification
618 == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE)
619 touchSlop * slopMultiplier
620 else
621 touchSlop
622 if (h > touchSlop && h > Math.abs(x - initialTouchX)) {
623 falsingCollector.onNotificationStartDraggingDown()
624 isDraggingDown = true
625 captureStartingChild(initialTouchX, initialTouchY)
626 initialTouchY = y
627 initialTouchX = x
628 dragDownCallback.onDragDownStarted()
629 dragDownAmountOnStart = dragDownCallback.dragDownAmount
630 return startingChild != null || dragDownCallback.isDragDownAnywhereEnabled
631 }
632 }
633 }
634 return false
635 }
636
onTouchEventnull637 override fun onTouchEvent(event: MotionEvent): Boolean {
638 if (!isDraggingDown) {
639 return false
640 }
641 val x = event.x
642 val y = event.y
643 when (event.actionMasked) {
644 MotionEvent.ACTION_MOVE -> {
645 lastHeight = y - initialTouchY
646 captureStartingChild(initialTouchX, initialTouchY)
647 dragDownCallback.dragDownAmount = lastHeight + dragDownAmountOnStart
648 if (startingChild != null) {
649 handleExpansion(lastHeight, startingChild!!)
650 }
651 if (lastHeight > minDragDistance) {
652 if (!draggedFarEnough) {
653 draggedFarEnough = true
654 dragDownCallback.onCrossedThreshold(true)
655 }
656 } else {
657 if (draggedFarEnough) {
658 draggedFarEnough = false
659 dragDownCallback.onCrossedThreshold(false)
660 }
661 }
662 return true
663 }
664 MotionEvent.ACTION_UP -> if (!falsingManager.isUnlockingDisabled && !isFalseTouch &&
665 dragDownCallback.canDragDown()) {
666 dragDownCallback.onDraggedDown(startingChild, (y - initialTouchY).toInt())
667 if (startingChild != null) {
668 expandCallback.setUserLockedChild(startingChild, false)
669 startingChild = null
670 }
671 isDraggingDown = false
672 } else {
673 stopDragging()
674 return false
675 }
676 MotionEvent.ACTION_CANCEL -> {
677 stopDragging()
678 return false
679 }
680 }
681 return false
682 }
683
captureStartingChildnull684 private fun captureStartingChild(x: Float, y: Float) {
685 if (startingChild == null) {
686 startingChild = findView(x, y)
687 if (startingChild != null) {
688 if (dragDownCallback.isDragDownEnabledForView(startingChild)) {
689 expandCallback.setUserLockedChild(startingChild, true)
690 } else {
691 startingChild = null
692 }
693 }
694 }
695 }
696
handleExpansionnull697 private fun handleExpansion(heightDelta: Float, child: ExpandableView) {
698 var hDelta = heightDelta
699 if (hDelta < 0) {
700 hDelta = 0f
701 }
702 val expandable = child.isContentExpandable
703 val rubberbandFactor = if (expandable) {
704 RUBBERBAND_FACTOR_EXPANDABLE
705 } else {
706 RUBBERBAND_FACTOR_STATIC
707 }
708 var rubberband = hDelta * rubberbandFactor
709 if (expandable && rubberband + child.collapsedHeight > child.maxContentHeight) {
710 var overshoot = rubberband + child.collapsedHeight - child.maxContentHeight
711 overshoot *= 1 - RUBBERBAND_FACTOR_STATIC
712 rubberband -= overshoot
713 }
714 child.actualHeight = (child.collapsedHeight + rubberband).toInt()
715 }
716
cancelChildExpansionnull717 private fun cancelChildExpansion(child: ExpandableView) {
718 if (child.actualHeight == child.collapsedHeight) {
719 expandCallback.setUserLockedChild(child, false)
720 return
721 }
722 val anim = ObjectAnimator.ofInt(child, "actualHeight",
723 child.actualHeight, child.collapsedHeight)
724 anim.interpolator = Interpolators.FAST_OUT_SLOW_IN
725 anim.duration = SPRING_BACK_ANIMATION_LENGTH_MS
726 anim.addListener(object : AnimatorListenerAdapter() {
727 override fun onAnimationEnd(animation: Animator) {
728 expandCallback.setUserLockedChild(child, false)
729 }
730 })
731 anim.start()
732 }
733
stopDraggingnull734 private fun stopDragging() {
735 falsingCollector.onNotificationStopDraggingDown()
736 if (startingChild != null) {
737 cancelChildExpansion(startingChild!!)
738 startingChild = null
739 }
740 isDraggingDown = false
741 dragDownCallback.onDragDownReset()
742 }
743
findViewnull744 private fun findView(x: Float, y: Float): ExpandableView? {
745 host.getLocationOnScreen(temp2)
746 return expandCallback.getChildAtRawPosition(x + temp2[0], y + temp2[1])
747 }
748 }