blob: 6a3816c50330fb77b96704771ba0b2dc713bc032 [file] [log] [blame]
Selim Cinekd5921a82019-01-29 19:04:08 -08001/*
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
17package com.android.systemui.statusbar.notification
18
19import android.animation.ObjectAnimator
Selim Cinek15af9762019-03-19 18:32:37 -070020import android.content.Context
Selim Cinekd5921a82019-01-29 19:04:08 -080021import android.util.FloatProperty
Selim Cinekd5921a82019-01-29 19:04:08 -080022import com.android.systemui.Interpolators
Selim Cinek34518f62019-02-28 19:41:18 -080023import com.android.systemui.plugins.statusbar.StatusBarStateController
Selim Cinekc1d9ab22019-05-21 18:08:30 -070024import com.android.systemui.statusbar.StatusBarState
Selim Cinek624d6ca2019-02-19 15:39:08 -080025import com.android.systemui.statusbar.notification.collection.NotificationEntry
Selim Cinekd5921a82019-01-29 19:04:08 -080026import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
27import com.android.systemui.statusbar.notification.stack.StackStateAnimator
Selim Cinek15af9762019-03-19 18:32:37 -070028import com.android.systemui.statusbar.phone.DozeParameters
Selim Cinekc3fec682019-06-06 18:11:07 -070029import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
Selim Cinekc1d9ab22019-05-21 18:08:30 -070030import com.android.systemui.statusbar.phone.KeyguardBypassController
Selim Cinek195dfc52019-05-30 19:35:05 -070031import com.android.systemui.statusbar.phone.NotificationIconAreaController
Selim Cinekc3fec682019-06-06 18:11:07 -070032import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
Selim Cinekd5921a82019-01-29 19:04:08 -080033
34import javax.inject.Inject
35import javax.inject.Singleton
36
37@Singleton
Selim Cinek5040f2e2019-02-14 18:22:42 -080038class NotificationWakeUpCoordinator @Inject constructor(
Selim Cinek15af9762019-03-19 18:32:37 -070039 private val mContext: Context,
Selim Cinekc3fec682019-06-06 18:11:07 -070040 private val mHeadsUpManagerPhone: HeadsUpManagerPhone,
Selim Cinek601cda62019-06-24 17:37:57 -070041 private val statusBarStateController: StatusBarStateController,
Selim Cinekb57dd8a2019-06-14 12:27:58 -070042 private val bypassController: KeyguardBypassController)
Selim Cinekc3fec682019-06-06 18:11:07 -070043 : OnHeadsUpChangedListener, StatusBarStateController.StateListener {
Selim Cinekd5921a82019-01-29 19:04:08 -080044
45 private val mNotificationVisibility
46 = object : FloatProperty<NotificationWakeUpCoordinator>("notificationVisibility") {
47
48 override fun setValue(coordinator: NotificationWakeUpCoordinator, value: Float) {
49 coordinator.setVisibilityAmount(value)
50 }
51
52 override fun get(coordinator: NotificationWakeUpCoordinator): Float? {
53 return coordinator.mLinearVisibilityAmount
54 }
55 }
56 private lateinit var mStackScroller: NotificationStackScrollLayout
57 private var mVisibilityInterpolator = Interpolators.FAST_OUT_SLOW_IN_REVERSE
58
59 private var mLinearDozeAmount: Float = 0.0f
60 private var mDozeAmount: Float = 0.0f
61 private var mNotificationVisibleAmount = 0.0f
62 private var mNotificationsVisible = false
Selim Cinek624d6ca2019-02-19 15:39:08 -080063 private var mNotificationsVisibleForExpansion = false
Selim Cinek195dfc52019-05-30 19:35:05 -070064 private var mVisibilityAnimator: ObjectAnimator? = null
Selim Cinekd5921a82019-01-29 19:04:08 -080065 private var mVisibilityAmount = 0.0f
66 private var mLinearVisibilityAmount = 0.0f
Selim Cinek459aee32019-02-20 11:18:56 -080067 private var mWakingUp = false
68 private val mEntrySetToClearWhenFinished = mutableSetOf<NotificationEntry>()
Selim Cinek820ba2d2019-06-18 18:59:09 -070069 private val mDozeParameters: DozeParameters
70 private var pulseExpanding: Boolean = false
71 private val wakeUpListeners = arrayListOf<WakeUpListener>()
Selim Cinek601cda62019-06-24 17:37:57 -070072 private var state: Int = StatusBarState.KEYGUARD
73
Selim Cinekb57dd8a2019-06-14 12:27:58 -070074 var fullyAwake: Boolean = false
75
Selim Cinekd0b48e32019-05-24 20:49:23 -070076 var willWakeUp = false
77 set(value) {
Selim Cinek2bbd4572019-05-28 15:31:10 -070078 if (!value || mDozeAmount != 0.0f) {
Selim Cinekd0b48e32019-05-24 20:49:23 -070079 field = value
80 }
81 }
82
Selim Cinek195dfc52019-05-30 19:35:05 -070083 lateinit var iconAreaController : NotificationIconAreaController
Selim Cinekd0b48e32019-05-24 20:49:23 -070084 var pulsing: Boolean = false
85 set(value) {
86 field = value
87 if (value) {
88 // Only when setting pulsing to true we want an immediate update, since we get
89 // this already when the doze service finishes which is usually before we get
90 // the waking up callback
91 updateNotificationVisibility(animate = shouldAnimateVisibility(),
92 increaseSpeed = false)
93 }
94 }
95
Selim Cinek820ba2d2019-06-18 18:59:09 -070096 var notificationsFullyHidden: Boolean = false
97 private set(value) {
98 if (field != value) {
99 field = value
100 for (listener in wakeUpListeners) {
101 listener.onFullyHiddenChanged(value)
102 }
103 }
104 }
105
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700106 /**
107 * True if we can show pulsing heads up notifications
108 */
109 var canShowPulsingHuns: Boolean = false
110 private set
111 get() {
112 var canShow = pulsing
113 if (bypassController.bypassEnabled) {
114 // We also allow pulsing on the lock screen!
115 canShow = canShow || (mWakingUp || willWakeUp || fullyAwake)
Selim Cinek601cda62019-06-24 17:37:57 -0700116 && statusBarStateController.state == StatusBarState.KEYGUARD
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700117 }
118 return canShow
119 }
120
Selim Cinek624d6ca2019-02-19 15:39:08 -0800121 init {
Selim Cinekc3fec682019-06-06 18:11:07 -0700122 mHeadsUpManagerPhone.addListener(this)
Selim Cinek601cda62019-06-24 17:37:57 -0700123 statusBarStateController.addCallback(this)
Selim Cinek15af9762019-03-19 18:32:37 -0700124 mDozeParameters = DozeParameters.getInstance(mContext)
Selim Cinek624d6ca2019-02-19 15:39:08 -0800125 }
Selim Cinekd5921a82019-01-29 19:04:08 -0800126
127 fun setStackScroller(stackScroller: NotificationStackScrollLayout) {
128 mStackScroller = stackScroller
Selim Cinek820ba2d2019-06-18 18:59:09 -0700129 pulseExpanding = stackScroller.isPulseExpanding
130 stackScroller.setOnPulseHeightChangedListener {
131 val nowExpanding = isPulseExpanding()
132 val changed = nowExpanding != pulseExpanding
133 pulseExpanding = nowExpanding
134 for (listener in wakeUpListeners) {
135 listener.onPulseExpansionChanged(changed)
136 }
137 }
Selim Cinekd5921a82019-01-29 19:04:08 -0800138 }
139
Selim Cinek820ba2d2019-06-18 18:59:09 -0700140 fun isPulseExpanding(): Boolean = mStackScroller.isPulseExpanding
141
Selim Cinek3d6ae232019-01-04 14:14:33 -0800142 /**
143 * @param visible should notifications be visible
144 * @param animate should this change be animated
145 * @param increaseSpeed should the speed be increased of the animation
146 */
Selim Cinek624d6ca2019-02-19 15:39:08 -0800147 fun setNotificationsVisibleForExpansion(visible: Boolean, animate: Boolean,
148 increaseSpeed: Boolean) {
149 mNotificationsVisibleForExpansion = visible
150 updateNotificationVisibility(animate, increaseSpeed)
Selim Cinek34518f62019-02-28 19:41:18 -0800151 if (!visible && mNotificationsVisible) {
152 // If we stopped expanding and we're still visible because we had a pulse that hasn't
153 // times out, let's release them all to make sure were not stuck in a state where
154 // notifications are visible
Selim Cinekc3fec682019-06-06 18:11:07 -0700155 mHeadsUpManagerPhone.releaseAllImmediately()
Selim Cinek34518f62019-02-28 19:41:18 -0800156 }
Selim Cinek624d6ca2019-02-19 15:39:08 -0800157 }
158
Selim Cinek820ba2d2019-06-18 18:59:09 -0700159 fun addListener(listener: WakeUpListener) {
160 wakeUpListeners.add(listener);
161 }
162
163 fun removeFullyHiddenChangedListener(listener: WakeUpListener) {
164 wakeUpListeners.remove(listener);
165 }
166
Selim Cinek624d6ca2019-02-19 15:39:08 -0800167 private fun updateNotificationVisibility(animate: Boolean, increaseSpeed: Boolean) {
Selim Cinekc3fec682019-06-06 18:11:07 -0700168 // TODO: handle Lockscreen wakeup for bypass when we're not pulsing anymore
Selim Cinekb57dd8a2019-06-14 12:27:58 -0700169 var visible = mNotificationsVisibleForExpansion || mHeadsUpManagerPhone.hasNotifications()
Selim Cinekb8cc6ef2019-06-14 16:37:53 -0700170 visible = visible && canShowPulsingHuns
Selim Cinekb57dd8a2019-06-14 12:27:58 -0700171
Selim Cinekd0b48e32019-05-24 20:49:23 -0700172 if (!visible && mNotificationsVisible && (mWakingUp || willWakeUp) && mDozeAmount != 0.0f) {
Selim Cinek459aee32019-02-20 11:18:56 -0800173 // let's not make notifications invisible while waking up, otherwise the animation
174 // is strange
175 return;
Selim Cinek624d6ca2019-02-19 15:39:08 -0800176 }
177 setNotificationsVisible(visible, animate, increaseSpeed)
178 }
179
180 private fun setNotificationsVisible(visible: Boolean, animate: Boolean,
181 increaseSpeed: Boolean) {
Selim Cinekd5921a82019-01-29 19:04:08 -0800182 if (mNotificationsVisible == visible) {
183 return
184 }
185 mNotificationsVisible = visible
Selim Cinek195dfc52019-05-30 19:35:05 -0700186 mVisibilityAnimator?.cancel();
Selim Cinekd5921a82019-01-29 19:04:08 -0800187 if (animate) {
Selim Cinekd5921a82019-01-29 19:04:08 -0800188 notifyAnimationStart(visible)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800189 startVisibilityAnimation(increaseSpeed)
Selim Cinekd5921a82019-01-29 19:04:08 -0800190 } else {
191 setVisibilityAmount(if (visible) 1.0f else 0.0f)
192 }
193 }
194
Selim Cinek34518f62019-02-28 19:41:18 -0800195 override fun onDozeAmountChanged(linear: Float, eased: Float) {
Selim Cinekc1d9ab22019-05-21 18:08:30 -0700196 if (updateDozeAmountIfBypass()) {
197 return
198 }
Selim Cinek34518f62019-02-28 19:41:18 -0800199 if (linear != 1.0f && linear != 0.0f
200 && (mLinearDozeAmount == 0.0f || mLinearDozeAmount == 1.0f)) {
201 // Let's notify the scroller that an animation started
202 notifyAnimationStart(mLinearDozeAmount == 1.0f)
203 }
Selim Cinek9a7520a2019-05-29 17:27:21 -0700204 setDozeAmount(linear, eased)
205 }
206
207 fun setDozeAmount(linear: Float, eased: Float) {
208 val changed = linear != mLinearDozeAmount
Selim Cinek34518f62019-02-28 19:41:18 -0800209 mLinearDozeAmount = linear
210 mDozeAmount = eased
Selim Cinek3d6ae232019-01-04 14:14:33 -0800211 mStackScroller.setDozeAmount(mDozeAmount)
Selim Cinek195dfc52019-05-30 19:35:05 -0700212 updateHideAmount()
Selim Cinek9a7520a2019-05-29 17:27:21 -0700213 if (changed && linear == 0.0f) {
Selim Cinek459aee32019-02-20 11:18:56 -0800214 setNotificationsVisible(visible = false, animate = false, increaseSpeed = false);
215 setNotificationsVisibleForExpansion(visible = false, animate = false,
216 increaseSpeed = false)
217 }
Selim Cinekd5921a82019-01-29 19:04:08 -0800218 }
219
Selim Cinekc1d9ab22019-05-21 18:08:30 -0700220 override fun onStateChanged(newState: Int) {
221 updateDozeAmountIfBypass();
Selim Cinek601cda62019-06-24 17:37:57 -0700222 if (bypassController.bypassEnabled &&
223 newState == StatusBarState.KEYGUARD && state == StatusBarState.SHADE_LOCKED
224 && (!statusBarStateController.isDozing || shouldAnimateVisibility())) {
225 // We're leaving shade locked. Let's animate the notifications away
226 setNotificationsVisible(visible = true, increaseSpeed = false, animate = false)
227 setNotificationsVisible(visible = false, increaseSpeed = false, animate = true)
228 }
229 this.state = newState
Selim Cinekc1d9ab22019-05-21 18:08:30 -0700230 }
231
232 private fun updateDozeAmountIfBypass(): Boolean {
Selim Cinekb57dd8a2019-06-14 12:27:58 -0700233 if (bypassController.bypassEnabled) {
Selim Cinek9a7520a2019-05-29 17:27:21 -0700234 var amount = 1.0f;
Selim Cinek601cda62019-06-24 17:37:57 -0700235 if (statusBarStateController.state == StatusBarState.SHADE
236 || statusBarStateController.state == StatusBarState.SHADE_LOCKED) {
Selim Cinek9a7520a2019-05-29 17:27:21 -0700237 amount = 0.0f;
Selim Cinekc1d9ab22019-05-21 18:08:30 -0700238 }
Selim Cinek9a7520a2019-05-29 17:27:21 -0700239 setDozeAmount(amount, amount)
Selim Cinekc1d9ab22019-05-21 18:08:30 -0700240 return true
241 }
242 return false
243 }
244
Selim Cinek3d6ae232019-01-04 14:14:33 -0800245 private fun startVisibilityAnimation(increaseSpeed: Boolean) {
Selim Cinekd5921a82019-01-29 19:04:08 -0800246 if (mNotificationVisibleAmount == 0f || mNotificationVisibleAmount == 1f) {
247 mVisibilityInterpolator = if (mNotificationsVisible)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800248 Interpolators.TOUCH_RESPONSE
Selim Cinekd5921a82019-01-29 19:04:08 -0800249 else
Selim Cinek3d6ae232019-01-04 14:14:33 -0800250 Interpolators.FAST_OUT_SLOW_IN_REVERSE
Selim Cinekd5921a82019-01-29 19:04:08 -0800251 }
252 val target = if (mNotificationsVisible) 1.0f else 0.0f
Selim Cinek195dfc52019-05-30 19:35:05 -0700253 val visibilityAnimator = ObjectAnimator.ofFloat(this, mNotificationVisibility, target)
254 visibilityAnimator.setInterpolator(Interpolators.LINEAR)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800255 var duration = StackStateAnimator.ANIMATION_DURATION_WAKEUP.toLong()
256 if (increaseSpeed) {
257 duration = (duration.toFloat() / 1.5F).toLong();
258 }
Selim Cinek195dfc52019-05-30 19:35:05 -0700259 visibilityAnimator.setDuration(duration)
260 visibilityAnimator.start()
261 mVisibilityAnimator = visibilityAnimator
Selim Cinekd5921a82019-01-29 19:04:08 -0800262 }
263
264 private fun setVisibilityAmount(visibilityAmount: Float) {
265 mLinearVisibilityAmount = visibilityAmount
266 mVisibilityAmount = mVisibilityInterpolator.getInterpolation(
267 visibilityAmount)
Selim Cinek459aee32019-02-20 11:18:56 -0800268 handleAnimationFinished();
Selim Cinek195dfc52019-05-30 19:35:05 -0700269 updateHideAmount()
Selim Cinekd5921a82019-01-29 19:04:08 -0800270 }
271
Selim Cinek459aee32019-02-20 11:18:56 -0800272 private fun handleAnimationFinished() {
273 if (mLinearDozeAmount == 0.0f || mLinearVisibilityAmount == 0.0f) {
Selim Cinekc3fec682019-06-06 18:11:07 -0700274 mEntrySetToClearWhenFinished.forEach { it.setHeadsUpAnimatingAway(false) }
Selim Cinek0e180ac2019-04-02 15:55:22 -0700275 mEntrySetToClearWhenFinished.clear()
Selim Cinek459aee32019-02-20 11:18:56 -0800276 }
277 }
278
Selim Cinek3d6ae232019-01-04 14:14:33 -0800279 fun getWakeUpHeight() : Float {
Selim Cinekb0fada62019-06-17 19:03:59 -0700280 return mStackScroller.wakeUpHeight
Selim Cinek3d6ae232019-01-04 14:14:33 -0800281 }
282
Selim Cinek195dfc52019-05-30 19:35:05 -0700283 private fun updateHideAmount() {
Selim Cinekd5921a82019-01-29 19:04:08 -0800284 val linearAmount = Math.min(1.0f - mLinearVisibilityAmount, mLinearDozeAmount)
285 val amount = Math.min(1.0f - mVisibilityAmount, mDozeAmount)
Selim Cinek195dfc52019-05-30 19:35:05 -0700286 mStackScroller.setHideAmount(linearAmount, amount)
Selim Cinek820ba2d2019-06-18 18:59:09 -0700287 notificationsFullyHidden = linearAmount == 1.0f;
Selim Cinekd5921a82019-01-29 19:04:08 -0800288 }
289
Selim Cinek3d6ae232019-01-04 14:14:33 -0800290 private fun notifyAnimationStart(awake: Boolean) {
Selim Cinek195dfc52019-05-30 19:35:05 -0700291 mStackScroller.notifyHideAnimationStart(!awake)
Selim Cinekd5921a82019-01-29 19:04:08 -0800292 }
Selim Cinek3d6ae232019-01-04 14:14:33 -0800293
Selim Cinek34518f62019-02-28 19:41:18 -0800294 override fun onDozingChanged(isDozing: Boolean) {
295 if (isDozing) {
Selim Cinek459aee32019-02-20 11:18:56 -0800296 setNotificationsVisible(visible = false, animate = false, increaseSpeed = false)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800297 }
Selim Cinek3d6ae232019-01-04 14:14:33 -0800298 }
299
Selim Cinekb0fada62019-06-17 19:03:59 -0700300 /**
301 * Set the height how tall notifications are pulsing. This is only set whenever we are expanding
302 * from a pulse and determines how much the notifications are expanded.
303 */
Selim Cinek5040f2e2019-02-14 18:22:42 -0800304 fun setPulseHeight(height: Float): Float {
Selim Cinekb0fada62019-06-17 19:03:59 -0700305 val overflow = mStackScroller.setPulseHeight(height)
306 // no overflow for the bypass experience
307 return if (bypassController.bypassEnabled) 0.0f else overflow
Selim Cinek5040f2e2019-02-14 18:22:42 -0800308 }
309
Selim Cinek459aee32019-02-20 11:18:56 -0800310 fun setWakingUp(wakingUp: Boolean) {
Selim Cinekd0b48e32019-05-24 20:49:23 -0700311 willWakeUp = false
Selim Cinek459aee32019-02-20 11:18:56 -0800312 mWakingUp = wakingUp
Selim Cinekb57dd8a2019-06-14 12:27:58 -0700313 if (wakingUp && mNotificationsVisible && !mNotificationsVisibleForExpansion
314 && !bypassController.bypassEnabled) {
Selim Cinek459aee32019-02-20 11:18:56 -0800315 // We're waking up while pulsing, let's make sure the animation looks nice
316 mStackScroller.wakeUpFromPulse();
317 }
Selim Cinek624d6ca2019-02-19 15:39:08 -0800318 }
319
Selim Cinekc3fec682019-06-06 18:11:07 -0700320 override fun onHeadsUpStateChanged(entry: NotificationEntry, isHeadsUp: Boolean) {
Selim Cinekd0b48e32019-05-24 20:49:23 -0700321 var animate = shouldAnimateVisibility()
Selim Cinekc3fec682019-06-06 18:11:07 -0700322 if (!isHeadsUp) {
Selim Cinekd0b48e32019-05-24 20:49:23 -0700323 if (mLinearDozeAmount != 0.0f && mLinearVisibilityAmount != 0.0f) {
Selim Cinekae55d832019-02-22 17:43:43 -0800324 if (entry.isRowDismissed) {
325 // if we animate, we see the shelf briefly visible. Instead we fully animate
326 // the notification and its background out
327 animate = false
Selim Cinekf434a742019-05-28 17:39:49 -0700328 } else if (!mWakingUp && !willWakeUp){
Selim Cinekc3fec682019-06-06 18:11:07 -0700329 // TODO: look that this is done properly and not by anyone else
330 entry.setHeadsUpAnimatingAway(true)
Selim Cinekae55d832019-02-22 17:43:43 -0800331 mEntrySetToClearWhenFinished.add(entry)
332 }
333 }
334 } else if (mEntrySetToClearWhenFinished.contains(entry)) {
Selim Cinek459aee32019-02-20 11:18:56 -0800335 mEntrySetToClearWhenFinished.remove(entry)
Selim Cinekc3fec682019-06-06 18:11:07 -0700336 entry.setHeadsUpAnimatingAway(false)
Selim Cinek459aee32019-02-20 11:18:56 -0800337 }
Selim Cinekae55d832019-02-22 17:43:43 -0800338 updateNotificationVisibility(animate, increaseSpeed = false)
Selim Cinek3d6ae232019-01-04 14:14:33 -0800339 }
Selim Cinekd0b48e32019-05-24 20:49:23 -0700340
341 private fun shouldAnimateVisibility() =
342 mDozeParameters.getAlwaysOn() && !mDozeParameters.getDisplayNeedsBlanking()
Selim Cinek820ba2d2019-06-18 18:59:09 -0700343
344 interface WakeUpListener {
345 /**
346 * Called whenever the notifications are fully hidden or shown
347 */
348 @JvmDefault fun onFullyHiddenChanged(isFullyHidden: Boolean) {}
349
350 /**
351 * Called whenever the pulseExpansion changes
352 * @param expandingChanged if the user has started or stopped expanding
353 */
354 @JvmDefault fun onPulseExpansionChanged(expandingChanged: Boolean) {}
355 }
Selim Cinekd5921a82019-01-29 19:04:08 -0800356}