Loading packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationsInteractorTest.kt 0 → 100644 +147 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.statusbar.notification.promoted.domain.interactor import android.app.Notification import android.content.applicationContext import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.dumpManager import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.core.StatusBarRootModernization import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.buildPromotedOngoingEntry import com.android.systemui.statusbar.notification.domain.interactor.renderNotificationListInteractor import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization import com.android.systemui.statusbar.policy.domain.interactor.sensitiveNotificationProtectionInteractor import com.android.systemui.statusbar.policy.mockSensitiveNotificationProtectionController import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) @EnableFlags( PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME, ) class AODPromotedNotificationsInteractorTest : SysuiTestCase() { private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val Kosmos.underTest by Fixture { AODPromotedNotificationInteractor( promotedNotificationsInteractor = promotedNotificationsInteractor, keyguardInteractor = keyguardInteractor, sensitiveNotificationProtectionInteractor = sensitiveNotificationProtectionInteractor, dumpManager = dumpManager, ) } @Before fun setUp() { kosmos.statusBarNotificationChipsInteractor.start() } private fun Kosmos.buildPublicPrivatePromotedOngoing(): NotificationEntry = buildPromotedOngoingEntry { modifyNotification(applicationContext) .setContentTitle("SENSITIVE") .setPublicVersion( Notification.Builder(applicationContext, "channel") .setContentTitle("REDACTED") .build() ) } @Test fun content_sensitive_unlocked() = kosmos.runTest { // GIVEN a promoted entry val ronEntry = buildPublicPrivatePromotedOngoing() setKeyguardLocked(false) setScreenSharingProtectionActive(false) renderNotificationListInteractor.setRenderedList(listOf(ronEntry)) // THEN aod content is sensitive val content by collectLastValue(underTest.content) assertThat(content?.title).isEqualTo("SENSITIVE") } @Test fun content_sensitive_locked() = kosmos.runTest { // GIVEN a promoted entry val ronEntry = buildPublicPrivatePromotedOngoing() setKeyguardLocked(true) setScreenSharingProtectionActive(false) renderNotificationListInteractor.setRenderedList(listOf(ronEntry)) // THEN aod content is sensitive val content by collectLastValue(underTest.content) assertThat(content).isNotNull() assertThat(content?.title).isNull() // SOON: .isEqualTo("REDACTED") } @Test fun content_sensitive_unlocked_screensharing() = kosmos.runTest { // GIVEN a promoted entry val ronEntry = buildPublicPrivatePromotedOngoing() setKeyguardLocked(false) setScreenSharingProtectionActive(true) renderNotificationListInteractor.setRenderedList(listOf(ronEntry)) // THEN aod content is sensitive val content by collectLastValue(underTest.content) assertThat(content).isNotNull() assertThat(content?.title).isNull() // SOON: .isEqualTo("REDACTED") } private fun Kosmos.setKeyguardLocked(locked: Boolean) { fakeKeyguardRepository.setKeyguardDismissible(!locked) } private fun Kosmos.setScreenSharingProtectionActive(active: Boolean) { whenever(mockSensitiveNotificationProtectionController.isSensitiveStateActive) .thenReturn(active) whenever(mockSensitiveNotificationProtectionController.shouldProtectNotification(any())) .thenReturn(active) } } packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt +24 −4 Original line number Diff line number Diff line Loading @@ -18,10 +18,13 @@ package com.android.systemui.statusbar.notification.promoted.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel import com.android.systemui.statusbar.policy.domain.interactor.SensitiveNotificationProtectionInteractor import com.android.systemui.util.kotlin.FlowDumperImpl import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map Loading @@ -30,14 +33,31 @@ class AODPromotedNotificationInteractor @Inject constructor( promotedNotificationsInteractor: PromotedNotificationsInteractor, keyguardInteractor: KeyguardInteractor, sensitiveNotificationProtectionInteractor: SensitiveNotificationProtectionInteractor, dumpManager: DumpManager, ) : FlowDumperImpl(dumpManager) { /** * Whether the system is unlocked and not screensharing such that private notification content * is allowed to show on the aod */ private val canShowPrivateNotificationContent: Flow<Boolean> = combine( keyguardInteractor.isKeyguardDismissible, sensitiveNotificationProtectionInteractor.isSensitiveStateActive, ) { isKeyguardDismissible, isSensitive -> isKeyguardDismissible && !isSensitive } /** The content to show as the promoted notification on AOD */ val content: Flow<PromotedNotificationContentModel?> = promotedNotificationsInteractor.aodPromotedNotification .map { // TODO(b/400991304): show the private version when unlocked it?.publicVersion combine( promotedNotificationsInteractor.aodPromotedNotification, canShowPrivateNotificationContent, ) { promotedContent, showPrivateContent -> if (showPrivateContent) promotedContent?.privateVersion else promotedContent?.publicVersion } .distinctUntilNewInstance() Loading packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/SensitiveNotificationProtectionInteractor.kt 0 → 100644 +48 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.statusbar.policy.domain.interactor import com.android.server.notification.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOf /** A interactor which provides the current sensitive notification protections status */ @SysUISingleton class SensitiveNotificationProtectionInteractor @Inject constructor(private val controller: SensitiveNotificationProtectionController) { /** sensitive notification protections status */ val isSensitiveStateActive: Flow<Boolean> = if (Flags.screenshareNotificationHiding()) { conflatedCallbackFlow { val listener = Runnable { trySend(controller.isSensitiveStateActive) } controller.registerSensitiveStateListener(listener) trySend(controller.isSensitiveStateActive) awaitClose { controller.unregisterSensitiveStateListener(listener) } } .distinctUntilChanged() } else { flowOf(false) } } packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt +2 −2 Original line number Diff line number Diff line Loading @@ -19,7 +19,7 @@ package com.android.systemui.statusbar.notification.promoted import android.app.Notification import android.content.applicationContext import com.android.systemui.kosmos.Kosmos import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_PUBLIC import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.row.RowImageInflater import com.android.systemui.statusbar.notification.row.shared.skeletonImageTransform Loading @@ -40,7 +40,7 @@ fun Kosmos.setPromotedContent(entry: NotificationEntry) { promotedNotificationContentExtractor.extractContent( entry, Notification.Builder.recoverBuilder(applicationContext, entry.sbn.notification), REDACTION_TYPE_NONE, REDACTION_TYPE_PUBLIC, RowImageInflater.newInstance(previousIndex = null, reinflating = false) .useForContentModel(), ) Loading packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractorKosmos.kt +4 −0 Original line number Diff line number Diff line Loading @@ -17,12 +17,16 @@ package com.android.systemui.statusbar.notification.promoted.domain.interactor import com.android.systemui.dump.dumpManager import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.statusbar.policy.domain.interactor.sensitiveNotificationProtectionInteractor val Kosmos.aodPromotedNotificationInteractor by Kosmos.Fixture { AODPromotedNotificationInteractor( promotedNotificationsInteractor = promotedNotificationsInteractor, keyguardInteractor = keyguardInteractor, sensitiveNotificationProtectionInteractor = sensitiveNotificationProtectionInteractor, dumpManager = dumpManager, ) } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationsInteractorTest.kt 0 → 100644 +147 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.statusbar.notification.promoted.domain.interactor import android.app.Notification import android.content.applicationContext import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.dumpManager import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.core.StatusBarRootModernization import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.buildPromotedOngoingEntry import com.android.systemui.statusbar.notification.domain.interactor.renderNotificationListInteractor import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization import com.android.systemui.statusbar.policy.domain.interactor.sensitiveNotificationProtectionInteractor import com.android.systemui.statusbar.policy.mockSensitiveNotificationProtectionController import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.any import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) @EnableFlags( PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME, StatusBarChipsModernization.FLAG_NAME, StatusBarRootModernization.FLAG_NAME, ) class AODPromotedNotificationsInteractorTest : SysuiTestCase() { private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val Kosmos.underTest by Fixture { AODPromotedNotificationInteractor( promotedNotificationsInteractor = promotedNotificationsInteractor, keyguardInteractor = keyguardInteractor, sensitiveNotificationProtectionInteractor = sensitiveNotificationProtectionInteractor, dumpManager = dumpManager, ) } @Before fun setUp() { kosmos.statusBarNotificationChipsInteractor.start() } private fun Kosmos.buildPublicPrivatePromotedOngoing(): NotificationEntry = buildPromotedOngoingEntry { modifyNotification(applicationContext) .setContentTitle("SENSITIVE") .setPublicVersion( Notification.Builder(applicationContext, "channel") .setContentTitle("REDACTED") .build() ) } @Test fun content_sensitive_unlocked() = kosmos.runTest { // GIVEN a promoted entry val ronEntry = buildPublicPrivatePromotedOngoing() setKeyguardLocked(false) setScreenSharingProtectionActive(false) renderNotificationListInteractor.setRenderedList(listOf(ronEntry)) // THEN aod content is sensitive val content by collectLastValue(underTest.content) assertThat(content?.title).isEqualTo("SENSITIVE") } @Test fun content_sensitive_locked() = kosmos.runTest { // GIVEN a promoted entry val ronEntry = buildPublicPrivatePromotedOngoing() setKeyguardLocked(true) setScreenSharingProtectionActive(false) renderNotificationListInteractor.setRenderedList(listOf(ronEntry)) // THEN aod content is sensitive val content by collectLastValue(underTest.content) assertThat(content).isNotNull() assertThat(content?.title).isNull() // SOON: .isEqualTo("REDACTED") } @Test fun content_sensitive_unlocked_screensharing() = kosmos.runTest { // GIVEN a promoted entry val ronEntry = buildPublicPrivatePromotedOngoing() setKeyguardLocked(false) setScreenSharingProtectionActive(true) renderNotificationListInteractor.setRenderedList(listOf(ronEntry)) // THEN aod content is sensitive val content by collectLastValue(underTest.content) assertThat(content).isNotNull() assertThat(content?.title).isNull() // SOON: .isEqualTo("REDACTED") } private fun Kosmos.setKeyguardLocked(locked: Boolean) { fakeKeyguardRepository.setKeyguardDismissible(!locked) } private fun Kosmos.setScreenSharingProtectionActive(active: Boolean) { whenever(mockSensitiveNotificationProtectionController.isSensitiveStateActive) .thenReturn(active) whenever(mockSensitiveNotificationProtectionController.shouldProtectNotification(any())) .thenReturn(active) } }
packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt +24 −4 Original line number Diff line number Diff line Loading @@ -18,10 +18,13 @@ package com.android.systemui.statusbar.notification.promoted.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel import com.android.systemui.statusbar.policy.domain.interactor.SensitiveNotificationProtectionInteractor import com.android.systemui.util.kotlin.FlowDumperImpl import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map Loading @@ -30,14 +33,31 @@ class AODPromotedNotificationInteractor @Inject constructor( promotedNotificationsInteractor: PromotedNotificationsInteractor, keyguardInteractor: KeyguardInteractor, sensitiveNotificationProtectionInteractor: SensitiveNotificationProtectionInteractor, dumpManager: DumpManager, ) : FlowDumperImpl(dumpManager) { /** * Whether the system is unlocked and not screensharing such that private notification content * is allowed to show on the aod */ private val canShowPrivateNotificationContent: Flow<Boolean> = combine( keyguardInteractor.isKeyguardDismissible, sensitiveNotificationProtectionInteractor.isSensitiveStateActive, ) { isKeyguardDismissible, isSensitive -> isKeyguardDismissible && !isSensitive } /** The content to show as the promoted notification on AOD */ val content: Flow<PromotedNotificationContentModel?> = promotedNotificationsInteractor.aodPromotedNotification .map { // TODO(b/400991304): show the private version when unlocked it?.publicVersion combine( promotedNotificationsInteractor.aodPromotedNotification, canShowPrivateNotificationContent, ) { promotedContent, showPrivateContent -> if (showPrivateContent) promotedContent?.privateVersion else promotedContent?.publicVersion } .distinctUntilNewInstance() Loading
packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/SensitiveNotificationProtectionInteractor.kt 0 → 100644 +48 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.statusbar.policy.domain.interactor import com.android.server.notification.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOf /** A interactor which provides the current sensitive notification protections status */ @SysUISingleton class SensitiveNotificationProtectionInteractor @Inject constructor(private val controller: SensitiveNotificationProtectionController) { /** sensitive notification protections status */ val isSensitiveStateActive: Flow<Boolean> = if (Flags.screenshareNotificationHiding()) { conflatedCallbackFlow { val listener = Runnable { trySend(controller.isSensitiveStateActive) } controller.registerSensitiveStateListener(listener) trySend(controller.isSensitiveStateActive) awaitClose { controller.unregisterSensitiveStateListener(listener) } } .distinctUntilChanged() } else { flowOf(false) } }
packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt +2 −2 Original line number Diff line number Diff line Loading @@ -19,7 +19,7 @@ package com.android.systemui.statusbar.notification.promoted import android.app.Notification import android.content.applicationContext import com.android.systemui.kosmos.Kosmos import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_PUBLIC import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.row.RowImageInflater import com.android.systemui.statusbar.notification.row.shared.skeletonImageTransform Loading @@ -40,7 +40,7 @@ fun Kosmos.setPromotedContent(entry: NotificationEntry) { promotedNotificationContentExtractor.extractContent( entry, Notification.Builder.recoverBuilder(applicationContext, entry.sbn.notification), REDACTION_TYPE_NONE, REDACTION_TYPE_PUBLIC, RowImageInflater.newInstance(previousIndex = null, reinflating = false) .useForContentModel(), ) Loading
packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractorKosmos.kt +4 −0 Original line number Diff line number Diff line Loading @@ -17,12 +17,16 @@ package com.android.systemui.statusbar.notification.promoted.domain.interactor import com.android.systemui.dump.dumpManager import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.statusbar.policy.domain.interactor.sensitiveNotificationProtectionInteractor val Kosmos.aodPromotedNotificationInteractor by Kosmos.Fixture { AODPromotedNotificationInteractor( promotedNotificationsInteractor = promotedNotificationsInteractor, keyguardInteractor = keyguardInteractor, sensitiveNotificationProtectionInteractor = sensitiveNotificationProtectionInteractor, dumpManager = dumpManager, ) }