blob: 88f148b00cdccb12bfded379a1229aedb862f57c [file] [log] [blame]
Selim Cinek3d6ae232019-01-04 14:14:33 -08001/*
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
17package com.android.systemui.statusbar
18
19import android.animation.Animator
20import android.animation.AnimatorListenerAdapter
21import android.animation.ObjectAnimator
22import android.animation.ValueAnimator
23import android.content.Context
24import android.os.PowerManager
25import android.os.PowerManager.WAKE_REASON_GESTURE
26import android.os.SystemClock
27import android.view.MotionEvent
Selim Cinekb8cc6ef2019-06-14 16:37:53 -070028import android.view.VelocityTracker
Selim Cinek3d6ae232019-01-04 14:14:33 -080029import android.view.ViewConfiguration
30
31import com.android.systemui.Gefingerpoken
32import com.android.systemui.Interpolators
33import com.android.systemui.R
Dave Mankoff468d4f62019-05-08 14:56:29 -040034import com.android.systemui.plugins.FalsingManager
Lucas Dupine7ff7be2019-09-17 11:19:53 -040035import com.android.systemui.plugins.statusbar.StatusBarStateController
Selim Cinek3d6ae232019-01-04 14:14:33 -080036import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
37import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
38import com.android.systemui.statusbar.notification.row.ExpandableView
Selim Cinekc7e4cb52019-06-20 15:41:45 -070039import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager
Selim Cinek3d6ae232019-01-04 14:14:33 -080040import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
Selim Cinekd21232e2019-06-20 14:15:59 -070041import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
Selim Cinekb8cc6ef2019-06-14 16:37:53 -070042import com.android.systemui.statusbar.phone.KeyguardBypassController
Selim Cinek3d6ae232019-01-04 14:14:33 -080043import com.android.systemui.statusbar.phone.ShadeController
44
45import javax.inject.Inject
46import javax.inject.Singleton
47import kotlin.math.max
48
49/**
50 * A utility class to enable the downward swipe on when pulsing.
51 */
52@Singleton
53class PulseExpansionHandler @Inject
Dan Sandlerda6e1792019-08-14 13:52:54 -040054constructor(
55 context: Context,
56 private val wakeUpCoordinator: NotificationWakeUpCoordinator,
57 private val bypassController: KeyguardBypassController,
58 private val headsUpManager: HeadsUpManagerPhone,
Lucas Dupine7ff7be2019-09-17 11:19:53 -040059 private val roundnessManager: NotificationRoundnessManager,
Dave Mankoff898e1bb2019-09-25 17:54:19 -040060 private val statusBarStateController: StatusBarStateController,
61 private val falsingManager: FalsingManager
Dan Sandlerda6e1792019-08-14 13:52:54 -040062) : Gefingerpoken {
Selim Cinek34518f62019-02-28 19:41:18 -080063 companion object {
64 private val RUBBERBAND_FACTOR_STATIC = 0.25f
65 private val SPRING_BACK_ANIMATION_LENGTH_MS = 375
66 }
Selim Cinek3d6ae232019-01-04 14:14:33 -080067 private val mPowerManager: PowerManager?
Selim Cinekb8cc6ef2019-06-14 16:37:53 -070068 private lateinit var shadeController: ShadeController
Selim Cinek3d6ae232019-01-04 14:14:33 -080069
70 private val mMinDragDistance: Int
71 private var mInitialTouchX: Float = 0.0f
72 private var mInitialTouchY: Float = 0.0f
Selim Cinekb0fada62019-06-17 19:03:59 -070073 var isExpanding: Boolean = false
Selim Cinekb8cc6ef2019-06-14 16:37:53 -070074 private set(value) {
75 val changed = field != value
76 field = value
77 bypassController.isPulseExpanding = value
Selim Cinekd21232e2019-06-20 14:15:59 -070078 if (changed) {
Selim Cinekc7e4cb52019-06-20 15:41:45 -070079 if (value) {
80 val topEntry = headsUpManager.topEntry
81 topEntry?.let {
82 roundnessManager.setTrackingHeadsUp(it.row)
83 }
84 } else {
85 roundnessManager.setTrackingHeadsUp(null)
86 if (!leavingLockscreen) {
87 bypassController.maybePerformPendingUnlock()
88 pulseExpandAbortListener?.run()
89 }
Selim Cinekd21232e2019-06-20 14:15:59 -070090 }
Selim Cinekc7e4cb52019-06-20 15:41:45 -070091 headsUpManager.unpinAll(true /* userUnPinned */)
Selim Cinekb8cc6ef2019-06-14 16:37:53 -070092 }
93 }
Selim Cinekb0fada62019-06-17 19:03:59 -070094 var leavingLockscreen: Boolean = false
95 private set
Selim Cinek3d6ae232019-01-04 14:14:33 -080096 private val mTouchSlop: Float
Selim Cinekb8cc6ef2019-06-14 16:37:53 -070097 private lateinit var expansionCallback: ExpansionCallback
98 private lateinit var stackScroller: NotificationStackScrollLayout
Selim Cinek3d6ae232019-01-04 14:14:33 -080099 private val mTemp2 = IntArray(2)
100 private var mDraggedFarEnough: Boolean = false
101 private var mStartingChild: ExpandableView? = null
Selim Cinek3d6ae232019-01-04 14:14:33 -0800102 private var mPulsing: Boolean = false
103 var isWakingToShadeLocked: Boolean = false
104 private set
105 private var mEmptyDragAmount: Float = 0.0f
106 private var mWakeUpHeight: Float = 0.0f
107 private var mReachedWakeUpHeight: Boolean = false
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700108 private var velocityTracker: VelocityTracker? = null
Selim Cinek3d6ae232019-01-04 14:14:33 -0800109
110 private val isFalseTouch: Boolean
Dave Mankoff898e1bb2019-09-25 17:54:19 -0400111 get() = falsingManager.isFalseTouch
Selim Cinekb0fada62019-06-17 19:03:59 -0700112 var qsExpanded: Boolean = false
113 var pulseExpandAbortListener: Runnable? = null
Selim Cinek820ba2d2019-06-18 18:59:09 -0700114 var bouncerShowing: Boolean = false
Selim Cinek3d6ae232019-01-04 14:14:33 -0800115
116 init {
117 mMinDragDistance = context.resources.getDimensionPixelSize(
118 R.dimen.keyguard_drag_down_min_distance)
119 mTouchSlop = ViewConfiguration.get(context).scaledTouchSlop.toFloat()
Selim Cinek3d6ae232019-01-04 14:14:33 -0800120 mPowerManager = context.getSystemService(PowerManager::class.java)
121 }
122
123 override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
Dave Mankoff49b33352020-05-12 20:23:16 +0000124 return maybeStartExpansion(event)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800125 }
126
Dave Mankoff49b33352020-05-12 20:23:16 +0000127 private fun maybeStartExpansion(event: MotionEvent): Boolean {
128 if (!wakeUpCoordinator.canShowPulsingHuns || qsExpanded ||
129 bouncerShowing) {
130 return false
131 }
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700132 if (velocityTracker == null) {
133 velocityTracker = VelocityTracker.obtain()
134 }
135 velocityTracker!!.addMovement(event)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800136 val x = event.x
137 val y = event.y
138
139 when (event.actionMasked) {
140 MotionEvent.ACTION_DOWN -> {
141 mDraggedFarEnough = false
142 isExpanding = false
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700143 leavingLockscreen = false
Selim Cinek3d6ae232019-01-04 14:14:33 -0800144 mStartingChild = null
145 mInitialTouchY = y
146 mInitialTouchX = x
147 }
148
149 MotionEvent.ACTION_MOVE -> {
150 val h = y - mInitialTouchY
151 if (h > mTouchSlop && h > Math.abs(x - mInitialTouchX)) {
Dave Mankoff898e1bb2019-09-25 17:54:19 -0400152 falsingManager.onStartExpandingFromPulse()
Selim Cinek3d6ae232019-01-04 14:14:33 -0800153 isExpanding = true
154 captureStartingChild(mInitialTouchX, mInitialTouchY)
155 mInitialTouchY = y
156 mInitialTouchX = x
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700157 mWakeUpHeight = wakeUpCoordinator.getWakeUpHeight()
Selim Cinek3d6ae232019-01-04 14:14:33 -0800158 mReachedWakeUpHeight = false
159 return true
160 }
161 }
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700162
163 MotionEvent.ACTION_UP -> {
Dan Sandlerda6e1792019-08-14 13:52:54 -0400164 recycleVelocityTracker()
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700165 }
166
167 MotionEvent.ACTION_CANCEL -> {
Dan Sandlerda6e1792019-08-14 13:52:54 -0400168 recycleVelocityTracker()
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700169 }
Selim Cinek3d6ae232019-01-04 14:14:33 -0800170 }
171 return false
172 }
173
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700174 private fun recycleVelocityTracker() {
Dan Sandlerda6e1792019-08-14 13:52:54 -0400175 velocityTracker?.recycle()
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700176 velocityTracker = null
177 }
178
Selim Cinek3d6ae232019-01-04 14:14:33 -0800179 override fun onTouchEvent(event: MotionEvent): Boolean {
Dave Mankoff49b33352020-05-12 20:23:16 +0000180 if (!isExpanding) {
181 return maybeStartExpansion(event)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800182 }
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700183 velocityTracker!!.addMovement(event)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800184 val y = event.y
185
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700186 val moveDistance = y - mInitialTouchY
Selim Cinek3d6ae232019-01-04 14:14:33 -0800187 when (event.actionMasked) {
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700188 MotionEvent.ACTION_MOVE -> updateExpansionHeight(moveDistance)
189 MotionEvent.ACTION_UP -> {
190 velocityTracker!!.computeCurrentVelocity(1000 /* units */)
Lucas Dupine7ff7be2019-09-17 11:19:53 -0400191 val canExpand = moveDistance > 0 && velocityTracker!!.getYVelocity() > -1000 &&
192 statusBarStateController.state != StatusBarState.SHADE
Dave Mankoff898e1bb2019-09-25 17:54:19 -0400193 if (!falsingManager.isUnlockingDisabled && !isFalseTouch && canExpand) {
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700194 finishExpansion()
195 } else {
196 cancelExpansion()
197 }
198 recycleVelocityTracker()
Selim Cinek3d6ae232019-01-04 14:14:33 -0800199 }
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700200 MotionEvent.ACTION_CANCEL -> {
201 cancelExpansion()
202 recycleVelocityTracker()
203 }
Selim Cinek3d6ae232019-01-04 14:14:33 -0800204 }
205 return isExpanding
206 }
207
208 private fun finishExpansion() {
209 resetClock()
210 if (mStartingChild != null) {
211 setUserLocked(mStartingChild!!, false)
212 mStartingChild = null
213 }
Heemin Seoge9f4e962019-12-05 11:55:27 -0800214 if (statusBarStateController.isDozing) {
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700215 isWakingToShadeLocked = true
216 wakeUpCoordinator.willWakeUp = true
217 mPowerManager!!.wakeUp(SystemClock.uptimeMillis(), WAKE_REASON_GESTURE,
218 "com.android.systemui:PULSEDRAG")
219 }
220 shadeController.goToLockedShade(mStartingChild)
Dan Sandlerda6e1792019-08-14 13:52:54 -0400221 leavingLockscreen = true
Selim Cinek3d6ae232019-01-04 14:14:33 -0800222 isExpanding = false
Selim Cinek3d6ae232019-01-04 14:14:33 -0800223 if (mStartingChild is ExpandableNotificationRow) {
224 val row = mStartingChild as ExpandableNotificationRow?
225 row!!.onExpandedByGesture(true /* userExpanded */)
226 }
227 }
228
229 private fun updateExpansionHeight(height: Float) {
230 var expansionHeight = max(height, 0.0f)
231 if (!mReachedWakeUpHeight && height > mWakeUpHeight) {
Dan Sandlerda6e1792019-08-14 13:52:54 -0400232 mReachedWakeUpHeight = true
Selim Cinek3d6ae232019-01-04 14:14:33 -0800233 }
234 if (mStartingChild != null) {
235 val child = mStartingChild!!
236 val newHeight = Math.min((child.collapsedHeight + expansionHeight).toInt(),
237 child.maxContentHeight)
238 child.actualHeight = newHeight
239 expansionHeight = max(newHeight.toFloat(), expansionHeight)
240 } else {
Selim Cinek624d6ca2019-02-19 15:39:08 -0800241 val target = if (mReachedWakeUpHeight) mWakeUpHeight else 0.0f
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700242 wakeUpCoordinator.setNotificationsVisibleForExpansion(height > target,
Selim Cinek624d6ca2019-02-19 15:39:08 -0800243 true /* animate */,
244 true /* increaseSpeed */)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800245 expansionHeight = max(mWakeUpHeight, expansionHeight)
246 }
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700247 val emptyDragAmount = wakeUpCoordinator.setPulseHeight(expansionHeight)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800248 setEmptyDragAmount(emptyDragAmount * RUBBERBAND_FACTOR_STATIC)
249 }
250
251 private fun captureStartingChild(x: Float, y: Float) {
Selim Cinekc7e4cb52019-06-20 15:41:45 -0700252 if (mStartingChild == null && !bypassController.bypassEnabled) {
Selim Cinek3d6ae232019-01-04 14:14:33 -0800253 mStartingChild = findView(x, y)
254 if (mStartingChild != null) {
255 setUserLocked(mStartingChild!!, true)
256 }
257 }
258 }
259
260 private fun setEmptyDragAmount(amount: Float) {
261 mEmptyDragAmount = amount
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700262 expansionCallback.setEmptyDragAmount(amount)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800263 }
264
265 private fun reset(child: ExpandableView) {
266 if (child.actualHeight == child.collapsedHeight) {
267 setUserLocked(child, false)
268 return
269 }
270 val anim = ObjectAnimator.ofInt(child, "actualHeight",
271 child.actualHeight, child.collapsedHeight)
272 anim.interpolator = Interpolators.FAST_OUT_SLOW_IN
273 anim.duration = SPRING_BACK_ANIMATION_LENGTH_MS.toLong()
274 anim.addListener(object : AnimatorListenerAdapter() {
275 override fun onAnimationEnd(animation: Animator) {
276 setUserLocked(child, false)
277 }
278 })
279 anim.start()
280 }
281
282 private fun setUserLocked(child: ExpandableView, userLocked: Boolean) {
283 if (child is ExpandableNotificationRow) {
284 child.isUserLocked = userLocked
285 }
286 }
287
288 private fun resetClock() {
289 val anim = ValueAnimator.ofFloat(mEmptyDragAmount, 0f)
290 anim.interpolator = Interpolators.FAST_OUT_SLOW_IN
291 anim.duration = SPRING_BACK_ANIMATION_LENGTH_MS.toLong()
292 anim.addUpdateListener { animation -> setEmptyDragAmount(animation.animatedValue as Float) }
293 anim.start()
294 }
295
296 private fun cancelExpansion() {
Selim Cinekb0fada62019-06-17 19:03:59 -0700297 isExpanding = false
Dave Mankoff898e1bb2019-09-25 17:54:19 -0400298 falsingManager.onExpansionFromPulseStopped()
Selim Cinek3d6ae232019-01-04 14:14:33 -0800299 if (mStartingChild != null) {
300 reset(mStartingChild!!)
301 mStartingChild = null
302 } else {
303 resetClock()
304 }
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700305 wakeUpCoordinator.setNotificationsVisibleForExpansion(false /* visible */,
Selim Cinek624d6ca2019-02-19 15:39:08 -0800306 true /* animate */,
307 false /* increaseSpeed */)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800308 }
309
310 private fun findView(x: Float, y: Float): ExpandableView? {
311 var totalX = x
312 var totalY = y
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700313 stackScroller.getLocationOnScreen(mTemp2)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800314 totalX += mTemp2[0].toFloat()
315 totalY += mTemp2[1].toFloat()
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700316 val childAtRawPosition = stackScroller.getChildAtRawPosition(totalX, totalY)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800317 return if (childAtRawPosition != null && childAtRawPosition.isContentExpandable) {
318 childAtRawPosition
319 } else null
320 }
321
Dan Sandlerda6e1792019-08-14 13:52:54 -0400322 fun setUp(
323 stackScroller: NotificationStackScrollLayout,
324 expansionCallback: ExpansionCallback,
325 shadeController: ShadeController
326 ) {
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700327 this.expansionCallback = expansionCallback
328 this.shadeController = shadeController
329 this.stackScroller = stackScroller
Selim Cinek3d6ae232019-01-04 14:14:33 -0800330 }
331
332 fun setPulsing(pulsing: Boolean) {
333 mPulsing = pulsing
Selim Cinek3d6ae232019-01-04 14:14:33 -0800334 }
335
336 fun onStartedWakingUp() {
337 isWakingToShadeLocked = false
338 }
339
340 interface ExpansionCallback {
341 fun setEmptyDragAmount(amount: Float)
342 }
Selim Cinek3d6ae232019-01-04 14:14:33 -0800343}