blob: 3613f9f3ea5f95336e252d62166db0dfb91f0b0b [file] [log] [blame]
/*
* 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.notification
import android.animation.ObjectAnimator
import android.content.Context
import android.util.FloatProperty
import com.android.systemui.Interpolators
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.AmbientPulseManager
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.KeyguardBypassController
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class NotificationWakeUpCoordinator @Inject constructor(
private val mContext: Context,
private val mAmbientPulseManager: AmbientPulseManager,
private val mStatusBarStateController: StatusBarStateController,
private val mBypassController: KeyguardBypassController)
: AmbientPulseManager.OnAmbientChangedListener, StatusBarStateController.StateListener {
private val mNotificationVisibility
= object : FloatProperty<NotificationWakeUpCoordinator>("notificationVisibility") {
override fun setValue(coordinator: NotificationWakeUpCoordinator, value: Float) {
coordinator.setVisibilityAmount(value)
}
override fun get(coordinator: NotificationWakeUpCoordinator): Float? {
return coordinator.mLinearVisibilityAmount
}
}
private lateinit var mStackScroller: NotificationStackScrollLayout
private var mVisibilityInterpolator = Interpolators.FAST_OUT_SLOW_IN_REVERSE
private var mLinearDozeAmount: Float = 0.0f
private var mDozeAmount: Float = 0.0f
private var mNotificationVisibleAmount = 0.0f
private var mNotificationsVisible = false
private var mNotificationsVisibleForExpansion = false
private var mDarkAnimator: ObjectAnimator? = null
private var mVisibilityAmount = 0.0f
private var mLinearVisibilityAmount = 0.0f
private var mWakingUp = false
private val mEntrySetToClearWhenFinished = mutableSetOf<NotificationEntry>()
private val mDozeParameters: DozeParameters;
var willWakeUp = false
set(value) {
if (!value || mDozeAmount != 0.0f) {
field = value
}
}
var pulsing: Boolean = false
set(value) {
field = value
if (value) {
// Only when setting pulsing to true we want an immediate update, since we get
// this already when the doze service finishes which is usually before we get
// the waking up callback
updateNotificationVisibility(animate = shouldAnimateVisibility(),
increaseSpeed = false)
}
}
init {
mAmbientPulseManager.addListener(this)
mStatusBarStateController.addCallback(this)
mDozeParameters = DozeParameters.getInstance(mContext)
}
fun setStackScroller(stackScroller: NotificationStackScrollLayout) {
mStackScroller = stackScroller
}
/**
* @param visible should notifications be visible
* @param animate should this change be animated
* @param increaseSpeed should the speed be increased of the animation
*/
fun setNotificationsVisibleForExpansion(visible: Boolean, animate: Boolean,
increaseSpeed: Boolean) {
mNotificationsVisibleForExpansion = visible
updateNotificationVisibility(animate, increaseSpeed)
if (!visible && mNotificationsVisible) {
// If we stopped expanding and we're still visible because we had a pulse that hasn't
// times out, let's release them all to make sure were not stuck in a state where
// notifications are visible
mAmbientPulseManager.releaseAllImmediately()
}
}
private fun updateNotificationVisibility(animate: Boolean, increaseSpeed: Boolean) {
var visible = (mNotificationsVisibleForExpansion || mAmbientPulseManager.hasNotifications())
&& pulsing;
if (!visible && mNotificationsVisible && (mWakingUp || willWakeUp) && mDozeAmount != 0.0f) {
// let's not make notifications invisible while waking up, otherwise the animation
// is strange
return;
}
setNotificationsVisible(visible, animate, increaseSpeed)
}
private fun setNotificationsVisible(visible: Boolean, animate: Boolean,
increaseSpeed: Boolean) {
if (mNotificationsVisible == visible) {
return
}
mNotificationsVisible = visible
mDarkAnimator?.cancel();
if (animate) {
notifyAnimationStart(visible)
startVisibilityAnimation(increaseSpeed)
} else {
setVisibilityAmount(if (visible) 1.0f else 0.0f)
}
}
override fun onDozeAmountChanged(linear: Float, eased: Float) {
if (updateDozeAmountIfBypass()) {
return
}
if (linear != 1.0f && linear != 0.0f
&& (mLinearDozeAmount == 0.0f || mLinearDozeAmount == 1.0f)) {
// Let's notify the scroller that an animation started
notifyAnimationStart(mLinearDozeAmount == 1.0f)
}
setDozeAmount(linear, eased)
}
fun setDozeAmount(linear: Float, eased: Float) {
val changed = linear != mLinearDozeAmount
mLinearDozeAmount = linear
mDozeAmount = eased
mStackScroller.setDozeAmount(mDozeAmount)
updateDarkAmount()
if (changed && linear == 0.0f) {
setNotificationsVisible(visible = false, animate = false, increaseSpeed = false);
setNotificationsVisibleForExpansion(visible = false, animate = false,
increaseSpeed = false)
}
}
override fun onStateChanged(newState: Int) {
updateDozeAmountIfBypass();
}
private fun updateDozeAmountIfBypass(): Boolean {
if (mBypassController.bypassEnabled) {
var amount = 1.0f;
if (mStatusBarStateController.state == StatusBarState.SHADE
|| mStatusBarStateController.state == StatusBarState.SHADE_LOCKED) {
amount = 0.0f;
}
setDozeAmount(amount, amount)
return true
}
return false
}
private fun startVisibilityAnimation(increaseSpeed: Boolean) {
if (mNotificationVisibleAmount == 0f || mNotificationVisibleAmount == 1f) {
mVisibilityInterpolator = if (mNotificationsVisible)
Interpolators.TOUCH_RESPONSE
else
Interpolators.FAST_OUT_SLOW_IN_REVERSE
}
val target = if (mNotificationsVisible) 1.0f else 0.0f
val darkAnimator = ObjectAnimator.ofFloat(this, mNotificationVisibility, target)
darkAnimator.setInterpolator(Interpolators.LINEAR)
var duration = StackStateAnimator.ANIMATION_DURATION_WAKEUP.toLong()
if (increaseSpeed) {
duration = (duration.toFloat() / 1.5F).toLong();
}
darkAnimator.setDuration(duration)
darkAnimator.start()
mDarkAnimator = darkAnimator
}
private fun setVisibilityAmount(visibilityAmount: Float) {
mLinearVisibilityAmount = visibilityAmount
mVisibilityAmount = mVisibilityInterpolator.getInterpolation(
visibilityAmount)
handleAnimationFinished();
updateDarkAmount()
}
private fun handleAnimationFinished() {
if (mLinearDozeAmount == 0.0f || mLinearVisibilityAmount == 0.0f) {
mEntrySetToClearWhenFinished.forEach { it.setAmbientGoingAway(false) }
mEntrySetToClearWhenFinished.clear()
}
}
fun getWakeUpHeight() : Float {
return mStackScroller.pulseHeight
}
private fun updateDarkAmount() {
val linearAmount = Math.min(1.0f - mLinearVisibilityAmount, mLinearDozeAmount)
val amount = Math.min(1.0f - mVisibilityAmount, mDozeAmount)
mStackScroller.setDarkAmount(linearAmount, amount)
}
private fun notifyAnimationStart(awake: Boolean) {
mStackScroller.notifyDarkAnimationStart(!awake)
}
override fun onDozingChanged(isDozing: Boolean) {
if (isDozing) {
setNotificationsVisible(visible = false, animate = false, increaseSpeed = false)
}
}
fun setPulseHeight(height: Float): Float {
return mStackScroller.setPulseHeight(height)
}
fun setWakingUp(wakingUp: Boolean) {
willWakeUp = false
mWakingUp = wakingUp
if (wakingUp && mNotificationsVisible && !mNotificationsVisibleForExpansion) {
// We're waking up while pulsing, let's make sure the animation looks nice
mStackScroller.wakeUpFromPulse();
}
}
override fun onAmbientStateChanged(entry: NotificationEntry, isPulsing: Boolean) {
var animate = shouldAnimateVisibility()
if (!isPulsing) {
if (mLinearDozeAmount != 0.0f && mLinearVisibilityAmount != 0.0f) {
if (entry.isRowDismissed) {
// if we animate, we see the shelf briefly visible. Instead we fully animate
// the notification and its background out
animate = false
} else if (!mWakingUp && !willWakeUp){
entry.setAmbientGoingAway(true)
mEntrySetToClearWhenFinished.add(entry)
}
}
} else if (mEntrySetToClearWhenFinished.contains(entry)) {
mEntrySetToClearWhenFinished.remove(entry)
entry.setAmbientGoingAway(false)
}
updateNotificationVisibility(animate, increaseSpeed = false)
}
private fun shouldAnimateVisibility() =
mDozeParameters.getAlwaysOn() && !mDozeParameters.getDisplayNeedsBlanking()
}