Merge "Dynamically size and order ongoing content" into main
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
similarity index 69%
rename from packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
index 455f986..92b75cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
@@ -1,5 +1,5 @@
/*
- * 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");
* you may not use this file except in compliance with the License.
@@ -25,13 +25,13 @@
import com.android.systemui.util.mockito.KotlinArgumentCaptor
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlin.test.Test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
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.Mock
import org.mockito.Mockito.verify
@@ -59,37 +59,43 @@
}
@Test
- fun mediaPlaying_defaultsToFalse() =
+ fun hasAnyMediaOrRecommendation_defaultsToFalse() =
testScope.runTest {
mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
- val isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
+ val mediaModel = collectLastValue(mediaRepository.mediaModel)
runCurrent()
- assertThat(isMediaPlaying()).isFalse()
+ assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
}
@Test
- fun mediaPlaying_emitsInitialValue() =
- testScope.runTest {
- // Start with media available.
- whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
-
- mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
-
- val isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
- runCurrent()
- assertThat(isMediaPlaying()).isTrue()
- }
-
- @Test
- fun mediaPlaying_updatesWhenMediaDataLoaded() =
+ fun mediaModel_updatesWhenMediaDataLoaded() =
testScope.runTest {
mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
+ // Listener is added
+ verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
+
// Initial value is false.
- var isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
+ val mediaModel = collectLastValue(mediaRepository.mediaModel)
runCurrent()
- assertThat(isMediaPlaying()).isFalse()
+ assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
+
+ // Change to media available and notify the listener.
+ whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
+ whenever(mediaData.createdTimestampMillis).thenReturn(1234L)
+ mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
+ runCurrent()
+
+ // Media active now returns true.
+ assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isTrue()
+ assertThat(mediaModel()?.createdTimestampMillis).isEqualTo(1234L)
+ }
+
+ @Test
+ fun mediaModel_updatesWhenMediaDataRemoved() =
+ testScope.runTest {
+ mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
// Listener is added
verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
@@ -97,36 +103,18 @@
// Change to media available and notify the listener.
whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
-
- // mediaPlaying now returns true.
- isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
runCurrent()
- assertThat(isMediaPlaying()).isTrue()
- }
- @Test
- fun mediaPlaying_updatesWhenMediaDataRemoved() =
- testScope.runTest {
- // Start with media available.
- whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
-
- mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
-
- // Initial value is true.
- var isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
- runCurrent()
- assertThat(isMediaPlaying()).isTrue()
-
- // Listener is added.
- verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
+ // Media active now returns true.
+ val mediaModel = collectLastValue(mediaRepository.mediaModel)
+ assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isTrue()
// Change to media unavailable and notify the listener.
whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(false)
- mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
-
- // mediaPlaying now returns false.
- isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
+ mediaDataListenerCaptor.value.onMediaDataRemoved("key")
runCurrent()
- assertThat(isMediaPlaying()).isFalse()
+
+ // Media active now returns false.
+ assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 62084aa..a4940c3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -148,25 +148,29 @@
whenever(target1.smartspaceTargetId).thenReturn("target1")
whenever(target1.featureType).thenReturn(SmartspaceTarget.FEATURE_WEATHER)
whenever(target1.remoteViews).thenReturn(mock(RemoteViews::class.java))
+ whenever(target1.creationTimeMillis).thenReturn(0L)
// Does not have RemoteViews
val target2 = mock(SmartspaceTarget::class.java)
- whenever(target1.smartspaceTargetId).thenReturn("target2")
- whenever(target1.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
- whenever(target1.remoteViews).thenReturn(null)
+ whenever(target2.smartspaceTargetId).thenReturn("target2")
+ whenever(target2.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
+ whenever(target2.remoteViews).thenReturn(null)
+ whenever(target2.creationTimeMillis).thenReturn(0L)
// Timer and has RemoteViews
val target3 = mock(SmartspaceTarget::class.java)
- whenever(target1.smartspaceTargetId).thenReturn("target3")
- whenever(target1.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
- whenever(target1.remoteViews).thenReturn(mock(RemoteViews::class.java))
+ whenever(target3.smartspaceTargetId).thenReturn("target3")
+ whenever(target3.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
+ whenever(target3.remoteViews).thenReturn(mock(RemoteViews::class.java))
+ whenever(target3.creationTimeMillis).thenReturn(0L)
val targets = listOf(target1, target2, target3)
smartspaceRepository.setCommunalSmartspaceTargets(targets)
- val smartspaceContent by collectLastValue(underTest.smartspaceContent)
+ val smartspaceContent by collectLastValue(underTest.ongoingContent)
assertThat(smartspaceContent?.size).isEqualTo(1)
- assertThat(smartspaceContent?.get(0)?.key).isEqualTo("smartspace_target3")
+ assertThat(smartspaceContent?.get(0)?.key)
+ .isEqualTo(CommunalContentModel.KEY.smartspace("target3"))
}
@Test
@@ -256,16 +260,12 @@
val targets = mutableListOf<SmartspaceTarget>()
for (index in 0 until totalTargets) {
- val target = mock(SmartspaceTarget::class.java)
- whenever(target.smartspaceTargetId).thenReturn("target$index")
- whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
- whenever(target.remoteViews).thenReturn(mock(RemoteViews::class.java))
- targets.add(target)
+ targets.add(smartspaceTimer(index.toString()))
}
smartspaceRepository.setCommunalSmartspaceTargets(targets)
- val smartspaceContent by collectLastValue(underTest.smartspaceContent)
+ val smartspaceContent by collectLastValue(underTest.ongoingContent)
assertThat(smartspaceContent?.size).isEqualTo(totalTargets)
for (index in 0 until totalTargets) {
assertThat(smartspaceContent?.get(index)?.size).isEqualTo(expectedSizes[index])
@@ -279,13 +279,51 @@
tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
// 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?.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
@@ -334,4 +372,13 @@
underTest.showWidgetEditor()
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
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index f2f9705..9b7688a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -119,7 +119,7 @@
smartspaceRepository.setCommunalSmartspaceTargets(listOf(target))
// Media playing.
- mediaRepository.mediaPlaying.value = true
+ mediaRepository.mediaActive()
val communalContent by collectLastValue(underTest.communalContent)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 182cc5d..6240f6a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -140,7 +140,7 @@
smartspaceRepository.setCommunalSmartspaceTargets(listOf(target))
// Media playing.
- mediaRepository.mediaPlaying.value = true
+ mediaRepository.mediaActive()
val communalContent by collectLastValue(underTest.communalContent)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
new file mode 100644
index 0000000..cf2e33c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
@@ -0,0 +1,30 @@
+/*
+ * 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,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
index e41c322..e8a561b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
@@ -16,18 +16,17 @@
package com.android.systemui.communal.data.repository
+import com.android.systemui.communal.data.model.CommunalMediaModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.pipeline.MediaDataManager
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.onCompletion
-import kotlinx.coroutines.flow.onStart
/** Encapsulates the state of smartspace in communal. */
interface CommunalMediaRepository {
- val mediaPlaying: Flow<Boolean>
+ val mediaModel: Flow<CommunalMediaModel>
}
@SysUISingleton
@@ -47,27 +46,32 @@
receivedSmartspaceCardLatency: Int,
isSsReactivated: Boolean
) {
- if (!mediaDataManager.hasAnyMediaOrRecommendation()) {
- return
- }
- _mediaPlaying.value = true
+ updateMediaModel(data)
}
override fun onMediaDataRemoved(key: String) {
- if (mediaDataManager.hasAnyMediaOrRecommendation()) {
- return
- }
- _mediaPlaying.value = false
+ updateMediaModel()
}
}
- private val _mediaPlaying: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ init {
+ mediaDataManager.addListener(mediaDataListener)
+ }
- override val mediaPlaying: Flow<Boolean> =
- _mediaPlaying
- .onStart {
- mediaDataManager.addListener(mediaDataListener)
- _mediaPlaying.value = mediaDataManager.hasAnyMediaOrRecommendation()
- }
- .onCompletion { mediaDataManager.removeListener(mediaDataListener) }
+ private val _mediaModel: MutableStateFlow<CommunalMediaModel> =
+ MutableStateFlow(CommunalMediaModel.INACTIVE)
+
+ override val mediaModel: Flow<CommunalMediaModel> = _mediaModel
+
+ private fun updateMediaModel(data: MediaData? = null) {
+ if (mediaDataManager.hasAnyMediaOrRecommendation()) {
+ _mediaModel.value =
+ CommunalMediaModel(
+ hasAnyMediaOrRecommendation = true,
+ createdTimestampMillis = data?.createdTimestampMillis ?: 0L,
+ )
+ } else {
+ _mediaModel.value = CommunalMediaModel.INACTIVE
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 18fb895..c0fdbf7 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -34,15 +34,13 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.smartspace.data.repository.SmartspaceRepository
import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
/** Encapsulates business-logic related to communal mode. */
-@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class CommunalInteractor
@Inject
@@ -125,24 +123,16 @@
}
}
- /** A flow of available smartspace content. Currently only showing timer targets. */
- val smartspaceContent: Flow<List<CommunalContentModel.Smartspace>> =
+ /** A flow of available smartspace targets. Currently only showing timers. */
+ private val smartspaceTargets: Flow<List<SmartspaceTarget>> =
if (!smartspaceRepository.isSmartspaceRemoteViewsEnabled) {
flowOf(emptyList())
} else {
smartspaceRepository.communalSmartspaceTargets.map { targets ->
- targets
- .filter { target ->
- target.featureType == SmartspaceTarget.FEATURE_TIMER &&
- target.remoteViews != null
- }
- .mapIndexed Target@{ index, target ->
- return@Target CommunalContentModel.Smartspace(
- smartspaceTargetId = target.smartspaceTargetId,
- remoteViews = target.remoteViews!!,
- size = dynamicContentSize(targets.size, index),
- )
- }
+ targets.filter { target ->
+ target.featureType == SmartspaceTarget.FEATURE_TIMER &&
+ target.remoteViews != null
+ }
}
}
@@ -159,14 +149,43 @@
CommunalContentModel.Tutorial(id = 7, HALF),
)
- val umoContent: Flow<List<CommunalContentModel.Umo>> =
- mediaRepository.mediaPlaying.flatMapLatest { mediaPlaying ->
- if (mediaPlaying) {
- // TODO(b/310254801): support HALF and FULL layouts
- flowOf(listOf(CommunalContentModel.Umo(THIRD)))
- } else {
- flowOf(emptyList())
+ /**
+ * A flow of ongoing content, including smartspace timers and umo, ordered by creation time and
+ * sized dynamically.
+ */
+ val ongoingContent: Flow<List<CommunalContentModel.Ongoing>> =
+ combine(smartspaceTargets, mediaRepository.mediaModel) { smartspace, media ->
+ val ongoingContent = mutableListOf<CommunalContentModel.Ongoing>()
+
+ // Add smartspace
+ ongoingContent.addAll(
+ smartspace.map { target ->
+ CommunalContentModel.Smartspace(
+ smartspaceTargetId = target.smartspaceTargetId,
+ remoteViews = target.remoteViews!!,
+ createdTimestampMillis = target.creationTimeMillis,
+ )
+ }
+ )
+
+ // Add UMO
+ if (media.hasAnyMediaOrRecommendation) {
+ ongoingContent.add(
+ CommunalContentModel.Umo(
+ createdTimestampMillis = media.createdTimestampMillis,
+ )
+ )
}
+
+ // Order by creation time descending
+ ongoingContent.sortByDescending { it.createdTimestampMillis }
+
+ // Dynamic sizing
+ ongoingContent.forEachIndexed { index, model ->
+ model.size = dynamicContentSize(ongoingContent.size, index)
+ }
+
+ return@combine ongoingContent
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index 3ae5229..e6cee38a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -30,46 +30,78 @@
/** Size to be rendered in the grid. */
val size: CommunalContentSize
+ /**
+ * A type of communal content is ongoing / live / ephemeral, and can be sized and ordered
+ * dynamically.
+ */
+ sealed interface Ongoing : CommunalContentModel {
+ override var size: CommunalContentSize
+
+ /** Timestamp in milliseconds of when the content was created. */
+ val createdTimestampMillis: Long
+ }
+
class Widget(
val appWidgetId: Int,
val providerInfo: AppWidgetProviderInfo,
val appWidgetHost: AppWidgetHost,
) : CommunalContentModel {
- override val key = "widget_$appWidgetId"
+ override val key = KEY.widget(appWidgetId)
// Widget size is always half.
override val size = CommunalContentSize.HALF
}
/** A placeholder item representing a new widget being added */
class WidgetPlaceholder : CommunalContentModel {
- override val key: String = "widget_placeholder_${UUID.randomUUID()}"
+ override val key: String = KEY.widgetPlaceholder()
// Same as widget size.
override val size = CommunalContentSize.HALF
}
class Tutorial(
id: Int,
- override val size: CommunalContentSize,
+ override var size: CommunalContentSize,
) : CommunalContentModel {
- override val key = "tutorial_$id"
+ override val key = KEY.tutorial(id)
}
class Smartspace(
smartspaceTargetId: String,
val remoteViews: RemoteViews,
- override val size: CommunalContentSize,
- ) : CommunalContentModel {
- override val key = "smartspace_$smartspaceTargetId"
+ override val createdTimestampMillis: Long,
+ override var size: CommunalContentSize = CommunalContentSize.HALF,
+ ) : Ongoing {
+ override val key = KEY.smartspace(smartspaceTargetId)
}
class Umo(
- override val size: CommunalContentSize,
- ) : CommunalContentModel {
- override val key = UMO_KEY
+ override val createdTimestampMillis: Long,
+ override var size: CommunalContentSize = CommunalContentSize.HALF,
+ ) : Ongoing {
+ override val key = KEY.umo()
}
- companion object {
- /** Key for the [Umo] in CommunalContentModel. There should only ever be one UMO. */
- const val UMO_KEY = "umo"
+ class KEY {
+ companion object {
+ fun widget(id: Int): String {
+ return "widget_$id"
+ }
+
+ fun widgetPlaceholder(): String {
+ return "widget_placeholder_${UUID.randomUUID()}"
+ }
+
+ fun tutorial(id: Int): String {
+ return "tutorial_$id"
+ }
+
+ fun smartspace(id: String): String {
+ return "smartspace_$id"
+ }
+
+ fun umo(): String {
+ return "umo"
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 2fae8b5..d8683d6 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -54,11 +54,10 @@
return@flatMapLatest flowOf(communalInteractor.tutorialContent)
}
combine(
- communalInteractor.smartspaceContent,
- communalInteractor.umoContent,
+ communalInteractor.ongoingContent,
communalInteractor.widgetContent,
- ) { smartspace, umo, widgets ->
- smartspace + umo + widgets
+ ) { ongoing, widgets ->
+ ongoing + widgets
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
index b98e9c2..5caa27f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
@@ -81,9 +81,12 @@
/** Set from the notification and used as fallback when PlaybackState cannot be determined */
val isClearable: Boolean = true,
- /** Timestamp when this player was last active. */
+ /** Milliseconds since boot when this player was last active. */
var lastActive: Long = 0L,
+ /** Timestamp in milliseconds when this player was created. */
+ var createdTimestampMillis: Long = 0L,
+
/** Instance ID for logging purposes */
val instanceId: InstanceId,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 3e8b49d..47df3b7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -400,7 +400,12 @@
val oldKey = findExistingEntry(key, sbn.packageName)
if (oldKey == null) {
val instanceId = logger.getNewInstanceId()
- val temp = LOADING.copy(packageName = sbn.packageName, instanceId = instanceId)
+ val temp =
+ LOADING.copy(
+ packageName = sbn.packageName,
+ instanceId = instanceId,
+ createdTimestampMillis = systemClock.currentTimeMillis(),
+ )
mediaEntries.put(key, temp)
isNewlyActiveEntry = true
} else if (oldKey != key) {
@@ -454,7 +459,8 @@
resumeAction = action,
hasCheckedForResume = true,
instanceId = instanceId,
- appUid = appUid
+ appUid = appUid,
+ createdTimestampMillis = systemClock.currentTimeMillis(),
)
mediaEntries.put(packageName, resumeData)
logSingleVsMultipleMediaAdded(appUid, packageName, instanceId)
@@ -732,6 +738,7 @@
val mediaAction = getResumeMediaAction(resumeAction)
val lastActive = systemClock.elapsedRealtime()
+ val createdTimestampMillis = currentEntry?.createdTimestampMillis ?: 0L
foregroundExecutor.execute {
onMediaDataLoaded(
packageName,
@@ -757,6 +764,7 @@
notificationKey = packageName,
hasCheckedForResume = true,
lastActive = lastActive,
+ createdTimestampMillis = createdTimestampMillis,
instanceId = instanceId,
appUid = appUid,
isExplicit = isExplicit,
@@ -907,6 +915,7 @@
}
val lastActive = systemClock.elapsedRealtime()
+ val createdTimestampMillis = currentEntry?.createdTimestampMillis ?: 0L
foregroundExecutor.execute {
val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
@@ -937,6 +946,7 @@
isPlaying = isPlaying,
isClearable = !sbn.isOngoing,
lastActive = lastActive,
+ createdTimestampMillis = createdTimestampMillis,
instanceId = instanceId,
appUid = appUid,
isExplicit = isExplicit,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
index 0a5b124..fb101dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
@@ -78,7 +78,7 @@
mMediaData = new MediaData(
USER_ID, true, APP, null, ARTIST, TITLE, null,
new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, true, null,
- MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L,
+ MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L, 0L,
InstanceId.fakeInstanceId(-1), -1, false, null);
mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME, null, false);
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
index 3ab1b6c..3ea3ccf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
@@ -16,8 +16,24 @@
package com.android.systemui.communal.data.repository
+import com.android.systemui.communal.data.model.CommunalMediaModel
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-class FakeCommunalMediaRepository(
- override val mediaPlaying: MutableStateFlow<Boolean> = MutableStateFlow(false)
-) : CommunalMediaRepository
+class FakeCommunalMediaRepository : CommunalMediaRepository {
+ private val _mediaModel = MutableStateFlow(CommunalMediaModel.INACTIVE)
+
+ override val mediaModel: Flow<CommunalMediaModel> = _mediaModel
+
+ fun mediaActive(timestamp: Long = 0L) {
+ _mediaModel.value =
+ CommunalMediaModel(
+ hasAnyMediaOrRecommendation = true,
+ createdTimestampMillis = timestamp,
+ )
+ }
+
+ fun mediaInactive() {
+ _mediaModel.value = CommunalMediaModel.INACTIVE
+ }
+}