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.ViewConfiguration 29 30 import com.android.systemui.Gefingerpoken 31 import com.android.systemui.Interpolators 32 import com.android.systemui.R 33 import com.android.systemui.classifier.FalsingManagerFactory 34 import com.android.systemui.plugins.FalsingManager 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.NotificationStackScrollLayout 39 import com.android.systemui.statusbar.phone.ShadeController 40 41 import javax.inject.Inject 42 import javax.inject.Singleton 43 import kotlin.math.max 44 45 /** 46 * A utility class to enable the downward swipe on when pulsing. 47 */ 48 @Singleton 49 class PulseExpansionHandler @Inject 50 constructor(context: Context, 51 private val mWakeUpCoordinator: NotificationWakeUpCoordinator) : Gefingerpoken { 52 companion object { 53 private val RUBBERBAND_FACTOR_STATIC = 0.25f 54 private val SPRING_BACK_ANIMATION_LENGTH_MS = 375 55 } 56 private val mPowerManager: PowerManager? 57 private var mShadeController: ShadeController? = null 58 59 private val mMinDragDistance: Int 60 private var mInitialTouchX: Float = 0.0f 61 private var mInitialTouchY: Float = 0.0f 62 var isExpanding: Boolean = false 63 private set 64 private val mTouchSlop: Float 65 private var mExpansionCallback: ExpansionCallback? = null 66 private lateinit var mStackScroller: NotificationStackScrollLayout 67 private val mTemp2 = IntArray(2) 68 private var mDraggedFarEnough: Boolean = false 69 private var mStartingChild: ExpandableView? = null 70 private val mFalsingManager: FalsingManager 71 private var mPulsing: Boolean = false 72 var isWakingToShadeLocked: Boolean = false 73 private set 74 private var mEmptyDragAmount: Float = 0.0f 75 private var mWakeUpHeight: Float = 0.0f 76 private var mReachedWakeUpHeight: Boolean = false 77 78 private val isFalseTouch: Boolean 79 get() = mFalsingManager.isFalseTouch 80 81 init { 82 mMinDragDistance = context.resources.getDimensionPixelSize( 83 R.dimen.keyguard_drag_down_min_distance) 84 mTouchSlop = ViewConfiguration.get(context).scaledTouchSlop.toFloat() 85 mFalsingManager = FalsingManagerFactory.getInstance(context) 86 mPowerManager = context.getSystemService(PowerManager::class.java) 87 } 88 89 override fun onInterceptTouchEvent(event: MotionEvent): Boolean { 90 return maybeStartExpansion(event) 91 } 92 93 private fun maybeStartExpansion(event: MotionEvent): Boolean { 94 if (!mPulsing) { 95 return false 96 } 97 val x = event.x 98 val y = event.y 99 100 when (event.actionMasked) { 101 MotionEvent.ACTION_DOWN -> { 102 mDraggedFarEnough = false 103 isExpanding = false 104 mStartingChild = null 105 mInitialTouchY = y 106 mInitialTouchX = x 107 } 108 109 MotionEvent.ACTION_MOVE -> { 110 val h = y - mInitialTouchY 111 if (h > mTouchSlop && h > Math.abs(x - mInitialTouchX)) { 112 mFalsingManager.onStartExpandingFromPulse() 113 isExpanding = true 114 captureStartingChild(mInitialTouchX, mInitialTouchY) 115 mInitialTouchY = y 116 mInitialTouchX = x 117 mWakeUpHeight = mWakeUpCoordinator.getWakeUpHeight() 118 mReachedWakeUpHeight = false 119 return true 120 } 121 } 122 } 123 return false 124 } 125 126 override fun onTouchEvent(event: MotionEvent): Boolean { 127 if (!isExpanding) { 128 return maybeStartExpansion(event) 129 } 130 val y = event.y 131 132 when (event.actionMasked) { 133 MotionEvent.ACTION_MOVE -> updateExpansionHeight(y - mInitialTouchY) 134 MotionEvent.ACTION_UP -> if (!mFalsingManager.isUnlockingDisabled && !isFalseTouch) { 135 finishExpansion() 136 } else { 137 cancelExpansion() 138 } 139 MotionEvent.ACTION_CANCEL -> cancelExpansion() 140 } 141 return isExpanding 142 } 143 144 private fun finishExpansion() { 145 resetClock() 146 if (mStartingChild != null) { 147 setUserLocked(mStartingChild!!, false) 148 mStartingChild = null 149 } 150 isExpanding = false 151 isWakingToShadeLocked = true 152 mWakeUpCoordinator.willWakeUp = true 153 mPowerManager!!.wakeUp(SystemClock.uptimeMillis(), WAKE_REASON_GESTURE, 154 "com.android.systemui:PULSEDRAG") 155 mShadeController!!.goToLockedShade(mStartingChild) 156 if (mStartingChild is ExpandableNotificationRow) { 157 val row = mStartingChild as ExpandableNotificationRow? 158 row!!.onExpandedByGesture(true /* userExpanded */) 159 } 160 } 161 162 private fun updateExpansionHeight(height: Float) { 163 var expansionHeight = max(height, 0.0f) 164 if (!mReachedWakeUpHeight && height > mWakeUpHeight) { 165 mReachedWakeUpHeight = true; 166 } 167 if (mStartingChild != null) { 168 val child = mStartingChild!! 169 val newHeight = Math.min((child.collapsedHeight + expansionHeight).toInt(), 170 child.maxContentHeight) 171 child.actualHeight = newHeight 172 expansionHeight = max(newHeight.toFloat(), expansionHeight) 173 } else { 174 val target = if (mReachedWakeUpHeight) mWakeUpHeight else 0.0f 175 mWakeUpCoordinator.setNotificationsVisibleForExpansion(height > target, 176 true /* animate */, 177 true /* increaseSpeed */) 178 expansionHeight = max(mWakeUpHeight, expansionHeight) 179 } 180 val emptyDragAmount = mWakeUpCoordinator.setPulseHeight(expansionHeight) 181 setEmptyDragAmount(emptyDragAmount * RUBBERBAND_FACTOR_STATIC) 182 } 183 184 private fun captureStartingChild(x: Float, y: Float) { 185 if (mStartingChild == null) { 186 mStartingChild = findView(x, y) 187 if (mStartingChild != null) { 188 setUserLocked(mStartingChild!!, true) 189 } 190 } 191 } 192 193 private fun setEmptyDragAmount(amount: Float) { 194 mEmptyDragAmount = amount 195 mExpansionCallback!!.setEmptyDragAmount(amount) 196 } 197 198 private fun reset(child: ExpandableView) { 199 if (child.actualHeight == child.collapsedHeight) { 200 setUserLocked(child, false) 201 return 202 } 203 val anim = ObjectAnimator.ofInt(child, "actualHeight", 204 child.actualHeight, child.collapsedHeight) 205 anim.interpolator = Interpolators.FAST_OUT_SLOW_IN 206 anim.duration = SPRING_BACK_ANIMATION_LENGTH_MS.toLong() 207 anim.addListener(object : AnimatorListenerAdapter() { 208 override fun onAnimationEnd(animation: Animator) { 209 setUserLocked(child, false) 210 } 211 }) 212 anim.start() 213 } 214 215 private fun setUserLocked(child: ExpandableView, userLocked: Boolean) { 216 if (child is ExpandableNotificationRow) { 217 child.isUserLocked = userLocked 218 } 219 } 220 221 private fun resetClock() { 222 val anim = ValueAnimator.ofFloat(mEmptyDragAmount, 0f) 223 anim.interpolator = Interpolators.FAST_OUT_SLOW_IN 224 anim.duration = SPRING_BACK_ANIMATION_LENGTH_MS.toLong() 225 anim.addUpdateListener { animation -> setEmptyDragAmount(animation.animatedValue as Float) } 226 anim.start() 227 } 228 229 private fun cancelExpansion() { 230 mFalsingManager.onExpansionFromPulseStopped() 231 if (mStartingChild != null) { 232 reset(mStartingChild!!) 233 mStartingChild = null 234 } else { 235 resetClock() 236 } 237 mWakeUpCoordinator.setNotificationsVisibleForExpansion(false /* visible */, 238 true /* animate */, 239 false /* increaseSpeed */) 240 isExpanding = false 241 } 242 243 private fun findView(x: Float, y: Float): ExpandableView? { 244 var totalX = x 245 var totalY = y 246 mStackScroller.getLocationOnScreen(mTemp2) 247 totalX += mTemp2[0].toFloat() 248 totalY += mTemp2[1].toFloat() 249 val childAtRawPosition = mStackScroller.getChildAtRawPosition(totalX, totalY) 250 return if (childAtRawPosition != null && childAtRawPosition.isContentExpandable) { 251 childAtRawPosition 252 } else null 253 } 254 255 fun setUp(notificationStackScroller: NotificationStackScrollLayout, 256 expansionCallback: ExpansionCallback, 257 shadeController: ShadeController) { 258 mExpansionCallback = expansionCallback 259 mShadeController = shadeController 260 mStackScroller = notificationStackScroller 261 } 262 263 fun setPulsing(pulsing: Boolean) { 264 mPulsing = pulsing 265 } 266 267 fun onStartedWakingUp() { 268 isWakingToShadeLocked = false 269 } 270 271 interface ExpansionCallback { 272 fun setEmptyDragAmount(amount: Float) 273 } 274 } 275