Merge "Secure setting to control seen notif filter" into tm-qpr-dev am: d0fea880e8
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20620796 Change-Id: I6e383f9d28993b2d0d48abd2d4b3e130b3f5117d Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -9513,6 +9513,16 @@ public final class Settings {
|
||||
public static final String LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS =
|
||||
"lock_screen_show_silent_notifications";
|
||||
|
||||
/**
|
||||
* Indicates whether "seen" notifications should be suppressed from the lockscreen.
|
||||
* <p>
|
||||
* Type: int (0 for false, 1 for true)
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final String LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS =
|
||||
"lock_screen_show_only_unseen_notifications";
|
||||
|
||||
/**
|
||||
* Indicates whether snooze options should be shown on notifications
|
||||
* <p>
|
||||
|
||||
@@ -165,6 +165,9 @@
|
||||
<!-- Default for Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS -->
|
||||
<bool name="def_lock_screen_allow_private_notifications">true</bool>
|
||||
|
||||
<!-- Default for Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS -->
|
||||
<bool name="def_lock_screen_show_only_unseen_notifications">false</bool>
|
||||
|
||||
<!-- Default for Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED, 1==on -->
|
||||
<integer name="def_heads_up_enabled">1</integer>
|
||||
|
||||
|
||||
@@ -146,6 +146,7 @@ public class SecureSettings {
|
||||
Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
|
||||
Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
|
||||
Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS,
|
||||
Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
|
||||
Settings.Secure.SHOW_NOTIFICATION_SNOOZE,
|
||||
Settings.Secure.NOTIFICATION_HISTORY_ENABLED,
|
||||
Settings.Secure.ZEN_DURATION,
|
||||
|
||||
@@ -219,6 +219,7 @@ public class SecureSettingsValidators {
|
||||
VALIDATORS.put(Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, BOOLEAN_VALIDATOR);
|
||||
VALIDATORS.put(Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, BOOLEAN_VALIDATOR);
|
||||
VALIDATORS.put(Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, BOOLEAN_VALIDATOR);
|
||||
VALIDATORS.put(Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, BOOLEAN_VALIDATOR);
|
||||
VALIDATORS.put(Secure.SHOW_NOTIFICATION_SNOOZE, BOOLEAN_VALIDATOR);
|
||||
VALIDATORS.put(Secure.NOTIFICATION_HISTORY_ENABLED, BOOLEAN_VALIDATOR);
|
||||
VALIDATORS.put(Secure.ZEN_DURATION, ANY_INTEGER_VALIDATOR);
|
||||
|
||||
@@ -3631,7 +3631,7 @@ public class SettingsProvider extends ContentProvider {
|
||||
}
|
||||
|
||||
private final class UpgradeController {
|
||||
private static final int SETTINGS_VERSION = 211;
|
||||
private static final int SETTINGS_VERSION = 212;
|
||||
|
||||
private final int mUserId;
|
||||
|
||||
@@ -5527,6 +5527,26 @@ public class SettingsProvider extends ContentProvider {
|
||||
}
|
||||
currentVersion = 211;
|
||||
}
|
||||
if (currentVersion == 211) {
|
||||
// Version 211: Set default value for
|
||||
// Secure#LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS
|
||||
final SettingsState secureSettings = getSecureSettingsLocked(userId);
|
||||
final Setting lockScreenUnseenSetting = secureSettings
|
||||
.getSettingLocked(Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS);
|
||||
if (lockScreenUnseenSetting.isNull()) {
|
||||
final boolean defSetting = getContext().getResources()
|
||||
.getBoolean(R.bool.def_lock_screen_show_only_unseen_notifications);
|
||||
secureSettings.insertSettingOverrideableByRestoreLocked(
|
||||
Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
|
||||
defSetting ? "1" : "0",
|
||||
null /* tag */,
|
||||
true /* makeDefault */,
|
||||
SettingsState.SYSTEM_PACKAGE_NAME);
|
||||
}
|
||||
|
||||
currentVersion = 212;
|
||||
}
|
||||
|
||||
// vXXX: Add new settings above this point.
|
||||
|
||||
if (currentVersion != newVersion) {
|
||||
|
||||
@@ -16,8 +16,13 @@
|
||||
|
||||
package com.android.systemui.statusbar.notification.collection.coordinator
|
||||
|
||||
import android.database.ContentObserver
|
||||
import android.os.UserHandle
|
||||
import android.provider.Settings
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.dagger.qualifiers.Background
|
||||
import com.android.systemui.keyguard.data.repository.KeyguardRepository
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController
|
||||
import com.android.systemui.statusbar.StatusBarState
|
||||
@@ -30,11 +35,20 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No
|
||||
import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
|
||||
import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProviderImpl
|
||||
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
|
||||
import com.android.systemui.util.settings.SecureSettings
|
||||
import com.android.systemui.util.settings.SettingsProxy
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
@@ -45,16 +59,19 @@ import kotlinx.coroutines.launch
|
||||
class KeyguardCoordinator
|
||||
@Inject
|
||||
constructor(
|
||||
@Background private val bgDispatcher: CoroutineDispatcher,
|
||||
private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
|
||||
private val keyguardRepository: KeyguardRepository,
|
||||
private val notifPipelineFlags: NotifPipelineFlags,
|
||||
@Application private val scope: CoroutineScope,
|
||||
private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
|
||||
private val secureSettings: SecureSettings,
|
||||
private val seenNotifsProvider: SeenNotificationsProviderImpl,
|
||||
private val statusBarStateController: StatusBarStateController,
|
||||
) : Coordinator {
|
||||
|
||||
private val unseenNotifications = mutableSetOf<NotificationEntry>()
|
||||
private var unseenFilterEnabled = false
|
||||
|
||||
override fun attach(pipeline: NotifPipeline) {
|
||||
setupInvalidateNotifListCallbacks()
|
||||
@@ -71,6 +88,7 @@ constructor(
|
||||
pipeline.addFinalizeFilter(unseenNotifFilter)
|
||||
pipeline.addCollectionListener(collectionListener)
|
||||
scope.launch { clearUnseenWhenKeyguardIsDismissed() }
|
||||
scope.launch { invalidateWhenUnseenSettingChanges() }
|
||||
}
|
||||
|
||||
private suspend fun clearUnseenWhenKeyguardIsDismissed() {
|
||||
@@ -85,6 +103,36 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun invalidateWhenUnseenSettingChanges() {
|
||||
secureSettings
|
||||
// emit whenever the setting has changed
|
||||
.settingChangesForUser(
|
||||
Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
|
||||
UserHandle.USER_ALL,
|
||||
)
|
||||
// perform a query immediately
|
||||
.onStart { emit(Unit) }
|
||||
// for each change, lookup the new value
|
||||
.map {
|
||||
secureSettings.getBoolForUser(
|
||||
Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
|
||||
UserHandle.USER_CURRENT,
|
||||
)
|
||||
}
|
||||
// perform lookups on the bg thread pool
|
||||
.flowOn(bgDispatcher)
|
||||
// only track the most recent emission, if events are happening faster than they can be
|
||||
// consumed
|
||||
.conflate()
|
||||
// update local field and invalidate if necessary
|
||||
.collect { setting ->
|
||||
if (setting != unseenFilterEnabled) {
|
||||
unseenFilterEnabled = setting
|
||||
unseenNotifFilter.invalidateList("unseen setting changed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val collectionListener =
|
||||
object : NotifCollectionListener {
|
||||
override fun onEntryAdded(entry: NotificationEntry) {
|
||||
@@ -112,6 +160,8 @@ constructor(
|
||||
|
||||
override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean =
|
||||
when {
|
||||
// Don't apply filter if the setting is disabled
|
||||
!unseenFilterEnabled -> false
|
||||
// Don't apply filter if the keyguard isn't currently showing
|
||||
!keyguardRepository.isKeyguardShowing() -> false
|
||||
// Don't apply the filter if the notification is unseen
|
||||
@@ -165,3 +215,15 @@ constructor(
|
||||
private val SEEN_TIMEOUT = 5.seconds
|
||||
}
|
||||
}
|
||||
|
||||
private fun SettingsProxy.settingChangesForUser(name: String, userHandle: Int): Flow<Unit> =
|
||||
conflatedCallbackFlow {
|
||||
val observer =
|
||||
object : ContentObserver(null) {
|
||||
override fun onChange(selfChange: Boolean) {
|
||||
trySend(Unit)
|
||||
}
|
||||
}
|
||||
registerContentObserverForUser(name, observer, userHandle)
|
||||
awaitClose { unregisterContentObserver(observer) }
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
package com.android.systemui.statusbar.notification.collection.coordinator
|
||||
|
||||
import android.app.Notification
|
||||
import android.os.UserHandle
|
||||
import android.provider.Settings
|
||||
import android.testing.AndroidTestingRunner
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
@@ -29,6 +31,7 @@ import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
|
||||
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
|
||||
import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
|
||||
import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProvider
|
||||
@@ -38,6 +41,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
|
||||
import com.android.systemui.util.mockito.eq
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.android.systemui.util.mockito.withArgCaptor
|
||||
import com.android.systemui.util.settings.FakeSettings
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
@@ -47,6 +51,8 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentMatchers.same
|
||||
import org.mockito.Mockito.anyString
|
||||
import org.mockito.Mockito.clearInvocations
|
||||
import org.mockito.Mockito.verify
|
||||
import java.util.function.Consumer
|
||||
@@ -175,6 +181,42 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun unseenFilterInvalidatesWhenSettingChanges() {
|
||||
whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
|
||||
|
||||
// GIVEN: Keyguard is not showing
|
||||
keyguardRepository.setKeyguardShowing(false)
|
||||
runKeyguardCoordinatorTest {
|
||||
// GIVEN: A notification is present
|
||||
val fakeEntry = NotificationEntryBuilder().build()
|
||||
collectionListener.onEntryAdded(fakeEntry)
|
||||
|
||||
// GIVEN: The setting for filtering unseen notifications is disabled
|
||||
showOnlyUnseenNotifsOnKeyguardSetting = false
|
||||
|
||||
// GIVEN: The pipeline has registered the unseen filter for invalidation
|
||||
val invalidationListener: Pluggable.PluggableListener<NotifFilter> = mock()
|
||||
unseenFilter.setInvalidationListener(invalidationListener)
|
||||
|
||||
// WHEN: The keyguard is now showing
|
||||
keyguardRepository.setKeyguardShowing(true)
|
||||
testScheduler.runCurrent()
|
||||
|
||||
// THEN: The notification is not filtered out
|
||||
assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
|
||||
|
||||
// WHEN: The secure setting is changed
|
||||
showOnlyUnseenNotifsOnKeyguardSetting = true
|
||||
|
||||
// THEN: The pipeline is invalidated
|
||||
verify(invalidationListener).onPluggableInvalidated(same(unseenFilter), anyString())
|
||||
|
||||
// THEN: The notification is recognized as "seen" and is filtered out.
|
||||
assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun unseenFilterAllowsNewNotif() {
|
||||
whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
|
||||
@@ -276,22 +318,32 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
|
||||
private fun runKeyguardCoordinatorTest(
|
||||
testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit
|
||||
) {
|
||||
val testScope = TestScope(UnconfinedTestDispatcher())
|
||||
val testDispatcher = UnconfinedTestDispatcher()
|
||||
val testScope = TestScope(testDispatcher)
|
||||
val fakeSettings = FakeSettings().apply {
|
||||
putBool(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, true)
|
||||
}
|
||||
val seenNotificationsProvider = SeenNotificationsProviderImpl()
|
||||
val keyguardCoordinator =
|
||||
KeyguardCoordinator(
|
||||
testDispatcher,
|
||||
keyguardNotifVisibilityProvider,
|
||||
keyguardRepository,
|
||||
notifPipelineFlags,
|
||||
testScope.backgroundScope,
|
||||
sectionHeaderVisibilityProvider,
|
||||
fakeSettings,
|
||||
seenNotificationsProvider,
|
||||
statusBarStateController,
|
||||
)
|
||||
keyguardCoordinator.attach(notifPipeline)
|
||||
testScope.runTest(dispatchTimeoutMs = 1.seconds.inWholeMilliseconds) {
|
||||
KeyguardCoordinatorTestScope(keyguardCoordinator, testScope, seenNotificationsProvider)
|
||||
.testBlock()
|
||||
KeyguardCoordinatorTestScope(
|
||||
keyguardCoordinator,
|
||||
testScope,
|
||||
seenNotificationsProvider,
|
||||
fakeSettings,
|
||||
).testBlock()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,6 +351,7 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
|
||||
private val keyguardCoordinator: KeyguardCoordinator,
|
||||
private val scope: TestScope,
|
||||
val seenNotificationsProvider: SeenNotificationsProvider,
|
||||
private val fakeSettings: FakeSettings,
|
||||
) : CoroutineScope by scope {
|
||||
val testScheduler: TestCoroutineScheduler
|
||||
get() = scope.testScheduler
|
||||
@@ -316,5 +369,19 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
|
||||
val collectionListener: NotifCollectionListener by lazy {
|
||||
withArgCaptor { verify(notifPipeline).addCollectionListener(capture()) }
|
||||
}
|
||||
|
||||
var showOnlyUnseenNotifsOnKeyguardSetting: Boolean
|
||||
get() =
|
||||
fakeSettings.getBoolForUser(
|
||||
Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
|
||||
UserHandle.USER_CURRENT,
|
||||
)
|
||||
set(value) {
|
||||
fakeSettings.putBoolForUser(
|
||||
Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
|
||||
value,
|
||||
UserHandle.USER_CURRENT,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user