• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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