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

Commit 417ff972 authored by Darrell Shi's avatar Darrell Shi
Browse files

Dynamically size and order ongoing content

- UMO is dynamically sized based on the number of ongoing content
- Ongoing cards are ordered based on creation time

Test: atest CommunalInteractorTest
Test: atest CommunalMediaRepositoryImplTest
Bug: 318423695
Bug: 311245493
Fix: 311245493
Flag: ACONFIG com.android.systemui.communal_hub DEVELOPMENT
Change-Id: I5d8086e77d42cde7420e17c1a77f3b6cd7c5b7c9
parent 13627b32
Loading
Loading
Loading
Loading
+29 −41
Original line number Original line Diff line number Diff line
/*
/*
 * Copyright (C) 2023 The Android Open Source Project
 * Copyright (C) 2024 The Android Open Source Project
 *
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * you may not use this file except in compliance with the License.
@@ -25,13 +25,13 @@ import com.android.systemui.media.controls.pipeline.MediaDataManager
import com.android.systemui.util.mockito.KotlinArgumentCaptor
import com.android.systemui.util.mockito.KotlinArgumentCaptor
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verify
@@ -59,74 +59,62 @@ class CommunalMediaRepositoryImplTest : SysuiTestCase() {
    }
    }


    @Test
    @Test
    fun mediaPlaying_defaultsToFalse() =
    fun hasAnyMediaOrRecommendation_defaultsToFalse() =
        testScope.runTest {
        testScope.runTest {
            mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
            mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)


            val isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
            val mediaModel = collectLastValue(mediaRepository.mediaModel)
            runCurrent()
            runCurrent()
            assertThat(isMediaPlaying()).isFalse()
            assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
        }
        }


    @Test
    @Test
    fun mediaPlaying_emitsInitialValue() =
    fun mediaModel_updatesWhenMediaDataLoaded() =
        testScope.runTest {
        testScope.runTest {
            // Start with media available.
            whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)

            mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
            mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)


            val isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
            // Listener is added
            runCurrent()
            verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
            assertThat(isMediaPlaying()).isTrue()
        }

    @Test
    fun mediaPlaying_updatesWhenMediaDataLoaded() =
        testScope.runTest {
            mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)


            // Initial value is false.
            // Initial value is false.
            var isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
            val mediaModel = collectLastValue(mediaRepository.mediaModel)
            runCurrent()
            runCurrent()
            assertThat(isMediaPlaying()).isFalse()
            assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()

            // Listener is added
            verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())


            // Change to media available and notify the listener.
            // Change to media available and notify the listener.
            whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
            whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
            whenever(mediaData.createdTimestampMillis).thenReturn(1234L)
            mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
            mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)

            // mediaPlaying now returns true.
            isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
            runCurrent()
            runCurrent()
            assertThat(isMediaPlaying()).isTrue()

            // Media active now returns true.
            assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isTrue()
            assertThat(mediaModel()?.createdTimestampMillis).isEqualTo(1234L)
        }
        }


    @Test
    @Test
    fun mediaPlaying_updatesWhenMediaDataRemoved() =
    fun mediaModel_updatesWhenMediaDataRemoved() =
        testScope.runTest {
        testScope.runTest {
            // Start with media available.
            whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)

            mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
            mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)


            // Initial value is true.
            // Listener is added
            var isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
            verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())

            // Change to media available and notify the listener.
            whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
            mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
            runCurrent()
            runCurrent()
            assertThat(isMediaPlaying()).isTrue()


            // Listener is added.
            // Media active now returns true.
            verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
            val mediaModel = collectLastValue(mediaRepository.mediaModel)
            assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isTrue()


            // Change to media unavailable and notify the listener.
            // Change to media unavailable and notify the listener.
            whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(false)
            whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(false)
            mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
            mediaDataListenerCaptor.value.onMediaDataRemoved("key")

            // mediaPlaying now returns false.
            isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
            runCurrent()
            runCurrent()
            assertThat(isMediaPlaying()).isFalse()

            // Media active now returns false.
            assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
        }
        }
}
}
+64 −17
Original line number Original line Diff line number Diff line
@@ -148,25 +148,29 @@ class CommunalInteractorTest : SysuiTestCase() {
            whenever(target1.smartspaceTargetId).thenReturn("target1")
            whenever(target1.smartspaceTargetId).thenReturn("target1")
            whenever(target1.featureType).thenReturn(SmartspaceTarget.FEATURE_WEATHER)
            whenever(target1.featureType).thenReturn(SmartspaceTarget.FEATURE_WEATHER)
            whenever(target1.remoteViews).thenReturn(mock(RemoteViews::class.java))
            whenever(target1.remoteViews).thenReturn(mock(RemoteViews::class.java))
            whenever(target1.creationTimeMillis).thenReturn(0L)


            // Does not have RemoteViews
            // Does not have RemoteViews
            val target2 = mock(SmartspaceTarget::class.java)
            val target2 = mock(SmartspaceTarget::class.java)
            whenever(target1.smartspaceTargetId).thenReturn("target2")
            whenever(target2.smartspaceTargetId).thenReturn("target2")
            whenever(target1.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
            whenever(target2.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
            whenever(target1.remoteViews).thenReturn(null)
            whenever(target2.remoteViews).thenReturn(null)
            whenever(target2.creationTimeMillis).thenReturn(0L)


            // Timer and has RemoteViews
            // Timer and has RemoteViews
            val target3 = mock(SmartspaceTarget::class.java)
            val target3 = mock(SmartspaceTarget::class.java)
            whenever(target1.smartspaceTargetId).thenReturn("target3")
            whenever(target3.smartspaceTargetId).thenReturn("target3")
            whenever(target1.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
            whenever(target3.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
            whenever(target1.remoteViews).thenReturn(mock(RemoteViews::class.java))
            whenever(target3.remoteViews).thenReturn(mock(RemoteViews::class.java))
            whenever(target3.creationTimeMillis).thenReturn(0L)


            val targets = listOf(target1, target2, target3)
            val targets = listOf(target1, target2, target3)
            smartspaceRepository.setCommunalSmartspaceTargets(targets)
            smartspaceRepository.setCommunalSmartspaceTargets(targets)


            val smartspaceContent by collectLastValue(underTest.smartspaceContent)
            val smartspaceContent by collectLastValue(underTest.ongoingContent)
            assertThat(smartspaceContent?.size).isEqualTo(1)
            assertThat(smartspaceContent?.size).isEqualTo(1)
            assertThat(smartspaceContent?.get(0)?.key).isEqualTo("smartspace_target3")
            assertThat(smartspaceContent?.get(0)?.key)
                .isEqualTo(CommunalContentModel.KEY.smartspace("target3"))
        }
        }


    @Test
    @Test
@@ -256,16 +260,12 @@ class CommunalInteractorTest : SysuiTestCase() {


            val targets = mutableListOf<SmartspaceTarget>()
            val targets = mutableListOf<SmartspaceTarget>()
            for (index in 0 until totalTargets) {
            for (index in 0 until totalTargets) {
                val target = mock(SmartspaceTarget::class.java)
                targets.add(smartspaceTimer(index.toString()))
                whenever(target.smartspaceTargetId).thenReturn("target$index")
                whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
                whenever(target.remoteViews).thenReturn(mock(RemoteViews::class.java))
                targets.add(target)
            }
            }


            smartspaceRepository.setCommunalSmartspaceTargets(targets)
            smartspaceRepository.setCommunalSmartspaceTargets(targets)


            val smartspaceContent by collectLastValue(underTest.smartspaceContent)
            val smartspaceContent by collectLastValue(underTest.ongoingContent)
            assertThat(smartspaceContent?.size).isEqualTo(totalTargets)
            assertThat(smartspaceContent?.size).isEqualTo(totalTargets)
            for (index in 0 until totalTargets) {
            for (index in 0 until totalTargets) {
                assertThat(smartspaceContent?.get(index)?.size).isEqualTo(expectedSizes[index])
                assertThat(smartspaceContent?.get(index)?.size).isEqualTo(expectedSizes[index])
@@ -279,13 +279,51 @@ class CommunalInteractorTest : SysuiTestCase() {
            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)


            // Media is playing.
            // Media is playing.
            mediaRepository.mediaPlaying.value = true
            mediaRepository.mediaActive()


            val umoContent by collectLastValue(underTest.umoContent)
            val umoContent by collectLastValue(underTest.ongoingContent)


            assertThat(umoContent?.size).isEqualTo(1)
            assertThat(umoContent?.size).isEqualTo(1)
            assertThat(umoContent?.get(0)).isInstanceOf(CommunalContentModel.Umo::class.java)
            assertThat(umoContent?.get(0)).isInstanceOf(CommunalContentModel.Umo::class.java)
            assertThat(umoContent?.get(0)?.key).isEqualTo(CommunalContentModel.UMO_KEY)
            assertThat(umoContent?.get(0)?.key).isEqualTo(CommunalContentModel.KEY.umo())
        }

    @Test
    fun ongoing_shouldOrderAndSizeByTimestamp() =
        testScope.runTest {
            // Keyguard showing, and tutorial completed.
            keyguardRepository.setKeyguardShowing(true)
            keyguardRepository.setKeyguardOccluded(false)
            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)

            // Timer1 started
            val timer1 = smartspaceTimer("timer1", timestamp = 1L)
            smartspaceRepository.setCommunalSmartspaceTargets(listOf(timer1))

            // Umo started
            mediaRepository.mediaActive(timestamp = 2L)

            // Timer2 started
            val timer2 = smartspaceTimer("timer2", timestamp = 3L)
            smartspaceRepository.setCommunalSmartspaceTargets(listOf(timer1, timer2))

            // Timer3 started
            val timer3 = smartspaceTimer("timer3", timestamp = 4L)
            smartspaceRepository.setCommunalSmartspaceTargets(listOf(timer1, timer2, timer3))

            val ongoingContent by collectLastValue(underTest.ongoingContent)
            assertThat(ongoingContent?.size).isEqualTo(4)
            assertThat(ongoingContent?.get(0)?.key)
                .isEqualTo(CommunalContentModel.KEY.smartspace("timer3"))
            assertThat(ongoingContent?.get(0)?.size).isEqualTo(CommunalContentSize.FULL)
            assertThat(ongoingContent?.get(1)?.key)
                .isEqualTo(CommunalContentModel.KEY.smartspace("timer2"))
            assertThat(ongoingContent?.get(1)?.size).isEqualTo(CommunalContentSize.THIRD)
            assertThat(ongoingContent?.get(2)?.key).isEqualTo(CommunalContentModel.KEY.umo())
            assertThat(ongoingContent?.get(2)?.size).isEqualTo(CommunalContentSize.THIRD)
            assertThat(ongoingContent?.get(3)?.key)
                .isEqualTo(CommunalContentModel.KEY.smartspace("timer1"))
            assertThat(ongoingContent?.get(3)?.size).isEqualTo(CommunalContentSize.THIRD)
        }
        }


    @Test
    @Test
@@ -334,4 +372,13 @@ class CommunalInteractorTest : SysuiTestCase() {
            underTest.showWidgetEditor()
            underTest.showWidgetEditor()
            verify(editWidgetsActivityStarter).startActivity()
            verify(editWidgetsActivityStarter).startActivity()
        }
        }

    private fun smartspaceTimer(id: String, timestamp: Long = 0L): SmartspaceTarget {
        val timer = mock(SmartspaceTarget::class.java)
        whenever(timer.smartspaceTargetId).thenReturn(id)
        whenever(timer.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
        whenever(timer.remoteViews).thenReturn(mock(RemoteViews::class.java))
        whenever(timer.creationTimeMillis).thenReturn(timestamp)
        return timer
    }
}
}
+1 −1
Original line number Original line Diff line number Diff line
@@ -119,7 +119,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
            smartspaceRepository.setCommunalSmartspaceTargets(listOf(target))
            smartspaceRepository.setCommunalSmartspaceTargets(listOf(target))


            // Media playing.
            // Media playing.
            mediaRepository.mediaPlaying.value = true
            mediaRepository.mediaActive()


            val communalContent by collectLastValue(underTest.communalContent)
            val communalContent by collectLastValue(underTest.communalContent)


+1 −1
Original line number Original line Diff line number Diff line
@@ -138,7 +138,7 @@ class CommunalViewModelTest : SysuiTestCase() {
            smartspaceRepository.setCommunalSmartspaceTargets(listOf(target))
            smartspaceRepository.setCommunalSmartspaceTargets(listOf(target))


            // Media playing.
            // Media playing.
            mediaRepository.mediaPlaying.value = true
            mediaRepository.mediaActive()


            val communalContent by collectLastValue(underTest.communalContent)
            val communalContent by collectLastValue(underTest.communalContent)


+30 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2024 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.data.model

/** Data model of media on the communal hub. */
data class CommunalMediaModel(
    val hasAnyMediaOrRecommendation: Boolean,
    val createdTimestampMillis: Long = 0L,
) {
    companion object {
        val INACTIVE =
            CommunalMediaModel(
                hasAnyMediaOrRecommendation = false,
            )
    }
}
Loading