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

Commit ff7ee00f authored by Darrell Shi's avatar Darrell Shi
Browse files

Introduce BaseCommunalViewModel

This change introduces a BaseCommunalViewModel, which is an abstract
class with basic implementation for shared behavior across view models.

The content merging has been moved to the view model.

This changes sets it up so that there can be a different view model for
the edit mode.

Test: atest CommunalViewModelTest
Test: atest CommunalInteractorTest
Bug: 309968801
Flag: ACONFIG com.android.systemui.communal_hub DEVELOPMENT
Change-Id: I7076b98fdbfd8b786b7086c9a579ca8515c4347f
parent 630a48f9
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -22,7 +22,7 @@ import android.view.View
import android.view.WindowInsets
import androidx.activity.ComponentActivity
import androidx.lifecycle.LifecycleOwner
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.scene.shared.model.Scene
@@ -67,12 +67,12 @@ object ComposeFacade : BaseComposeFacade {

    override fun createCommunalView(
        context: Context,
        viewModel: CommunalViewModel,
        viewModel: BaseCommunalViewModel,
    ): View {
        throwComposeUnavailableError()
    }

    override fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View {
    override fun createCommunalContainer(context: Context, viewModel: BaseCommunalViewModel): View {
        throwComposeUnavailableError()
    }

+3 −3
Original line number Diff line number Diff line
@@ -32,7 +32,7 @@ import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider
import com.android.systemui.communal.ui.compose.CommunalContainer
import com.android.systemui.communal.ui.compose.CommunalHub
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.people.ui.compose.PeopleScreen
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import com.android.systemui.qs.footer.ui.compose.FooterActions
@@ -98,14 +98,14 @@ object ComposeFacade : BaseComposeFacade {

    override fun createCommunalView(
        context: Context,
        viewModel: CommunalViewModel,
        viewModel: BaseCommunalViewModel,
    ): View {
        return ComposeView(context).apply {
            setContent { PlatformTheme { CommunalHub(viewModel = viewModel) } }
        }
    }

    override fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View {
    override fun createCommunalContainer(context: Context, viewModel: BaseCommunalViewModel): View {
        return ComposeView(context).apply {
            setContent { PlatformTheme { CommunalContainer(viewModel = viewModel) } }
        }
+3 −3
Original line number Diff line number Diff line
@@ -32,7 +32,7 @@ import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.transitions
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import kotlinx.coroutines.flow.transform

object Communal {
@@ -59,7 +59,7 @@ val sceneTransitions = transitions {
@Composable
fun CommunalContainer(
    modifier: Modifier = Modifier,
    viewModel: CommunalViewModel,
    viewModel: BaseCommunalViewModel,
) {
    val currentScene: SceneKey by
        viewModel.currentScene
@@ -129,7 +129,7 @@ private fun BlankScene(
/** Scene containing the glanceable hub UI. */
@Composable
private fun SceneScope.CommunalScene(
    viewModel: CommunalViewModel,
    viewModel: BaseCommunalViewModel,
    modifier: Modifier = Modifier,
) {
    Box(modifier.element(Communal.Elements.Content)) { CommunalHub(viewModel = viewModel) }
+4 −4
Original line number Diff line number Diff line
@@ -51,7 +51,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.media.controls.ui.MediaHierarchyManager
import com.android.systemui.media.controls.ui.MediaHostState
import com.android.systemui.res.R
@@ -59,7 +59,7 @@ import com.android.systemui.res.R
@Composable
fun CommunalHub(
    modifier: Modifier = Modifier,
    viewModel: CommunalViewModel,
    viewModel: BaseCommunalViewModel,
) {
    val communalContent by viewModel.communalContent.collectAsState(initial = emptyList())
    Box(
@@ -109,7 +109,7 @@ fun CommunalHub(
@Composable
private fun CommunalContent(
    model: CommunalContentModel,
    viewModel: CommunalViewModel,
    viewModel: BaseCommunalViewModel,
    size: SizeF,
    deleteOnClick: (id: Int) -> Unit,
    modifier: Modifier = Modifier,
@@ -173,7 +173,7 @@ private fun TutorialContent(modifier: Modifier = Modifier) {
}

@Composable
private fun Umo(viewModel: CommunalViewModel, modifier: Modifier = Modifier) {
private fun Umo(viewModel: BaseCommunalViewModel, modifier: Modifier = Modifier) {
    AndroidView(
        modifier = modifier,
        factory = {
+148 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.communal.view.viewmodel

import android.app.smartspace.SmartspaceTarget
import android.provider.Settings
import android.widget.RemoteViews
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations

@SmallTest
@RunWith(AndroidJUnit4::class)
class CommunalViewModelTest : SysuiTestCase() {
    @Mock private lateinit var mediaHost: MediaHost

    private lateinit var testScope: TestScope

    private lateinit var keyguardRepository: FakeKeyguardRepository
    private lateinit var communalRepository: FakeCommunalRepository
    private lateinit var tutorialRepository: FakeCommunalTutorialRepository
    private lateinit var widgetRepository: FakeCommunalWidgetRepository
    private lateinit var smartspaceRepository: FakeSmartspaceRepository
    private lateinit var mediaRepository: FakeCommunalMediaRepository

    private lateinit var underTest: CommunalViewModel

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)

        testScope = TestScope()

        val withDeps = CommunalInteractorFactory.create()
        keyguardRepository = withDeps.keyguardRepository
        communalRepository = withDeps.communalRepository
        tutorialRepository = withDeps.tutorialRepository
        widgetRepository = withDeps.widgetRepository
        smartspaceRepository = withDeps.smartspaceRepository
        mediaRepository = withDeps.mediaRepository

        underTest =
            CommunalViewModel(
                withDeps.communalInteractor,
                withDeps.tutorialInteractor,
                mediaHost,
            )
    }

    @Test
    fun tutorial_tutorialNotCompletedAndKeyguardVisible_showTutorialContent() =
        testScope.runTest {
            // Keyguard showing, and tutorial not started.
            keyguardRepository.setKeyguardShowing(true)
            keyguardRepository.setKeyguardOccluded(false)
            tutorialRepository.setTutorialSettingState(
                Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED
            )

            val communalContent by collectLastValue(underTest.communalContent)

            assertThat(communalContent!!).isNotEmpty()
            communalContent!!.forEach { model ->
                assertThat(model is CommunalContentModel.Tutorial).isTrue()
            }
        }

    @Test
    fun ordering_smartspaceBeforeUmoBeforeWidgets() =
        testScope.runTest {
            tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)

            // Widgets available.
            val widgets =
                listOf(
                    CommunalWidgetContentModel(
                        appWidgetId = 0,
                        priority = 30,
                        providerInfo = mock(),
                    ),
                    CommunalWidgetContentModel(
                        appWidgetId = 1,
                        priority = 20,
                        providerInfo = mock(),
                    ),
                )
            widgetRepository.setCommunalWidgets(widgets)

            // Smartspace available.
            val target = Mockito.mock(SmartspaceTarget::class.java)
            whenever(target.smartspaceTargetId).thenReturn("target")
            whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
            whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java))
            smartspaceRepository.setLockscreenSmartspaceTargets(listOf(target))

            // Media playing.
            mediaRepository.mediaPlaying.value = true

            val communalContent by collectLastValue(underTest.communalContent)

            // Order is smart space, then UMO, then widget content.
            assertThat(communalContent?.size).isEqualTo(4)
            assertThat(communalContent?.get(0))
                .isInstanceOf(CommunalContentModel.Smartspace::class.java)
            assertThat(communalContent?.get(1)).isInstanceOf(CommunalContentModel.Umo::class.java)
            assertThat(communalContent?.get(2))
                .isInstanceOf(CommunalContentModel.Widget::class.java)
            assertThat(communalContent?.get(3))
                .isInstanceOf(CommunalContentModel.Widget::class.java)
        }
}
Loading