Home | History | Annotate | Download | only in statusbar
      1 /*
      2  * 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