Selim Cinek | d5921a8 | 2019-01-29 19:04:08 -0800 | [diff] [blame] | 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 |
Selim Cinek | 15af976 | 2019-03-19 18:32:37 -0700 | [diff] [blame] | 20 | import android.content.Context |
Selim Cinek | d5921a8 | 2019-01-29 19:04:08 -0800 | [diff] [blame] | 21 | import android.util.FloatProperty |
Selim Cinek | d5921a8 | 2019-01-29 19:04:08 -0800 | [diff] [blame] | 22 | import com.android.systemui.Interpolators |
Selim Cinek | 34518f6 | 2019-02-28 19:41:18 -0800 | [diff] [blame] | 23 | import com.android.systemui.plugins.statusbar.StatusBarStateController |
Selim Cinek | 5040f2e | 2019-02-14 18:22:42 -0800 | [diff] [blame] | 24 | import com.android.systemui.statusbar.AmbientPulseManager |
Selim Cinek | 34518f6 | 2019-02-28 19:41:18 -0800 | [diff] [blame] | 25 | import com.android.systemui.statusbar.SysuiStatusBarStateController |
Selim Cinek | 624d6ca | 2019-02-19 15:39:08 -0800 | [diff] [blame] | 26 | import com.android.systemui.statusbar.notification.collection.NotificationEntry |
Selim Cinek | d5921a8 | 2019-01-29 19:04:08 -0800 | [diff] [blame] | 27 | import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout |
| 28 | import com.android.systemui.statusbar.notification.stack.StackStateAnimator |
Selim Cinek | 15af976 | 2019-03-19 18:32:37 -0700 | [diff] [blame] | 29 | import com.android.systemui.statusbar.phone.DozeParameters |
Selim Cinek | d5921a8 | 2019-01-29 19:04:08 -0800 | [diff] [blame] | 30 | |
| 31 | import javax.inject.Inject |
| 32 | import javax.inject.Singleton |
| 33 | |
| 34 | @Singleton |
Selim Cinek | 5040f2e | 2019-02-14 18:22:42 -0800 | [diff] [blame] | 35 | class NotificationWakeUpCoordinator @Inject constructor( |
Selim Cinek | 15af976 | 2019-03-19 18:32:37 -0700 | [diff] [blame] | 36 | private val mContext: Context, |
Selim Cinek | 34518f6 | 2019-02-28 19:41:18 -0800 | [diff] [blame] | 37 | private val mAmbientPulseManager: AmbientPulseManager, |
| 38 | private val mStatusBarStateController: StatusBarStateController) |
| 39 | : AmbientPulseManager.OnAmbientChangedListener, StatusBarStateController.StateListener { |
Selim Cinek | d5921a8 | 2019-01-29 19:04:08 -0800 | [diff] [blame] | 40 | |
| 41 | private val mNotificationVisibility |
| 42 | = object : FloatProperty<NotificationWakeUpCoordinator>("notificationVisibility") { |
| 43 | |
| 44 | override fun setValue(coordinator: NotificationWakeUpCoordinator, value: Float) { |
| 45 | coordinator.setVisibilityAmount(value) |
| 46 | } |
| 47 | |
| 48 | 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 |
Selim Cinek | 624d6ca | 2019-02-19 15:39:08 -0800 | [diff] [blame] | 59 | private var mNotificationsVisibleForExpansion = false |
Selim Cinek | d5921a8 | 2019-01-29 19:04:08 -0800 | [diff] [blame] | 60 | private var mDarkAnimator: ObjectAnimator? = null |
| 61 | private var mVisibilityAmount = 0.0f |
| 62 | private var mLinearVisibilityAmount = 0.0f |
Selim Cinek | 459aee3 | 2019-02-20 11:18:56 -0800 | [diff] [blame] | 63 | private var mWakingUp = false |
| 64 | private val mEntrySetToClearWhenFinished = mutableSetOf<NotificationEntry>() |
Selim Cinek | 15af976 | 2019-03-19 18:32:37 -0700 | [diff] [blame] | 65 | private val mDozeParameters: DozeParameters; |
Selim Cinek | d0b48e3 | 2019-05-24 20:49:23 -0700 | [diff] [blame] | 66 | var willWakeUp = false |
| 67 | set(value) { |
Selim Cinek | 2bbd457 | 2019-05-28 15:31:10 -0700 | [diff] [blame] | 68 | if (!value || mDozeAmount != 0.0f) { |
Selim Cinek | d0b48e3 | 2019-05-24 20:49:23 -0700 | [diff] [blame] | 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 | |
Selim Cinek | 624d6ca | 2019-02-19 15:39:08 -0800 | [diff] [blame] | 85 | |
| 86 | init { |
| 87 | mAmbientPulseManager.addListener(this) |
Selim Cinek | 34518f6 | 2019-02-28 19:41:18 -0800 | [diff] [blame] | 88 | mStatusBarStateController.addCallback(this) |
Selim Cinek | 15af976 | 2019-03-19 18:32:37 -0700 | [diff] [blame] | 89 | mDozeParameters = DozeParameters.getInstance(mContext) |
Selim Cinek | 624d6ca | 2019-02-19 15:39:08 -0800 | [diff] [blame] | 90 | } |
Selim Cinek | d5921a8 | 2019-01-29 19:04:08 -0800 | [diff] [blame] | 91 | |
| 92 | fun setStackScroller(stackScroller: NotificationStackScrollLayout) { |
| 93 | mStackScroller = stackScroller |
| 94 | } |
| 95 | |
Selim Cinek | 3d6ae23 | 2019-01-04 14:14:33 -0800 | [diff] [blame] | 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 | */ |
Selim Cinek | 624d6ca | 2019-02-19 15:39:08 -0800 | [diff] [blame] | 101 | fun setNotificationsVisibleForExpansion(visible: Boolean, animate: Boolean, |
| 102 | increaseSpeed: Boolean) { |
| 103 | mNotificationsVisibleForExpansion = visible |
| 104 | updateNotificationVisibility(animate, increaseSpeed) |
Selim Cinek | 34518f6 | 2019-02-28 19:41:18 -0800 | [diff] [blame] | 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 | } |
Selim Cinek | 624d6ca | 2019-02-19 15:39:08 -0800 | [diff] [blame] | 111 | } |
| 112 | |
| 113 | private fun updateNotificationVisibility(animate: Boolean, increaseSpeed: Boolean) { |
Selim Cinek | d0b48e3 | 2019-05-24 20:49:23 -0700 | [diff] [blame] | 114 | var visible = (mNotificationsVisibleForExpansion || mAmbientPulseManager.hasNotifications()) |
| 115 | && pulsing; |
| 116 | if (!visible && mNotificationsVisible && (mWakingUp || willWakeUp) && mDozeAmount != 0.0f) { |
Selim Cinek | 459aee3 | 2019-02-20 11:18:56 -0800 | [diff] [blame] | 117 | // let's not make notifications invisible while waking up, otherwise the animation |
| 118 | // is strange |
| 119 | return; |
Selim Cinek | 624d6ca | 2019-02-19 15:39:08 -0800 | [diff] [blame] | 120 | } |
| 121 | setNotificationsVisible(visible, animate, increaseSpeed) |
| 122 | } |
| 123 | |
| 124 | private fun setNotificationsVisible(visible: Boolean, animate: Boolean, |
| 125 | increaseSpeed: Boolean) { |
Selim Cinek | d5921a8 | 2019-01-29 19:04:08 -0800 | [diff] [blame] | 126 | if (mNotificationsVisible == visible) { |
| 127 | return |
| 128 | } |
| 129 | mNotificationsVisible = visible |
| 130 | mDarkAnimator?.cancel(); |
| 131 | if (animate) { |
Selim Cinek | d5921a8 | 2019-01-29 19:04:08 -0800 | [diff] [blame] | 132 | notifyAnimationStart(visible) |
Selim Cinek | 3d6ae23 | 2019-01-04 14:14:33 -0800 | [diff] [blame] | 133 | startVisibilityAnimation(increaseSpeed) |
Selim Cinek | d5921a8 | 2019-01-29 19:04:08 -0800 | [diff] [blame] | 134 | } else { |
| 135 | setVisibilityAmount(if (visible) 1.0f else 0.0f) |
| 136 | } |
| 137 | } |
| 138 | |
Selim Cinek | 34518f6 | 2019-02-28 19:41:18 -0800 | [diff] [blame] | 139 | 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 |
Selim Cinek | 3d6ae23 | 2019-01-04 14:14:33 -0800 | [diff] [blame] | 147 | mStackScroller.setDozeAmount(mDozeAmount) |
Selim Cinek | d5921a8 | 2019-01-29 19:04:08 -0800 | [diff] [blame] | 148 | updateDarkAmount() |
Selim Cinek | 34518f6 | 2019-02-28 19:41:18 -0800 | [diff] [blame] | 149 | if (linear == 0.0f) { |
Selim Cinek | 459aee3 | 2019-02-20 11:18:56 -0800 | [diff] [blame] | 150 | setNotificationsVisible(visible = false, animate = false, increaseSpeed = false); |
| 151 | setNotificationsVisibleForExpansion(visible = false, animate = false, |
| 152 | increaseSpeed = false) |
| 153 | } |
Selim Cinek | d5921a8 | 2019-01-29 19:04:08 -0800 | [diff] [blame] | 154 | } |
| 155 | |
Selim Cinek | 3d6ae23 | 2019-01-04 14:14:33 -0800 | [diff] [blame] | 156 | private fun startVisibilityAnimation(increaseSpeed: Boolean) { |
Selim Cinek | d5921a8 | 2019-01-29 19:04:08 -0800 | [diff] [blame] | 157 | if (mNotificationVisibleAmount == 0f || mNotificationVisibleAmount == 1f) { |
| 158 | mVisibilityInterpolator = if (mNotificationsVisible) |
Selim Cinek | 3d6ae23 | 2019-01-04 14:14:33 -0800 | [diff] [blame] | 159 | Interpolators.TOUCH_RESPONSE |
Selim Cinek | d5921a8 | 2019-01-29 19:04:08 -0800 | [diff] [blame] | 160 | else |
Selim Cinek | 3d6ae23 | 2019-01-04 14:14:33 -0800 | [diff] [blame] | 161 | Interpolators.FAST_OUT_SLOW_IN_REVERSE |
Selim Cinek | d5921a8 | 2019-01-29 19:04:08 -0800 | [diff] [blame] | 162 | } |
| 163 | val target = if (mNotificationsVisible) 1.0f else 0.0f |
| 164 | val darkAnimator = ObjectAnimator.ofFloat(this, mNotificationVisibility, target) |
| 165 | darkAnimator.setInterpolator(Interpolators.LINEAR) |
Selim Cinek | 3d6ae23 | 2019-01-04 14:14:33 -0800 | [diff] [blame] | 166 | var duration = StackStateAnimator.ANIMATION_DURATION_WAKEUP.toLong() |
| 167 | if (increaseSpeed) { |
| 168 | duration = (duration.toFloat() / 1.5F).toLong(); |
| 169 | } |
| 170 | darkAnimator.setDuration(duration) |
Selim Cinek | d5921a8 | 2019-01-29 19:04:08 -0800 | [diff] [blame] | 171 | darkAnimator.start() |
| 172 | mDarkAnimator = darkAnimator |
| 173 | } |
| 174 | |
| 175 | private fun setVisibilityAmount(visibilityAmount: Float) { |
| 176 | mLinearVisibilityAmount = visibilityAmount |
| 177 | mVisibilityAmount = mVisibilityInterpolator.getInterpolation( |
| 178 | visibilityAmount) |
Selim Cinek | 459aee3 | 2019-02-20 11:18:56 -0800 | [diff] [blame] | 179 | handleAnimationFinished(); |
Selim Cinek | d5921a8 | 2019-01-29 19:04:08 -0800 | [diff] [blame] | 180 | updateDarkAmount() |
| 181 | } |
| 182 | |
Selim Cinek | 459aee3 | 2019-02-20 11:18:56 -0800 | [diff] [blame] | 183 | private fun handleAnimationFinished() { |
| 184 | if (mLinearDozeAmount == 0.0f || mLinearVisibilityAmount == 0.0f) { |
| 185 | mEntrySetToClearWhenFinished.forEach { it.setAmbientGoingAway(false) } |
Selim Cinek | 0e180ac | 2019-04-02 15:55:22 -0700 | [diff] [blame] | 186 | mEntrySetToClearWhenFinished.clear() |
Selim Cinek | 459aee3 | 2019-02-20 11:18:56 -0800 | [diff] [blame] | 187 | } |
| 188 | } |
| 189 | |
Selim Cinek | 3d6ae23 | 2019-01-04 14:14:33 -0800 | [diff] [blame] | 190 | fun getWakeUpHeight() : Float { |
Selim Cinek | 34518f6 | 2019-02-28 19:41:18 -0800 | [diff] [blame] | 191 | return mStackScroller.pulseHeight |
Selim Cinek | 3d6ae23 | 2019-01-04 14:14:33 -0800 | [diff] [blame] | 192 | } |
| 193 | |
Selim Cinek | d5921a8 | 2019-01-29 19:04:08 -0800 | [diff] [blame] | 194 | 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 | |
Selim Cinek | 3d6ae23 | 2019-01-04 14:14:33 -0800 | [diff] [blame] | 200 | private fun notifyAnimationStart(awake: Boolean) { |
Selim Cinek | d5921a8 | 2019-01-29 19:04:08 -0800 | [diff] [blame] | 201 | mStackScroller.notifyDarkAnimationStart(!awake) |
| 202 | } |
Selim Cinek | 3d6ae23 | 2019-01-04 14:14:33 -0800 | [diff] [blame] | 203 | |
Selim Cinek | 34518f6 | 2019-02-28 19:41:18 -0800 | [diff] [blame] | 204 | override fun onDozingChanged(isDozing: Boolean) { |
| 205 | if (isDozing) { |
Selim Cinek | 459aee3 | 2019-02-20 11:18:56 -0800 | [diff] [blame] | 206 | setNotificationsVisible(visible = false, animate = false, increaseSpeed = false) |
Selim Cinek | 3d6ae23 | 2019-01-04 14:14:33 -0800 | [diff] [blame] | 207 | } |
Selim Cinek | 3d6ae23 | 2019-01-04 14:14:33 -0800 | [diff] [blame] | 208 | } |
| 209 | |
Selim Cinek | 5040f2e | 2019-02-14 18:22:42 -0800 | [diff] [blame] | 210 | fun setPulseHeight(height: Float): Float { |
| 211 | return mStackScroller.setPulseHeight(height) |
| 212 | } |
| 213 | |
Selim Cinek | 459aee3 | 2019-02-20 11:18:56 -0800 | [diff] [blame] | 214 | fun setWakingUp(wakingUp: Boolean) { |
Selim Cinek | d0b48e3 | 2019-05-24 20:49:23 -0700 | [diff] [blame] | 215 | willWakeUp = false |
Selim Cinek | 459aee3 | 2019-02-20 11:18:56 -0800 | [diff] [blame] | 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 | } |
Selim Cinek | 624d6ca | 2019-02-19 15:39:08 -0800 | [diff] [blame] | 221 | } |
| 222 | |
Selim Cinek | 459aee3 | 2019-02-20 11:18:56 -0800 | [diff] [blame] | 223 | override fun onAmbientStateChanged(entry: NotificationEntry, isPulsing: Boolean) { |
Selim Cinek | d0b48e3 | 2019-05-24 20:49:23 -0700 | [diff] [blame] | 224 | var animate = shouldAnimateVisibility() |
Selim Cinek | ae55d83 | 2019-02-22 17:43:43 -0800 | [diff] [blame] | 225 | if (!isPulsing) { |
Selim Cinek | d0b48e3 | 2019-05-24 20:49:23 -0700 | [diff] [blame] | 226 | if (mLinearDozeAmount != 0.0f && mLinearVisibilityAmount != 0.0f) { |
Selim Cinek | ae55d83 | 2019-02-22 17:43:43 -0800 | [diff] [blame] | 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 |
Selim Cinek | f434a74 | 2019-05-28 17:39:49 -0700 | [diff] [blame] | 231 | } else if (!mWakingUp && !willWakeUp){ |
Selim Cinek | ae55d83 | 2019-02-22 17:43:43 -0800 | [diff] [blame] | 232 | entry.setAmbientGoingAway(true) |
| 233 | mEntrySetToClearWhenFinished.add(entry) |
| 234 | } |
| 235 | } |
| 236 | } else if (mEntrySetToClearWhenFinished.contains(entry)) { |
Selim Cinek | 459aee3 | 2019-02-20 11:18:56 -0800 | [diff] [blame] | 237 | mEntrySetToClearWhenFinished.remove(entry) |
| 238 | entry.setAmbientGoingAway(false) |
| 239 | } |
Selim Cinek | ae55d83 | 2019-02-22 17:43:43 -0800 | [diff] [blame] | 240 | updateNotificationVisibility(animate, increaseSpeed = false) |
Selim Cinek | 3d6ae23 | 2019-01-04 14:14:33 -0800 | [diff] [blame] | 241 | } |
Selim Cinek | d0b48e3 | 2019-05-24 20:49:23 -0700 | [diff] [blame] | 242 | |
| 243 | private fun shouldAnimateVisibility() = |
| 244 | mDozeParameters.getAlwaysOn() && !mDozeParameters.getDisplayNeedsBlanking() |
Selim Cinek | d5921a8 | 2019-01-29 19:04:08 -0800 | [diff] [blame] | 245 | } |