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