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

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

Merge "Show animation when location indicator appears, to avoid cases where...

Merge "Show animation when location indicator appears, to avoid cases where the dot is invisible due to app / wallpaper color." into main
parents f49e5ab2 80f0f594
Loading
Loading
Loading
Loading
+386 −46
Original line number Diff line number Diff line
@@ -15,11 +15,14 @@
 */
package com.android.systemui.statusbar.events

import android.platform.test.annotations.EnableFlags
import android.location.flags.Flags.FLAG_LOCATION_INDICATORS_ENABLED
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.annotations.UsesFlags
import android.platform.test.flag.junit.FlagsParameterization
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
import com.android.systemui.Flags.FLAG_STATUS_BAR_PRIVACY_CHIP_ANIMATION_EXEMPTION
import com.android.systemui.SysuiTestCase
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
@@ -50,11 +53,14 @@ import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters

@RunWith(AndroidJUnit4::class)
@RunWith(ParameterizedAndroidJunit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
class SystemEventCoordinatorTest : SysuiTestCase() {
@UsesFlags(Flags::class, android.location.flags.Flags::class)
class SystemEventCoordinatorTest(flags: FlagsParameterization) : SysuiTestCase() {

    private val fakeSystemClock = FakeSystemClock()
    private val testScope = TestScope(UnconfinedTestDispatcher())
@@ -67,6 +73,22 @@ class SystemEventCoordinatorTest : SysuiTestCase() {

    private lateinit var systemEventCoordinator: SystemEventCoordinator

    companion object {
        @JvmStatic
        @Parameters(name = "{0}")
        fun getParams(): List<FlagsParameterization> {
            return FlagsParameterization.allCombinationsOf(FLAG_LOCATION_INDICATORS_ENABLED)
        }

        private const val ADVANCE_TIME_WITHIN_DEBOUNCE_MS = DEBOUNCE_TIME_LOCATION - 100_000L
        private const val ADVANCE_TIME_OUTSIDE_DEBOUNCE_MS = DEBOUNCE_TIME_LOCATION + 100_000L
        private const val ADVANCE_TIME_SHORT_MS = 50_000L
    }

    init {
        mSetFlagsRule.setFlagsParameterization(flags)
    }

    @Before
    fun setup() {
        MockitoAnnotations.initMocks(this)
@@ -113,11 +135,185 @@ class SystemEventCoordinatorTest : SysuiTestCase() {
            verifyNoMoreInteractions(scheduler)
        }

    @Test
    @EnableFlags(FLAG_LOCATION_INDICATORS_ENABLED)
    fun onPrivacyItemsChanged_locationOnly_locationFlagOn_showsAnimation() =
        testScope.runTest {
            val privacyList =
                listOf(
                    PrivacyItem(
                        application = PrivacyApplication("maps", 1),
                        privacyType = PrivacyType.TYPE_LOCATION,
                    )
                )
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList)

            verify(scheduler).onStatusEvent(argThat { it.showAnimation })
        }

    @Test
    @DisableFlags(FLAG_LOCATION_INDICATORS_ENABLED)
    fun onPrivacyItemsChanged_locationOnly_locationFlagOff_doesNotShowAnimation() =
        testScope.runTest {
            val privacyList =
                listOf(
                    PrivacyItem(
                        application = PrivacyApplication("maps", 1),
                        privacyType = PrivacyType.TYPE_LOCATION,
                    )
                )
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList)

            verify(scheduler).onStatusEvent(argThat { !it.showAnimation })
        }

    @Test
    @DisableFlags(FLAG_LOCATION_INDICATORS_ENABLED)
    fun onPrivacyItemsChanged_locationAndMic_locationFlagOff_showsAnimation() =
        testScope.runTest {
            val privacyList =
                listOf(
                    PrivacyItem(
                        application = PrivacyApplication("maps", 1),
                        privacyType = PrivacyType.TYPE_LOCATION,
                    ),
                    PrivacyItem(
                        application = PrivacyApplication("recorder", 2),
                        privacyType = PrivacyType.TYPE_MICROPHONE,
                    ),
                )
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList)

            verify(scheduler).onStatusEvent(argThat { it.showAnimation })
        }

    @Test
    @EnableFlags(FLAG_LOCATION_INDICATORS_ENABLED)
    fun onPrivacyItemsChanged_locationAndMic_locationFlagOn_showsAnimation() =
        testScope.runTest {
            val privacyList =
                listOf(
                    PrivacyItem(
                        application = PrivacyApplication("maps", 1),
                        privacyType = PrivacyType.TYPE_LOCATION,
                    ),
                    PrivacyItem(
                        application = PrivacyApplication("recorder", 2),
                        privacyType = PrivacyType.TYPE_MICROPHONE,
                    ),
                )
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList)

            verify(scheduler).onStatusEvent(argThat { it.showAnimation })
        }

    @Test
    @EnableFlags(FLAG_LOCATION_INDICATORS_ENABLED)
    fun onPrivacyItemsChanged_locationOnly_respectsDebounceWindow() =
        testScope.runTest {
            val privacyList =
                listOf(
                    PrivacyItem(
                        application = PrivacyApplication("maps", 1),
                        privacyType = PrivacyType.TYPE_LOCATION,
                    )
                )

            // First event, show animation
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList)
            verify(scheduler).onStatusEvent(argThat { it.showAnimation })

            // Second event, within debounce window, should not show animation
            fakeSystemClock.advanceTime(ADVANCE_TIME_WITHIN_DEBOUNCE_MS)
            // We must clear the privacy items first to trigger the listener.
            // The listener no-ops if the list of privacy items is identical to the last one.
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(emptyList())
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList)
            verify(scheduler).onStatusEvent(argThat { !it.showAnimation })

            // Third event, after debounce window, should show animation again (More than 600_000L
            // since last animation)
            fakeSystemClock.advanceTime(ADVANCE_TIME_OUTSIDE_DEBOUNCE_MS)
            // We must clear the privacy items first to trigger the listener.
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(emptyList())
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList)
            verify(scheduler, times(2)).onStatusEvent(argThat { it.showAnimation })
        }

    @Test
    @EnableFlags(FLAG_LOCATION_INDICATORS_ENABLED)
    fun onPrivacyItemsChanged_locationOnly_differentApps_respectsDebounceWindow() =
        testScope.runTest {
            val privacyList1 =
                listOf(
                    PrivacyItem(
                        application = PrivacyApplication("maps", 1),
                        privacyType = PrivacyType.TYPE_LOCATION,
                    )
                )
            val privacyList2 =
                listOf(
                    PrivacyItem(
                        application = PrivacyApplication("photos", 2),
                        privacyType = PrivacyType.TYPE_LOCATION,
                    )
                )

            // First event, show animation
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList1)
            verify(scheduler).onStatusEvent(argThat { it.showAnimation })

            // Second event from a different app, within debounce window, should show animation
            fakeSystemClock.advanceTime(ADVANCE_TIME_WITHIN_DEBOUNCE_MS)
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList2)
            verify(scheduler, times(2)).onStatusEvent(argThat { it.showAnimation })

            // Third event, from the first app again, within its debounce window, should NOT show
            // animation
            fakeSystemClock.advanceTime(ADVANCE_TIME_SHORT_MS)
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList1)
            verify(scheduler).onStatusEvent(argThat { !it.showAnimation })
        }

    @Test
    @DisableFlags(FLAG_LOCATION_INDICATORS_ENABLED)
    fun onPrivacyItemsChanged_locationOnly_flagOff_neverShowsAnimation() =
        testScope.runTest {
            val privacyList =
                listOf(
                    PrivacyItem(
                        application = PrivacyApplication("maps", 1),
                        privacyType = PrivacyType.TYPE_LOCATION,
                    )
                )

            // First event, should not show animation
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList)
            verify(scheduler).onStatusEvent(argThat { !it.showAnimation })

            // Second event, within debounce window, should not show animation
            fakeSystemClock.advanceTime(ADVANCE_TIME_WITHIN_DEBOUNCE_MS)
            // We must clear the privacy items first to trigger the listener.
            // The listener no-ops if the list of privacy items is identical to the last one.
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(emptyList())
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList)
            verify(scheduler, times(2)).onStatusEvent(argThat { !it.showAnimation })

            // Third event, after debounce window, should not show animation (More than 600_000L
            // since last animation)
            fakeSystemClock.advanceTime(ADVANCE_TIME_OUTSIDE_DEBOUNCE_MS)
            // We must clear the privacy items first to trigger the listener.
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(emptyList())
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList)
            verify(scheduler, times(3)).onStatusEvent(argThat { !it.showAnimation })
        }

    @Test
    @EnableFlags(FLAG_STATUS_BAR_PRIVACY_CHIP_ANIMATION_EXEMPTION)
    fun onPrivacyItemsChanged_notDefaultCamera_showsAnimation() =
        testScope.runTest {
            val privacyList = listOf(
            val privacyList =
                listOf(
                    PrivacyItem(
                        application = PrivacyApplication("package1", 1),
                        privacyType = PrivacyType.TYPE_CAMERA,
@@ -132,11 +328,12 @@ class SystemEventCoordinatorTest : SysuiTestCase() {
    @EnableFlags(FLAG_STATUS_BAR_PRIVACY_CHIP_ANIMATION_EXEMPTION)
    fun onPrivacyItemsChanged_defaultCameraApp_cameraAccess_doesNotShowAnimation() =
        testScope.runTest {
            val privacyList = listOf(
            val privacyList =
                listOf(
                    PrivacyItem(
                        application = PrivacyApplication(DEFAULT_CAMERA_PACKAGE_NAME, 1),
                        privacyType = PrivacyType.TYPE_CAMERA,
                ),
                    )
                )
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList)

@@ -147,7 +344,8 @@ class SystemEventCoordinatorTest : SysuiTestCase() {
    @EnableFlags(FLAG_STATUS_BAR_PRIVACY_CHIP_ANIMATION_EXEMPTION)
    fun onPrivacyItemsChanged_defaultCameraApp_microphoneAccess_doesNotShowAnimation() =
        testScope.runTest {
            val privacyList = listOf(
            val privacyList =
                listOf(
                    PrivacyItem(
                        application = PrivacyApplication(DEFAULT_CAMERA_PACKAGE_NAME, 1),
                        privacyType = PrivacyType.TYPE_MICROPHONE,
@@ -162,16 +360,18 @@ class SystemEventCoordinatorTest : SysuiTestCase() {
    @EnableFlags(FLAG_STATUS_BAR_PRIVACY_CHIP_ANIMATION_EXEMPTION)
    fun onPrivacyItemsChanged_defaultCamera_thenAnotherApp_showsAnimation() =
        testScope.runTest {
            val privacyList1 = listOf(
            val privacyList1 =
                listOf(
                    PrivacyItem(
                        application = PrivacyApplication(DEFAULT_CAMERA_PACKAGE_NAME, 1),
                        privacyType = PrivacyType.TYPE_CAMERA,
                ),
                    )
                )
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList1)
            verify(scheduler).onStatusEvent(argThat { !it.showAnimation })

            val privacyList2 = listOf(
            val privacyList2 =
                listOf(
                    PrivacyItem(
                        application = PrivacyApplication(DEFAULT_CAMERA_PACKAGE_NAME, 1),
                        privacyType = PrivacyType.TYPE_CAMERA,
@@ -189,7 +389,8 @@ class SystemEventCoordinatorTest : SysuiTestCase() {
    @DisableFlags(FLAG_STATUS_BAR_PRIVACY_CHIP_ANIMATION_EXEMPTION)
    fun onPrivacyItemsChanged_defaultCameraApp_cameraAccess_flagOff_showsAnimation() =
        testScope.runTest {
            val privacyList = listOf(
            val privacyList =
                listOf(
                    PrivacyItem(
                        application = PrivacyApplication(DEFAULT_CAMERA_PACKAGE_NAME, 1),
                        privacyType = PrivacyType.TYPE_CAMERA,
@@ -204,7 +405,8 @@ class SystemEventCoordinatorTest : SysuiTestCase() {
    @DisableFlags(FLAG_STATUS_BAR_PRIVACY_CHIP_ANIMATION_EXEMPTION)
    fun onPrivacyItemsChanged_defaultCameraApp_microphoneAccess_flagOff_showsAnimation() =
        testScope.runTest {
            val privacyList = listOf(
            val privacyList =
                listOf(
                    PrivacyItem(
                        application = PrivacyApplication(DEFAULT_CAMERA_PACKAGE_NAME, 1),
                        privacyType = PrivacyType.TYPE_MICROPHONE,
@@ -215,6 +417,144 @@ class SystemEventCoordinatorTest : SysuiTestCase() {
            verify(scheduler).onStatusEvent(argThat { it.showAnimation })
        }

    @Test
    @EnableFlags(FLAG_LOCATION_INDICATORS_ENABLED, FLAG_STATUS_BAR_PRIVACY_CHIP_ANIMATION_EXEMPTION)
    fun onPrivacyItemsChanged_locationThenMicForDefaultCamera_noAnimation() =
        testScope.runTest {
            val locationList =
                listOf(
                    PrivacyItem(
                        application = PrivacyApplication("maps", 1),
                        privacyType = PrivacyType.TYPE_LOCATION,
                    )
                )
            val micAndLocationList =
                locationList +
                    PrivacyItem(
                        application = PrivacyApplication(DEFAULT_CAMERA_PACKAGE_NAME, 2),
                        privacyType = PrivacyType.TYPE_MICROPHONE,
                    )

            // First, location access shows animation
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(locationList)
            verify(scheduler).onStatusEvent(argThat { it.showAnimation })

            // Second, location access within debounce window, no animation
            fakeSystemClock.advanceTime(ADVANCE_TIME_WITHIN_DEBOUNCE_MS)
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(emptyList())
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(locationList)
            verify(scheduler).onStatusEvent(argThat { !it.showAnimation })

            // Third, add mic access for default camera, no animation
            systemEventCoordinator
                .getPrivacyStateListener()
                .onPrivacyItemsChanged(micAndLocationList)
            verify(scheduler, times(2)).onStatusEvent(argThat { !it.showAnimation })
        }

    @Test
    @EnableFlags(FLAG_LOCATION_INDICATORS_ENABLED, FLAG_STATUS_BAR_PRIVACY_CHIP_ANIMATION_EXEMPTION)
    fun onPrivacyItemsChanged_micForDefaultCameraThenLocation_showsAnimationOnce() =
        testScope.runTest {
            val micList =
                listOf(
                    PrivacyItem(
                        application = PrivacyApplication(DEFAULT_CAMERA_PACKAGE_NAME, 1),
                        privacyType = PrivacyType.TYPE_MICROPHONE,
                    )
                )
            val micAndLocationList =
                micList +
                    PrivacyItem(
                        application = PrivacyApplication("maps", 2),
                        privacyType = PrivacyType.TYPE_LOCATION,
                    )

            // First, mic access for default camera, no animation
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(micList)
            verify(scheduler).onStatusEvent(argThat { !it.showAnimation })

            // Second, add location access, shows animation
            systemEventCoordinator
                .getPrivacyStateListener()
                .onPrivacyItemsChanged(micAndLocationList)
            verify(scheduler).onStatusEvent(argThat { it.showAnimation })

            // Third, second location access within debounce window, no animation
            fakeSystemClock.advanceTime(ADVANCE_TIME_WITHIN_DEBOUNCE_MS)
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(emptyList())
            systemEventCoordinator
                .getPrivacyStateListener()
                .onPrivacyItemsChanged(micAndLocationList)
            verify(scheduler, times(2)).onStatusEvent(argThat { !it.showAnimation })
        }

    @Test
    @EnableFlags(FLAG_LOCATION_INDICATORS_ENABLED)
    fun onPrivacyItemsChanged_locationDebounced_thenMic_showsAnimation() =
        testScope.runTest {
            val locationList =
                listOf(
                    PrivacyItem(
                        application = PrivacyApplication("maps", 1),
                        privacyType = PrivacyType.TYPE_LOCATION,
                    )
                )
            val micAndLocationList =
                locationList +
                    PrivacyItem(
                        application = PrivacyApplication("recorder", 2),
                        privacyType = PrivacyType.TYPE_MICROPHONE,
                    )

            // First, location access shows animation
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(locationList)
            verify(scheduler).onStatusEvent(argThat { it.showAnimation })

            // Second, location access within debounce window, no animation
            fakeSystemClock.advanceTime(ADVANCE_TIME_WITHIN_DEBOUNCE_MS)
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(emptyList())
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(locationList)
            verify(scheduler).onStatusEvent(argThat { !it.showAnimation })

            // Third, add mic access, which should show an animation
            systemEventCoordinator
                .getPrivacyStateListener()
                .onPrivacyItemsChanged(micAndLocationList)
            verify(scheduler, times(2)).onStatusEvent(argThat { it.showAnimation })
        }

    @Test
    @EnableFlags(FLAG_LOCATION_INDICATORS_ENABLED)
    fun onPrivacyItemsChanged_location_continuousUsage_onlyAnimatesFirstTime() =
        testScope.runTest {
            val privacyList =
                listOf(
                    PrivacyItem(
                        application = PrivacyApplication("maps", 1),
                        privacyType = PrivacyType.TYPE_LOCATION,
                    )
                )

            // First event, show animation
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList)

            // Second event, within debounce window, should not show animation
            fakeSystemClock.advanceTime(ADVANCE_TIME_WITHIN_DEBOUNCE_MS)
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(emptyList())
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList)

            // Third event, also within the debounce window of the *last usage*, should not show
            // animation
            fakeSystemClock.advanceTime(ADVANCE_TIME_WITHIN_DEBOUNCE_MS)
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(emptyList())
            systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList)

            // Verify that the animation was only shown once, and no-animation was shown twice.
            verify(scheduler).onStatusEvent(argThat { it.showAnimation })
            verify(scheduler, times(2)).onStatusEvent(argThat { !it.showAnimation })
        }

    class FakeConnectedDisplayInteractor : ConnectedDisplayInteractor {
        private val flow = MutableSharedFlow<Unit>()

+96 −35

File changed.

Preview size limit exceeded, changes collapsed.