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

Commit e3ed9b0a authored by Lucas Silva's avatar Lucas Silva
Browse files

Remove unnecessary logic from HomeControlsComponentInteractor

There is logic to attempt a restart of the dream if the dream crashed
due to a package update. However, this is not necessary as we already
poke the user activity timeout when a dream crashes - so the dream would
naturally start again the next time the device times out.

This is necessary since it reduces the amount of information we need to
pass between the dream (running in foreground user) and the system user.

Bug: 370691405
Test: atest HomeControlsComponentInteractorTest
Flag: com.android.systemui.home_controls_dream_hsum
Change-Id: I6e71156c603478350ae283ca972c86dbc4732d52
parent d30a2c8b
Loading
Loading
Loading
Loading
+7 −123
Original line number Diff line number Diff line
@@ -20,40 +20,32 @@ import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.ServiceInfo
import android.content.pm.UserInfo
import android.os.PowerManager
import android.os.UserHandle
import android.os.powerManager
import android.service.dream.dreamManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.data.repository.fakePackageChangeRepository
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.panels.SelectedComponentRepository
import com.android.systemui.controls.panels.authorizedPanelsRepository
import com.android.systemui.controls.panels.selectedComponentRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor.Companion.MAX_UPDATE_CORRELATION_DELAY
import com.android.systemui.dreams.homecontrols.system.domain.interactor.controlsComponent
import com.android.systemui.dreams.homecontrols.system.domain.interactor.controlsListingController
import com.android.systemui.dreams.homecontrols.system.domain.interactor.homeControlsComponentInteractor
import com.android.systemui.kosmos.testScope
import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.anyLong
import org.mockito.Mockito.never
import org.mockito.Mockito.verify

@OptIn(ExperimentalCoroutinesApi::class)
@@ -68,7 +60,6 @@ class HomeControlsComponentInteractorTest : SysuiTestCase() {
    @Before
    fun setUp() =
        with(kosmos) {
            fakeSystemClock.setCurrentTimeMillis(0)
            fakeUserRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER))
            whenever(controlsComponent.getControlsListingController())
                .thenReturn(Optional.of(controlsListingController))
@@ -172,113 +163,6 @@ class HomeControlsComponentInteractorTest : SysuiTestCase() {
            }
        }

    @Test
    fun testMonitoringUpdatesAndRestart() =
        with(kosmos) {
            testScope.runTest {
                setActiveUser(PRIMARY_USER)
                authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
                selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
                whenever(controlsListingController.getCurrentServices())
                    .thenReturn(
                        listOf(ControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = true))
                    )

                val job = launch { underTest.monitorUpdatesAndRestart() }
                val panelComponent by collectLastValue(underTest.panelComponent)

                assertThat(panelComponent).isEqualTo(TEST_COMPONENT)
                verify(dreamManager, never()).startDream()

                fakeSystemClock.advanceTime(100)
                // The package update is started.
                fakePackageChangeRepository.notifyUpdateStarted(
                    TEST_PACKAGE,
                    UserHandle.of(PRIMARY_USER_ID),
                )
                fakeSystemClock.advanceTime(MAX_UPDATE_CORRELATION_DELAY.inWholeMilliseconds)
                // Task fragment becomes empty as a result of the update.
                underTest.onDreamEndUnexpectedly()

                runCurrent()
                verify(dreamManager, never()).startDream()

                fakeSystemClock.advanceTime(500)
                // The package update is finished.
                fakePackageChangeRepository.notifyUpdateFinished(
                    TEST_PACKAGE,
                    UserHandle.of(PRIMARY_USER_ID),
                )

                runCurrent()
                verify(dreamManager).startDream()
                job.cancel()
            }
        }

    @Test
    fun testMonitoringUpdatesAndRestart_dreamEndsAfterDelay() =
        with(kosmos) {
            testScope.runTest {
                setActiveUser(PRIMARY_USER)
                authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE))
                selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
                whenever(controlsListingController.getCurrentServices())
                    .thenReturn(
                        listOf(ControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = true))
                    )

                val job = launch { underTest.monitorUpdatesAndRestart() }
                val panelComponent by collectLastValue(underTest.panelComponent)

                assertThat(panelComponent).isEqualTo(TEST_COMPONENT)
                verify(dreamManager, never()).startDream()

                fakeSystemClock.advanceTime(100)
                // The package update is started.
                fakePackageChangeRepository.notifyUpdateStarted(
                    TEST_PACKAGE,
                    UserHandle.of(PRIMARY_USER_ID),
                )
                fakeSystemClock.advanceTime(MAX_UPDATE_CORRELATION_DELAY.inWholeMilliseconds + 100)
                // Task fragment becomes empty as a result of the update.
                underTest.onDreamEndUnexpectedly()

                runCurrent()
                verify(dreamManager, never()).startDream()

                fakeSystemClock.advanceTime(500)
                // The package update is finished.
                fakePackageChangeRepository.notifyUpdateFinished(
                    TEST_PACKAGE,
                    UserHandle.of(PRIMARY_USER_ID),
                )

                runCurrent()
                verify(dreamManager, never()).startDream()
                job.cancel()
            }
        }

    @Test
    fun testDreamUnexpectedlyEnds_triggersUserActivity() =
        with(kosmos) {
            testScope.runTest {
                fakeSystemClock.setUptimeMillis(100000L)
                verify(powerManager, never()).userActivity(anyLong(), anyInt(), anyInt())

                // Dream ends unexpectedly
                underTest.onDreamEndUnexpectedly()

                verify(powerManager)
                    .userActivity(
                        100000L,
                        PowerManager.USER_ACTIVITY_EVENT_OTHER,
                        PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS
                    )
            }
        }

    private fun runServicesUpdate(hasPanelBoolean: Boolean = true) {
        val listings =
            listOf(ControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = hasPanelBoolean))
