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

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

Merge "[flexiglass] SceneContainerViewModel is SysUiViewModel" into main

parents 3c8bbf8b 4ef84815
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -133,6 +133,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
                sceneInteractor = sceneInteractor,
                falsingInteractor = kosmos.falsingInteractor,
                powerInteractor = kosmos.powerInteractor,
                motionEventHandlerReceiver = {},
            )
            .apply { setTransitionState(transitionState) }
    }
@@ -199,6 +200,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
        shadeSceneContentViewModel.activateIn(testScope)
        shadeSceneActionsViewModel.activateIn(testScope)
        bouncerSceneContentViewModel.activateIn(testScope)
        sceneContainerViewModel.activateIn(testScope)

        assertWithMessage("Initial scene key mismatch!")
            .that(sceneContainerViewModel.currentScene.value)
+26 −0
Original line number Diff line number Diff line
@@ -14,6 +14,8 @@
 * limitations under the License.
 */

@file:OptIn(ExperimentalCoroutinesApi::class)

package com.android.systemui.scene.ui.viewmodel

import android.view.MotionEvent
@@ -25,6 +27,7 @@ import com.android.systemui.classifier.fakeFalsingManager
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -37,6 +40,8 @@ import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -57,6 +62,9 @@ class SceneContainerViewModelTest : SysuiTestCase() {

    private lateinit var underTest: SceneContainerViewModel

    private lateinit var activationJob: Job
    private var motionEventHandler: SceneContainerViewModel.MotionEventHandler? = null

    @Before
    fun setUp() {
        underTest =
@@ -64,7 +72,25 @@ class SceneContainerViewModelTest : SysuiTestCase() {
                sceneInteractor = sceneInteractor,
                falsingInteractor = kosmos.falsingInteractor,
                powerInteractor = kosmos.powerInteractor,
                motionEventHandlerReceiver = { motionEventHandler ->
                    this@SceneContainerViewModelTest.motionEventHandler = motionEventHandler
                },
            )
        activationJob = Job()
        underTest.activateIn(testScope, activationJob)
    }

    @Test
    fun activate_setsMotionEventHandler() =
        testScope.runTest { assertThat(motionEventHandler).isNotNull() }

    @Test
    fun deactivate_clearsMotionEventHandler() =
        testScope.runTest {
            activationJob.cancel()
            runCurrent()

            assertThat(motionEventHandler).isNull()
        }

    @Test
+8 −7
Original line number Diff line number Diff line
@@ -26,13 +26,12 @@ class SceneWindowRootView(
        attrs,
    ) {

    private lateinit var viewModel: SceneContainerViewModel

    private var motionEventHandler: SceneContainerViewModel.MotionEventHandler? = null
    // TODO(b/298525212): remove once Compose exposes window inset bounds.
    private val windowInsets: MutableStateFlow<WindowInsets?> = MutableStateFlow(null)

    fun init(
        viewModel: SceneContainerViewModel,
        viewModelFactory: SceneContainerViewModel.Factory,
        containerConfig: SceneContainerConfig,
        sharedNotificationContainer: SharedNotificationContainer,
        scenes: Set<Scene>,
@@ -40,11 +39,13 @@ class SceneWindowRootView(
        sceneDataSourceDelegator: SceneDataSourceDelegator,
        alternateBouncerDependencies: AlternateBouncerDependencies,
    ) {
        this.viewModel = viewModel
        setLayoutInsetsController(layoutInsetController)
        SceneWindowRootViewBinder.bind(
            view = this@SceneWindowRootView,
            viewModel = viewModel,
            viewModelFactory = viewModelFactory,
            motionEventHandlerReceiver = { motionEventHandler ->
                this.motionEventHandler = motionEventHandler
            },
            windowInsets = windowInsets,
            containerConfig = containerConfig,
            sharedNotificationContainer = sharedNotificationContainer,
@@ -69,10 +70,10 @@ class SceneWindowRootView(
    }

    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
        viewModel.onMotionEvent(ev)
        motionEventHandler?.onMotionEvent(ev)
        return super.dispatchTouchEvent(ev).also {
            TouchLogger.logDispatchTouch(TAG, ev, it)
            viewModel.onMotionEventComplete()
            motionEventHandler?.onMotionEventComplete()
        }
    }

+14 −8
Original line number Diff line number Diff line
@@ -29,8 +29,6 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.compose.animation.scene.SceneKey
import com.android.compose.theme.PlatformTheme
import com.android.internal.policy.ScreenDecorationsUtils
@@ -39,7 +37,9 @@ import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout
import com.android.systemui.common.ui.compose.windowinsets.ScreenDecorProvider
import com.android.systemui.keyguard.ui.composable.AlternateBouncer
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies
import com.android.systemui.lifecycle.WindowLifecycleState
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.lifecycle.viewModel
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scene
@@ -51,6 +51,7 @@ import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
@@ -63,7 +64,8 @@ object SceneWindowRootViewBinder {
    /** Binds between the view and view-model pertaining to a specific scene container. */
    fun bind(
        view: ViewGroup,
        viewModel: SceneContainerViewModel,
        viewModelFactory: SceneContainerViewModel.Factory,
        motionEventHandlerReceiver: (SceneContainerViewModel.MotionEventHandler?) -> Unit,
        windowInsets: StateFlow<WindowInsets?>,
        containerConfig: SceneContainerConfig,
        sharedNotificationContainer: SharedNotificationContainer,
@@ -85,8 +87,11 @@ object SceneWindowRootViewBinder {
        }

        view.repeatWhenAttached {
            lifecycleScope.launch {
                repeatOnLifecycle(Lifecycle.State.CREATED) {
            view.viewModel(
                minWindowLifecycleState = WindowLifecycleState.ATTACHED,
                factory = { viewModelFactory.create(motionEventHandlerReceiver) },
            ) { viewModel ->
                try {
                    view.setViewTreeOnBackPressedDispatcherOwner(
                        object : OnBackPressedDispatcherOwner {
                            override val onBackPressedDispatcher =
@@ -140,13 +145,14 @@ object SceneWindowRootViewBinder {
                            onVisibilityChangedInternal(isVisible)
                        }
                    }
                }

                    awaitCancellation()
                } finally {
                    // Here when destroyed.
                    view.removeAllViews()
                }
            }
        }
    }

    private fun createSceneContainerView(
        scope: CoroutineScope,
+48 −23
Original line number Diff line number Diff line
@@ -23,25 +23,26 @@ import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.classifier.Classifier
import com.android.systemui.classifier.domain.interactor.FalsingInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map

/** Models UI state for the scene container. */
@SysUISingleton
class SceneContainerViewModel
@Inject
@AssistedInject
constructor(
    private val sceneInteractor: SceneInteractor,
    private val falsingInteractor: FalsingInteractor,
    private val powerInteractor: PowerInteractor,
) {
    @Assisted private val motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
) : SysUiViewModel() {
    /**
     * Keys of all scenes in the container.
     *
@@ -56,6 +57,29 @@ constructor(
    /** Whether the container is visible. */
    val isVisible: StateFlow<Boolean> = sceneInteractor.isVisible

    override suspend fun onActivated() {
        try {
            // Sends a MotionEventHandler to the owner of the view-model so they can report
            // MotionEvents into the view-model.
            motionEventHandlerReceiver(
                object : MotionEventHandler {
                    override fun onMotionEvent(motionEvent: MotionEvent) {
                        this@SceneContainerViewModel.onMotionEvent(motionEvent)
                    }

                    override fun onMotionEventComplete() {
                        this@SceneContainerViewModel.onMotionEventComplete()
                    }
                }
            )
            awaitCancellation()
        } finally {
            // Clears the previously-sent MotionEventHandler so the owner of the view-model releases
            // their reference to it.
            motionEventHandlerReceiver(null)
        }
    }

    /**
     * Binds the given flow so the system remembers it.
     *
@@ -136,21 +160,22 @@ constructor(
        }
    }

    private fun replaceSceneFamilies(
        destinationScenes: Map<UserAction, UserActionResult>,
    ): Flow<Map<UserAction, UserActionResult>> {
        return destinationScenes
            .mapValues { (_, actionResult) ->
                sceneInteractor.resolveSceneFamily(actionResult.toScene).map { scene ->
                    actionResult.copy(toScene = scene)
                }
    /** Defines interface for classes that can handle externally-reported [MotionEvent]s. */
    interface MotionEventHandler {
        /** Notifies that a [MotionEvent] has occurred. */
        fun onMotionEvent(motionEvent: MotionEvent)

        /**
         * Notifies that the previous [MotionEvent] reported by [onMotionEvent] has finished
         * processing.
         */
        fun onMotionEventComplete()
    }
            .combineValueFlows()

    @AssistedFactory
    interface Factory {
        fun create(
            motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
        ): SceneContainerViewModel
    }
}

private fun <K, V> Map<K, Flow<V>>.combineValueFlows(): Flow<Map<K, V>> =
    combine(
        asIterable().map { (k, fv) -> fv.map { k to it } },
        transform = Array<Pair<K, V>>::toMap,
    )
Loading