diff --git a/README.md b/README.md index dee9039..7ad1b9a 100644 --- a/README.md +++ b/README.md @@ -58,10 +58,11 @@ force direct custom sound or vibration through Do Not Disturb. ## Notification visibility -The optional LSPosed integration can hide an app's notification icons -independently from the unlocked status bar, collapsed lockscreen, and -always-on display. Text exceptions can override an app's default on individual -notifications. Pull-down notification cards remain available. +The optional LSPosed integration can hide an app's notifications independently +from the unlocked status bar, collapsed lockscreen, always-on display, and +notification drawer. The drawer choice is shared between its locked and +unlocked forms. Text exceptions can override an app's default for individual +notifications; hiding a drawer card does not dismiss the notification. On OneUI, Notifications Master can also lay out the extra notification icons in Cards mode, with separate lockscreen and always-on-display controls for row diff --git a/app/src/androidTest/java/se/ajpanton/notificationsmaster/visibility/VisibilityModuleFixtureTest.kt b/app/src/androidTest/java/se/ajpanton/notificationsmaster/visibility/VisibilityModuleFixtureTest.kt index 0a7ba2c..bd54e68 100644 --- a/app/src/androidTest/java/se/ajpanton/notificationsmaster/visibility/VisibilityModuleFixtureTest.kt +++ b/app/src/androidTest/java/se/ajpanton/notificationsmaster/visibility/VisibilityModuleFixtureTest.kt @@ -21,6 +21,13 @@ class VisibilityModuleFixtureTest { )))) } + @Test fun blockHelperInDrawerOnly() { + requireFixtureMode() + store.save(VisibilityPolicy(apps = listOf( + AppVisibilityPolicy(HELPER_PACKAGE, setOf(NotificationSurface.DRAWER)), + ))) + } + @Test fun allowHelper() { requireFixtureMode() store.save(VisibilityPolicy()) diff --git a/app/src/main/java/se/ajpanton/notificationsmaster/AppVisibilityFragment.kt b/app/src/main/java/se/ajpanton/notificationsmaster/AppVisibilityFragment.kt index 6b7e7e8..8bec5b3 100644 --- a/app/src/main/java/se/ajpanton/notificationsmaster/AppVisibilityFragment.kt +++ b/app/src/main/java/se/ajpanton/notificationsmaster/AppVisibilityFragment.kt @@ -85,6 +85,7 @@ class AppVisibilityFragment : Fragment(R.layout.fragment_app_visibility) { bindSurface(binding!!.showAod, NotificationSurface.AOD, policy) bindSurface(binding!!.showLockscreen, NotificationSurface.LOCKSCREEN_COLLAPSED, policy) bindSurface(binding!!.showUnlocked, NotificationSurface.UNLOCKED_STATUSBAR, policy) + bindSurface(binding!!.showDrawer, NotificationSurface.DRAWER, policy) adapter.submit(policy.exceptions) } @@ -204,6 +205,7 @@ class AppVisibilityFragment : Fragment(R.layout.fragment_app_visibility) { rule, NotificationSurface.UNLOCKED_STATUSBAR, ) + bindSurface(binding.drawerIcon, binding.drawerCross, rule, NotificationSurface.DRAWER) binding.aodCell.setOnClickListener { toggle(holder, NotificationSurface.AOD) } binding.lockscreenCell.setOnClickListener { toggle(holder, NotificationSurface.LOCKSCREEN_COLLAPSED) @@ -211,6 +213,7 @@ class AppVisibilityFragment : Fragment(R.layout.fragment_app_visibility) { binding.unlockedCell.setOnClickListener { toggle(holder, NotificationSurface.UNLOCKED_STATUSBAR) } + binding.drawerCell.setOnClickListener { toggle(holder, NotificationSurface.DRAWER) } binding.edit.setOnClickListener { holder.positionOrNull()?.let(edit) } binding.delete.setOnClickListener { holder.positionOrNull()?.let(delete) } binding.dragHandle.setOnTouchListener { _, event -> diff --git a/app/src/main/java/se/ajpanton/notificationsmaster/NotificationVisibilityFragment.kt b/app/src/main/java/se/ajpanton/notificationsmaster/NotificationVisibilityFragment.kt index 5491b6f..e694a05 100644 --- a/app/src/main/java/se/ajpanton/notificationsmaster/NotificationVisibilityFragment.kt +++ b/app/src/main/java/se/ajpanton/notificationsmaster/NotificationVisibilityFragment.kt @@ -188,6 +188,12 @@ class NotificationVisibilityFragment : Fragment(R.layout.fragment_notification_v row, NotificationSurface.UNLOCKED_STATUSBAR, ) + bindSurface( + binding.visibilityDrawerIcon, + binding.visibilityDrawerCross, + row, + NotificationSurface.DRAWER, + ) binding.visibilityAodCell.setOnClickListener { toggle(row, NotificationSurface.AOD) } binding.visibilityLockscreenCell.setOnClickListener { toggle(row, NotificationSurface.LOCKSCREEN_COLLAPSED) @@ -195,6 +201,9 @@ class NotificationVisibilityFragment : Fragment(R.layout.fragment_notification_v binding.visibilityUnlockedCell.setOnClickListener { toggle(row, NotificationSurface.UNLOCKED_STATUSBAR) } + binding.visibilityDrawerCell.setOnClickListener { + toggle(row, NotificationSurface.DRAWER) + } binding.visibilityAppRoot.setOnClickListener { open(row.app.app) } } diff --git a/app/src/main/java/se/ajpanton/notificationsmaster/module/AospLockscreenBackend.kt b/app/src/main/java/se/ajpanton/notificationsmaster/module/AospLockscreenBackend.kt index c1a3b40..48e1d02 100644 --- a/app/src/main/java/se/ajpanton/notificationsmaster/module/AospLockscreenBackend.kt +++ b/app/src/main/java/se/ajpanton/notificationsmaster/module/AospLockscreenBackend.kt @@ -12,7 +12,7 @@ import java.lang.reflect.Method import java.util.Collections import java.util.WeakHashMap -/** Hides blocked rows only in AOSP's collapsed lockscreen notification stack. */ +/** Filters AOSP notification rows for the collapsed lockscreen or notification drawer. */ internal class AospLockscreenBackend( private val framework: XposedInterface, ) : VisibilitySurfaceBackend { @@ -21,6 +21,7 @@ internal class AospLockscreenBackend( private val hiddenRows = WeakHashMap() private val groupOrders = WeakHashMap>() private lateinit var onKeyguard: Method + private var stackExpanded: Field? = null private lateinit var entryForRow: (View) -> Any? private lateinit var sbnForEntry: (Any) -> StatusBarNotification? private lateinit var attachedChildrenForRow: (View) -> List @@ -42,6 +43,7 @@ internal class AospLockscreenBackend( it.returnType == Boolean::class.javaPrimitiveType }?.also { it.isAccessible = true } ?: error("AOSP lockscreen state method not found") + stackExpanded = findField(stackClass, "mIsExpanded") prepareNotificationAccess(rowClass) installShelfFilter(classLoader) @@ -73,7 +75,9 @@ internal class AospLockscreenBackend( chain.proceed() } } - stackClass.declaredMethods.filter { it.name == "setStatusBarState" }.forEach { method -> + stackClass.declaredMethods.filter { + it.name == "setStatusBarState" || it.name == "setIsExpanded" + }.forEach { method -> method.isAccessible = true framework.hook(method).intercept { chain -> val result = chain.proceed() @@ -85,7 +89,7 @@ internal class AospLockscreenBackend( } } }.onSuccess { - Log.i(TAG, "Installed AOSP collapsed-lockscreen notification backend") + Log.i(TAG, "Installed AOSP lockscreen/drawer notification backend") }.onFailure { Log.w(TAG, "AOSP lockscreen shape is unsupported; leaving rows unchanged", it) }.isSuccess @@ -147,11 +151,11 @@ internal class AospLockscreenBackend( ?: return@intercept chain.proceed() if (container.parent?.javaClass?.name != SHELF) return@intercept chain.proceed() synchronized(shelves) { shelves += container } - if (!isCollapsedLockscreen(container)) return@intercept chain.proceed() + val surface = surfaceFor(container) ?: return@intercept chain.proceed() val children = List(container.childCount, container::getChildAt) val states = shelfIconStates.get(container) as? Map<*, *> ?: return@intercept chain.proceed() - val blocked = children.filter(::isBlockedShelfIcon) + val blocked = children.filter { isBlockedShelfIcon(it, surface) } blocked.forEach { states[it]?.let(iconState::hide) } children.filterNot(blocked::contains).forEach(container::bringChildToFront) val result = try { @@ -169,22 +173,27 @@ internal class AospLockscreenBackend( } private fun applyStack(stack: ViewGroup) { - val active = isCollapsedLockscreen(stack) - repeat(stack.childCount) { applyRow(stack, stack.getChildAt(it), active) } + val surface = surfaceFor(stack) + repeat(stack.childCount) { applyRow(stack.getChildAt(it), surface) } stack.requestLayout() } - private fun applyRow(stack: ViewGroup, row: View, active: Boolean? = null) { + private fun applyRow(stack: ViewGroup, row: View) = applyRow(row, surfaceFor(stack)) + + private fun applyRow(row: View, surface: NotificationSurface?) { if (row.javaClass.name != ROW) return - val filter = active ?: isCollapsedLockscreen(stack) - if (!filter) restoreGroupOrder(row) + if (surface == null) { + restoreTree(row) + return + } + restoreGroupOrder(row) val children = attachedChildrenForRow(row) - val parentBlocked = filter && isBlocked(row) - var blockedChildren = children.map { it to (parentBlocked && isBlocked(it)) } - if (parentBlocked && children.isNotEmpty()) { + val parentBlocked = isBlocked(row, surface) + val blockedChildren = children.map { it to isBlocked(it, surface) } + if (blockedChildren.any { it.second }) { groupOrders.putIfAbsent(row, children) - blockedChildren = blockedChildren.partition { !it.second }.let { it.first + it.second } - reorderChildren(row, blockedChildren.map { it.first }) + val ordered = blockedChildren.partition { !it.second }.let { it.first + it.second } + reorderChildren(row, ordered.map { it.first }) setGroupCount(row, blockedChildren.count { !it.second }) } blockedChildren.forEach { (child, blocked) -> @@ -193,18 +202,19 @@ internal class AospLockscreenBackend( if (parentBlocked && blockedChildren.all { it.second }) hideRow(row) else restoreRow(row) } - private fun isBlocked(row: View): Boolean { + private fun isBlocked(row: View, surface: NotificationSurface): Boolean { val sbn = runCatching { entryForRow(row)?.let(sbnForEntry) }.getOrNull() ?: return false - return isBlocked(sbn) + return isBlocked(sbn, surface) } - private fun isBlockedShelfIcon(icon: View): Boolean { + private fun isBlockedShelfIcon(icon: View, surface: NotificationSurface): Boolean { if (icon.javaClass.name != STATUS_BAR_ICON_VIEW) return false val sbn = runCatching { shelfNotification.get(icon) as? StatusBarNotification }.getOrNull() ?: return false val row = rowForKey(icon, sbn.key) - return row?.let { isBlocked(it) && attachedChildrenForRow(it).none { child -> !isBlocked(child) } } - ?: isBlocked(sbn) + return row?.let { + isBlocked(it, surface) && attachedChildrenForRow(it).none { child -> !isBlocked(child, surface) } + } ?: isBlocked(sbn, surface) } private fun rowForKey(view: View, key: String): View? { @@ -219,21 +229,27 @@ internal class AospLockscreenBackend( } } - private fun isBlocked(sbn: StatusBarNotification): Boolean = + private fun isBlocked(sbn: StatusBarNotification, surface: NotificationSurface): Boolean = ProcessVisibilityPolicyCache.isBlocked( VisibilityNotificationExtractor.from(sbn), - NotificationSurface.LOCKSCREEN_COLLAPSED, + surface, ).also { blocked -> if (blocked && !blockedObserved) { blockedObserved = true - Log.i(TAG, "Filtered an AOSP collapsed-lockscreen notification") + Log.i(TAG, "Filtered an AOSP notification from $surface") } } - private fun isCollapsedLockscreen(view: View): Boolean { + private fun surfaceFor(view: View): NotificationSurface? { val root = view.rootView - val stack = synchronized(stacks) { stacks.firstOrNull { it.rootView === root } } ?: return false - return runCatching { onKeyguard.invoke(stack) as Boolean }.getOrDefault(false) + val stack = synchronized(stacks) { stacks.firstOrNull { it.rootView === root } } ?: return null + return runCatching { + when { + onKeyguard.invoke(stack) as Boolean -> NotificationSurface.LOCKSCREEN_COLLAPSED + stackExpanded?.getBoolean(stack) == true -> NotificationSurface.DRAWER + else -> null + } + }.getOrNull() } private fun hideRow(row: View) { diff --git a/app/src/main/java/se/ajpanton/notificationsmaster/module/OneUiLockscreenCardsBackend.kt b/app/src/main/java/se/ajpanton/notificationsmaster/module/OneUiLockscreenCardsBackend.kt index 13f4d05..290f980 100644 --- a/app/src/main/java/se/ajpanton/notificationsmaster/module/OneUiLockscreenCardsBackend.kt +++ b/app/src/main/java/se/ajpanton/notificationsmaster/module/OneUiLockscreenCardsBackend.kt @@ -12,7 +12,7 @@ import java.util.Collections import java.util.WeakHashMap import java.util.function.Function -/** Hides blocked OneUI notification rows only on the collapsed cards lockscreen. */ +/** Filters OneUI notification rows in Cards-mode lockscreen and the notification drawer. */ internal class OneUiLockscreenCardsBackend( private val framework: XposedInterface, ) : VisibilitySurfaceBackend { @@ -21,6 +21,7 @@ internal class OneUiLockscreenCardsBackend( private val gridRenderer = OneUiCardsGridRenderer() private lateinit var entryForRow: (View) -> Any? private lateinit var sbnForEntry: (Any) -> StatusBarNotification? + private var stackExpanded: Field? = null private lateinit var shelfNotification: Field private lateinit var shelfIconStates: Field private lateinit var iconState: IconStateAccess @@ -36,6 +37,7 @@ internal class OneUiLockscreenCardsBackend( val stackClass = Class.forName(STACK, false, classLoader) val rowClass = Class.forName(ROW, false, classLoader) val stateController = Class.forName(STATE_CONTROLLER, false, classLoader) + stackExpanded = findField(stackClass, "mIsExpanded") prepareNotificationAccess(rowClass) installShelfFilter(classLoader) installGridSourceHook(classLoader) @@ -98,7 +100,7 @@ internal class OneUiLockscreenCardsBackend( } } }.onSuccess { - Log.i(TAG, "Installed OneUI lockscreen-cards notification backend") + Log.i(TAG, "Installed OneUI lockscreen-cards/drawer notification backend") }.onFailure { Log.d(TAG, "OneUI lockscreen-cards boundary is unavailable", it) }.isSuccess @@ -141,11 +143,12 @@ internal class OneUiLockscreenCardsBackend( if (isDozeActive() && isCardsMode(container)) { gridRenderer.suspend(container, true) } - if (!shouldFilterLockscreenCards(container)) return@intercept chain.proceed() + val surface = rowSurface(container) ?: return@intercept chain.proceed() + val lockscreenCards = surface == NotificationSurface.LOCKSCREEN_COLLAPSED val children = List(container.childCount, container::getChildAt) val states = shelfIconStates.get(container) as? Map<*, *> ?: return@intercept chain.proceed() - val blocked = children.filter(::isBlockedShelfIcon) + val blocked = children.filter { isBlockedShelfIcon(it, surface) } blocked.forEach { icon -> states[icon]?.let(iconState::hide) } children.filterNot(blocked::contains).forEach(container::bringChildToFront) val result = try { @@ -153,10 +156,13 @@ internal class OneUiLockscreenCardsBackend( } finally { children.forEach(container::bringChildToFront) } - // OneUI applies these states after this calculation returns. Suppress every - // stock shelf icon in Cards mode; the renderer owns the supplemental grid. - children.forEach { icon -> states[icon]?.let(iconState::hide) } - if (!nativeShelfSuppressedObserved) { + // OneUI applies these states after this calculation returns. In Cards mode, + // the renderer owns the whole supplemental shelf; in the drawer, only hide + // icons whose notification rows are hidden. + (if (lockscreenCards) children else blocked).forEach { icon -> + states[icon]?.let(iconState::hide) + } + if (lockscreenCards && !nativeShelfSuppressedObserved) { nativeShelfSuppressedObserved = true Log.i(TAG, "Suppressed the native OneUI Cards shelf icon states") } @@ -228,19 +234,32 @@ internal class OneUiLockscreenCardsBackend( } private fun applyStack(stack: ViewGroup) { - val active = shouldFilterLockscreenCards(stack) + val surface = rowSurface(stack) repeat(stack.childCount) { index -> val row = stack.getChildAt(index) if (row.javaClass.name == ROW) { - if (active && isBlocked(row)) hideRow(row) else restoreRow(row) + if (surface != null && isBlocked(row, surface)) hideRow(row) else restoreRow(row) } } } private fun applyRow(row: View) { val stack = row.parent as? ViewGroup ?: return - val active = shouldFilterLockscreenCards(stack) - if (active && isBlocked(row)) hideRow(row) else restoreRow(row) + val surface = rowSurface(stack) + if (surface != null && isBlocked(row, surface)) hideRow(row) else restoreRow(row) + } + + private fun rowSurface(view: View): NotificationSurface? = when { + shouldFilterLockscreenCards(view) -> NotificationSurface.LOCKSCREEN_COLLAPSED + statusBarStateKnown && statusBarState != KEYGUARD && !isDozeActive() && isStackExpanded(view) -> + NotificationSurface.DRAWER + else -> null + } + + private fun isStackExpanded(view: View): Boolean { + val root = view.rootView + val stack = synchronized(stacks) { stacks.firstOrNull { it.rootView === root } } ?: return false + return runCatching { stackExpanded?.getBoolean(stack) == true }.getOrDefault(false) } private fun isLockscreenCards(view: View) = @@ -257,26 +276,29 @@ internal class OneUiLockscreenCardsBackend( Settings.System.getInt(view.context.contentResolver, LOCKSCREEN_STYLE) == CARDS }.getOrDefault(false) - private fun isBlocked(row: View): Boolean { + private fun isBlocked(row: View, surface: NotificationSurface): Boolean { val sbn = runCatching { entryForRow(row)?.let(sbnForEntry) }.getOrNull() ?: return false - return isBlocked(sbn) + return isBlocked(sbn, surface) } - private fun isBlockedShelfIcon(icon: View): Boolean { + private fun isBlockedShelfIcon(icon: View, surface: NotificationSurface): Boolean { if (icon.javaClass.name != STATUS_BAR_ICON_VIEW) return false val sbn = runCatching { shelfNotification.get(icon) as? StatusBarNotification }.getOrNull() ?: return false - return isBlocked(sbn) + return isBlocked(sbn, surface) } - private fun isBlocked(sbn: StatusBarNotification): Boolean { + private fun isBlocked(sbn: StatusBarNotification) = + isBlocked(sbn, NotificationSurface.LOCKSCREEN_COLLAPSED) + + private fun isBlocked(sbn: StatusBarNotification, surface: NotificationSurface): Boolean { return ProcessVisibilityPolicyCache.isBlocked( VisibilityNotificationExtractor.from(sbn), - NotificationSurface.LOCKSCREEN_COLLAPSED, + surface, ).also { blocked -> if (blocked && !blockedObserved) { blockedObserved = true - Log.i(TAG, "Filtered a OneUI lockscreen notification card") + Log.i(TAG, "Filtered a OneUI notification from $surface") } } } diff --git a/app/src/main/java/se/ajpanton/notificationsmaster/visibility/VisibilityPolicy.kt b/app/src/main/java/se/ajpanton/notificationsmaster/visibility/VisibilityPolicy.kt index 18e743b..7ec96f3 100644 --- a/app/src/main/java/se/ajpanton/notificationsmaster/visibility/VisibilityPolicy.kt +++ b/app/src/main/java/se/ajpanton/notificationsmaster/visibility/VisibilityPolicy.kt @@ -2,7 +2,7 @@ package se.ajpanton.notificationsmaster.visibility import com.google.re2j.Pattern -enum class NotificationSurface { AOD, LOCKSCREEN_COLLAPSED, UNLOCKED_STATUSBAR } +enum class NotificationSurface { AOD, LOCKSCREEN_COLLAPSED, UNLOCKED_STATUSBAR, DRAWER } data class CardsGridSettings( val maxRows: Int = 1, diff --git a/app/src/main/res/drawable/bg_visibility_drawer_card.xml b/app/src/main/res/drawable/bg_visibility_drawer_card.xml new file mode 100644 index 0000000..9826a99 --- /dev/null +++ b/app/src/main/res/drawable/bg_visibility_drawer_card.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/app/src/main/res/layout/fragment_app_visibility.xml b/app/src/main/res/layout/fragment_app_visibility.xml index cc5ecaf..1f54a80 100644 --- a/app/src/main/res/layout/fragment_app_visibility.xml +++ b/app/src/main/res/layout/fragment_app_visibility.xml @@ -51,6 +51,12 @@ android:layout_height="wrap_content" android:text="Unlocked status bar" /> + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index 780dc82..8667df0 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -17,4 +17,6 @@ #B8CCFF #FF8A9A #24294775 + #FF3C4043 + #99FFFFFF diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index da0101f..14506fe 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -17,6 +17,8 @@ #34588E #B00020 #183B5C9C + #FFF3F3F3 + #61000000 #2E7D32 #B3261E #D9E2FF diff --git a/app/src/test/java/se/ajpanton/notificationsmaster/visibility/VisibilityPolicyJsonTest.kt b/app/src/test/java/se/ajpanton/notificationsmaster/visibility/VisibilityPolicyJsonTest.kt index 5210b3e..6aa7786 100644 --- a/app/src/test/java/se/ajpanton/notificationsmaster/visibility/VisibilityPolicyJsonTest.kt +++ b/app/src/test/java/se/ajpanton/notificationsmaster/visibility/VisibilityPolicyJsonTest.kt @@ -14,10 +14,10 @@ class VisibilityPolicyJsonTest { systemUiMisc = SystemUiMiscSettings(true, false, true), apps = listOf(AppVisibilityPolicy( packageName = "example.app", - blockedSurfaces = setOf(NotificationSurface.AOD), + blockedSurfaces = setOf(NotificationSurface.AOD, NotificationSurface.DRAWER), exceptions = listOf( VisibilityExceptionRule("first", setOf(NotificationSurface.LOCKSCREEN_COLLAPSED)), - VisibilityExceptionRule("second", emptySet()), + VisibilityExceptionRule("second", setOf(NotificationSurface.DRAWER)), ), )), ) diff --git a/app/src/test/java/se/ajpanton/notificationsmaster/visibility/VisibilityPolicyTest.kt b/app/src/test/java/se/ajpanton/notificationsmaster/visibility/VisibilityPolicyTest.kt index f71b25e..8709776 100644 --- a/app/src/test/java/se/ajpanton/notificationsmaster/visibility/VisibilityPolicyTest.kt +++ b/app/src/test/java/se/ajpanton/notificationsmaster/visibility/VisibilityPolicyTest.kt @@ -30,16 +30,21 @@ class VisibilityPolicyTest { } @Test fun `package defaults are independent by surface`() { - val policy = compiled(defaults = setOf(NotificationSurface.AOD, NotificationSurface.UNLOCKED_STATUSBAR)) + val policy = compiled(defaults = setOf( + NotificationSurface.AOD, + NotificationSurface.UNLOCKED_STATUSBAR, + NotificationSurface.DRAWER, + )) assertTrue(policy.isBlocked(notification, NotificationSurface.AOD)) assertFalse(policy.isBlocked(notification, NotificationSurface.LOCKSCREEN_COLLAPSED)) assertTrue(policy.isBlocked(notification, NotificationSurface.UNLOCKED_STATUSBAR)) + assertTrue(policy.isBlocked(notification, NotificationSurface.DRAWER)) } @Test fun `first matching exception replaces the package default`() { val policy = compiled( - defaults = setOf(NotificationSurface.AOD), + defaults = setOf(NotificationSurface.AOD, NotificationSurface.DRAWER), exceptions = listOf( VisibilityExceptionRule("tomorrow", setOf(NotificationSurface.LOCKSCREEN_COLLAPSED)), VisibilityExceptionRule("Alice", setOf(NotificationSurface.UNLOCKED_STATUSBAR)), @@ -49,6 +54,7 @@ class VisibilityPolicyTest { assertFalse(policy.isBlocked(notification, NotificationSurface.AOD)) assertTrue(policy.isBlocked(notification, NotificationSurface.LOCKSCREEN_COLLAPSED)) assertFalse(policy.isBlocked(notification, NotificationSurface.UNLOCKED_STATUSBAR)) + assertFalse(policy.isBlocked(notification, NotificationSurface.DRAWER)) } @Test fun `exceptions match stable notification metadata`() {