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