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

Commit 01883486 authored by Jeff DeCew's avatar Jeff DeCew Committed by Android (Google) Code Review
Browse files

Merge "[RONs] Show private view on AOD when unlocked" into main

parents 6258b9ad f47454cf
Loading
Loading
Loading
Loading
+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)
    }
}
+24 −4
Original line number Diff line number Diff line
@@ -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

@@ -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()

+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)
        }
}
+2 −2
Original line number Diff line number Diff line
@@ -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
@@ -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(),
        )
+4 −0
Original line number Diff line number Diff line
@@ -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