blob: 986486ac14d4860ec585616875dbd6fdd6338d6f [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 Mankoffdde5ee62019-05-02 17:36:11 -040034import com.android.systemui.classifier.FalsingManagerFactory
Dave Mankoff468d4f62019-05-08 14:56:29 -040035import com.android.systemui.plugins.FalsingManager
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
Selim Cinekd21232e2019-06-20 14:15:59 -070044import com.android.systemui.statusbar.policy.HeadsUpManager
Selim Cinek3d6ae232019-01-04 14:14:33 -080045
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
55constructor(context: Context,
Selim Cinekb8cc6ef2019-06-14 16:37:53 -070056 private val wakeUpCoordinator: NotificationWakeUpCoordinator,
Selim Cinekd21232e2019-06-20 14:15:59 -070057 private val bypassController: KeyguardBypassController,
Selim Cinekc7e4cb52019-06-20 15:41:45 -070058 private val headsUpManager: HeadsUpManagerPhone,
59 private val roundnessManager: NotificationRoundnessManager) : Gefingerpoken {
Selim Cinek34518f62019-02-28 19:41:18 -080060 companion object {
61 private val RUBBERBAND_FACTOR_STATIC = 0.25f
62 private val SPRING_BACK_ANIMATION_LENGTH_MS = 375
63 }
Selim Cinek3d6ae232019-01-04 14:14:33 -080064 private val mPowerManager: PowerManager?
Selim Cinekb8cc6ef2019-06-14 16:37:53 -070065 private lateinit var shadeController: ShadeController
Selim Cinek3d6ae232019-01-04 14:14:33 -080066
67 private val mMinDragDistance: Int
68 private var mInitialTouchX: Float = 0.0f
69 private var mInitialTouchY: Float = 0.0f
Selim Cinekb0fada62019-06-17 19:03:59 -070070 var isExpanding: Boolean = false
Selim Cinekb8cc6ef2019-06-14 16:37:53 -070071 private set(value) {
72 val changed = field != value
73 field = value
74 bypassController.isPulseExpanding = value
Selim Cinekd21232e2019-06-20 14:15:59 -070075 if (changed) {
Selim Cinekc7e4cb52019-06-20 15:41:45 -070076 if (value) {
77 val topEntry = headsUpManager.topEntry
78 topEntry?.let {
79 roundnessManager.setTrackingHeadsUp(it.row)
80 }
81 } else {
82 roundnessManager.setTrackingHeadsUp(null)
83 if (!leavingLockscreen) {
84 bypassController.maybePerformPendingUnlock()
85 pulseExpandAbortListener?.run()
86 }
Selim Cinekd21232e2019-06-20 14:15:59 -070087 }
Selim Cinekc7e4cb52019-06-20 15:41:45 -070088 headsUpManager.unpinAll(true /* userUnPinned */)
Selim Cinekb8cc6ef2019-06-14 16:37:53 -070089 }
90 }
Selim Cinekb0fada62019-06-17 19:03:59 -070091 var leavingLockscreen: Boolean = false
92 private set
Selim Cinek3d6ae232019-01-04 14:14:33 -080093 private val mTouchSlop: Float
Selim Cinekb8cc6ef2019-06-14 16:37:53 -070094 private lateinit var expansionCallback: ExpansionCallback
95 private lateinit var stackScroller: NotificationStackScrollLayout
Selim Cinek3d6ae232019-01-04 14:14:33 -080096 private val mTemp2 = IntArray(2)
97 private var mDraggedFarEnough: Boolean = false
98 private var mStartingChild: ExpandableView? = null
99 private val mFalsingManager: FalsingManager
100 private var mPulsing: Boolean = false
101 var isWakingToShadeLocked: Boolean = false
102 private set
103 private var mEmptyDragAmount: Float = 0.0f
104 private var mWakeUpHeight: Float = 0.0f
105 private var mReachedWakeUpHeight: Boolean = false
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700106 private var velocityTracker: VelocityTracker? = null
Selim Cinek3d6ae232019-01-04 14:14:33 -0800107
108 private val isFalseTouch: Boolean
109 get() = mFalsingManager.isFalseTouch
Selim Cinekb0fada62019-06-17 19:03:59 -0700110 var qsExpanded: Boolean = false
111 var pulseExpandAbortListener: Runnable? = null
Selim Cinek820ba2d2019-06-18 18:59:09 -0700112 var bouncerShowing: Boolean = false
Selim Cinek3d6ae232019-01-04 14:14:33 -0800113
114 init {
115 mMinDragDistance = context.resources.getDimensionPixelSize(
116 R.dimen.keyguard_drag_down_min_distance)
117 mTouchSlop = ViewConfiguration.get(context).scaledTouchSlop.toFloat()
Dave Mankoffdde5ee62019-05-02 17:36:11 -0400118 mFalsingManager = FalsingManagerFactory.getInstance(context)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800119 mPowerManager = context.getSystemService(PowerManager::class.java)
120 }
121
122 override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
123 return maybeStartExpansion(event)
124 }
125
126 private fun maybeStartExpansion(event: MotionEvent): Boolean {
Selim Cinek820ba2d2019-06-18 18:59:09 -0700127 if (!wakeUpCoordinator.canShowPulsingHuns || qsExpanded
128 || bouncerShowing) {
Selim Cinek3d6ae232019-01-04 14:14:33 -0800129 return false
130 }
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700131 if (velocityTracker == null) {
132 velocityTracker = VelocityTracker.obtain()
133 }
134 velocityTracker!!.addMovement(event)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800135 val x = event.x
136 val y = event.y
137
138 when (event.actionMasked) {
139 MotionEvent.ACTION_DOWN -> {
140 mDraggedFarEnough = false
141 isExpanding = false
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700142 leavingLockscreen = false
Selim Cinek3d6ae232019-01-04 14:14:33 -0800143 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 mFalsingManager.onStartExpandingFromPulse()
152 isExpanding = true
153 captureStartingChild(mInitialTouchX, mInitialTouchY)
154 mInitialTouchY = y
155 mInitialTouchX = x
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700156 mWakeUpHeight = wakeUpCoordinator.getWakeUpHeight()
Selim Cinek3d6ae232019-01-04 14:14:33 -0800157 mReachedWakeUpHeight = false
158 return true
159 }
160 }
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700161
162 MotionEvent.ACTION_UP -> {
163 recycleVelocityTracker();
164 }
165
166 MotionEvent.ACTION_CANCEL -> {
167 recycleVelocityTracker();
168 }
Selim Cinek3d6ae232019-01-04 14:14:33 -0800169 }
170 return false
171 }
172
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700173 private fun recycleVelocityTracker() {
174 velocityTracker?.recycle();
175 velocityTracker = null
176 }
177
Selim Cinek3d6ae232019-01-04 14:14:33 -0800178 override fun onTouchEvent(event: MotionEvent): Boolean {
179 if (!isExpanding) {
180 return maybeStartExpansion(event)
181 }
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700182 velocityTracker!!.addMovement(event)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800183 val y = event.y
184
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700185 val moveDistance = y - mInitialTouchY
Selim Cinek3d6ae232019-01-04 14:14:33 -0800186 when (event.actionMasked) {
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700187 MotionEvent.ACTION_MOVE -> updateExpansionHeight(moveDistance)
188 MotionEvent.ACTION_UP -> {
189 velocityTracker!!.computeCurrentVelocity(1000 /* units */)
190 val canExpand = moveDistance > 0 && velocityTracker!!.getYVelocity() > -1000
191 if (!mFalsingManager.isUnlockingDisabled && !isFalseTouch && canExpand) {
192 finishExpansion()
193 } else {
194 cancelExpansion()
195 }
196 recycleVelocityTracker()
Selim Cinek3d6ae232019-01-04 14:14:33 -0800197 }
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700198 MotionEvent.ACTION_CANCEL -> {
199 cancelExpansion()
200 recycleVelocityTracker()
201 }
Selim Cinek3d6ae232019-01-04 14:14:33 -0800202 }
203 return isExpanding
204 }
205
206 private fun finishExpansion() {
207 resetClock()
208 if (mStartingChild != null) {
209 setUserLocked(mStartingChild!!, false)
210 mStartingChild = null
211 }
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700212 if (shadeController.isDozing) {
213 isWakingToShadeLocked = true
214 wakeUpCoordinator.willWakeUp = true
215 mPowerManager!!.wakeUp(SystemClock.uptimeMillis(), WAKE_REASON_GESTURE,
216 "com.android.systemui:PULSEDRAG")
217 }
218 shadeController.goToLockedShade(mStartingChild)
219 leavingLockscreen = true;
Selim Cinek3d6ae232019-01-04 14:14:33 -0800220 isExpanding = false
Selim Cinek3d6ae232019-01-04 14:14:33 -0800221 if (mStartingChild is ExpandableNotificationRow) {
222 val row = mStartingChild as ExpandableNotificationRow?
223 row!!.onExpandedByGesture(true /* userExpanded */)
224 }
225 }
226
227 private fun updateExpansionHeight(height: Float) {
228 var expansionHeight = max(height, 0.0f)
229 if (!mReachedWakeUpHeight && height > mWakeUpHeight) {
230 mReachedWakeUpHeight = true;
231 }
232 if (mStartingChild != null) {
233 val child = mStartingChild!!
234 val newHeight = Math.min((child.collapsedHeight + expansionHeight).toInt(),
235 child.maxContentHeight)
236 child.actualHeight = newHeight
237 expansionHeight = max(newHeight.toFloat(), expansionHeight)
238 } else {
Selim Cinek624d6ca2019-02-19 15:39:08 -0800239 val target = if (mReachedWakeUpHeight) mWakeUpHeight else 0.0f
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700240 wakeUpCoordinator.setNotificationsVisibleForExpansion(height > target,
Selim Cinek624d6ca2019-02-19 15:39:08 -0800241 true /* animate */,
242 true /* increaseSpeed */)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800243 expansionHeight = max(mWakeUpHeight, expansionHeight)
244 }
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700245 val emptyDragAmount = wakeUpCoordinator.setPulseHeight(expansionHeight)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800246 setEmptyDragAmount(emptyDragAmount * RUBBERBAND_FACTOR_STATIC)
247 }
248
249 private fun captureStartingChild(x: Float, y: Float) {
Selim Cinekc7e4cb52019-06-20 15:41:45 -0700250 if (mStartingChild == null && !bypassController.bypassEnabled) {
Selim Cinek3d6ae232019-01-04 14:14:33 -0800251 mStartingChild = findView(x, y)
252 if (mStartingChild != null) {
253 setUserLocked(mStartingChild!!, true)
254 }
255 }
256 }
257
258 private fun setEmptyDragAmount(amount: Float) {
259 mEmptyDragAmount = amount
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700260 expansionCallback.setEmptyDragAmount(amount)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800261 }
262
263 private fun reset(child: ExpandableView) {
264 if (child.actualHeight == child.collapsedHeight) {
265 setUserLocked(child, false)
266 return
267 }
268 val anim = ObjectAnimator.ofInt(child, "actualHeight",
269 child.actualHeight, child.collapsedHeight)
270 anim.interpolator = Interpolators.FAST_OUT_SLOW_IN
271 anim.duration = SPRING_BACK_ANIMATION_LENGTH_MS.toLong()
272 anim.addListener(object : AnimatorListenerAdapter() {
273 override fun onAnimationEnd(animation: Animator) {
274 setUserLocked(child, false)
275 }
276 })
277 anim.start()
278 }
279
280 private fun setUserLocked(child: ExpandableView, userLocked: Boolean) {
281 if (child is ExpandableNotificationRow) {
282 child.isUserLocked = userLocked
283 }
284 }
285
286 private fun resetClock() {
287 val anim = ValueAnimator.ofFloat(mEmptyDragAmount, 0f)
288 anim.interpolator = Interpolators.FAST_OUT_SLOW_IN
289 anim.duration = SPRING_BACK_ANIMATION_LENGTH_MS.toLong()
290 anim.addUpdateListener { animation -> setEmptyDragAmount(animation.animatedValue as Float) }
291 anim.start()
292 }
293
294 private fun cancelExpansion() {
Selim Cinekb0fada62019-06-17 19:03:59 -0700295 isExpanding = false
Selim Cinek3d6ae232019-01-04 14:14:33 -0800296 mFalsingManager.onExpansionFromPulseStopped()
297 if (mStartingChild != null) {
298 reset(mStartingChild!!)
299 mStartingChild = null
300 } else {
301 resetClock()
302 }
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700303 wakeUpCoordinator.setNotificationsVisibleForExpansion(false /* visible */,
Selim Cinek624d6ca2019-02-19 15:39:08 -0800304 true /* animate */,
305 false /* increaseSpeed */)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800306 }
307
308 private fun findView(x: Float, y: Float): ExpandableView? {
309 var totalX = x
310 var totalY = y
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700311 stackScroller.getLocationOnScreen(mTemp2)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800312 totalX += mTemp2[0].toFloat()
313 totalY += mTemp2[1].toFloat()
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700314 val childAtRawPosition = stackScroller.getChildAtRawPosition(totalX, totalY)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800315 return if (childAtRawPosition != null && childAtRawPosition.isContentExpandable) {
316 childAtRawPosition
317 } else null
318 }
319
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700320 fun setUp(stackScroller: NotificationStackScrollLayout,
321 expansionCallback: ExpansionCallback,
322 shadeController: ShadeController) {
323 this.expansionCallback = expansionCallback
324 this.shadeController = shadeController
325 this.stackScroller = stackScroller
Selim Cinek3d6ae232019-01-04 14:14:33 -0800326 }
327
328 fun setPulsing(pulsing: Boolean) {
329 mPulsing = pulsing
Selim Cinek3d6ae232019-01-04 14:14:33 -0800330 }
331
332 fun onStartedWakingUp() {
333 isWakingToShadeLocked = false
334 }
335
336 interface ExpansionCallback {
337 fun setEmptyDragAmount(amount: Float)
338 }
Selim Cinek3d6ae232019-01-04 14:14:33 -0800339}