1 /* 2 * Copyright (C) 2024 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.headsup 18 19 import android.graphics.Region 20 import com.android.systemui.Dumpable 21 import com.android.systemui.dagger.SysUISingleton 22 import com.android.systemui.statusbar.notification.collection.NotificationEntry 23 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow 24 import dagger.Binds 25 import dagger.Module 26 import java.io.PrintWriter 27 import java.util.stream.Stream 28 import javax.inject.Inject 29 import kotlinx.coroutines.flow.MutableStateFlow 30 import kotlinx.coroutines.flow.StateFlow 31 32 /** 33 * A manager which handles heads up notifications which is a special mode where they simply peek 34 * from the top of the screen. 35 */ 36 interface HeadsUpManager : Dumpable { 37 /** The stream of all current notifications managed by this manager. */ 38 val allEntries: Stream<NotificationEntry> 39 40 /** Add a listener to receive callbacks onHeadsUpGoingAway. */ addHeadsUpPhoneListenernull41 fun addHeadsUpPhoneListener(listener: OnHeadsUpPhoneListenerChange) 42 43 /** Adds an OnHeadUpChangedListener to observe events. */ 44 fun addListener(listener: OnHeadsUpChangedListener) 45 46 fun addSwipedOutNotification(key: String) 47 48 /** 49 * Whether or not the alert can be removed currently. If it hasn't been on screen long enough it 50 * should not be removed unless forced 51 * 52 * @param key the key to check if removable 53 * @return true if the alert entry can be removed 54 */ 55 fun canRemoveImmediately(key: String): Boolean 56 57 /** 58 * Compare two entries and decide how they should be ranked. 59 * 60 * @return -1 if the first argument should be ranked higher than the second, 1 if the second one 61 * should be ranked higher and 0 if they are equal. 62 */ 63 fun compare(a: NotificationEntry?, b: NotificationEntry?): Int 64 65 /** 66 * Extends the lifetime of the currently showing pulsing notification so that the pulse lasts 67 * longer. 68 */ 69 fun extendHeadsUp() 70 71 /** Returns when a HUN entry should be removed in milliseconds from now. */ 72 fun getEarliestRemovalTime(key: String?): Long 73 74 /** Returns the top Heads Up Notification, which appears to show at first. */ 75 fun getTopEntry(): NotificationEntry? 76 77 /** 78 * Gets the touchable region needed for heads up notifications. Returns null if no touchable 79 * region is required (ie: no heads up notification currently exists). 80 */ 81 // TODO(b/347007367): With scene container enabled this method may report outdated regions 82 fun getTouchableRegion(): Region? 83 84 /** 85 * Whether or not there are any entries managed by HeadsUpManager. 86 * 87 * @return true if there is a heads up entry, false otherwise 88 */ 89 fun hasNotifications(): Boolean = false 90 91 /** Returns whether there are any pinned Heads Up Notifications or not. */ 92 fun hasPinnedHeadsUp(): Boolean 93 94 /** 95 * Returns the status of the top Heads Up Notification, or returns [PinnedStatus.NotPinned] if 96 * there is no pinned HUN. 97 */ 98 fun pinnedHeadsUpStatus(): PinnedStatus 99 100 /** Returns whether or not the given notification is managed by this manager. */ 101 fun isHeadsUpEntry(key: String): Boolean 102 103 /** @see setHeadsUpAnimatingAway */ 104 fun isHeadsUpAnimatingAwayValue(): Boolean 105 106 /** Returns if the given notification is snoozed or not. */ 107 fun isSnoozed(packageName: String): Boolean 108 109 /** Returns whether the entry is (pinned and expanded) or (has an active remote input). */ 110 fun isSticky(key: String?): Boolean 111 112 /** 113 * Returns the value of the tracking-heads-up flag. See the doc of {@code setTrackingHeadsUp} as 114 * well. 115 */ 116 fun isTrackingHeadsUp(): StateFlow<Boolean> 117 118 fun onExpandingFinished() 119 120 /** Removes the OnHeadUpChangedListener from the observer list. */ 121 fun removeListener(listener: OnHeadsUpChangedListener) 122 123 /** 124 * Try to remove the notification. May not succeed if the notification has not been shown long 125 * enough and needs to be kept around. 126 * 127 * @param key the key of the notification to remove 128 * @param releaseImmediately force a remove regardless of earliest removal time 129 * @param reason reason for removing the notification 130 * @return true if notification is removed, false otherwise 131 */ 132 fun removeNotification(key: String, releaseImmediately: Boolean, reason: String): Boolean 133 134 /** 135 * Try to remove the notification. May not succeed if the notification has not been shown long 136 * enough and needs to be kept around. 137 * 138 * @param key the key of the notification to remove 139 * @param releaseImmediately force a remove regardless of earliest removal time 140 * @param animate if true, animate the removal 141 * @param reason reason for removing the notification 142 * @return true if notification is removed, false otherwise 143 */ 144 fun removeNotification( 145 key: String, 146 releaseImmediately: Boolean, 147 animate: Boolean, 148 reason: String, 149 ): Boolean 150 151 /** Clears all managed notifications. */ 152 fun releaseAllImmediately() 153 154 fun setAnimationStateHandler(handler: AnimationStateHandler) 155 156 /** 157 * Set an entry to be expanded and therefore stick in the heads up area if it's pinned until 158 * it's collapsed again. 159 */ 160 fun setExpanded(key: String, row: ExpandableNotificationRow, expanded: Boolean) 161 162 /** 163 * Set an entry to be expanded and therefore stick in the heads up area if it's pinned until 164 * it's collapsed again. 165 */ 166 fun setExpanded(entry: NotificationEntry, expanded: Boolean) 167 168 /** 169 * Sets whether an entry's guts are exposed and therefore it should stick in the heads up area 170 * if it's pinned until it's hidden again. 171 */ 172 fun setGutsShown(entry: NotificationEntry, gutsShown: Boolean) 173 174 /** 175 * Set that we are exiting the headsUp pinned mode, but some notifications might still be 176 * animating out. This is used to keep the touchable regions in a reasonable state. 177 */ 178 fun setHeadsUpAnimatingAway(headsUpAnimatingAway: Boolean) 179 180 /** 181 * Notifies that a remote input textbox in notification gets active or inactive. 182 * 183 * @param entry The entry of the target notification. 184 * @param remoteInputActive True to notify active, False to notify inactive. 185 */ 186 fun setRemoteInputActive(entry: NotificationEntry, remoteInputActive: Boolean) 187 188 /** 189 * Sets the tracking-heads-up flag. If the flag is true, HeadsUpManager doesn't remove the entry 190 * from the list even after a Heads Up Notification is gone. 191 */ 192 fun setTrackingHeadsUp(tracking: Boolean) 193 194 /** Sets the current user. */ 195 fun setUser(user: Int) 196 197 /** 198 * Notes that the user took an action on an entry that might indirectly cause the system or the 199 * app to remove the notification. 200 * 201 * @param entry the key of the entry that might be indirectly removed by the user's action 202 * @see 203 * com.android.systemui.statusbar.notification.collection.coordinator.HeadsUpCoordinator.mActionPressListener 204 * @see .canRemoveImmediately 205 */ 206 fun setUserActionMayIndirectlyRemove(entryKey: String) 207 208 /** 209 * Decides whether a click is invalid for a notification, i.e. it has not been shown long enough 210 * that a user might have consciously clicked on it. 211 * 212 * @param key the key of the touched notification 213 * @return whether the touch is invalid and should be discarded 214 */ 215 fun shouldSwallowClick(key: String): Boolean 216 217 /** 218 * Called when posting a new notification that should alert the user and appear on screen. Adds 219 * the notification to be managed. 220 * 221 * @param entry entry to show 222 * @param isPinnedByUser true if the notification was pinned by the user and false if the 223 * notification was pinned by the system. 224 */ 225 fun showNotification(entry: NotificationEntry, isPinnedByUser: Boolean = false) 226 227 fun snooze() 228 229 /** 230 * Unpins all pinned Heads Up Notifications. 231 * 232 * @param userUnPinned The unpinned action is trigger by user real operation. 233 */ 234 fun unpinAll(userUnPinned: Boolean) 235 236 /** 237 * Called when the notification state has been updated. 238 * 239 * @param key the key of the entry that was updated 240 * @param requestedPinnedStatus whether and how the notification should be pinned. If equal to 241 * [PinnedStatus.NotPinned], the notification won't show again. Otherwise, the notification 242 * should show again and will force reevaluation of removal time. 243 */ 244 fun updateNotification(key: String, requestedPinnedStatus: PinnedStatus) 245 246 fun onEntryAnimatingAwayEnded(entry: NotificationEntry) 247 } 248 249 /** Sets the animation state of the HeadsUpManager. */ 250 interface AnimationStateHandler { 251 fun setHeadsUpGoingAwayAnimationsAllowed(allowed: Boolean) 252 } 253 254 /** Listener to register for HeadsUpNotification Phone changes. */ 255 interface OnHeadsUpPhoneListenerChange { 256 /** 257 * Called when a heads up notification is 'going away' or no longer 'going away'. See 258 * [HeadsUpManager.setHeadsUpAnimatingAway]. 259 */ 260 // TODO(b/325936094) delete this callback, and listen to the flow instead onHeadsUpAnimatingAwayStateChangednull261 fun onHeadsUpAnimatingAwayStateChanged(headsUpAnimatingAway: Boolean) 262 } 263 264 /* No op impl of HeadsUpManager. */ 265 class HeadsUpManagerEmptyImpl @Inject constructor() : HeadsUpManager { 266 override val allEntries = Stream.empty<NotificationEntry>() 267 268 override fun addHeadsUpPhoneListener(listener: OnHeadsUpPhoneListenerChange) {} 269 270 override fun addListener(listener: OnHeadsUpChangedListener) {} 271 272 override fun addSwipedOutNotification(key: String) {} 273 274 override fun canRemoveImmediately(key: String) = false 275 276 override fun compare(a: NotificationEntry?, b: NotificationEntry?) = 0 277 278 override fun dump(pw: PrintWriter, args: Array<out String>) {} 279 280 override fun extendHeadsUp() {} 281 282 override fun getEarliestRemovalTime(key: String?) = 0L 283 284 override fun getTouchableRegion(): Region? = null 285 286 override fun getTopEntry() = null 287 288 override fun hasPinnedHeadsUp() = false 289 290 override fun pinnedHeadsUpStatus() = PinnedStatus.NotPinned 291 292 override fun isHeadsUpEntry(key: String) = false 293 294 override fun isHeadsUpAnimatingAwayValue() = false 295 296 override fun isTrackingHeadsUp(): StateFlow<Boolean> = MutableStateFlow(false) 297 298 override fun isSnoozed(packageName: String) = false 299 300 override fun isSticky(key: String?) = false 301 302 override fun onExpandingFinished() {} 303 304 override fun releaseAllImmediately() {} 305 306 override fun removeListener(listener: OnHeadsUpChangedListener) {} 307 308 override fun removeNotification(key: String, releaseImmediately: Boolean, reason: String) = 309 false 310 311 override fun removeNotification( 312 key: String, 313 releaseImmediately: Boolean, 314 animate: Boolean, 315 reason: String, 316 ) = false 317 318 override fun setAnimationStateHandler(handler: AnimationStateHandler) {} 319 320 override fun setExpanded(key: String, row: ExpandableNotificationRow, expanded: Boolean) {} 321 322 override fun setExpanded(entry: NotificationEntry, expanded: Boolean) {} 323 324 override fun setGutsShown(entry: NotificationEntry, gutsShown: Boolean) {} 325 326 override fun setHeadsUpAnimatingAway(headsUpAnimatingAway: Boolean) {} 327 328 override fun setRemoteInputActive(entry: NotificationEntry, remoteInputActive: Boolean) {} 329 330 override fun setTrackingHeadsUp(tracking: Boolean) {} 331 332 override fun setUser(user: Int) {} 333 334 override fun setUserActionMayIndirectlyRemove(entryKey: String) {} 335 336 override fun shouldSwallowClick(key: String): Boolean = false 337 338 override fun showNotification(entry: NotificationEntry, isPinnedByUser: Boolean) {} 339 340 override fun snooze() {} 341 342 override fun unpinAll(userUnPinned: Boolean) {} 343 344 override fun updateNotification(key: String, requestedPinnedStatus: PinnedStatus) {} 345 346 override fun onEntryAnimatingAwayEnded(entry: NotificationEntry) {} 347 } 348 349 @Module 350 interface HeadsUpEmptyImplModule { bindsHeadsUpManagernull351 @Binds @SysUISingleton fun bindsHeadsUpManager(noOpHum: HeadsUpManagerEmptyImpl): HeadsUpManager 352 } 353