• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2019 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.statusbar
18 
19 import android.animation.Animator
20 import android.animation.AnimatorListenerAdapter
21 import android.animation.ObjectAnimator
22 import android.animation.ValueAnimator
23 import android.content.Context
24 import android.os.PowerManager
25 import android.os.PowerManager.WAKE_REASON_GESTURE
26 import android.os.SystemClock
27 import android.view.MotionEvent
28 import android.view.VelocityTracker
29 import android.view.ViewConfiguration
30 import com.android.systemui.Gefingerpoken
31 import com.android.systemui.Interpolators
32 import com.android.systemui.R
33 import com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN
34 import com.android.systemui.plugins.FalsingManager
35 import com.android.systemui.plugins.statusbar.StatusBarStateController
36 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
37 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
38 import com.android.systemui.statusbar.notification.row.ExpandableView
39 import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager
40 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
41 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
42 import com.android.systemui.statusbar.phone.KeyguardBypassController
43 import com.android.systemui.statusbar.phone.ShadeController
44 import javax.inject.Inject
45 import javax.inject.Singleton
46 import kotlin.math.max
47 
48 /**
49  * A utility class to enable the downward swipe on when pulsing.
50  */
51 @Singleton
52 class PulseExpansionHandler @Inject
53 constructor(
54     context: Context,
55     private val wakeUpCoordinator: NotificationWakeUpCoordinator,
56     private val bypassController: KeyguardBypassController,
57     private val headsUpManager: HeadsUpManagerPhone,
58     private val roundnessManager: NotificationRoundnessManager,
59     private val statusBarStateController: StatusBarStateController,
60     private val falsingManager: FalsingManager
61 ) : Gefingerpoken {
62     companion object {
63         private val RUBBERBAND_FACTOR_STATIC = 0.25f
64         private val SPRING_BACK_ANIMATION_LENGTH_MS = 375
65     }
66     private val mPowerManager: PowerManager?
67     private lateinit var shadeController: ShadeController
68 
69     private val mMinDragDistance: Int
70     private var mInitialTouchX: Float = 0.0f
71     private var mInitialTouchY: Float = 0.0f
72     var isExpanding: Boolean = false
73         private set(value) {
74             val changed = field != value
75             field = value
76             bypassController.isPulseExpanding = value
77             if (changed) {
78                 if (value) {
79                     val topEntry = headsUpManager.topEntry
80                     topEntry?.let {
81                         roundnessManager.setTrackingHeadsUp(it.row)
82                     }
83                 } else {
84                     roundnessManager.setTrackingHeadsUp(null)
85                     if (!leavingLockscreen) {
86                         bypassController.maybePerformPendingUnlock()
87                         pulseExpandAbortListener?.run()
88                     }
89                 }
90                 headsUpManager.unpinAll(true /* userUnPinned */)
91             }
92         }
93     var leavingLockscreen: Boolean = false
94         private set
95     private val mTouchSlop: Float
96     private lateinit var expansionCallback: ExpansionCallback
97     private lateinit var stackScroller: NotificationStackScrollLayout
98     private val mTemp2 = IntArray(2)
99     private var mDraggedFarEnough: Boolean = false
100     private var mStartingChild: ExpandableView? = null
101     private var mPulsing: Boolean = false
102     var isWakingToShadeLocked: Boolean = false
103         private set
104     private var mEmptyDragAmount: Float = 0.0f
105     private var mWakeUpHeight: Float = 0.0f
106     private var mReachedWakeUpHeight: Boolean = false
107     private var velocityTracker: VelocityTracker? = null
108 
109     private val isFalseTouch: Boolean
110         get() = falsingManager.isFalseTouch(NOTIFICATION_DRAG_DOWN)
111     var qsExpanded: Boolean = false
112     var pulseExpandAbortListener: Runnable? = null
113     var bouncerShowing: Boolean = false
114 
115     init {
116         mMinDragDistance = context.resources.getDimensionPixelSize(
117                 R.dimen.keyguard_drag_down_min_distance)
118         mTouchSlop = ViewConfiguration.get(context).scaledTouchSlop.toFloat()
119         mPowerManager = context.getSystemService(PowerManager::class.java)
120     }
121 
122     override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
123         return canHandleMotionEvent() && startExpansion(event)
124     }
125 
126     private fun canHandleMotionEvent(): Boolean {
127         return wakeUpCoordinator.canShowPulsingHuns && !qsExpanded && !bouncerShowing
128     }
129 
130     private fun startExpansion(event: MotionEvent): Boolean {
131         if (velocityTracker == null) {
132             velocityTracker = VelocityTracker.obtain()
133         }
134         velocityTracker!!.addMovement(event)
135         val x = event.x
136         val y = event.y
137 
138         when (event.actionMasked) {
139             MotionEvent.ACTION_DOWN -> {
140                 mDraggedFarEnough = false
141                 isExpanding = false
142                 leavingLockscreen = false
143                 mStartingChild = null
144                 mInitialTouchY = y
145                 mInitialTouchX = x
146             }
147 
148             MotionEvent.ACTION_MOVE -> {
149                 val h = y - mInitialTouchY
150                 if (h > mTouchSlop && h > Math.abs(x - mInitialTouchX)) {
151                     falsingManager.onStartExpandingFromPulse()
152                     isExpanding = true
153                     captureStartingChild(mInitialTouchX, mInitialTouchY)
154                     mInitialTouchY = y
155                     mInitialTouchX = x
156                     mWakeUpHeight = wakeUpCoordinator.getWakeUpHeight()
157                     mReachedWakeUpHeight = false
158                     return true
159                 }
160             }
161 
162             MotionEvent.ACTION_UP -> {
163                 recycleVelocityTracker()
164                 isExpanding = false
165             }
166 
167             MotionEvent.ACTION_CANCEL -> {
168                 recycleVelocityTracker()
169                 isExpanding = false
170             }
171         }
172         return false
173     }
174 
175     private fun recycleVelocityTracker() {
176         velocityTracker?.recycle()
177         velocityTracker = null
178     }
179 
180     override fun onTouchEvent(event: MotionEvent): Boolean {
181         if (!canHandleMotionEvent()) {
182             return false
183         }
184 
185         if (velocityTracker == null || !isExpanding ||
186                 event.actionMasked == MotionEvent.ACTION_DOWN) {
187             return startExpansion(event)
188         }
189         velocityTracker!!.addMovement(event)
190         val y = event.y
191 
192         val moveDistance = y - mInitialTouchY
193         when (event.actionMasked) {
194             MotionEvent.ACTION_MOVE -> updateExpansionHeight(moveDistance)
195             MotionEvent.ACTION_UP -> {
196                 velocityTracker!!.computeCurrentVelocity(1000 /* units */)
197                 val canExpand = moveDistance > 0 && velocityTracker!!.getYVelocity() > -1000 &&
198                         statusBarStateController.state != StatusBarState.SHADE
199                 if (!falsingManager.isUnlockingDisabled && !isFalseTouch && canExpand) {
200                     finishExpansion()
201                 } else {
202                     cancelExpansion()
203                 }
204                 recycleVelocityTracker()
205             }
206             MotionEvent.ACTION_CANCEL -> {
207                 cancelExpansion()
208                 recycleVelocityTracker()
209             }
210         }
211         return isExpanding
212     }
213 
214     private fun finishExpansion() {
215         resetClock()
216         if (mStartingChild != null) {
217             setUserLocked(mStartingChild!!, false)
218             mStartingChild = null
219         }
220         if (statusBarStateController.isDozing) {
221             isWakingToShadeLocked = true
222             wakeUpCoordinator.willWakeUp = true
223             mPowerManager!!.wakeUp(SystemClock.uptimeMillis(), WAKE_REASON_GESTURE,
224                     "com.android.systemui:PULSEDRAG")
225         }
226         shadeController.goToLockedShade(mStartingChild)
227         leavingLockscreen = true
228         isExpanding = false
229         if (mStartingChild is ExpandableNotificationRow) {
230             val row = mStartingChild as ExpandableNotificationRow?
231             row!!.onExpandedByGesture(true /* userExpanded */)
232         }
233     }
234 
235     private fun updateExpansionHeight(height: Float) {
236         var expansionHeight = max(height, 0.0f)
237         if (!mReachedWakeUpHeight && height > mWakeUpHeight) {
238             mReachedWakeUpHeight = true
239         }
240         if (mStartingChild != null) {
241             val child = mStartingChild!!
242             val newHeight = Math.min((child.collapsedHeight + expansionHeight).toInt(),
243                     child.maxContentHeight)
244             child.actualHeight = newHeight
245             expansionHeight = max(newHeight.toFloat(), expansionHeight)
246         } else {
247             val target = if (mReachedWakeUpHeight) mWakeUpHeight else 0.0f
248             wakeUpCoordinator.setNotificationsVisibleForExpansion(height > target,
249                     true /* animate */,
250                     true /* increaseSpeed */)
251             expansionHeight = max(mWakeUpHeight, expansionHeight)
252         }
253         val emptyDragAmount = wakeUpCoordinator.setPulseHeight(expansionHeight)
254         setEmptyDragAmount(emptyDragAmount * RUBBERBAND_FACTOR_STATIC)
255     }
256 
257     private fun captureStartingChild(x: Float, y: Float) {
258         if (mStartingChild == null && !bypassController.bypassEnabled) {
259             mStartingChild = findView(x, y)
260             if (mStartingChild != null) {
261                 setUserLocked(mStartingChild!!, true)
262             }
263         }
264     }
265 
266     private fun setEmptyDragAmount(amount: Float) {
267         mEmptyDragAmount = amount
268         expansionCallback.setEmptyDragAmount(amount)
269     }
270 
271     private fun reset(child: ExpandableView) {
272         if (child.actualHeight == child.collapsedHeight) {
273             setUserLocked(child, false)
274             return
275         }
276         val anim = ObjectAnimator.ofInt(child, "actualHeight",
277                 child.actualHeight, child.collapsedHeight)
278         anim.interpolator = Interpolators.FAST_OUT_SLOW_IN
279         anim.duration = SPRING_BACK_ANIMATION_LENGTH_MS.toLong()
280         anim.addListener(object : AnimatorListenerAdapter() {
281             override fun onAnimationEnd(animation: Animator) {
282                 setUserLocked(child, false)
283             }
284         })
285         anim.start()
286     }
287 
288     private fun setUserLocked(child: ExpandableView, userLocked: Boolean) {
289         if (child is ExpandableNotificationRow) {
290             child.isUserLocked = userLocked
291         }
292     }
293 
294     private fun resetClock() {
295         val anim = ValueAnimator.ofFloat(mEmptyDragAmount, 0f)
296         anim.interpolator = Interpolators.FAST_OUT_SLOW_IN
297         anim.duration = SPRING_BACK_ANIMATION_LENGTH_MS.toLong()
298         anim.addUpdateListener { animation -> setEmptyDragAmount(animation.animatedValue as Float) }
299         anim.start()
300     }
301 
302     private fun cancelExpansion() {
303         isExpanding = false
304         falsingManager.onExpansionFromPulseStopped()
305         if (mStartingChild != null) {
306             reset(mStartingChild!!)
307             mStartingChild = null
308         } else {
309             resetClock()
310         }
311         wakeUpCoordinator.setNotificationsVisibleForExpansion(false /* visible */,
312                 true /* animate */,
313                 false /* increaseSpeed */)
314     }
315 
316     private fun findView(x: Float, y: Float): ExpandableView? {
317         var totalX = x
318         var totalY = y
319         stackScroller.getLocationOnScreen(mTemp2)
320         totalX += mTemp2[0].toFloat()
321         totalY += mTemp2[1].toFloat()
322         val childAtRawPosition = stackScroller.getChildAtRawPosition(totalX, totalY)
323         return if (childAtRawPosition != null && childAtRawPosition.isContentExpandable) {
324             childAtRawPosition
325         } else null
326     }
327 
328     fun setUp(
329         stackScroller: NotificationStackScrollLayout,
330         expansionCallback: ExpansionCallback,
331         shadeController: ShadeController
332     ) {
333         this.expansionCallback = expansionCallback
334         this.shadeController = shadeController
335         this.stackScroller = stackScroller
336     }
337 
338     fun setPulsing(pulsing: Boolean) {
339         mPulsing = pulsing
340     }
341 
342     fun onStartedWakingUp() {
343         isWakingToShadeLocked = false
344     }
345 
346     interface ExpansionCallback {
347         fun setEmptyDragAmount(amount: Float)
348     }
349 }
350