• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.interruption
18 
19 import android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST
20 import android.app.Notification
21 import android.app.Notification.BubbleMetadata
22 import android.app.Notification.CATEGORY_ALARM
23 import android.app.Notification.CATEGORY_CAR_EMERGENCY
24 import android.app.Notification.CATEGORY_CAR_WARNING
25 import android.app.Notification.CATEGORY_EVENT
26 import android.app.Notification.CATEGORY_REMINDER
27 import android.app.Notification.VISIBILITY_PRIVATE
28 import android.app.NotificationManager
29 import android.app.NotificationManager.IMPORTANCE_DEFAULT
30 import android.app.NotificationManager.IMPORTANCE_HIGH
31 import android.app.PendingIntent
32 import android.content.Context
33 import android.content.Intent
34 import android.content.pm.PackageManager
35 import android.content.pm.PackageManager.PERMISSION_GRANTED
36 import android.database.ContentObserver
37 import android.hardware.display.AmbientDisplayConfiguration
38 import android.os.Bundle
39 import android.os.Handler
40 import android.os.PowerManager
41 import android.os.SystemProperties
42 import android.provider.Settings
43 import android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED
44 import android.provider.Settings.Global.HEADS_UP_OFF
45 import android.service.notification.Flags
46 import com.android.internal.logging.UiEvent
47 import com.android.internal.logging.UiEventLogger
48 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage
49 import com.android.systemui.dagger.qualifiers.Main
50 import com.android.systemui.plugins.statusbar.StatusBarStateController
51 import com.android.systemui.settings.UserTracker
52 import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
53 import com.android.systemui.statusbar.StatusBarState.SHADE
54 import com.android.systemui.statusbar.notification.collection.NotificationEntry
55 import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
56 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.MAX_HUN_WHEN_AGE_MS
57 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.HUN_SUPPRESSED_OLD_WHEN
58 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE
59 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK
60 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE
61 import com.android.systemui.statusbar.policy.BatteryController
62 import com.android.systemui.util.NotificationChannels
63 import com.android.systemui.util.settings.GlobalSettings
64 import com.android.systemui.util.settings.SystemSettings
65 import com.android.systemui.util.time.SystemClock
66 import com.android.wm.shell.bubbles.Bubbles
67 import java.util.Optional
68 import kotlin.jvm.optionals.getOrElse
69 
70 class PeekDisabledSuppressor(
71     private val globalSettings: GlobalSettings,
72     private val headsUpManager: HeadsUpManager,
73     private val logger: VisualInterruptionDecisionLogger,
74     @Main private val mainHandler: Handler,
75 ) : VisualInterruptionCondition(types = setOf(PEEK), reason = "peek disabled by global setting") {
76     private var isEnabled = false
77 
shouldSuppressnull78     override fun shouldSuppress(): Boolean = !isEnabled
79 
80     override fun start() {
81         val observer =
82             object : ContentObserver(mainHandler) {
83                 override fun onChange(selfChange: Boolean) {
84                     val wasEnabled = isEnabled
85 
86                     isEnabled =
87                         globalSettings.getInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_OFF) !=
88                             HEADS_UP_OFF
89 
90                     // QQQ: Do we want to log this even if it hasn't changed?
91                     logger.logHeadsUpFeatureChanged(isEnabled)
92 
93                     // QQQ: Is there a better place for this side effect? What if HeadsUpManager
94                     // registered for it directly?
95                     if (wasEnabled && !isEnabled) {
96                         logger.logWillDismissAll()
97                         headsUpManager.releaseAllImmediately()
98                     }
99                 }
100             }
101 
102         globalSettings.registerContentObserverSync(
103             globalSettings.getUriFor(HEADS_UP_NOTIFICATIONS_ENABLED),
104             /* notifyForDescendants = */ true,
105             observer,
106         )
107 
108         // QQQ: Do we need to register for SETTING_HEADS_UP_TICKER? It seems unused.
109 
110         observer.onChange(/* selfChange= */ true)
111     }
112 }
113 
114 class PulseDisabledSuppressor(
115     private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
116     private val userTracker: UserTracker,
117 ) : VisualInterruptionCondition(types = setOf(PULSE), reason = "pulse disabled by user setting") {
shouldSuppressnull118     override fun shouldSuppress(): Boolean =
119         !ambientDisplayConfiguration.pulseOnNotificationEnabled(userTracker.userId)
120 }
121 
122 class PulseBatterySaverSuppressor(private val batteryController: BatteryController) :
123     VisualInterruptionCondition(types = setOf(PULSE), reason = "pulse disabled by battery saver") {
124     override fun shouldSuppress() = batteryController.isAodPowerSave()
125 }
126 
127 class PeekPackageSnoozedSuppressor(private val headsUpManager: HeadsUpManager) :
128     VisualInterruptionFilter(types = setOf(PEEK), reason = "package snoozed") {
shouldSuppressnull129     override fun shouldSuppress(entry: NotificationEntry) =
130         when {
131             // Assume any notification with an FSI is time-sensitive (like an alarm or incoming
132             // call) and ignore whether HUNs have been snoozed for the package.
133             entry.sbn.notification.fullScreenIntent != null -> false
134 
135             // Otherwise, check if the package is snoozed.
136             else -> headsUpManager.isSnoozed(entry.sbn.packageName)
137         }
138 }
139 
140 class PeekAlreadyBubbledSuppressor(
141     private val statusBarStateController: StatusBarStateController,
142     private val bubbles: Optional<Bubbles>,
143 ) : VisualInterruptionFilter(types = setOf(PEEK), reason = "already bubbled") {
shouldSuppressnull144     override fun shouldSuppress(entry: NotificationEntry) =
145         when {
146             statusBarStateController.state != SHADE -> false
147             else -> {
148                 val bubblesCanShowNotification =
149                     bubbles.map { it.canShowBubbleNotification() }.getOrElse { false }
150                 entry.isBubble && bubblesCanShowNotification
151             }
152         }
153 }
154 
155 class PeekDndSuppressor :
156     VisualInterruptionFilter(types = setOf(PEEK), reason = "suppressed by DND") {
shouldSuppressnull157     override fun shouldSuppress(entry: NotificationEntry) = entry.shouldSuppressPeek()
158 }
159 
160 class PeekNotImportantSuppressor :
161     VisualInterruptionFilter(types = setOf(PEEK), reason = "importance < HIGH") {
162     override fun shouldSuppress(entry: NotificationEntry) = entry.importance < IMPORTANCE_HIGH
163 }
164 
165 class PeekDeviceNotInUseSuppressor(
166     private val powerManager: PowerManager,
167     private val statusBarStateController: StatusBarStateController,
168 ) : VisualInterruptionCondition(types = setOf(PEEK), reason = "device not in use") {
shouldSuppressnull169     override fun shouldSuppress() =
170         when {
171             !powerManager.isScreenOn || statusBarStateController.isDreaming -> true
172             else -> false
173         }
174 }
175 
176 class PeekOldWhenSuppressor(private val systemClock: SystemClock) :
177     VisualInterruptionFilter(
178         types = setOf(PEEK),
179         reason = "has old `when`",
180         uiEventId = HUN_SUPPRESSED_OLD_WHEN,
181     ) {
whenAgenull182     private fun whenAge(entry: NotificationEntry) =
183         systemClock.currentTimeMillis() - entry.sbn.notification.getWhen()
184 
185     override fun shouldSuppress(entry: NotificationEntry): Boolean =
186         when {
187             // Ignore a "when" of 0, as it is unlikely to be a meaningful timestamp.
188             entry.sbn.notification.getWhen() <= 0L -> false
189 
190             // Assume all HUNs with FSIs, foreground services, or user-initiated jobs are
191             // time-sensitive, regardless of their "when".
192             entry.sbn.notification.fullScreenIntent != null ||
193                 entry.sbn.notification.isForegroundService ||
194                 entry.sbn.notification.isUserInitiatedJob -> false
195 
196             // Otherwise, check if the HUN's "when" is too old.
197             else -> whenAge(entry) >= MAX_HUN_WHEN_AGE_MS
198         }
199 }
200 
201 class PulseEffectSuppressor :
202     VisualInterruptionFilter(types = setOf(PULSE), reason = "suppressed by DND") {
shouldSuppressnull203     override fun shouldSuppress(entry: NotificationEntry) = entry.shouldSuppressAmbient()
204 }
205 
206 class PulseLockscreenVisibilityPrivateSuppressor :
207     VisualInterruptionFilter(
208         types = setOf(PULSE),
209         reason = "hidden by lockscreen visibility override",
210     ) {
211     override fun shouldSuppress(entry: NotificationEntry) =
212         entry.ranking.lockscreenVisibilityOverride == VISIBILITY_PRIVATE
213 }
214 
215 class PulseLowImportanceSuppressor :
216     VisualInterruptionFilter(types = setOf(PULSE), reason = "importance < DEFAULT") {
shouldSuppressnull217     override fun shouldSuppress(entry: NotificationEntry) = entry.importance < IMPORTANCE_DEFAULT
218 }
219 
220 class HunGroupAlertBehaviorSuppressor :
221     VisualInterruptionFilter(
222         types = setOf(PEEK, PULSE),
223         reason = "suppressive group alert behavior",
224     ) {
225     override fun shouldSuppress(entry: NotificationEntry) =
226         entry.sbn.let { it.isGroup && it.notification.suppressAlertingDueToGrouping() }
227 }
228 
229 class HunSilentNotificationSuppressor :
230     VisualInterruptionFilter(types = setOf(PEEK, PULSE), reason = "notification isSilent") {
shouldSuppressnull231     override fun shouldSuppress(entry: NotificationEntry) =
232         entry.sbn.let { Flags.notificationSilentFlag() && it.notification.isSilent }
233 }
234 
235 class HunJustLaunchedFsiSuppressor :
236     VisualInterruptionFilter(types = setOf(PEEK, PULSE), reason = "just launched FSI") {
shouldSuppressnull237     override fun shouldSuppress(entry: NotificationEntry) = entry.hasJustLaunchedFullScreenIntent()
238 }
239 
240 class BubbleNotAllowedSuppressor :
241     VisualInterruptionFilter(types = setOf(BUBBLE), reason = "cannot bubble", isSpammy = true) {
242     override fun shouldSuppress(entry: NotificationEntry) = !entry.canBubble()
243 }
244 
245 class BubbleNoMetadataSuppressor :
246     VisualInterruptionFilter(types = setOf(BUBBLE), reason = "has no or invalid bubble metadata") {
247 
isValidMetadatanull248     private fun isValidMetadata(metadata: BubbleMetadata?) =
249         metadata != null && (metadata.intent != null || metadata.shortcutId != null)
250 
251     override fun shouldSuppress(entry: NotificationEntry) = !isValidMetadata(entry.bubbleMetadata)
252 }
253 
254 class AlertAppSuspendedSuppressor :
255     VisualInterruptionFilter(types = setOf(PEEK, PULSE, BUBBLE), reason = "app is suspended") {
256     override fun shouldSuppress(entry: NotificationEntry) = entry.ranking.isSuspended
257 }
258 
259 class AlertKeyguardVisibilitySuppressor(
260     private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider
261 ) : VisualInterruptionFilter(types = setOf(PEEK, PULSE, BUBBLE), reason = "hidden on keyguard") {
shouldSuppressnull262     override fun shouldSuppress(entry: NotificationEntry) =
263         keyguardNotificationVisibilityProvider.shouldHideNotification(entry)
264 }
265 
266 /**
267  * Set with:
268  *
269  * adb shell setprop persist.force_show_avalanche_edu_once 1 && adb shell stop; adb shell start
270  */
271 private const val FORCE_SHOW_AVALANCHE_EDU_ONCE = "persist.force_show_avalanche_edu_once"
272 
273 private const val PREF_HAS_SEEN_AVALANCHE_EDU = "has_seen_avalanche_edu"
274 
275 class AvalancheSuppressor(
276     private val avalancheProvider: AvalancheProvider,
277     private val systemClock: SystemClock,
278     private val settingsInteractor: NotificationSettingsInteractor,
279     private val packageManager: PackageManager,
280     private val uiEventLogger: UiEventLogger,
281     private val context: Context,
282     private val notificationManager: NotificationManager,
283     private val systemSettings: SystemSettings,
284 ) : VisualInterruptionFilter(types = setOf(PEEK, PULSE), reason = "avalanche") {
285     val TAG = "AvalancheSuppressor"
286 
287     private val prefs = context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
288 
289     // SharedPreferences are persisted across reboots
290     var hasSeenEdu: Boolean
291         get() = prefs.getBoolean(PREF_HAS_SEEN_AVALANCHE_EDU, false)
292         set(value) = prefs.edit().putBoolean(PREF_HAS_SEEN_AVALANCHE_EDU, value).apply()
293 
294     // Reset on reboot.
295     // The pipeline runs these suppressors many times very fast, so we must use a separate bool
296     // to force show for debug so that phone does not get stuck sending out infinite number of
297     // education HUNs.
298     private var hasShownOnceForDebug = false
299 
300     // Sometimes the kotlin flow value is false even when the cooldown setting is true (b/356768397)
301     // so let's directly check settings until we confirm that the flow is initialized and in sync
302     // with the real settings value.
303     private var isCooldownFlowInSync = false
304 
305     private fun shouldShowEdu(): Boolean {
306         val forceShowOnce = SystemProperties.get(FORCE_SHOW_AVALANCHE_EDU_ONCE, "").equals("1")
307         return !hasSeenEdu || (forceShowOnce && !hasShownOnceForDebug)
308     }
309 
310     enum class State {
311         ALLOW_CONVERSATION_AFTER_AVALANCHE,
312         ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME,
313         ALLOW_CALLSTYLE,
314         ALLOW_CATEGORY_REMINDER,
315         ALLOW_CATEGORY_EVENT,
316         ALLOW_CATEGORY_ALARM,
317         ALLOW_CATEGORY_CAR_EMERGENCY,
318         ALLOW_CATEGORY_CAR_WARNING,
319         ALLOW_FSI_WITH_PERMISSION_ON,
320         ALLOW_COLORIZED,
321         ALLOW_EMERGENCY,
322         SUPPRESS,
323     }
324 
325     enum class AvalancheEvent(private val id: Int) : UiEventLogger.UiEventEnum {
326         @UiEvent(doc = "An avalanche event occurred, and a suppression period will start now.")
327         AVALANCHE_SUPPRESSOR_RECEIVED_TRIGGERING_EVENT(1824),
328         @UiEvent(doc = "HUN was suppressed in avalanche.")
329         AVALANCHE_SUPPRESSOR_HUN_SUPPRESSED(1825),
330         @UiEvent(doc = "HUN allowed during avalanche because conversation newer than the trigger.")
331         AVALANCHE_SUPPRESSOR_HUN_ALLOWED_NEW_CONVERSATION(1826),
332         @UiEvent(doc = "HUN allowed during avalanche because it is a priority conversation.")
333         AVALANCHE_SUPPRESSOR_HUN_ALLOWED_PRIORITY_CONVERSATION(1827),
334         @UiEvent(doc = "HUN allowed during avalanche because it is a CallStyle notification.")
335         AVALANCHE_SUPPRESSOR_HUN_ALLOWED_CALL_STYLE(1828),
336         @UiEvent(doc = "HUN allowed during avalanche because it is a reminder notification.")
337         AVALANCHE_SUPPRESSOR_HUN_ALLOWED_CATEGORY_REMINDER(1829),
338         @UiEvent(doc = "HUN allowed during avalanche because it is a calendar notification.")
339         AVALANCHE_SUPPRESSOR_HUN_ALLOWED_CATEGORY_EVENT(1830),
340         @UiEvent(doc = "HUN allowed during avalanche because it has FSI.")
341         AVALANCHE_SUPPRESSOR_HUN_ALLOWED_FSI_WITH_PERMISSION(1831),
342         @UiEvent(doc = "HUN allowed during avalanche because it is colorized.")
343         AVALANCHE_SUPPRESSOR_HUN_ALLOWED_COLORIZED(1832),
344         @UiEvent(doc = "HUN allowed during avalanche because it is an emergency notification.")
345         AVALANCHE_SUPPRESSOR_HUN_ALLOWED_EMERGENCY(1833),
346         @UiEvent(doc = "HUN allowed during avalanche because it is an alarm.")
347         AVALANCHE_SUPPRESSOR_HUN_ALLOWED_CATEGORY_ALARM(1867),
348         @UiEvent(doc = "HUN allowed during avalanche because it is a car emergency.")
349         AVALANCHE_SUPPRESSOR_HUN_ALLOWED_CATEGORY_CAR_EMERGENCY(1868),
350         @UiEvent(doc = "HUN allowed during avalanche because it is a car warning")
351         AVALANCHE_SUPPRESSOR_HUN_ALLOWED_CATEGORY_CAR_WARNING(1869);
352 
353         override fun getId(): Int {
354             return id
355         }
356     }
357 
358     override fun shouldSuppress(entry: NotificationEntry): Boolean {
359         if (!isCooldownEnabled()) {
360             return false
361         }
362         val timeSinceAvalancheMs = systemClock.currentTimeMillis() - avalancheProvider.startTime
363         val timedOut = timeSinceAvalancheMs >= avalancheProvider.timeoutMs
364         if (timedOut) {
365             return false
366         }
367         val state = calculateState(entry)
368         if (state != State.SUPPRESS) {
369             return false
370         }
371         if (shouldShowEdu()) {
372             showEdu()
373         }
374         return true
375     }
376 
377     /** Show avalanche education HUN from SystemUI. */
378     private fun showEdu() {
379         val res = context.resources
380         val titleStr =
381             res.getString(com.android.systemui.res.R.string.adaptive_notification_edu_hun_title)
382         val textStr =
383             res.getString(com.android.systemui.res.R.string.adaptive_notification_edu_hun_text)
384         val actionStr =
385             res.getString(com.android.systemui.res.R.string.go_to_adaptive_notification_settings)
386 
387         val intent = Intent(Settings.ACTION_MANAGE_ADAPTIVE_NOTIFICATIONS)
388         val pendingIntent =
389             PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
390 
391         // Replace "System UI" app name with "Android System"
392         val bundle = Bundle()
393         bundle.putString(
394             Notification.EXTRA_SUBSTITUTE_APP_NAME,
395             context.getString(com.android.internal.R.string.android_system_label),
396         )
397 
398         val builder =
399             Notification.Builder(context, NotificationChannels.ALERTS)
400                 .setTicker(titleStr)
401                 .setContentTitle(titleStr)
402                 .setContentText(textStr)
403                 .setStyle(Notification.BigTextStyle().bigText(textStr))
404                 .setSmallIcon(com.android.systemui.res.R.drawable.ic_settings)
405                 .setCategory(Notification.CATEGORY_SYSTEM)
406                 .setTimeoutAfter(/* one day in ms */ 24 * 60 * 60 * 1000L)
407                 .setAutoCancel(true)
408                 .addAction(android.R.drawable.button_onoff_indicator_off, actionStr, pendingIntent)
409                 .setContentIntent(pendingIntent)
410                 .addExtras(bundle)
411 
412         notificationManager.notify(SystemMessage.NOTE_ADAPTIVE_NOTIFICATIONS, builder.build())
413         hasSeenEdu = true
414         hasShownOnceForDebug = true
415     }
416 
417     private fun calculateState(entry: NotificationEntry): State {
418         if (
419             entry.ranking.isConversation &&
420                 entry.sbn.notification.getWhen() > avalancheProvider.startTime
421         ) {
422             uiEventLogger.log(AvalancheEvent.AVALANCHE_SUPPRESSOR_HUN_ALLOWED_NEW_CONVERSATION)
423             return State.ALLOW_CONVERSATION_AFTER_AVALANCHE
424         }
425 
426         if (entry.channel?.isImportantConversation == true) {
427             uiEventLogger.log(AvalancheEvent.AVALANCHE_SUPPRESSOR_HUN_ALLOWED_PRIORITY_CONVERSATION)
428             return State.ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME
429         }
430 
431         if (entry.sbn.notification.isStyle(Notification.CallStyle::class.java)) {
432             uiEventLogger.log(AvalancheEvent.AVALANCHE_SUPPRESSOR_HUN_ALLOWED_CALL_STYLE)
433             return State.ALLOW_CALLSTYLE
434         }
435 
436         if (entry.sbn.notification.category == CATEGORY_REMINDER) {
437             uiEventLogger.log(AvalancheEvent.AVALANCHE_SUPPRESSOR_HUN_ALLOWED_CATEGORY_REMINDER)
438             return State.ALLOW_CATEGORY_REMINDER
439         }
440 
441         if (entry.sbn.notification.category == CATEGORY_ALARM) {
442             uiEventLogger.log(AvalancheEvent.AVALANCHE_SUPPRESSOR_HUN_ALLOWED_CATEGORY_ALARM)
443             return State.ALLOW_CATEGORY_ALARM
444         }
445 
446         if (entry.sbn.notification.category == CATEGORY_CAR_EMERGENCY) {
447             uiEventLogger.log(
448                 AvalancheEvent.AVALANCHE_SUPPRESSOR_HUN_ALLOWED_CATEGORY_CAR_EMERGENCY
449             )
450             return State.ALLOW_CATEGORY_CAR_EMERGENCY
451         }
452 
453         if (entry.sbn.notification.category == CATEGORY_CAR_WARNING) {
454             uiEventLogger.log(AvalancheEvent.AVALANCHE_SUPPRESSOR_HUN_ALLOWED_CATEGORY_CAR_WARNING)
455             return State.ALLOW_CATEGORY_CAR_WARNING
456         }
457 
458         if (entry.sbn.notification.category == CATEGORY_EVENT) {
459             uiEventLogger.log(AvalancheEvent.AVALANCHE_SUPPRESSOR_HUN_ALLOWED_CATEGORY_EVENT)
460             return State.ALLOW_CATEGORY_EVENT
461         }
462 
463         if (entry.sbn.notification.fullScreenIntent != null) {
464             uiEventLogger.log(AvalancheEvent.AVALANCHE_SUPPRESSOR_HUN_ALLOWED_FSI_WITH_PERMISSION)
465             return State.ALLOW_FSI_WITH_PERMISSION_ON
466         }
467         if (entry.sbn.notification.isColorized) {
468             uiEventLogger.log(AvalancheEvent.AVALANCHE_SUPPRESSOR_HUN_ALLOWED_COLORIZED)
469             return State.ALLOW_COLORIZED
470         }
471         if (
472             packageManager.checkPermission(RECEIVE_EMERGENCY_BROADCAST, entry.sbn.packageName) ==
473                 PERMISSION_GRANTED
474         ) {
475             uiEventLogger.log(AvalancheEvent.AVALANCHE_SUPPRESSOR_HUN_ALLOWED_EMERGENCY)
476             return State.ALLOW_EMERGENCY
477         }
478         uiEventLogger.log(AvalancheEvent.AVALANCHE_SUPPRESSOR_HUN_SUPPRESSED)
479         return State.SUPPRESS
480     }
481 
482     private fun isCooldownEnabled(): Boolean {
483         val isEnabledFromFlow = settingsInteractor.isCooldownEnabled.value
484         if (isCooldownFlowInSync) {
485             return isEnabledFromFlow
486         }
487         val isEnabled =
488             systemSettings.getInt(Settings.System.NOTIFICATION_COOLDOWN_ENABLED, /* def */ 1) == 1
489         if (isEnabled == isEnabledFromFlow) {
490             isCooldownFlowInSync = true
491         }
492         return isEnabled
493     }
494 }
495