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

Commit ef40a94d authored by Caitlin Shkuratov's avatar Caitlin Shkuratov
Browse files

[SB] In fullscreen mode, always auto-hide status bar after 2 seconds.

When users are in fullscreen mode, the status bar and navigation bar are
hidden. Users can temporarily show them by swiping down from the top of
the sceen or up from the bottom of the screen. We call these "transient
bars". After a few seconds, the transient status bar and navigation bar
should automatically hide.

In code, we do this using AutoHideController. Any time the status bar
transitions object or the status bar mode changes, StatusBarOrchestrator
calls AutoHideController#touchAutoHide, which checks to see if we're
showing the transient bars. If we are, then we schedule them to be
hidden in ~2 seconds.

The bug: Most fullscreen apps request their status bar mode to be
TRANSPARENT. When a user swipes down from the top of the screen, the
status bar mode changes to be SEMI_TRANSPARENT, which causes
StatusBarOrchestrator to tell AutoHideController that we might need to
schedule a timeout.

However, some fullscreen apps are requesting their status bar mode to be
SEMI_TRANSPARENT. When a user swipes down from the top of the screen,
the bar mode didn't actually change, so StatusBarOrchestrator doesn't
ask AutoHideController to schedule anything.

The fix: Also call AutoHideController#touchAutoHide whenever the
transient mode changes.

Bug: 428659575
Flag: com.android.systemui.status_bar_always_schedule_auto_hide
Test: With fullscreen app that requests SEMI_TRANSPARENT, swipe down
from top of screen. Verify the status bar and navigation bar show, but
then hide after a few seconds.
Test: atest StatusBarOrchestratorTest

Change-Id: Ia7057ff8c3691d03df628fd40f0d49664e901f1e
parent 60b62275
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -446,6 +446,16 @@ flag {
    }
}

flag {
    name: "status_bar_always_schedule_auto_hide"
    namespace: "systemui"
    description: "Schedule the status bar auto-hide functionality more often for fullscreen mode"
    bug: "428659575"
    metadata {
      purpose: PURPOSE_BUGFIX
    }
}

