• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * 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.content.Context
23 import android.content.res.Configuration
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.R
32 import com.android.systemui.animation.Interpolators
33 import com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN
34 import com.android.systemui.classifier.FalsingCollector
35 import com.android.systemui.dagger.SysUISingleton
36 import com.android.systemui.plugins.FalsingManager
37 import com.android.systemui.plugins.statusbar.StatusBarStateController
38 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
39 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
40 import com.android.systemui.statusbar.notification.row.ExpandableView
41 import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager
42 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
43 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
44 import com.android.systemui.statusbar.phone.KeyguardBypassController
45 import com.android.systemui.statusbar.policy.ConfigurationController
46 import javax.inject.Inject
47 import kotlin.math.max
48 
49 /**
50  * A utility class to enable the downward swipe on when pulsing.
51  */
52 @SysUISingleton
53 class PulseExpansionHandler @Inject
54 constructor(
55     context: Context,
56     private val wakeUpCoordinator: NotificationWakeUpCoordinator,
57     private val bypassController: KeyguardBypassController,
58     private val headsUpManager: HeadsUpManagerPhone,
59     private val roundnessManager: NotificationRoundnessManager,
60     private val configurationController: ConfigurationController,
61     private val statusBarStateController: StatusBarStateController,
62     private val falsingManager: FalsingManager,
63     private val lockscreenShadeTransitionController: LockscreenShadeTransitionController,
64     private val falsingCollector: FalsingCollector
65 ) : Gefingerpoken {
66     companion object {
67         private val SPRING_BACK_ANIMATION_LENGTH_MS = 375
68     }
69     private val mPowerManager: PowerManager?
70 
71     private var mInitialTouchX: Float = 0.0f
72     private var mInitialTouchY: Float = 0.0f
73     var isExpanding: Boolean = false
74         private set(value) {
75             val changed = field != value
76             field = value
77             bypassController.isPulseExpanding = value
78             if (changed) {
79                 if (value) {
80                     val topEntry = headsUpManager.topEntry
<lambda>null81                     topEntry?.let {
82                         roundnessManager.setTrackingHeadsUp(it.row)
83                     }
84                     lockscreenShadeTransitionController.onPulseExpansionStarted()
85                 } else {
86                     roundnessManager.setTrackingHeadsUp(null)
87                     if (!leavingLockscreen) {
88                         bypassController.maybePerformPendingUnlock()
89                         pulseExpandAbortListener?.run()
90                     }
91                 }
92                 headsUpManager.unpinAll(true /* userUnPinned */)
93             }
94         }
95     var leavingLockscreen: Boolean = false
96         private set
97     private var touchSlop = 0f
98     private var minDragDistance = 0
99     private lateinit var stackScrollerController: NotificationStackScrollLayoutController
100     private val mTemp2 = IntArray(2)
101     private var mDraggedFarEnough: Boolean = false
102     private var mStartingChild: ExpandableView? = null
103     private var mPulsing: Boolean = false
104     var isWakingToShadeLocked: Boolean = false
105         private set
106 
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         initResources(context)
117         configurationController.addCallback(object : ConfigurationController.ConfigurationListener {
onConfigChangednull118             override fun onConfigChanged(newConfig: Configuration?) {
119                 initResources(context)
120             }
121         })
122         mPowerManager = context.getSystemService(PowerManager::class.java)
123     }
124 
initResourcesnull125     private fun initResources(context: Context) {
126         minDragDistance = context.resources.getDimensionPixelSize(
127             R.dimen.keyguard_drag_down_min_distance)
128         touchSlop = ViewConfiguration.get(context).scaledTouchSlop.toFloat()
129     }
130 
onInterceptTouchEventnull131     override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
132         return canHandleMotionEvent() && startExpansion(event)
133     }
134 
canHandleMotionEventnull135     private fun canHandleMotionEvent(): Boolean {
136         return wakeUpCoordinator.canShowPulsingHuns && !qsExpanded && !bouncerShowing
137     }
138 
startExpansionnull139     private fun startExpansion(event: MotionEvent): Boolean {
140         if (velocityTracker == null) {
141             velocityTracker = VelocityTracker.obtain()
142         }
143         velocityTracker!!.addMovement(event)
144         val x = event.x
145         val y = event.y
146 
147         when (event.actionMasked) {
148             MotionEvent.ACTION_DOWN -> {
149                 mDraggedFarEnough = false
150                 isExpanding = false
151                 leavingLockscreen = false
152                 mStartingChild = null
153                 mInitialTouchY = y
154                 mInitialTouchX = x
155             }
156 
157             MotionEvent.ACTION_MOVE -> {
158                 val h = y - mInitialTouchY
159                 if (h > touchSlop && h > Math.abs(x - mInitialTouchX)) {
160                     falsingCollector.onStartExpandingFromPulse()
161                     isExpanding = true
162                     captureStartingChild(mInitialTouchX, mInitialTouchY)
163                     mInitialTouchY = y
164                     mInitialTouchX = x
165                     return true
166                 }
167             }
168 
169             MotionEvent.ACTION_UP -> {
170                 recycleVelocityTracker()
171                 isExpanding = false
172             }
173 
174             MotionEvent.ACTION_CANCEL -> {
175                 recycleVelocityTracker()
176                 isExpanding = false
177             }
178         }
179         return false
180     }
181 
recycleVelocityTrackernull182     private fun recycleVelocityTracker() {
183         velocityTracker?.recycle()
184         velocityTracker = null
185     }
186 
onTouchEventnull187     override fun onTouchEvent(event: MotionEvent): Boolean {
188         val finishExpanding = (event.action == MotionEvent.ACTION_CANCEL ||
189             event.action == MotionEvent.ACTION_UP) && isExpanding
190         if (!canHandleMotionEvent() && !finishExpanding) {
191             // We allow cancellations/finishing to still go through here to clean up the state
192             return false
193         }
194 
195         if (velocityTracker == null || !isExpanding ||
196                 event.actionMasked == MotionEvent.ACTION_DOWN) {
197             return startExpansion(event)
198         }
199         velocityTracker!!.addMovement(event)
200         val y = event.y
201 
202         val moveDistance = y - mInitialTouchY
203         when (event.actionMasked) {
204             MotionEvent.ACTION_MOVE -> updateExpansionHeight(moveDistance)
205             MotionEvent.ACTION_UP -> {
206                 velocityTracker!!.computeCurrentVelocity(1000 /* units */)
207                 val canExpand = moveDistance > 0 && velocityTracker!!.getYVelocity() > -1000 &&
208                         statusBarStateController.state != StatusBarState.SHADE
209                 if (!falsingManager.isUnlockingDisabled && !isFalseTouch && canExpand) {
210                     finishExpansion()
211                 } else {
212                     cancelExpansion()
213                 }
214                 recycleVelocityTracker()
215             }
216             MotionEvent.ACTION_CANCEL -> {
217                 cancelExpansion()
218                 recycleVelocityTracker()
219             }
220         }
221         return isExpanding
222     }
223 
finishExpansionnull224     private fun finishExpansion() {
225         val startingChild = mStartingChild
226         if (mStartingChild != null) {
227             setUserLocked(mStartingChild!!, false)
228             mStartingChild = null
229         }
230         if (statusBarStateController.isDozing) {
231             isWakingToShadeLocked = true
232             wakeUpCoordinator.willWakeUp = true
233             mPowerManager!!.wakeUp(SystemClock.uptimeMillis(), WAKE_REASON_GESTURE,
234                     "com.android.systemui:PULSEDRAG")
235         }
236         lockscreenShadeTransitionController.goToLockedShade(startingChild,
237                 needsQSAnimation = false)
238         lockscreenShadeTransitionController.finishPulseAnimation(cancelled = false)
239         leavingLockscreen = true
240         isExpanding = false
241         if (mStartingChild is ExpandableNotificationRow) {
242             val row = mStartingChild as ExpandableNotificationRow?
243             row!!.onExpandedByGesture(true /* userExpanded */)
244         }
245     }
246 
updateExpansionHeightnull247     private fun updateExpansionHeight(height: Float) {
248         var expansionHeight = max(height, 0.0f)
249         if (mStartingChild != null) {
250             val child = mStartingChild!!
251             val newHeight = Math.min((child.collapsedHeight + expansionHeight).toInt(),
252                     child.maxContentHeight)
253             child.actualHeight = newHeight
254         } else {
255             wakeUpCoordinator.setNotificationsVisibleForExpansion(
256                 height
257                     > lockscreenShadeTransitionController.distanceUntilShowingPulsingNotifications,
258                 true /* animate */,
259                 true /* increaseSpeed */)
260         }
261         lockscreenShadeTransitionController.setPulseHeight(expansionHeight, animate = false)
262     }
263 
captureStartingChildnull264     private fun captureStartingChild(x: Float, y: Float) {
265         if (mStartingChild == null && !bypassController.bypassEnabled) {
266             mStartingChild = findView(x, y)
267             if (mStartingChild != null) {
268                 setUserLocked(mStartingChild!!, true)
269             }
270         }
271     }
272 
resetnull273     private fun reset(child: ExpandableView) {
274         if (child.actualHeight == child.collapsedHeight) {
275             setUserLocked(child, false)
276             return
277         }
278         val anim = ObjectAnimator.ofInt(child, "actualHeight",
279                 child.actualHeight, child.collapsedHeight)
280         anim.interpolator = Interpolators.FAST_OUT_SLOW_IN
281         anim.duration = SPRING_BACK_ANIMATION_LENGTH_MS.toLong()
282         anim.addListener(object : AnimatorListenerAdapter() {
283             override fun onAnimationEnd(animation: Animator) {
284                 setUserLocked(child, false)
285             }
286         })
287         anim.start()
288     }
289 
setUserLockednull290     private fun setUserLocked(child: ExpandableView, userLocked: Boolean) {
291         if (child is ExpandableNotificationRow) {
292             child.isUserLocked = userLocked
293         }
294     }
295 
cancelExpansionnull296     private fun cancelExpansion() {
297         isExpanding = false
298         falsingCollector.onExpansionFromPulseStopped()
299         if (mStartingChild != null) {
300             reset(mStartingChild!!)
301             mStartingChild = null
302         }
303         lockscreenShadeTransitionController.finishPulseAnimation(cancelled = true)
304         wakeUpCoordinator.setNotificationsVisibleForExpansion(false /* visible */,
305                 true /* animate */,
306                 false /* increaseSpeed */)
307     }
308 
findViewnull309     private fun findView(x: Float, y: Float): ExpandableView? {
310         var totalX = x
311         var totalY = y
312         stackScrollerController.getLocationOnScreen(mTemp2)
313         totalX += mTemp2[0].toFloat()
314         totalY += mTemp2[1].toFloat()
315         val childAtRawPosition = stackScrollerController.getChildAtRawPosition(totalX, totalY)
316         return if (childAtRawPosition != null && childAtRawPosition.isContentExpandable) {
317             childAtRawPosition
318         } else null
319     }
320 
setUpnull321     fun setUp(stackScrollerController: NotificationStackScrollLayoutController) {
322         this.stackScrollerController = stackScrollerController
323     }
324 
setPulsingnull325     fun setPulsing(pulsing: Boolean) {
326         mPulsing = pulsing
327     }
328 
onStartedWakingUpnull329     fun onStartedWakingUp() {
330         isWakingToShadeLocked = false
331     }
332 }
333