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

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

Merge changes I3012b6a9,I3f47da63 into main

* changes:
  [SB][Notifs] Hide status bar notif chip if the app is open.
  [SB][Notifs] Create individual interactors for each status bar chip.
parents d9192ee9 acc477c4
Loading
Loading
Loading
Loading
+143 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.activity.data.repository

import android.app.ActivityManager
import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE
import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_TOP_SLEEPING
import android.app.activityManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.log.core.Logger
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever

@SmallTest
@RunWith(AndroidJUnit4::class)
class ActivityManagerRepositoryTest : SysuiTestCase() {
    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
    private val logger = Logger(logcatLogBuffer("ActivityManagerRepositoryTest"), "tag")

    private val Kosmos.underTest by Kosmos.Fixture { realActivityManagerRepository }

    @Test
    fun createIsAppVisibleFlow_fetchesInitialValue_true() =
        kosmos.runTest {
            whenever(activityManager.getUidImportance(THIS_UID)).thenReturn(IMPORTANCE_FOREGROUND)

            val latest by
                collectLastValue(underTest.createIsAppVisibleFlow(THIS_UID, logger, LOG_TAG))

            assertThat(latest).isTrue()
        }

    @Test
    fun createIsAppVisibleFlow_fetchesInitialValue_false() =
        kosmos.runTest {
            whenever(activityManager.getUidImportance(THIS_UID)).thenReturn(IMPORTANCE_GONE)

            val latest by
                collectLastValue(underTest.createIsAppVisibleFlow(THIS_UID, logger, LOG_TAG))

            assertThat(latest).isFalse()
        }

    @Test
    fun createIsAppVisibleFlow_getsImportanceUpdates() =
        kosmos.runTest {
            val latest by
                collectLastValue(underTest.createIsAppVisibleFlow(THIS_UID, logger, LOG_TAG))

            val listenerCaptor = argumentCaptor<ActivityManager.OnUidImportanceListener>()
            verify(activityManager).addOnUidImportanceListener(listenerCaptor.capture(), any())
            val listener = listenerCaptor.firstValue

            listener.onUidImportance(THIS_UID, IMPORTANCE_GONE)
            assertThat(latest).isFalse()

            listener.onUidImportance(THIS_UID, IMPORTANCE_FOREGROUND)
            assertThat(latest).isTrue()

            listener.onUidImportance(THIS_UID, IMPORTANCE_TOP_SLEEPING)
            assertThat(latest).isFalse()
        }

    @Test
    fun createIsAppVisibleFlow_ignoresUpdatesForOtherUids() =
        kosmos.runTest {
            val latest by
                collectLastValue(underTest.createIsAppVisibleFlow(THIS_UID, logger, LOG_TAG))

            val listenerCaptor = argumentCaptor<ActivityManager.OnUidImportanceListener>()
            verify(activityManager).addOnUidImportanceListener(listenerCaptor.capture(), any())
            val listener = listenerCaptor.firstValue

            listener.onUidImportance(THIS_UID, IMPORTANCE_GONE)
            assertThat(latest).isFalse()

            // WHEN another UID becomes foreground
            listener.onUidImportance(THIS_UID + 2, IMPORTANCE_FOREGROUND)

            // THEN this UID still stays not visible
            assertThat(latest).isFalse()
        }

    @Test
    fun createIsAppVisibleFlow_securityExceptionOnUidRegistration_ok() =
        kosmos.runTest {
            whenever(activityManager.getUidImportance(THIS_UID)).thenReturn(IMPORTANCE_GONE)
            whenever(activityManager.addOnUidImportanceListener(any(), any()))
                .thenThrow(SecurityException())

            val latest by
                collectLastValue(underTest.createIsAppVisibleFlow(THIS_UID, logger, LOG_TAG))

            // Verify no crash, and we get a value emitted
            assertThat(latest).isFalse()
        }

    /** Regression test for b/216248574. */
    @Test
    fun createIsAppVisibleFlow_getUidImportanceThrowsException_ok() =
        kosmos.runTest {
            whenever(activityManager.getUidImportance(any())).thenThrow(SecurityException())

            val latest by
                collectLastValue(underTest.createIsAppVisibleFlow(THIS_UID, logger, LOG_TAG))

            // Verify no crash, and we get a value emitted
            assertThat(latest).isFalse()
        }

    companion object {
        private const val THIS_UID = 558
        private const val LOG_TAG = "LogTag"
    }
}
+207 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.chips.notification.domain.interactor

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.activity.data.repository.activityManagerRepository
import com.android.systemui.activity.data.repository.fake
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock

