Add notification drawer visibility controls
This commit is contained in:
+7
@@ -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())
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
|
||||
|
||||
@@ -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<View, RowState>()
|
||||
private val groupOrders = WeakHashMap<View, List<View>>()
|
||||
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<View>
|
||||
@@ -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) {
|
||||
|
||||
+41
-19
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/visibility_card_background" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/visibility_card_outline" />
|
||||
<corners android:radius="6dp" />
|
||||
</shape>
|
||||
@@ -51,6 +51,12 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Unlocked status bar" />
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/show_drawer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Notification drawer" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="Icons represent AOD, Lockscreen and Unlocked, respectively. Tap to toggle visibility. Tap the app for more advanced options."
|
||||
android:text="Icons represent AOD, Lockscreen, Unlocked and the notification drawer, respectively. Tap to toggle visibility. Tap the app for more advanced options."
|
||||
android:textAppearance="?attr/textAppearanceCaption" />
|
||||
|
||||
<EditText
|
||||
|
||||
@@ -92,6 +92,35 @@
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/visibility_drawer_cell"
|
||||
android:layout_width="38dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:foreground="?attr/selectableItemBackgroundBorderless">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/visibility_drawer_icon"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="28dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/bg_visibility_drawer_card"
|
||||
android:contentDescription="Notification drawer visibility"
|
||||
android:padding="4dp"
|
||||
android:scaleType="fitCenter" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/visibility_drawer_cross"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:text="✕"
|
||||
android:textColor="@color/visibility_hidden_cross"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/visibility_app_text"
|
||||
android:layout_width="0dp"
|
||||
|
||||
@@ -115,6 +115,34 @@
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/drawer_cell"
|
||||
android:layout_width="38dp"
|
||||
android:layout_height="40dp"
|
||||
android:foreground="?attr/selectableItemBackgroundBorderless">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/drawer_icon"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="28dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/bg_visibility_drawer_card"
|
||||
android:contentDescription="Notification drawer visibility"
|
||||
android:padding="4dp"
|
||||
android:scaleType="fitCenter" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/drawer_cross"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:text="✕"
|
||||
android:textColor="@color/visibility_hidden_cross"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -17,4 +17,6 @@
|
||||
<color name="app_filter_override_icon">#B8CCFF</color>
|
||||
<color name="visibility_hidden_cross">#FF8A9A</color>
|
||||
<color name="visibility_edited_background">#24294775</color>
|
||||
<color name="visibility_card_background">#FF3C4043</color>
|
||||
<color name="visibility_card_outline">#99FFFFFF</color>
|
||||
</resources>
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
<color name="app_filter_override_icon">#34588E</color>
|
||||
<color name="visibility_hidden_cross">#B00020</color>
|
||||
<color name="visibility_edited_background">#183B5C9C</color>
|
||||
<color name="visibility_card_background">#FFF3F3F3</color>
|
||||
<color name="visibility_card_outline">#61000000</color>
|
||||
<color name="notification_access_granted">#2E7D32</color>
|
||||
<color name="notification_access_missing">#B3261E</color>
|
||||
<color name="launcher_icon_background">#D9E2FF</color>
|
||||
|
||||
+2
-2
@@ -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)),
|
||||
),
|
||||
)),
|
||||
)
|
||||
|
||||
+8
-2
@@ -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`() {
|
||||
|
||||
Reference in New Issue
Block a user