Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit aec03f18 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Flexiglass: Make SensitiveContentCoordinator Scene-aware" into main

parents e153c2f2 09141f01
Loading
Loading
Loading
Loading
+102 −49
Original line number Diff line number Diff line
@@ -20,10 +20,14 @@ import android.app.Notification
import android.os.UserHandle
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.server.notification.Flags.screenshareNotificationHiding
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
import com.android.systemui.statusbar.notification.DynamicPrivacyController
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
@@ -32,27 +36,33 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import dagger.Binds
import dagger.Module
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch

@Module(includes = [PrivateSensitiveContentCoordinatorModule::class])
interface SensitiveContentCoordinatorModule

@Module
interface PrivateSensitiveContentCoordinatorModule {
    @Binds
    fun bindCoordinator(impl: SensitiveContentCoordinatorImpl): SensitiveContentCoordinator
    @Binds fun bindCoordinator(impl: SensitiveContentCoordinatorImpl): SensitiveContentCoordinator
}

/** Coordinates re-inflation and post-processing of sensitive notification content. */
interface SensitiveContentCoordinator : Coordinator

@CoordinatorScope
class SensitiveContentCoordinatorImpl @Inject constructor(
class SensitiveContentCoordinatorImpl
@Inject
constructor(
    private val dynamicPrivacyController: DynamicPrivacyController,
    private val lockscreenUserManager: NotificationLockscreenUserManager,
    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
@@ -61,18 +71,25 @@ class SensitiveContentCoordinatorImpl @Inject constructor(
    private val selectedUserInteractor: SelectedUserInteractor,
    private val sensitiveNotificationProtectionController:
        SensitiveNotificationProtectionController,
) : Invalidator("SensitiveContentInvalidator"),
    private val deviceEntryInteractor: DeviceEntryInteractor,
    private val sceneInteractor: SceneInteractor,
    @Application private val scope: CoroutineScope,
) :
    Invalidator("SensitiveContentInvalidator"),
    SensitiveContentCoordinator,
    DynamicPrivacyController.Listener,
    OnBeforeRenderListListener {
    private val onSensitiveStateChanged = Runnable() {
        invalidateList("onSensitiveStateChanged")
    }
    private var inTransitionFromLockedToGone = false

    private val onSensitiveStateChanged = Runnable() { invalidateList("onSensitiveStateChanged") }

    private val screenshareSecretFilter = object : NotifFilter("ScreenshareSecretFilter") {
    private val screenshareSecretFilter =
        object : NotifFilter("ScreenshareSecretFilter") {
            val NotificationEntry.isSecret
            get() = channel?.lockscreenVisibility == Notification.VISIBILITY_SECRET ||
                get() =
                    channel?.lockscreenVisibility == Notification.VISIBILITY_SECRET ||
                        sbn.notification?.visibility == Notification.VISIBILITY_SECRET

            override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean {
                return screenshareNotificationHiding() &&
                    sensitiveNotificationProtectionController.isSensitiveStateActive &&
@@ -83,23 +100,56 @@ class SensitiveContentCoordinatorImpl @Inject constructor(
    override fun attach(pipeline: NotifPipeline) {
        dynamicPrivacyController.addListener(this)
        if (screenshareNotificationHiding()) {
            sensitiveNotificationProtectionController
                .registerSensitiveStateListener(onSensitiveStateChanged)
            sensitiveNotificationProtectionController.registerSensitiveStateListener(
                onSensitiveStateChanged
            )
        }
        pipeline.addOnBeforeRenderListListener(this)
        pipeline.addPreRenderInvalidator(this)
        if (screenshareNotificationHiding()) {
            pipeline.addFinalizeFilter(screenshareSecretFilter)
        }

        if (SceneContainerFlag.isEnabled) {
            scope.launch {
                sceneInteractor.transitionState
                    .mapNotNull {
                        val transitioningToGone = it.isTransitioning(to = Scenes.Gone)
                        val deviceEntered = deviceEntryInteractor.isDeviceEntered.value
                        when {
                            transitioningToGone && !deviceEntered -> true
                            !transitioningToGone -> false
                            else -> null
                        }
                    }
                    .distinctUntilChanged()
                    .collect {
                        inTransitionFromLockedToGone = it
                        invalidateList("inTransitionFromLockedToGoneChanged")
                    }
            }
        }
    }

    override fun onDynamicPrivacyChanged(): Unit = invalidateList("onDynamicPrivacyChanged")

    private val isKeyguardGoingAway: Boolean
        get() {
            if (SceneContainerFlag.isEnabled) {
                return inTransitionFromLockedToGone
            } else {
                return keyguardStateController.isKeyguardGoingAway
            }
        }

    override fun onBeforeRenderList(entries: List<ListEntry>) {
        if (keyguardStateController.isKeyguardGoingAway ||
        if (
            isKeyguardGoingAway ||
                statusBarStateController.state == StatusBarState.KEYGUARD &&
                    keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(
                        selectedUserInteractor.getSelectedUserId())) {
                        selectedUserInteractor.getSelectedUserId()
                    )
        ) {
            // don't update yet if:
            // - the keyguard is currently going away
            // - LS is about to be dismissed by a biometric that bypasses LS (avoid notif flash)
@@ -109,19 +159,22 @@ class SensitiveContentCoordinatorImpl @Inject constructor(
            return
        }

        val isSensitiveContentProtectionActive = screenshareNotificationHiding() &&
        val isSensitiveContentProtectionActive =
            screenshareNotificationHiding() &&
                sensitiveNotificationProtectionController.isSensitiveStateActive
        val currentUserId = lockscreenUserManager.currentUserId
        val devicePublic = lockscreenUserManager.isLockscreenPublicMode(currentUserId)
        val deviceSensitive = (devicePublic &&
        val deviceSensitive =
            (devicePublic &&
                !lockscreenUserManager.userAllowsPrivateNotificationsInPublic(currentUserId)) ||
                isSensitiveContentProtectionActive
        val dynamicallyUnlocked = dynamicPrivacyController.isDynamicallyUnlocked
        for (entry in extractAllRepresentativeEntries(entries).filter { it.rowExists() }) {
            val notifUserId = entry.sbn.user.identifier
            val userLockscreen = devicePublic ||
                    lockscreenUserManager.isLockscreenPublicMode(notifUserId)
            val userPublic = when {
            val userLockscreen =
                devicePublic || lockscreenUserManager.isLockscreenPublicMode(notifUserId)
            val userPublic =
                when {
                    // if we're not on the lockscreen, we're definitely private
                    !userLockscreen -> false
                    // we are on the lockscreen, so unless we're dynamically unlocked, we're
@@ -129,14 +182,16 @@ class SensitiveContentCoordinatorImpl @Inject constructor(
                    !dynamicallyUnlocked -> true
                    // we're dynamically unlocked, but check if the notification needs
                    // a separate challenge if it's from a work profile
                else -> when (notifUserId) {
                    else ->
                        when (notifUserId) {
                            currentUserId -> false
                            UserHandle.USER_ALL -> false
                            else -> lockscreenUserManager.needsSeparateWorkChallenge(notifUserId)
                        }
                }

            val shouldProtectNotification = screenshareNotificationHiding() &&
            val shouldProtectNotification =
                screenshareNotificationHiding() &&
                    sensitiveNotificationProtectionController.shouldProtectNotification(entry)

            val needsRedaction = lockscreenUserManager.needsRedaction(entry)
@@ -149,9 +204,7 @@ class SensitiveContentCoordinatorImpl @Inject constructor(
    }
}

private fun extractAllRepresentativeEntries(
    entries: List<ListEntry>
): Sequence<NotificationEntry> =
private fun extractAllRepresentativeEntries(entries: List<ListEntry>): Sequence<NotificationEntry> =
    entries.asSequence().flatMap(::extractAllRepresentativeEntries)

private fun extractAllRepresentativeEntries(listEntry: ListEntry): Sequence<NotificationEntry> =
+23 −7
Original line number Diff line number Diff line
@@ -28,7 +28,11 @@ import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING
import com.android.systemui.SysuiTestCase
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.RankingBuilder
import com.android.systemui.statusbar.StatusBarState
@@ -45,6 +49,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
@@ -52,6 +57,7 @@ import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.withArgCaptor
import dagger.BindsInstance
import dagger.Component
import kotlinx.coroutines.CoroutineScope
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
@@ -64,6 +70,8 @@ import org.mockito.Mockito.`when` as whenever
@RunWith(AndroidJUnit4::class)
class SensitiveContentCoordinatorTest : SysuiTestCase() {

    val kosmos = testKosmos()

    val dynamicPrivacyController: DynamicPrivacyController = mock()
    val lockscreenUserManager: NotificationLockscreenUserManager = mock()
    val pipeline: NotifPipeline = mock()
@@ -73,6 +81,8 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
    val mSelectedUserInteractor: SelectedUserInteractor = mock()
    val sensitiveNotificationProtectionController: SensitiveNotificationProtectionController =
        mock()
    val deviceEntryInteractor: DeviceEntryInteractor = mock()
    val sceneInteractor: SceneInteractor = mock()

    val coordinator: SensitiveContentCoordinator =
        DaggerTestSensitiveContentCoordinatorComponent.factory()
@@ -83,7 +93,10 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
                statusBarStateController,
                keyguardStateController,
                mSelectedUserInteractor,
                sensitiveNotificationProtectionController
                sensitiveNotificationProtectionController,
                deviceEntryInteractor,
                sceneInteractor,
                kosmos.applicationCoroutineScope,
            )
            .coordinator

@@ -136,8 +149,7 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
    @Test
    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
    fun screenshareSecretFilter_sensitiveInctive_noFiltersSecret() {
        whenever(sensitiveNotificationProtectionController.isSensitiveStateActive)
            .thenReturn(false)
        whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(false)

        coordinator.attach(pipeline)
        val filter = withArgCaptor<NotifFilter> { verify(pipeline).addFinalizeFilter(capture()) }
@@ -683,7 +695,8 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
        val mockSbn: StatusBarNotification =
            mock<StatusBarNotification>().apply { whenever(user).thenReturn(mockUserHandle) }
        val mockRow: ExpandableNotificationRow = mock<ExpandableNotificationRow>()
        val mockEntry = mock<NotificationEntry>().apply {
        val mockEntry =
            mock<NotificationEntry>().apply {
                whenever(sbn).thenReturn(mockSbn)
                whenever(row).thenReturn(mockRow)
            }
@@ -737,6 +750,9 @@ interface TestSensitiveContentCoordinatorComponent {
            @BindsInstance selectedUserInteractor: SelectedUserInteractor,
            @BindsInstance
            sensitiveNotificationProtectionController: SensitiveNotificationProtectionController,
            @BindsInstance deviceEntryInteractor: DeviceEntryInteractor,
            @BindsInstance sceneInteractor: SceneInteractor,
            @BindsInstance @Application scope: CoroutineScope,
        ): TestSensitiveContentCoordinatorComponent
    }
}