@@ -297,7 +181,7 @@ class HomeControlsComponentInteractorTest : SysuiTestCase() {
    private fun ControlsServiceInfo(
        componentName: ComponentName,
        label: CharSequence,
        hasPanel: Boolean
        hasPanel: Boolean,
    ): ControlsServiceInfo {
        val serviceInfo =
            ServiceInfo().apply {
@@ -312,7 +196,7 @@ class HomeControlsComponentInteractorTest : SysuiTestCase() {
        context: Context,
        serviceInfo: ServiceInfo,
        private val label: CharSequence,
        hasPanel: Boolean
        hasPanel: Boolean,
    ) : ControlsServiceInfo(context, serviceInfo) {

        init {
@@ -332,7 +216,7 @@ class HomeControlsComponentInteractorTest : SysuiTestCase() {
            UserInfo(
                /* id= */ PRIMARY_USER_ID,
                /* name= */ "primary user",
                /* flags= */ UserInfo.FLAG_PRIMARY
                /* flags= */ UserInfo.FLAG_PRIMARY,
            )

        private const val ANOTHER_USER_ID = 1
@@ -340,7 +224,7 @@ class HomeControlsComponentInteractorTest : SysuiTestCase() {
            UserInfo(
                /* id= */ ANOTHER_USER_ID,
                /* name= */ "another user",
                /* flags= */ UserInfo.FLAG_PRIMARY
                /* flags= */ UserInfo.FLAG_PRIMARY,
            )
        private const val TEST_PACKAGE = "pkg"
        private val TEST_COMPONENT = ComponentName(TEST_PACKAGE, "service")
+4 −88
Original line number Diff line number Diff line
@@ -14,15 +14,9 @@
 * limitations under the License.
 */

package com.android.systemui.dreams.homecontrols.domain.interactor
package com.android.systemui.dreams.homecontrols.system.domain.interactor

import android.annotation.SuppressLint
import android.app.DreamManager
import android.content.ComponentName
import android.os.PowerManager
import android.os.UserHandle
import com.android.systemui.common.domain.interactor.PackageChangeInteractor
import com.android.systemui.common.shared.model.PackageChangeModel
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
@@ -32,25 +26,16 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.getOrNull
import com.android.systemui.util.kotlin.pairwiseBy
import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.time.SystemClock
import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import javax.inject.Inject
import kotlin.math.abs
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
@@ -65,11 +50,7 @@ constructor(
    controlsComponent: ControlsComponent,
    authorizedPanelsRepository: AuthorizedPanelsRepository,
    userRepository: UserRepository,
    private val packageChangeInteractor: PackageChangeInteractor,
    private val systemClock: SystemClock,
    private val powerManager: PowerManager,
    private val dreamManager: DreamManager,
    @Background private val bgScope: CoroutineScope
    @Background private val bgScope: CoroutineScope,
) {
    private val controlsListingController: ControlsListingController? =
        controlsComponent.getControlsListingController().getOrNull()
@@ -108,10 +89,7 @@ constructor(

    /** Gets all panels which are available and authorized by the user */
    private val allAvailableAndAuthorizedPanels: Flow<List<PanelComponent>> =
        combine(
            allAvailableServices(),
            allAuthorizedPanels,
        ) { serviceInfos, authorizedPanels ->
        combine(allAvailableServices(), allAuthorizedPanels) { serviceInfos, authorizedPanels ->
            serviceInfos.mapNotNull {
                val panelActivity = it.panelActivity
                if (it.componentName.packageName in authorizedPanels && panelActivity != null) {
@@ -123,10 +101,7 @@ constructor(
        }

    val panelComponent: StateFlow<ComponentName?> =
        combine(
                allAvailableAndAuthorizedPanels,
                selectedPanel,
            ) { panels, selected ->
        combine(allAvailableAndAuthorizedPanels, selectedPanel) { panels, selected ->
                val item =
                    panels.firstOrNull { it.componentName == selected?.componentName }
                        ?: panels.firstOrNull()
@@ -134,67 +109,8 @@ constructor(
            }
            .stateIn(bgScope, SharingStarted.Eagerly, null)

    private val taskFragmentFinished =
        MutableSharedFlow<Long>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)

    fun onDreamEndUnexpectedly() {
        powerManager.userActivity(
            systemClock.uptimeMillis(),
            PowerManager.USER_ACTIVITY_EVENT_OTHER,
            PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS,
        )
        taskFragmentFinished.tryEmit(systemClock.currentTimeMillis())
    }

    /**
     * Monitors if the current home panel package is updated and causes the dream to finish, and
     * attempts to restart the dream in this case.
     */
    @SuppressLint("MissingPermission")
    suspend fun monitorUpdatesAndRestart() {
        taskFragmentFinished.resetReplayCache()
        panelComponent
            .flatMapLatest { component ->
                if (component == null) return@flatMapLatest emptyFlow()
                packageChangeInteractor.packageChanged(UserHandle.CURRENT, component.packageName)
            }
            .filter { it.isUpdate() }
            // Wait for an UpdatedStarted - UpdateFinished pair to ensure the update has finished.
            .pairwiseBy(::validateUpdatePair)
            .filterNotNull()
            .sample(taskFragmentFinished, ::Pair)
            .filter { (updateStarted, lastFinishedTimestamp) ->
                abs(updateStarted.timeMillis - lastFinishedTimestamp) <=
                    MAX_UPDATE_CORRELATION_DELAY.inWholeMilliseconds
            }
            .collect { dreamManager.startDream() }
    }

    private data class PanelComponent(
        val componentName: ComponentName,
        val panelActivity: ComponentName,
    )

    companion object {
        /**
         * The maximum delay between a package update **starting** and the task fragment finishing
         * which causes us to correlate the package update as the cause of the task fragment
         * finishing.
         */
        val MAX_UPDATE_CORRELATION_DELAY = 500.milliseconds
    }
}

private fun PackageChangeModel.isUpdate() =
    this is PackageChangeModel.UpdateStarted || this is PackageChangeModel.UpdateFinished

private fun validateUpdatePair(
    updateStarted: PackageChangeModel,
    updateFinished: PackageChangeModel
): PackageChangeModel.UpdateStarted? =
    when {
        !updateStarted.isSamePackage(updateFinished) -> null
        updateStarted !is PackageChangeModel.UpdateStarted -> null
        updateFinished !is PackageChangeModel.UpdateFinished -> null
        else -> updateStarted
}
+1 −10
Original line number Diff line number Diff line
@@ -13,21 +13,16 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.systemui.dreams.homecontrols
package com.android.systemui.dreams.homecontrols.system.domain.interactor

import android.os.powerManager
import android.service.dream.dreamManager
import com.android.systemui.common.domain.interactor.packageChangeInteractor
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.panels.authorizedPanelsRepository
import com.android.systemui.controls.panels.selectedComponentRepository
import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.time.fakeSystemClock

val Kosmos.homeControlsComponentInteractor by
    Kosmos.Fixture {
@@ -37,10 +32,6 @@ val Kosmos.homeControlsComponentInteractor by
            authorizedPanelsRepository = authorizedPanelsRepository,
            userRepository = fakeUserRepository,
            bgScope = applicationCoroutineScope,
            systemClock = fakeSystemClock,
            powerManager = powerManager,
            dreamManager = dreamManager,
            packageChangeInteractor = packageChangeInteractor,
        )
    }