• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download

<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 }