/*
|
* 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)
|
}
|
}
|