blob: a70dc7c0ec5d1658155656c00616cbd4eefb19ea [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
Dave Mankoff781ef7e2019-06-28 16:33:25 -040030import com.android.systemui.Dependency
Selim Cinek3d6ae232019-01-04 14:14:33 -080031
32import com.android.systemui.Gefingerpoken
33import com.android.systemui.Interpolators
34import com.android.systemui.R
Dave Mankoff468d4f62019-05-08 14:56:29 -040035import com.android.systemui.plugins.FalsingManager
Lucas Dupine7ff7be2019-09-17 11:19:53 -040036import com.android.systemui.plugins.statusbar.StatusBarStateController
Selim Cinek3d6ae232019-01-04 14:14:33 -080037import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
38import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
39import com.android.systemui.statusbar.notification.row.ExpandableView
Selim Cinekc7e4cb52019-06-20 15:41:45 -070040import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager
Selim Cinek3d6ae232019-01-04 14:14:33 -080041import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
Selim Cinekd21232e2019-06-20 14:15:59 -070042import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
Selim Cinekb8cc6ef2019-06-14 16:37:53 -070043import com.android.systemui.statusbar.phone.KeyguardBypassController
Selim Cinek3d6ae232019-01-04 14:14:33 -080044import com.android.systemui.statusbar.phone.ShadeController
45
46import javax.inject.Inject
47import javax.inject.Singleton
48import kotlin.math.max
49
50/**
51 * A utility class to enable the downward swipe on when pulsing.
52 */
53@Singleton
54class PulseExpansionHandler @Inject
Dan Sandlerda6e1792019-08-14 13:52:54 -040055constructor(
56 context: Context,
57 private val wakeUpCoordinator: NotificationWakeUpCoordinator,
58 private val bypassController: KeyguardBypassController,
59 private val headsUpManager: HeadsUpManagerPhone,
Lucas Dupine7ff7be2019-09-17 11:19:53 -040060 private val roundnessManager: NotificationRoundnessManager,
61 private val statusBarStateController: StatusBarStateController
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
102 private val mFalsingManager: FalsingManager
103 private var mPulsing: Boolean = false
104 var isWakingToShadeLocked: Boolean = false
105 private set
106 private var mEmptyDragAmount: Float = 0.0f
107 private var mWakeUpHeight: Float = 0.0f
108 private var mReachedWakeUpHeight: Boolean = false
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700109 private var velocityTracker: VelocityTracker? = null
Selim Cinek3d6ae232019-01-04 14:14:33 -0800110
111 private val isFalseTouch: Boolean
112 get() = mFalsingManager.isFalseTouch
Selim Cinekb0fada62019-06-17 19:03:59 -0700113 var qsExpanded: Boolean = false
114 var pulseExpandAbortListener: Runnable? = null
Selim Cinek820ba2d2019-06-18 18:59:09 -0700115 var bouncerShowing: Boolean = false
Selim Cinek3d6ae232019-01-04 14:14:33 -0800116
117 init {
118 mMinDragDistance = context.resources.getDimensionPixelSize(
119 R.dimen.keyguard_drag_down_min_distance)
120 mTouchSlop = ViewConfiguration.get(context).scaledTouchSlop.toFloat()
Dave Mankoff781ef7e2019-06-28 16:33:25 -0400121 mFalsingManager = Dependency.get(FalsingManager::class.java)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800122 mPowerManager = context.getSystemService(PowerManager::class.java)
123 }
124
125 override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
126 return maybeStartExpansion(event)
127 }
128
129 private fun maybeStartExpansion(event: MotionEvent): Boolean {
Dan Sandlerda6e1792019-08-14 13:52:54 -0400130 if (!wakeUpCoordinator.canShowPulsingHuns || qsExpanded ||
131 bouncerShowing) {
Selim Cinek3d6ae232019-01-04 14:14:33 -0800132 return false
133 }
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700134 if (velocityTracker == null) {
135 velocityTracker = VelocityTracker.obtain()
136 }
137 velocityTracker!!.addMovement(event)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800138 val x = event.x
139 val y = event.y
140
141 when (event.actionMasked) {
142 MotionEvent.ACTION_DOWN -> {
143 mDraggedFarEnough = false
144 isExpanding = false
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700145 leavingLockscreen = false
Selim Cinek3d6ae232019-01-04 14:14:33 -0800146 mStartingChild = null
147 mInitialTouchY = y
148 mInitialTouchX = x
149 }
150
151 MotionEvent.ACTION_MOVE -> {
152 val h = y - mInitialTouchY
153 if (h > mTouchSlop && h > Math.abs(x - mInitialTouchX)) {
154 mFalsingManager.onStartExpandingFromPulse()
155 isExpanding = true
156 captureStartingChild(mInitialTouchX, mInitialTouchY)
157 mInitialTouchY = y
158 mInitialTouchX = x
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700159 mWakeUpHeight = wakeUpCoordinator.getWakeUpHeight()
Selim Cinek3d6ae232019-01-04 14:14:33 -0800160 mReachedWakeUpHeight = false
161 return true
162 }
163 }
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700164
165 MotionEvent.ACTION_UP -> {
Dan Sandlerda6e1792019-08-14 13:52:54 -0400166 recycleVelocityTracker()
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700167 }
168
169 MotionEvent.ACTION_CANCEL -> {
Dan Sandlerda6e1792019-08-14 13:52:54 -0400170 recycleVelocityTracker()
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700171 }
Selim Cinek3d6ae232019-01-04 14:14:33 -0800172 }
173 return false
174 }
175
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700176 private fun recycleVelocityTracker() {
Dan Sandlerda6e1792019-08-14 13:52:54 -0400177 velocityTracker?.recycle()
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700178 velocityTracker = null
179 }
180
Selim Cinek3d6ae232019-01-04 14:14:33 -0800181 override fun onTouchEvent(event: MotionEvent): Boolean {
182 if (!isExpanding) {
183 return maybeStartExpansion(event)
184 }
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700185 velocityTracker!!.addMovement(event)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800186 val y = event.y
187
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700188 val moveDistance = y - mInitialTouchY
Selim Cinek3d6ae232019-01-04 14:14:33 -0800189 when (event.actionMasked) {
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700190 MotionEvent.ACTION_MOVE -> updateExpansionHeight(moveDistance)
191 MotionEvent.ACTION_UP -> {
192 velocityTracker!!.computeCurrentVelocity(1000 /* units */)
Lucas Dupine7ff7be2019-09-17 11:19:53 -0400193 val canExpand = moveDistance > 0 && velocityTracker!!.getYVelocity() > -1000 &&
194 statusBarStateController.state != StatusBarState.SHADE
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700195 if (!mFalsingManager.isUnlockingDisabled && !isFalseTouch && canExpand) {
196 finishExpansion()
197 } else {
198 cancelExpansion()
199 }
200 recycleVelocityTracker()
Selim Cinek3d6ae232019-01-04 14:14:33 -0800201 }
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700202 MotionEvent.ACTION_CANCEL -> {
203 cancelExpansion()
204 recycleVelocityTracker()
205 }
Selim Cinek3d6ae232019-01-04 14:14:33 -0800206 }
207 return isExpanding
208 }
209
210 private fun finishExpansion() {
211 resetClock()
212 if (mStartingChild != null) {
213 setUserLocked(mStartingChild!!, false)
214 mStartingChild = null
215 }
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700216 if (shadeController.isDozing) {
217 isWakingToShadeLocked = true
218 wakeUpCoordinator.willWakeUp = true
219 mPowerManager!!.wakeUp(SystemClock.uptimeMillis(), WAKE_REASON_GESTURE,
220 "com.android.systemui:PULSEDRAG")
221 }
222 shadeController.goToLockedShade(mStartingChild)
Dan Sandlerda6e1792019-08-14 13:52:54 -0400223 leavingLockscreen = true
Selim Cinek3d6ae232019-01-04 14:14:33 -0800224 isExpanding = false
Selim Cinek3d6ae232019-01-04 14:14:33 -0800225 if (mStartingChild is ExpandableNotificationRow) {
226 val row = mStartingChild as ExpandableNotificationRow?
227 row!!.onExpandedByGesture(true /* userExpanded */)
228 }
229 }
230
231 private fun updateExpansionHeight(height: Float) {
232 var expansionHeight = max(height, 0.0f)
233 if (!mReachedWakeUpHeight && height > mWakeUpHeight) {
Dan Sandlerda6e1792019-08-14 13:52:54 -0400234 mReachedWakeUpHeight = true
Selim Cinek3d6ae232019-01-04 14:14:33 -0800235 }
236 if (mStartingChild != null) {
237 val child = mStartingChild!!
238 val newHeight = Math.min((child.collapsedHeight + expansionHeight).toInt(),
239 child.maxContentHeight)
240 child.actualHeight = newHeight
241 expansionHeight = max(newHeight.toFloat(), expansionHeight)
242 } else {
Selim Cinek624d6ca2019-02-19 15:39:08 -0800243 val target = if (mReachedWakeUpHeight) mWakeUpHeight else 0.0f
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700244 wakeUpCoordinator.setNotificationsVisibleForExpansion(height > target,
Selim Cinek624d6ca2019-02-19 15:39:08 -0800245 true /* animate */,
246 true /* increaseSpeed */)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800247 expansionHeight = max(mWakeUpHeight, expansionHeight)
248 }
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700249 val emptyDragAmount = wakeUpCoordinator.setPulseHeight(expansionHeight)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800250 setEmptyDragAmount(emptyDragAmount * RUBBERBAND_FACTOR_STATIC)
251 }
252
253 private fun captureStartingChild(x: Float, y: Float) {
Selim Cinekc7e4cb52019-06-20 15:41:45 -0700254 if (mStartingChild == null && !bypassController.bypassEnabled) {
Selim Cinek3d6ae232019-01-04 14:14:33 -0800255 mStartingChild = findView(x, y)
256 if (mStartingChild != null) {
257 setUserLocked(mStartingChild!!, true)
258 }
259 }
260 }
261
262 private fun setEmptyDragAmount(amount: Float) {
263 mEmptyDragAmount = amount
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700264 expansionCallback.setEmptyDragAmount(amount)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800265 }
266
267 private fun reset(child: ExpandableView) {
268 if (child.actualHeight == child.collapsedHeight) {
269 setUserLocked(child, false)
270 return
271 }
272 val anim = ObjectAnimator.ofInt(child, "actualHeight",
273 child.actualHeight, child.collapsedHeight)
274 anim.interpolator = Interpolators.FAST_OUT_SLOW_IN
275 anim.duration = SPRING_BACK_ANIMATION_LENGTH_MS.toLong()
276 anim.addListener(object : AnimatorListenerAdapter() {
277 override fun onAnimationEnd(animation: Animator) {
278 setUserLocked(child, false)
279 }
280 })
281 anim.start()
282 }
283
284 private fun setUserLocked(child: ExpandableView, userLocked: Boolean) {
285 if (child is ExpandableNotificationRow) {
286 child.isUserLocked = userLocked
287 }
288 }
289
290 private fun resetClock() {
291 val anim = ValueAnimator.ofFloat(mEmptyDragAmount, 0f)
292 anim.interpolator = Interpolators.FAST_OUT_SLOW_IN
293 anim.duration = SPRING_BACK_ANIMATION_LENGTH_MS.toLong()
294 anim.addUpdateListener { animation -> setEmptyDragAmount(animation.animatedValue as Float) }
295 anim.start()
296 }
297
298 private fun cancelExpansion() {
Selim Cinekb0fada62019-06-17 19:03:59 -0700299 isExpanding = false
Selim Cinek3d6ae232019-01-04 14:14:33 -0800300 mFalsingManager.onExpansionFromPulseStopped()
301 if (mStartingChild != null) {
302 reset(mStartingChild!!)
303 mStartingChild = null
304 } else {
305 resetClock()
306 }
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700307 wakeUpCoordinator.setNotificationsVisibleForExpansion(false /* visible */,
Selim Cinek624d6ca2019-02-19 15:39:08 -0800308 true /* animate */,
309 false /* increaseSpeed */)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800310 }
311
312 private fun findView(x: Float, y: Float): ExpandableView? {
313 var totalX = x
314 var totalY = y
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700315 stackScroller.getLocationOnScreen(mTemp2)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800316 totalX += mTemp2[0].toFloat()
317 totalY += mTemp2[1].toFloat()
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700318 val childAtRawPosition = stackScroller.getChildAtRawPosition(totalX, totalY)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800319 return if (childAtRawPosition != null && childAtRawPosition.isContentExpandable) {
320 childAtRawPosition
321 } else null
322 }
323
Dan Sandlerda6e1792019-08-14 13:52:54 -0400324 fun setUp(
325 stackScroller: NotificationStackScrollLayout,
326 expansionCallback: ExpansionCallback,
327 shadeController: ShadeController
328 ) {
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700329 this.expansionCallback = expansionCallback
330 this.shadeController = shadeController
331 this.stackScroller = stackScroller
Selim Cinek3d6ae232019-01-04 14:14:33 -0800332 }
333
334 fun setPulsing(pulsing: Boolean) {
335 mPulsing = pulsing
Selim Cinek3d6ae232019-01-04 14:14:33 -0800336 }
337
338 fun onStartedWakingUp() {
339 isWakingToShadeLocked = false
340 }
341
342 interface ExpansionCallback {
343 fun setEmptyDragAmount(amount: Float)
344 }
Selim Cinek3d6ae232019-01-04 14:14:33 -0800345}