@SmallTest
@RunWith(AndroidJUnit4::class)
class SingleNotificationChipInteractorTest : SysuiTestCase() {
    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
    val factory = kosmos.singleNotificationChipInteractorFactory

    @Test
    fun notificationChip_startsWithStartingModel() =
        kosmos.runTest {
            val icon = mock<StatusBarIconView>()
            val startingNotif = activeNotificationModel(key = "notif1", statusBarChipIcon = icon)

            val underTest = factory.create(startingNotif)

            val latest by collectLastValue(underTest.notificationChip)

            assertThat(latest!!.key).isEqualTo("notif1")
            assertThat(latest!!.statusBarChipIconView).isEqualTo(icon)
        }

    @Test
    fun notificationChip_updatesAfterSet() =
        kosmos.runTest {
            val originalIconView = mock<StatusBarIconView>()
            val underTest =
                factory.create(
                    activeNotificationModel(key = "notif1", statusBarChipIcon = originalIconView)
                )

            val latest by collectLastValue(underTest.notificationChip)

            val newIconView = mock<StatusBarIconView>()
            underTest.setNotification(
                activeNotificationModel(key = "notif1", statusBarChipIcon = newIconView)
            )

            assertThat(latest!!.key).isEqualTo("notif1")
            assertThat(latest!!.statusBarChipIconView).isEqualTo(newIconView)
        }

    @Test
    fun notificationChip_ignoresSetWithDifferentKey() =
        kosmos.runTest {
            val originalIconView = mock<StatusBarIconView>()
            val underTest =
                factory.create(
                    activeNotificationModel(key = "notif1", statusBarChipIcon = originalIconView)
                )

            val latest by collectLastValue(underTest.notificationChip)

            val newIconView = mock<StatusBarIconView>()
            underTest.setNotification(
                activeNotificationModel(key = "other_notif", statusBarChipIcon = newIconView)
            )

            assertThat(latest!!.key).isEqualTo("notif1")
            assertThat(latest!!.statusBarChipIconView).isEqualTo(originalIconView)
        }

    @Test
    fun notificationChip_missingStatusBarIconChipView_inConstructor_emitsNull() =
        kosmos.runTest {
            val underTest =
                factory.create(activeNotificationModel(key = "notif1", statusBarChipIcon = null))

            val latest by collectLastValue(underTest.notificationChip)

            assertThat(latest).isNull()
        }

    @Test
    fun notificationChip_missingStatusBarIconChipView_inSet_emitsNull() =
        kosmos.runTest {
            val startingNotif = activeNotificationModel(key = "notif1", statusBarChipIcon = mock())
            val underTest = factory.create(startingNotif)
            val latest by collectLastValue(underTest.notificationChip)
            assertThat(latest).isNotNull()

            underTest.setNotification(
                activeNotificationModel(key = "notif1", statusBarChipIcon = null)
            )

            assertThat(latest).isNull()
        }

    @Test
    fun notificationChip_appIsVisibleOnCreation_emitsNull() =
        kosmos.runTest {
            activityManagerRepository.fake.startingIsAppVisibleValue = true

            val underTest =
                factory.create(
                    activeNotificationModel(key = "notif", uid = UID, statusBarChipIcon = mock())
                )

            val latest by collectLastValue(underTest.notificationChip)

            assertThat(latest).isNull()
        }

    @Test
    fun notificationChip_appNotVisibleOnCreation_emitsValue() =
        kosmos.runTest {
            activityManagerRepository.fake.startingIsAppVisibleValue = false

            val underTest =
                factory.create(
                    activeNotificationModel(key = "notif", uid = UID, statusBarChipIcon = mock())
                )

            val latest by collectLastValue(underTest.notificationChip)

            assertThat(latest).isNotNull()
        }

    @Test
    fun notificationChip_hidesWhenAppIsVisible() =
        kosmos.runTest {
            val underTest =
                factory.create(
                    activeNotificationModel(key = "notif", uid = UID, statusBarChipIcon = mock())
                )

            val latest by collectLastValue(underTest.notificationChip)

            activityManagerRepository.fake.setIsAppVisible(UID, false)
            assertThat(latest).isNotNull()

            activityManagerRepository.fake.setIsAppVisible(UID, true)
            assertThat(latest).isNull()

            activityManagerRepository.fake.setIsAppVisible(UID, false)
            assertThat(latest).isNotNull()
        }