flag {
    name: "status_bar_app_handle_tracking"
    namespace: "systemui"
+137 −1
Original line number Diff line number Diff line
@@ -16,13 +16,16 @@

package com.android.systemui.statusbar.core

import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.dump.dumpManager
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.plugins.DarkIconDispatcher
@@ -36,8 +39,11 @@ import com.android.systemui.statusbar.data.model.StatusBarMode
import com.android.systemui.statusbar.data.model.StatusBarMode.LIGHTS_OUT
import com.android.systemui.statusbar.data.model.StatusBarMode.LIGHTS_OUT_TRANSPARENT
import com.android.systemui.statusbar.data.model.StatusBarMode.OPAQUE
import com.android.systemui.statusbar.data.model.StatusBarMode.SEMI_TRANSPARENT
import com.android.systemui.statusbar.data.model.StatusBarMode.TRANSPARENT
import com.android.systemui.statusbar.data.repository.fakeStatusBarModePerDisplayRepository
import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions
import com.android.systemui.statusbar.phone.mockAutoHideController
import com.android.systemui.statusbar.phone.ui.statusBarIconController
import com.android.systemui.statusbar.policy.fakeConfigurationController
import com.android.systemui.statusbar.window.data.repository.fakeStatusBarWindowStatePerDisplayRepository
@@ -50,7 +56,9 @@ import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.reset
import org.mockito.kotlin.times
import org.mockito.kotlin.verify

@@ -72,7 +80,6 @@ class StatusBarOrchestratorTest : SysuiTestCase() {
    private val fakeStatusBarWindowController = kosmos.fakeStatusBarWindowController
    private val fakeStatusBarInitializer = kosmos.fakeStatusBarInitializer
    private val dumpManager = kosmos.dumpManager
    private val statusBarIconRefreshInteractor = kosmos.statusBarIconRefreshInteractor

    private val orchestrator = kosmos.statusBarOrchestrator

@@ -141,6 +148,135 @@ class StatusBarOrchestratorTest : SysuiTestCase() {
            verify(fakeStatusBarInitializer.statusBarTransitions, never()).finishAnimations()
        }

    @Test
    @DisableFlags(Flags.FLAG_STATUS_BAR_ALWAYS_SCHEDULE_AUTO_HIDE)
    fun autoHide_invokedWhenBarModeChanges_flagOff() =
        kosmos.runTest {
            setStatusBarMode(TRANSPARENT)
            orchestrator.start()
            reset(mockAutoHideController)

            setStatusBarMode(SEMI_TRANSPARENT)

            verify(mockAutoHideController).touchAutoHide()

            reset(mockAutoHideController)
            setStatusBarMode(OPAQUE)

            verify(mockAutoHideController).touchAutoHide()
        }

    @Test
    @EnableFlags(Flags.FLAG_STATUS_BAR_ALWAYS_SCHEDULE_AUTO_HIDE)
    fun autoHide_invokedWhenBarModeChanges_flagOn() =
        kosmos.runTest {
            setStatusBarMode(TRANSPARENT)
            orchestrator.start()
            reset(mockAutoHideController)

            setStatusBarMode(SEMI_TRANSPARENT)

            verify(mockAutoHideController).touchAutoHide()

            reset(mockAutoHideController)
            setStatusBarMode(OPAQUE)

            verify(mockAutoHideController).touchAutoHide()
        }

    @Test
    @DisableFlags(Flags.FLAG_STATUS_BAR_ALWAYS_SCHEDULE_AUTO_HIDE)
    fun autoHide_invokedWhenTransitionsChanges_flagOff() =
        kosmos.runTest {
            setStatusBarMode(TRANSPARENT)
            orchestrator.start()
            reset(mockAutoHideController)

            fakeStatusBarInitializer.setNewTransitions(mock<PhoneStatusBarTransitions>())

            verify(mockAutoHideController).touchAutoHide()
        }

    @Test
    @EnableFlags(Flags.FLAG_STATUS_BAR_ALWAYS_SCHEDULE_AUTO_HIDE)
    fun autoHide_invokedWhenTransitionsChanges_flagOn() =
        kosmos.runTest {
            setStatusBarMode(TRANSPARENT)
            orchestrator.start()
            reset(mockAutoHideController)

            fakeStatusBarInitializer.setNewTransitions(mock<PhoneStatusBarTransitions>())

            verify(mockAutoHideController).touchAutoHide()
        }

    @Test
    @DisableFlags(Flags.FLAG_STATUS_BAR_ALWAYS_SCHEDULE_AUTO_HIDE)
    fun autoHide_notInvokedWhenAnimateChanges_flagOff() =
        kosmos.runTest {
            setStatusBarMode(TRANSPARENT)
            setStatusBarWindowState(StatusBarWindowState.Showing)
            orchestrator.start()
            reset(mockAutoHideController)

            // Changing the window state will affect the `shouldAnimateNextBarModeChange` value
            setStatusBarWindowState(StatusBarWindowState.Hidden)

            verify(mockAutoHideController, never()).touchAutoHide()
        }

    @Test
    @EnableFlags(Flags.FLAG_STATUS_BAR_ALWAYS_SCHEDULE_AUTO_HIDE)
    fun autoHide_notInvokedWhenAnimateChanges_flagOn() =
        kosmos.runTest {
            setStatusBarMode(TRANSPARENT)
            setStatusBarWindowState(StatusBarWindowState.Showing)
            orchestrator.start()
            reset(mockAutoHideController)

            // Changing the window state will affect the `shouldAnimateNextBarModeChange` value
            setStatusBarWindowState(StatusBarWindowState.Hidden)

            verify(mockAutoHideController, never()).touchAutoHide()
        }

    @Test
    @DisableFlags(Flags.FLAG_STATUS_BAR_ALWAYS_SCHEDULE_AUTO_HIDE)
    fun autoHide_flagOff_notInvokedWhenTransientShownStateChanges() =
        kosmos.runTest {
            setStatusBarMode(TRANSPARENT)
            orchestrator.start()
            reset(mockAutoHideController)

            setTransientStatusBar()

            verify(mockAutoHideController, never()).touchAutoHide()

            reset(mockAutoHideController)
            abortTransientStatusBar()

            verify(mockAutoHideController, never()).touchAutoHide()
        }

    /** Regression test for b/428659575. */
    @Test
    @EnableFlags(Flags.FLAG_STATUS_BAR_ALWAYS_SCHEDULE_AUTO_HIDE)
    fun autoHide_flagOn_invokedWhenTransientShownStateChanges() =
        kosmos.runTest {
            setStatusBarMode(TRANSPARENT)
            orchestrator.start()
            reset(mockAutoHideController)

            setTransientStatusBar()

            verify(mockAutoHideController).touchAutoHide()

            reset(mockAutoHideController)
            abortTransientStatusBar()

            verify(mockAutoHideController).touchAutoHide()
        }

    @Test
    fun statusBarVisible_notifiesBubbles() =
        testScope.runTest {
+37 −14
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.view.Display
import android.view.View
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.Dumpable
import com.android.systemui.Flags
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.demomode.DemoModeController
@@ -133,17 +134,33 @@ constructor(
                statusBarWindowState != StatusBarWindowState.Hidden
        }

    private val barModeUpdate =
    private data class BarModeAppearance(
        val animate: Boolean,
        val barTransitions: PhoneStatusBarTransitions,
        val statusBarMode: StatusBarMode,
        val isTransientShown: Boolean,
    )

    private val barModeAppearance =
        combine(
            shouldAnimateNextBarModeChange,
            phoneStatusBarTransitions.filterNotNull(),
            statusBarModeRepository.statusBarMode,
                ::Triple,
            statusBarModeRepository.isTransientShown,
            ::BarModeAppearance,
        )
            .distinctUntilChangedBy { (_, barTransitions, statusBarMode) ->
                // We only want to collect when either bar transitions or status bar mode
                // changed.
                Pair(barTransitions, statusBarMode)

    private val barModeUpdate =
        barModeAppearance.distinctUntilChangedBy {
            // We only want to collect when either bar transitions or status bar mode changed.
            Pair(it.barTransitions, it.statusBarMode)
        }

    private val autoHideUpdate =
        barModeAppearance.distinctUntilChangedBy {
            // Update auto-hide whenever `isTransientShown` changes so that we always hide the
            // transient status bar even if `statusBarMode` hasn't changed. See b/428659575.
            Triple(it.barTransitions, it.statusBarMode, it.isTransientShown)
        }

    /** Starts status bar orchestration. To be called when status bar is created. */
@@ -168,9 +185,12 @@ constructor(
                    }
                    launch { statusBarVisible.collect { updateBubblesVisibility(it) } }
                    launch {
                        barModeUpdate.collect { (animate, barTransitions, statusBarMode) ->
                            updateBarMode(animate, barTransitions, statusBarMode)
                        barModeUpdate.collect {
                            updateBarMode(it.animate, it.barTransitions, it.statusBarMode)
                        }
                    }
                    if (Flags.statusBarAlwaysScheduleAutoHide()) {
                        launch { autoHideUpdate.collect { autoHideController.touchAutoHide() } }
                    }
                }
        createAndAddWindow()
@@ -238,8 +258,11 @@ constructor(
        if (!demoModeController.isInDemoMode) {
            barTransitions.transitionTo(barMode.toTransitionModeInt(), animate)
        }

        if (!Flags.statusBarAlwaysScheduleAutoHide()) {
            autoHideController.touchAutoHide()
        }
    }

    private fun updateBubblesVisibility(statusBarVisible: Boolean) {
        if (displayId != Display.DEFAULT_DISPLAY) {
+9 −1
Original line number Diff line number Diff line
@@ -24,7 +24,7 @@ import org.mockito.kotlin.mock
class FakeStatusBarInitializer : StatusBarInitializer {

    val statusBarViewController = mock<PhoneStatusBarViewController>()
    val statusBarTransitions = mock<PhoneStatusBarTransitions>()
    var statusBarTransitions = mock<PhoneStatusBarTransitions>()

    var startedByCoreStartable: Boolean = false
        private set
@@ -47,4 +47,12 @@ class FakeStatusBarInitializer : StatusBarInitializer {
    override fun start() {
        startedByCoreStartable = true
    }

    fun setNewTransitions(transitions: PhoneStatusBarTransitions) {
        statusBarTransitions = transitions
        statusBarViewUpdatedListener?.onStatusBarViewUpdated(
            statusBarViewController,
            statusBarTransitions,
        )
    }
}