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.notification 18 19 import android.animation.ObjectAnimator 20 import android.content.Context 21 import android.util.FloatProperty 22 import com.android.systemui.Interpolators 23 import com.android.systemui.plugins.statusbar.StatusBarStateController 24 import com.android.systemui.statusbar.AmbientPulseManager 25 import com.android.systemui.statusbar.SysuiStatusBarStateController 26 import com.android.systemui.statusbar.notification.collection.NotificationEntry 27 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout 28 import com.android.systemui.statusbar.notification.stack.StackStateAnimator 29 import com.android.systemui.statusbar.phone.DozeParameters 30 31 import javax.inject.Inject 32 import javax.inject.Singleton 33 34 @Singleton 35 class NotificationWakeUpCoordinator @Inject constructor( 36 private val mContext: Context, 37 private val mAmbientPulseManager: AmbientPulseManager, 38 private val mStatusBarStateController: StatusBarStateController) 39 : AmbientPulseManager.OnAmbientChangedListener, StatusBarStateController.StateListener { 40 41 private val mNotificationVisibility 42 = object : FloatProperty<NotificationWakeUpCoordinator>("notificationVisibility") { 43 setValuenull44 override fun setValue(coordinator: NotificationWakeUpCoordinator, value: Float) { 45 coordinator.setVisibilityAmount(value) 46 } 47 getnull48 override fun get(coordinator: NotificationWakeUpCoordinator): Float? { 49 return coordinator.mLinearVisibilityAmount 50 } 51 } 52 private lateinit var mStackScroller: NotificationStackScrollLayout 53 private var mVisibilityInterpolator = Interpolators.FAST_OUT_SLOW_IN_REVERSE 54 55 private var mLinearDozeAmount: Float = 0.0f 56 private var mDozeAmount: Float = 0.0f 57 private var mNotificationVisibleAmount = 0.0f 58 private var mNotificationsVisible = false 59 private var mNotificationsVisibleForExpansion = false 60 private var mDarkAnimator: ObjectAnimator? = null 61 private var mVisibilityAmount = 0.0f 62 private var mLinearVisibilityAmount = 0.0f 63 private var mWakingUp = false 64 private val mEntrySetToClearWhenFinished = mutableSetOf<NotificationEntry>() 65 private val mDozeParameters: DozeParameters; 66 var willWakeUp = false 67 set(value) { 68 if (!value || mDozeAmount != 0.0f) { 69 field = value 70 } 71 } 72 73 var pulsing: Boolean = false 74 set(value) { 75 field = value 76 if (value) { 77 // Only when setting pulsing to true we want an immediate update, since we get 78 // this already when the doze service finishes which is usually before we get 79 // the waking up callback 80 updateNotificationVisibility(animate = shouldAnimateVisibility(), 81 increaseSpeed = false) 82 } 83 } 84 85 86 init { 87 mAmbientPulseManager.addListener(this) 88 mStatusBarStateController.addCallback(this) 89 mDozeParameters = DozeParameters.getInstance(mContext) 90 } 91 setStackScrollernull92 fun setStackScroller(stackScroller: NotificationStackScrollLayout) { 93 mStackScroller = stackScroller 94 } 95 96 /** 97 * @param visible should notifications be visible 98 * @param animate should this change be animated 99 * @param increaseSpeed should the speed be increased of the animation 100 */ setNotificationsVisibleForExpansionnull101 fun setNotificationsVisibleForExpansion(visible: Boolean, animate: Boolean, 102 increaseSpeed: Boolean) { 103 mNotificationsVisibleForExpansion = visible 104 updateNotificationVisibility(animate, increaseSpeed) 105 if (!visible && mNotificationsVisible) { 106 // If we stopped expanding and we're still visible because we had a pulse that hasn't 107 // times out, let's release them all to make sure were not stuck in a state where 108 // notifications are visible 109 mAmbientPulseManager.releaseAllImmediately() 110 } 111 } 112 updateNotificationVisibilitynull113 private fun updateNotificationVisibility(animate: Boolean, increaseSpeed: Boolean) { 114 var visible = (mNotificationsVisibleForExpansion || mAmbientPulseManager.hasNotifications()) 115 && pulsing; 116 if (!visible && mNotificationsVisible && (mWakingUp || willWakeUp) && mDozeAmount != 0.0f) { 117 // let's not make notifications invisible while waking up, otherwise the animation 118 // is strange 119 return; 120 } 121 setNotificationsVisible(visible, animate, increaseSpeed) 122 } 123 setNotificationsVisiblenull124 private fun setNotificationsVisible(visible: Boolean, animate: Boolean, 125 increaseSpeed: Boolean) { 126 if (mNotificationsVisible == visible) { 127 return 128 } 129 mNotificationsVisible = visible 130 mDarkAnimator?.cancel(); 131 if (animate) { 132 notifyAnimationStart(visible) 133 startVisibilityAnimation(increaseSpeed) 134 } else { 135 setVisibilityAmount(if (visible) 1.0f else 0.0f) 136 } 137 } 138 onDozeAmountChangednull139 override fun onDozeAmountChanged(linear: Float, eased: Float) { 140 if (linear != 1.0f && linear != 0.0f 141 && (mLinearDozeAmount == 0.0f || mLinearDozeAmount == 1.0f)) { 142 // Let's notify the scroller that an animation started 143 notifyAnimationStart(mLinearDozeAmount == 1.0f) 144 } 145 mLinearDozeAmount = linear 146 mDozeAmount = eased 147 mStackScroller.setDozeAmount(mDozeAmount) 148 updateDarkAmount() 149 if (linear == 0.0f) { 150 setNotificationsVisible(visible = false, animate = false, increaseSpeed = false); 151 setNotificationsVisibleForExpansion(visible = false, animate = false, 152 increaseSpeed = false) 153 } 154 } 155 startVisibilityAnimationnull156 private fun startVisibilityAnimation(increaseSpeed: Boolean) { 157 if (mNotificationVisibleAmount == 0f || mNotificationVisibleAmount == 1f) { 158 mVisibilityInterpolator = if (mNotificationsVisible) 159 Interpolators.TOUCH_RESPONSE 160 else 161 Interpolators.FAST_OUT_SLOW_IN_REVERSE 162 } 163 val target = if (mNotificationsVisible) 1.0f else 0.0f 164 val darkAnimator = ObjectAnimator.ofFloat(this, mNotificationVisibility, target) 165 darkAnimator.setInterpolator(Interpolators.LINEAR) 166 var duration = StackStateAnimator.ANIMATION_DURATION_WAKEUP.toLong() 167 if (increaseSpeed) { 168 duration = (duration.toFloat() / 1.5F).toLong(); 169 } 170 darkAnimator.setDuration(duration) 171 darkAnimator.start() 172 mDarkAnimator = darkAnimator 173 } 174 setVisibilityAmountnull175 private fun setVisibilityAmount(visibilityAmount: Float) { 176 mLinearVisibilityAmount = visibilityAmount 177 mVisibilityAmount = mVisibilityInterpolator.getInterpolation( 178 visibilityAmount) 179 handleAnimationFinished(); 180 updateDarkAmount() 181 } 182 handleAnimationFinishednull183 private fun handleAnimationFinished() { 184 if (mLinearDozeAmount == 0.0f || mLinearVisibilityAmount == 0.0f) { 185 mEntrySetToClearWhenFinished.forEach { it.setAmbientGoingAway(false) } 186 mEntrySetToClearWhenFinished.clear() 187 } 188 } 189 getWakeUpHeightnull190 fun getWakeUpHeight() : Float { 191 return mStackScroller.pulseHeight 192 } 193 updateDarkAmountnull194 private fun updateDarkAmount() { 195 val linearAmount = Math.min(1.0f - mLinearVisibilityAmount, mLinearDozeAmount) 196 val amount = Math.min(1.0f - mVisibilityAmount, mDozeAmount) 197 mStackScroller.setDarkAmount(linearAmount, amount) 198 } 199 notifyAnimationStartnull200 private fun notifyAnimationStart(awake: Boolean) { 201 mStackScroller.notifyDarkAnimationStart(!awake) 202 } 203 onDozingChangednull204 override fun onDozingChanged(isDozing: Boolean) { 205 if (isDozing) { 206 setNotificationsVisible(visible = false, animate = false, increaseSpeed = false) 207 } 208 } 209 setPulseHeightnull210 fun setPulseHeight(height: Float): Float { 211 return mStackScroller.setPulseHeight(height) 212 } 213 setWakingUpnull214 fun setWakingUp(wakingUp: Boolean) { 215 willWakeUp = false 216 mWakingUp = wakingUp 217 if (wakingUp && mNotificationsVisible && !mNotificationsVisibleForExpansion) { 218 // We're waking up while pulsing, let's make sure the animation looks nice 219 mStackScroller.wakeUpFromPulse(); 220 } 221 } 222 onAmbientStateChangednull223 override fun onAmbientStateChanged(entry: NotificationEntry, isPulsing: Boolean) { 224 var animate = shouldAnimateVisibility() 225 if (!isPulsing) { 226 if (mLinearDozeAmount != 0.0f && mLinearVisibilityAmount != 0.0f) { 227 if (entry.isRowDismissed) { 228 // if we animate, we see the shelf briefly visible. Instead we fully animate 229 // the notification and its background out 230 animate = false 231 } else if (!mWakingUp && !willWakeUp){ 232 entry.setAmbientGoingAway(true) 233 mEntrySetToClearWhenFinished.add(entry) 234 } 235 } 236 } else if (mEntrySetToClearWhenFinished.contains(entry)) { 237 mEntrySetToClearWhenFinished.remove(entry) 238 entry.setAmbientGoingAway(false) 239 } 240 updateNotificationVisibility(animate, increaseSpeed = false) 241 } 242 shouldAnimateVisibilitynull243 private fun shouldAnimateVisibility() = 244 mDozeParameters.getAlwaysOn() && !mDozeParameters.getDisplayNeedsBlanking() 245 }