    // Note: This test is theoretically impossible because the notification key should contain the
    // UID, so if the UID changes then the key would also change and a new interactor would be
    // created. But, test it just in case.
    @Test
    fun notificationChip_updatedUid_rechecksAppVisibility_oldObserverUnregistered() =
        kosmos.runTest {
            activityManagerRepository.fake.startingIsAppVisibleValue = false

            val hiddenUid = 100
            val shownUid = 101

            val underTest =
                factory.create(
                    activeNotificationModel(
                        key = "notif",
                        uid = hiddenUid,
                        statusBarChipIcon = mock(),
                    )
                )
            val latest by collectLastValue(underTest.notificationChip)
            assertThat(latest).isNotNull()

            // WHEN the notif gets a new UID that starts as visible
            activityManagerRepository.fake.startingIsAppVisibleValue = true
            underTest.setNotification(
                activeNotificationModel(key = "notif", uid = shownUid, statusBarChipIcon = mock())
            )

            // THEN we re-fetch the app visibility state with the new UID, and since that UID is
            // visible, we hide the chip
            assertThat(latest).isNull()
        }

    companion object {
        private const val UID = 885
    }
}
+257 −2
Original line number Diff line number Diff line
@@ -16,30 +16,277 @@

package com.android.systemui.statusbar.chips.notification.domain.interactor

import android.platform.test.annotations.DisableFlags
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.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.test.runTest
import org.junit.runner.RunWith
import org.mockito.kotlin.mock

