Loading packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt +2 −0 Original line number Diff line number Diff line Loading @@ -88,6 +88,8 @@ data class SmartspaceMediaData( } } /** Key to indicate whether this card should be used to re-show recent media */ const val EXTRA_KEY_TRIGGER_RESUME = "SHOULD_TRIGGER_RESUME" /** Key for extras [SmartspaceMediaData.cardAction] indicating why the card was sent */ const val EXTRA_KEY_TRIGGER_SOURCE = "MEDIA_RECOMMENDATION_TRIGGER_SOURCE" /** Value for [EXTRA_KEY_TRIGGER_SOURCE] when the card is sent on headphone connection */ Loading packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt +13 −3 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import com.android.internal.annotations.VisibleForTesting import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.models.recommendation.EXTRA_KEY_TRIGGER_RESUME import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.media.controls.util.MediaUiEventLogger Loading Loading @@ -138,14 +139,23 @@ constructor( val sorted = userEntries.toSortedMap(compareBy { userEntries.get(it)?.lastActive ?: -1 }) val timeSinceActive = timeSinceActiveForMostRecentMedia(sorted) var smartspaceMaxAgeMillis = SMARTSPACE_MAX_AGE data.cardAction?.let { val smartspaceMaxAgeSeconds = it.extras.getLong(RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY, 0) data.cardAction?.extras?.let { val smartspaceMaxAgeSeconds = it.getLong(RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY, 0) if (smartspaceMaxAgeSeconds > 0) { smartspaceMaxAgeMillis = TimeUnit.SECONDS.toMillis(smartspaceMaxAgeSeconds) } } val shouldReactivate = !hasActiveMedia() && hasAnyMedia() && data.isActive // Check if smartspace has explicitly specified whether to re-activate resumable media. // The default behavior is to trigger if the smartspace data is active. val shouldTriggerResume = if (data.cardAction?.extras?.containsKey(EXTRA_KEY_TRIGGER_RESUME) == true) { data.cardAction.extras.getBoolean(EXTRA_KEY_TRIGGER_RESUME, true) } else { true } val shouldReactivate = shouldTriggerResume && !hasActiveMedia() && hasAnyMedia() && data.isActive if (timeSinceActive < smartspaceMaxAgeMillis) { // It could happen there are existing active media resume cards, then we don't need to Loading packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt +55 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.systemui.media.controls.pipeline import android.app.smartspace.SmartspaceAction import android.os.Bundle import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest Loading @@ -25,6 +26,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.media.controls.MediaTestUtils import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.models.recommendation.EXTRA_KEY_TRIGGER_RESUME import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData import com.android.systemui.media.controls.ui.MediaPlayerData import com.android.systemui.media.controls.util.MediaFlags Loading Loading @@ -75,6 +77,7 @@ class MediaDataFilterTest : SysuiTestCase() { @Mock private lateinit var smartspaceMediaRecommendationItem: SmartspaceAction @Mock private lateinit var logger: MediaUiEventLogger @Mock private lateinit var mediaFlags: MediaFlags @Mock private lateinit var cardAction: SmartspaceAction private lateinit var mediaDataFilter: MediaDataFilter private lateinit var dataMain: MediaData Loading Loading @@ -122,6 +125,7 @@ class MediaDataFilterTest : SysuiTestCase() { whenever(smartspaceData.headphoneConnectionTimeMillis) .thenReturn(clock.currentTimeMillis() - 100) whenever(smartspaceData.instanceId).thenReturn(SMARTSPACE_INSTANCE_ID) whenever(smartspaceData.cardAction).thenReturn(cardAction) } private fun setUser(id: Int) { Loading Loading @@ -574,4 +578,55 @@ class MediaDataFilterTest : SysuiTestCase() { verify(mediaDataManager, never()) .dismissSmartspaceRecommendation(eq(SMARTSPACE_KEY), anyLong()) } @Test fun testSmartspaceLoaded_shouldTriggerResume_doesTrigger() { // WHEN we have media that was recently played, but not currently active val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) // AND we get a smartspace signal with extra to trigger resume val extras = Bundle().apply { putBoolean(EXTRA_KEY_TRIGGER_RESUME, true) } whenever(cardAction.extras).thenReturn(extras) mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) // THEN we should tell listeners to treat the media as active instead val dataCurrentAndActive = dataCurrent.copy(active = true) verify(listener) .onMediaDataLoaded( eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true), eq(100), eq(true) ) assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue() // And send the smartspace data, but not prioritized verify(listener) .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) } @Test fun testSmartspaceLoaded_notShouldTriggerResume_doesNotTrigger() { // WHEN we have media that was recently played, but not currently active val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) // AND we get a smartspace signal with extra to not trigger resume val extras = Bundle().apply { putBoolean(EXTRA_KEY_TRIGGER_RESUME, false) } whenever(cardAction.extras).thenReturn(extras) mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) // THEN listeners are not updated to show media verify(listener, never()) .onMediaDataLoaded(eq(KEY), eq(KEY), any(), eq(true), eq(100), eq(true)) // But the smartspace update is still propagated verify(listener) .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) } } Loading
packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt +2 −0 Original line number Diff line number Diff line Loading @@ -88,6 +88,8 @@ data class SmartspaceMediaData( } } /** Key to indicate whether this card should be used to re-show recent media */ const val EXTRA_KEY_TRIGGER_RESUME = "SHOULD_TRIGGER_RESUME" /** Key for extras [SmartspaceMediaData.cardAction] indicating why the card was sent */ const val EXTRA_KEY_TRIGGER_SOURCE = "MEDIA_RECOMMENDATION_TRIGGER_SOURCE" /** Value for [EXTRA_KEY_TRIGGER_SOURCE] when the card is sent on headphone connection */ Loading
packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt +13 −3 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import com.android.internal.annotations.VisibleForTesting import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.models.recommendation.EXTRA_KEY_TRIGGER_RESUME import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.media.controls.util.MediaUiEventLogger Loading Loading @@ -138,14 +139,23 @@ constructor( val sorted = userEntries.toSortedMap(compareBy { userEntries.get(it)?.lastActive ?: -1 }) val timeSinceActive = timeSinceActiveForMostRecentMedia(sorted) var smartspaceMaxAgeMillis = SMARTSPACE_MAX_AGE data.cardAction?.let { val smartspaceMaxAgeSeconds = it.extras.getLong(RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY, 0) data.cardAction?.extras?.let { val smartspaceMaxAgeSeconds = it.getLong(RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY, 0) if (smartspaceMaxAgeSeconds > 0) { smartspaceMaxAgeMillis = TimeUnit.SECONDS.toMillis(smartspaceMaxAgeSeconds) } } val shouldReactivate = !hasActiveMedia() && hasAnyMedia() && data.isActive // Check if smartspace has explicitly specified whether to re-activate resumable media. // The default behavior is to trigger if the smartspace data is active. val shouldTriggerResume = if (data.cardAction?.extras?.containsKey(EXTRA_KEY_TRIGGER_RESUME) == true) { data.cardAction.extras.getBoolean(EXTRA_KEY_TRIGGER_RESUME, true) } else { true } val shouldReactivate = shouldTriggerResume && !hasActiveMedia() && hasAnyMedia() && data.isActive if (timeSinceActive < smartspaceMaxAgeMillis) { // It could happen there are existing active media resume cards, then we don't need to Loading
packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt +55 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.systemui.media.controls.pipeline import android.app.smartspace.SmartspaceAction import android.os.Bundle import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest Loading @@ -25,6 +26,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.media.controls.MediaTestUtils import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.models.recommendation.EXTRA_KEY_TRIGGER_RESUME import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData import com.android.systemui.media.controls.ui.MediaPlayerData import com.android.systemui.media.controls.util.MediaFlags Loading Loading @@ -75,6 +77,7 @@ class MediaDataFilterTest : SysuiTestCase() { @Mock private lateinit var smartspaceMediaRecommendationItem: SmartspaceAction @Mock private lateinit var logger: MediaUiEventLogger @Mock private lateinit var mediaFlags: MediaFlags @Mock private lateinit var cardAction: SmartspaceAction private lateinit var mediaDataFilter: MediaDataFilter private lateinit var dataMain: MediaData Loading Loading @@ -122,6 +125,7 @@ class MediaDataFilterTest : SysuiTestCase() { whenever(smartspaceData.headphoneConnectionTimeMillis) .thenReturn(clock.currentTimeMillis() - 100) whenever(smartspaceData.instanceId).thenReturn(SMARTSPACE_INSTANCE_ID) whenever(smartspaceData.cardAction).thenReturn(cardAction) } private fun setUser(id: Int) { Loading Loading @@ -574,4 +578,55 @@ class MediaDataFilterTest : SysuiTestCase() { verify(mediaDataManager, never()) .dismissSmartspaceRecommendation(eq(SMARTSPACE_KEY), anyLong()) } @Test fun testSmartspaceLoaded_shouldTriggerResume_doesTrigger() { // WHEN we have media that was recently played, but not currently active val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) // AND we get a smartspace signal with extra to trigger resume val extras = Bundle().apply { putBoolean(EXTRA_KEY_TRIGGER_RESUME, true) } whenever(cardAction.extras).thenReturn(extras) mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) // THEN we should tell listeners to treat the media as active instead val dataCurrentAndActive = dataCurrent.copy(active = true) verify(listener) .onMediaDataLoaded( eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true), eq(100), eq(true) ) assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue() // And send the smartspace data, but not prioritized verify(listener) .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) } @Test fun testSmartspaceLoaded_notShouldTriggerResume_doesNotTrigger() { // WHEN we have media that was recently played, but not currently active val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) // AND we get a smartspace signal with extra to not trigger resume val extras = Bundle().apply { putBoolean(EXTRA_KEY_TRIGGER_RESUME, false) } whenever(cardAction.extras).thenReturn(extras) mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) // THEN listeners are not updated to show media verify(listener, never()) .onMediaDataLoaded(eq(KEY), eq(KEY), any(), eq(true), eq(100), eq(true)) // But the smartspace update is still propagated verify(listener) .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) } }