@SmallTest
@RunWith(AndroidJUnit4::class)
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
    private val testScope = kosmos.testScope
    private val activeNotificationListRepository = kosmos.activeNotificationListRepository

    private val underTest by lazy {
        kosmos.statusBarNotificationChipsInteractor.also { it.start() }
    }

    @Test
    @DisableFlags(StatusBarNotifChips.FLAG_NAME)
    fun notificationChips_flagOff_noNotifs() =
        testScope.runTest {
            val latest by collectLastValue(underTest.notificationChips)

            setNotifs(
                listOf(
                    activeNotificationModel(
                        key = "notif",
                        statusBarChipIcon = mock<StatusBarIconView>(),
                        isPromoted = true,
                    )
                )
            )

            assertThat(latest).isEmpty()
        }

    @Test
    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
    fun notificationChips_noNotifs_empty() =
        testScope.runTest {
            val latest by collectLastValue(underTest.notificationChips)

            setNotifs(emptyList())

            assertThat(latest).isEmpty()
        }

    @Test
    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
    fun notificationChips_notifMissingStatusBarChipIconView_empty() =
        testScope.runTest {
            val latest by collectLastValue(underTest.notificationChips)

            setNotifs(
                listOf(
                    activeNotificationModel(
                        key = "notif",
                        statusBarChipIcon = null,
                        isPromoted = true,
                    )
                )
            )

            assertThat(latest).isEmpty()
        }

    @Test
    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
    fun notificationChips_onePromotedNotif_statusBarIconViewMatches() =
        testScope.runTest {
            val latest by collectLastValue(underTest.notificationChips)

            val icon = mock<StatusBarIconView>()
            setNotifs(
                listOf(
                    activeNotificationModel(
                        key = "notif",
                        statusBarChipIcon = icon,
                        isPromoted = true,
                    )
                )
            )

            assertThat(latest).hasSize(1)
            assertThat(latest!![0].key).isEqualTo("notif")
            assertThat(latest!![0].statusBarChipIconView).isEqualTo(icon)
        }

    @Test
    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
    fun notificationChips_onlyForPromotedNotifs() =
        testScope.runTest {
            val latest by collectLastValue(underTest.notificationChips)

            val firstIcon = mock<StatusBarIconView>()
            val secondIcon = mock<StatusBarIconView>()
            setNotifs(
                listOf(
                    activeNotificationModel(
                        key = "notif1",
                        statusBarChipIcon = firstIcon,
                        isPromoted = true,
                    ),
                    activeNotificationModel(
                        key = "notif2",
                        statusBarChipIcon = secondIcon,
                        isPromoted = true,
                    ),
                    activeNotificationModel(
                        key = "notif3",
                        statusBarChipIcon = mock<StatusBarIconView>(),
                        isPromoted = false,
                    ),
                )
            )

            assertThat(latest).hasSize(2)
            assertThat(latest!![0].key).isEqualTo("notif1")
            assertThat(latest!![0].statusBarChipIconView).isEqualTo(firstIcon)
            assertThat(latest!![1].key).isEqualTo("notif2")
            assertThat(latest!![1].statusBarChipIconView).isEqualTo(secondIcon)
        }

    @Test
    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
    fun notificationChips_notifUpdatesGoThrough() =
        testScope.runTest {
            val latest by collectLastValue(underTest.notificationChips)

            val firstIcon = mock<StatusBarIconView>()
            val secondIcon = mock<StatusBarIconView>()
            val thirdIcon = mock<StatusBarIconView>()

    private val underTest = kosmos.statusBarNotificationChipsInteractor
            setNotifs(
                listOf(
                    activeNotificationModel(
                        key = "notif",
                        statusBarChipIcon = firstIcon,
                        isPromoted = true,
                    )
                )
            )
            assertThat(latest).hasSize(1)
            assertThat(latest!![0].key).isEqualTo("notif")
            assertThat(latest!![0].statusBarChipIconView).isEqualTo(firstIcon)

            setNotifs(
                listOf(
                    activeNotificationModel(
                        key = "notif",
                        statusBarChipIcon = secondIcon,
                        isPromoted = true,
                    )
                )
            )
            assertThat(latest).hasSize(1)
            assertThat(latest!![0].key).isEqualTo("notif")
            assertThat(latest!![0].statusBarChipIconView).isEqualTo(secondIcon)

            setNotifs(
                listOf(
                    activeNotificationModel(
                        key = "notif",
                        statusBarChipIcon = thirdIcon,
                        isPromoted = true,
                    )
                )
            )
            assertThat(latest).hasSize(1)
            assertThat(latest!![0].key).isEqualTo("notif")
            assertThat(latest!![0].statusBarChipIconView).isEqualTo(thirdIcon)
        }

    @Test
    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
    fun notificationChips_promotedNotifDisappearsThenReappears() =
        testScope.runTest {
            val latest by collectLastValue(underTest.notificationChips)

            setNotifs(
                listOf(
                    activeNotificationModel(
                        key = "notif",
                        statusBarChipIcon = mock(),
                        isPromoted = true,
                    )
                )
            )
            assertThat(latest).hasSize(1)
            assertThat(latest!![0].key).isEqualTo("notif")

            setNotifs(
                listOf(
                    activeNotificationModel(
                        key = "notif",
                        statusBarChipIcon = mock(),
                        isPromoted = false,
                    )
                )
            )
            assertThat(latest).isEmpty()

            setNotifs(
                listOf(
                    activeNotificationModel(
                        key = "notif",
                        statusBarChipIcon = mock(),
                        isPromoted = true,
                    )
                )
            )
            assertThat(latest).hasSize(1)
            assertThat(latest!![0].key).isEqualTo("notif")
        }

    @Test
    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
    fun notificationChips_notifChangesKey() =
        testScope.runTest {
            val latest by collectLastValue(underTest.notificationChips)

            val firstIcon = mock<StatusBarIconView>()
            val secondIcon = mock<StatusBarIconView>()
            setNotifs(
                listOf(
                    activeNotificationModel(
                        key = "notif|uid1",
                        statusBarChipIcon = firstIcon,
                        isPromoted = true,
                    )
                )
            )
            assertThat(latest).hasSize(1)
            assertThat(latest!![0].key).isEqualTo("notif|uid1")
            assertThat(latest!![0].statusBarChipIconView).isEqualTo(firstIcon)

            // WHEN a notification changes UID, which is a key change
            setNotifs(
                listOf(
                    activeNotificationModel(
                        key = "notif|uid2",
                        statusBarChipIcon = secondIcon,
                        isPromoted = true,
                    )
                )
            )

            // THEN we correctly update
            assertThat(latest).hasSize(1)
            assertThat(latest!![0].key).isEqualTo("notif|uid2")
            assertThat(latest!![0].statusBarChipIconView).isEqualTo(secondIcon)
        }

    @Test
    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
    fun onPromotedNotificationChipTapped_emitsKeys() =
        testScope.runTest {
            val latest by collectValues(underTest.promotedNotificationChipTapEvent)
@@ -56,6 +303,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
        }

    @Test
    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
    fun onPromotedNotificationChipTapped_sameKeyTwice_emitsTwice() =
        testScope.runTest {
            val latest by collectValues(underTest.promotedNotificationChipTapEvent)
@@ -67,4 +315,11 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
            assertThat(latest[0]).isEqualTo("fakeKey")
            assertThat(latest[1]).isEqualTo("fakeKey")
        }

    private fun setNotifs(notifs: List<ActiveNotificationModel>) {
        activeNotificationListRepository.activeNotifications.value =
            ActiveNotificationsStore.Builder()
                .apply { notifs.forEach { addIndividualNotif(it) } }
                .build()
    }
}
+16 −13

File changed.

Preview size limit exceeded, changes collapsed.

+7 −2

File changed.

Preview size limit exceeded, changes collapsed.

Loading