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

Commit b3a9d46b authored by Michael Mikhail's avatar Michael Mikhail Committed by Android (Google) Code Review
Browse files

Merge changes Iffe0f84b,Ice8b7f18 into main

* changes:
  Add media smartspace received logs.
  Add smartspace logger
parents ba45d797 2b154b3d
Loading
Loading
Loading
Loading
+163 −29
Original line number Diff line number Diff line
@@ -30,11 +30,21 @@ import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel
import com.android.systemui.media.controls.util.SmallHash
import com.android.systemui.media.controls.util.mediaSmartspaceLogger
import com.android.systemui.media.controls.util.mockMediaSmartspaceLogger
import com.android.systemui.testKosmos
import com.android.systemui.util.time.systemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.eq
import org.mockito.kotlin.never
import org.mockito.kotlin.reset
import org.mockito.kotlin.verify

@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -42,8 +52,20 @@ class MediaFilterRepositoryTest : SysuiTestCase() {

    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private val smartspaceLogger = kosmos.mockMediaSmartspaceLogger
    private val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
    private val mediaRecommendation =
        SmartspaceMediaData(
            targetId = KEY_MEDIA_SMARTSPACE,
            isActive = true,
            recommendations = MediaTestHelper.getValidRecommendationList(icon),
        )

    private val underTest: MediaFilterRepository = kosmos.mediaFilterRepository
    private val underTest: MediaFilterRepository =
        with(kosmos) {
            mediaSmartspaceLogger = mockMediaSmartspaceLogger
            mediaFilterRepository
        }

    @Test
    fun addSelectedUserMediaEntry_activeThenInactivate() =
@@ -137,14 +159,6 @@ class MediaFilterRepositoryTest : SysuiTestCase() {
        testScope.runTest {
            val smartspaceMediaData by collectLastValue(underTest.smartspaceMediaData)

            val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
            val mediaRecommendation =
                SmartspaceMediaData(
                    targetId = KEY_MEDIA_SMARTSPACE,
                    isActive = true,
                    recommendations = MediaTestHelper.getValidRecommendationList(icon),
                )

            underTest.setRecommendation(mediaRecommendation)

            assertThat(smartspaceMediaData).isEqualTo(mediaRecommendation)
@@ -164,16 +178,38 @@ class MediaFilterRepositoryTest : SysuiTestCase() {
            val playingData = createMediaData("app1", true, LOCAL, false, playingInstanceId)
            val remoteData = createMediaData("app2", true, REMOTE, false, remoteInstanceId)

            underTest.setRecommendation(mediaRecommendation)
            underTest.setRecommendationsLoadingState(
                SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true)
            )
            underTest.addSelectedUserMediaEntry(playingData)
            underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId))

            verify(smartspaceLogger)
                .logSmartspaceCardReceived(
                    playingData.smartspaceId,
                    playingData.appUid,
                    cardinality = 2
                )

            underTest.addSelectedUserMediaEntry(remoteData)
            underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(remoteInstanceId))

            assertThat(currentMedia?.size).isEqualTo(2)
            verify(smartspaceLogger)
                .logSmartspaceCardReceived(
                    remoteData.smartspaceId,
                    playingData.appUid,
                    cardinality = 3,
                    rank = 1
                )
            assertThat(currentMedia?.size).isEqualTo(3)
            assertThat(currentMedia)
                .containsExactly(
                    MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId)),
                    MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(remoteInstanceId))
                    MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(remoteInstanceId)),
                    MediaCommonModel.MediaRecommendations(
                        SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true)
                    )
                )
                .inOrder()
        }
@@ -222,6 +258,16 @@ class MediaFilterRepositoryTest : SysuiTestCase() {

            underTest.setOrderedMedia()

            verify(smartspaceLogger, never())
                .logSmartspaceCardReceived(
                    anyInt(),
                    anyInt(),
                    anyInt(),
                    anyBoolean(),
                    anyBoolean(),
                    anyInt(),
                    anyInt()
                )
            assertThat(currentMedia?.size).isEqualTo(2)
            assertThat(currentMedia)
                .containsExactly(
@@ -248,14 +294,6 @@ class MediaFilterRepositoryTest : SysuiTestCase() {
            val stoppedAndRemoteData = createMediaData("app4", false, REMOTE, false, instanceId4)
            val canResumeData = createMediaData("app5", false, LOCAL, true, instanceId5)

            val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
            val mediaRecommendations =
                SmartspaceMediaData(
                    targetId = KEY_MEDIA_SMARTSPACE,
                    isActive = true,
                    recommendations = MediaTestHelper.getValidRecommendationList(icon),
                )

            underTest.addSelectedUserMediaEntry(stoppedAndLocalData)
            underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId3))

@@ -271,11 +309,33 @@ class MediaFilterRepositoryTest : SysuiTestCase() {
            underTest.addSelectedUserMediaEntry(playingAndRemoteData)
            underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId2))

            underTest.setRecommendation(mediaRecommendations)
            underTest.setRecommendation(mediaRecommendation)
            underTest.setRecommendationsLoadingState(
                SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true)
            )
            underTest.setOrderedMedia()

            val smartspaceId = SmallHash.hash(mediaRecommendation.targetId)
            verify(smartspaceLogger)
                .logSmartspaceCardReceived(
                    eq(smartspaceId),
                    anyInt(),
                    eq(6),
                    anyBoolean(),
                    anyBoolean(),
                    eq(2),
                    anyInt()
                )
            verify(smartspaceLogger, never())
                .logSmartspaceCardReceived(
                    eq(playingAndLocalData.smartspaceId),
                    anyInt(),
                    anyInt(),
                    anyBoolean(),
                    anyBoolean(),
                    anyInt(),
                    anyInt()
                )
            assertThat(currentMedia?.size).isEqualTo(6)
            assertThat(currentMedia)
                .containsExactly(
@@ -312,18 +372,10 @@ class MediaFilterRepositoryTest : SysuiTestCase() {
                    isPlaying = true,
                    notificationKey = KEY_2
                )
            val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
            val mediaRecommendations =
                SmartspaceMediaData(
                    targetId = KEY_MEDIA_SMARTSPACE,
                    isActive = true,
                    packageName = PACKAGE_NAME,
                    recommendations = MediaTestHelper.getValidRecommendationList(icon),
                )

            underTest.setMediaFromRecPackageName(PACKAGE_NAME)
            underTest.addSelectedUserMediaEntry(data)
            underTest.setRecommendation(mediaRecommendations)
            underTest.setRecommendation(mediaRecommendation)
            underTest.setRecommendationsLoadingState(
                SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE)
            )
@@ -365,6 +417,88 @@ class MediaFilterRepositoryTest : SysuiTestCase() {
    fun hasActiveMedia_noMediaSet_returnsFalse() =
        testScope.runTest { assertThat(underTest.hasActiveMedia()).isFalse() }

    @Test
    fun updateMediaWithLatency_smartspaceIsLogged() =
        testScope.runTest {
            val instanceId = InstanceId.fakeInstanceId(123)
            val data = createMediaData("app", true, LOCAL, false, instanceId)

            underTest.setRecommendation(mediaRecommendation)
            underTest.setRecommendationsLoadingState(
                SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true)
            )

            val smartspaceId = SmallHash.hash(mediaRecommendation.targetId)
            verify(smartspaceLogger)
                .logSmartspaceCardReceived(
                    eq(smartspaceId),
                    anyInt(),
                    eq(1),
                    eq(true),
                    anyBoolean(),
                    eq(0),
                    anyInt()
                )
            reset(smartspaceLogger)

            underTest.addSelectedUserMediaEntry(data)
            underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))

            verify(smartspaceLogger)
                .logSmartspaceCardReceived(data.smartspaceId, data.appUid, cardinality = 2)

            reset(smartspaceLogger)

            underTest.addSelectedUserMediaEntry(data)
            underTest.addMediaDataLoadingState(
                MediaDataLoadingModel.Loaded(instanceId, receivedSmartspaceCardLatency = 123)
            )

            verify(smartspaceLogger)
                .logSmartspaceCardReceived(
                    SmallHash.hash(data.appUid + kosmos.systemClock.currentTimeMillis().toInt()),
                    data.appUid,
                    cardinality = 2,
                    rank = 0,
                    receivedLatencyMillis = 123
                )
        }

    @Test
    fun resumeMedia_loadSmartspace_allSmartspaceIsLogged() =
        testScope.runTest {
            val resumeInstanceId = InstanceId.fakeInstanceId(123)
            val data = createMediaData("app", false, LOCAL, true, resumeInstanceId)

            underTest.addSelectedUserMediaEntry(data.copy(active = false))
            underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(resumeInstanceId))
            underTest.setRecommendation(mediaRecommendation)
            underTest.setRecommendationsLoadingState(
                SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true)
            )

            assertThat(underTest.hasActiveMedia()).isFalse()
            assertThat(underTest.hasAnyMedia()).isTrue()
            val smartspaceId = SmallHash.hash(mediaRecommendation.targetId)
            verify(smartspaceLogger)
                .logSmartspaceCardReceived(
                    eq(smartspaceId),
                    anyInt(),
                    eq(2),
                    eq(true),
                    anyBoolean(),
                    eq(0),
                    anyInt()
                )
            verify(smartspaceLogger)
                .logSmartspaceCardReceived(
                    SmallHash.hash(data.appUid + kosmos.systemClock.currentTimeMillis().toInt()),
                    data.appUid,
                    cardinality = 2,
                    rank = 1
                )
        }

    private fun createMediaData(
        app: String,
        playing: Boolean,
+105 −20
Original line number Diff line number Diff line
@@ -26,6 +26,8 @@ import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel
import com.android.systemui.media.controls.util.MediaSmartspaceLogger
import com.android.systemui.media.controls.util.SmallHash
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.time.SystemClock
import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
@@ -43,9 +45,10 @@ import kotlinx.coroutines.flow.asStateFlow
class MediaFilterRepository
@Inject
constructor(
    @Application applicationContext: Context,
    @Application private val applicationContext: Context,
    private val systemClock: SystemClock,
    private val configurationController: ConfigurationController,
    private val smartspaceLogger: MediaSmartspaceLogger,
) {

    val onAnyMediaConfigurationChange: Flow<Unit> = conflatedCallbackFlow {
@@ -211,6 +214,12 @@ constructor(
                        isMediaFromRec(it)
                    )
                sortedMap[sortKey] = newCommonModel
                val isUpdate =
                    sortedMedia.values.any { commonModel ->
                        commonModel is MediaCommonModel.MediaControl &&
                            commonModel.mediaLoadedModel.instanceId ==
                                mediaDataLoadingModel.instanceId
                    }

                // On Addition or tapping on recommendations, we should show the new order of media.
                if (mediaFromRecPackageName == it.packageName) {
@@ -218,30 +227,50 @@ constructor(
                        mediaFromRecPackageName = null
                        _currentMedia.value = sortedMap.values.toList()
                    }
                } else if (sortedMap.size > _currentMedia.value.size && it.active) {
                    _currentMedia.value = sortedMap.values.toList()
                } else {
                    // When loading an update for an existing media control.
                    var isNewToCurrentMedia = true
                    val currentList =
                        mutableListOf<MediaCommonModel>().apply { addAll(_currentMedia.value) }
                    currentList.forEachIndexed { index, mediaCommonModel ->
                        if (
                            mediaCommonModel is MediaCommonModel.MediaControl &&
                                mediaCommonModel.mediaLoadedModel.instanceId ==
                                    mediaDataLoadingModel.instanceId &&
                                mediaCommonModel != newCommonModel
                                    mediaDataLoadingModel.instanceId
                        ) {
                            // When loading an update for an existing media control.
                            isNewToCurrentMedia = false
                            if (mediaCommonModel != newCommonModel) {
                                // Update media model if changed.
                                currentList[index] = newCommonModel
                            }
                        }
                    _currentMedia.value = currentList
                    }
                    if (isNewToCurrentMedia && it.active) {
                        _currentMedia.value = sortedMap.values.toList()
                    } else {
                        _currentMedia.value = currentList
                    }
                }

                sortedMedia = sortedMap

                if (!isUpdate) {
                    val rank = sortedMedia.values.indexOf(newCommonModel)
                    if (isSmartspaceLoggingEnabled(newCommonModel, rank)) {
                        smartspaceLogger.logSmartspaceCardReceived(
                            it.smartspaceId,
                            it.appUid,
                            cardinality = _currentMedia.value.size,
                            isSsReactivated = mediaDataLoadingModel.isSsReactivated,
                            rank = rank,
                        )
                    }
                } else if (mediaDataLoadingModel.receivedSmartspaceCardLatency != 0) {
                    logSmartspaceAllMediaCards(mediaDataLoadingModel.receivedSmartspaceCardLatency)
                }
            }
        }

        // On removal we want to keep the order being shown to user.
        if (mediaDataLoadingModel is MediaDataLoadingModel.Removed) {
            _currentMedia.value =
@@ -249,6 +278,7 @@ constructor(
                    commonModel !is MediaCommonModel.MediaControl ||
                        mediaDataLoadingModel.instanceId != commonModel.mediaLoadedModel.instanceId
                }
            sortedMedia = sortedMap
        }
    }

@@ -271,21 +301,45 @@ constructor(
                isPlaying = false,
                active = _smartspaceMediaData.value.isActive,
            )
        val newCommonModel = MediaCommonModel.MediaRecommendations(smartspaceMediaLoadingModel)
        when (smartspaceMediaLoadingModel) {
            is SmartspaceMediaLoadingModel.Loaded ->
                sortedMap[sortKey] =
                    MediaCommonModel.MediaRecommendations(smartspaceMediaLoadingModel)
            is SmartspaceMediaLoadingModel.Removed ->
            is SmartspaceMediaLoadingModel.Loaded -> {
                sortedMap[sortKey] = newCommonModel
                _currentMedia.value = sortedMap.values.toList()
                sortedMedia = sortedMap

                if (isRecommendationActive()) {
                    val hasActivatedExistedResumeMedia =
                        !hasActiveMedia() &&
                            hasAnyMedia() &&
                            smartspaceMediaLoadingModel.isPrioritized
                    if (hasActivatedExistedResumeMedia) {
                        // Log resume card received if resumable media card is reactivated and
                        // recommendation card is valid and ranked first
                        logSmartspaceAllMediaCards(
                            (systemClock.currentTimeMillis() -
                                    _smartspaceMediaData.value.headphoneConnectionTimeMillis)
                                .toInt()
                        )
                    }

                    smartspaceLogger.logSmartspaceCardReceived(
                        SmallHash.hash(_smartspaceMediaData.value.targetId),
                        _smartspaceMediaData.value.getUid(applicationContext),
                        cardinality = _currentMedia.value.size,
                        isRecommendationCard = true,
                        rank = _currentMedia.value.indexOf(newCommonModel),
                    )
                }
            }
            is SmartspaceMediaLoadingModel.Removed -> {
                _currentMedia.value =
                    _currentMedia.value.filter { commonModel ->
                        commonModel !is MediaCommonModel.MediaRecommendations
                    }
                sortedMedia = sortedMap
            }

        if (sortedMap.size > sortedMedia.size) {
            _currentMedia.value = sortedMap.values.toList()
        }
        sortedMedia = sortedMap
    }

    fun setOrderedMedia() {
@@ -315,4 +369,35 @@ constructor(
    private fun isMediaFromRec(data: MediaData): Boolean {
        return data.isPlaying == true && mediaFromRecPackageName == data.packageName
    }

    /** Log all media cards if smartspace logging is enabled for each. */
    private fun logSmartspaceAllMediaCards(receivedSmartspaceCardLatency: Int) {
        sortedMedia.values.forEachIndexed { index, mediaCommonModel ->
            if (mediaCommonModel is MediaCommonModel.MediaControl) {
                _selectedUserEntries.value[mediaCommonModel.mediaLoadedModel.instanceId]?.let {
                    it.smartspaceId =
                        SmallHash.hash(it.appUid + systemClock.currentTimeMillis().toInt())
                    it.isImpressed = false

                    if (isSmartspaceLoggingEnabled(mediaCommonModel, index)) {
                        smartspaceLogger.logSmartspaceCardReceived(
                            it.smartspaceId,
                            it.appUid,
                            cardinality = _currentMedia.value.size,
                            isSsReactivated = mediaCommonModel.mediaLoadedModel.isSsReactivated,
                            rank = index,
                            receivedLatencyMillis = receivedSmartspaceCardLatency,
                        )
                    }
                }
            }
        }
    }

    private fun isSmartspaceLoggingEnabled(commonModel: MediaCommonModel, index: Int): Boolean {
        return sortedMedia.size > index &&
            (_smartspaceMediaData.value.expiryTimeMs != 0L ||
                isRecommendationActive() ||
                commonModel is MediaCommonModel.MediaRecommendations)
    }
}
+7 −1
Original line number Diff line number Diff line
@@ -180,7 +180,13 @@ constructor(
                    mediaData.instanceId
                )
                mediaFilterRepository.addMediaDataLoadingState(
                    MediaDataLoadingModel.Loaded(lastActiveId)
                    MediaDataLoadingModel.Loaded(
                        lastActiveId,
                        receivedSmartspaceCardLatency =
                            (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis)
                                .toInt(),
                        isSsReactivated = true
                    )
                )
                mediaLoadingLogger.logMediaLoaded(
                    mediaData.instanceId,
+3 −0
Original line number Diff line number Diff line
@@ -86,6 +86,7 @@ import com.android.systemui.media.controls.util.MediaControllerFactory
import com.android.systemui.media.controls.util.MediaDataUtils
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.media.controls.util.SmallHash
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.res.R
@@ -721,6 +722,7 @@ class MediaDataProcessor(
                    appUid = appUid,
                    isExplicit = isExplicit,
                    resumeProgress = progress,
                    smartspaceId = SmallHash.hash(appUid + systemClock.currentTimeMillis().toInt()),
                )
            )
        }
@@ -902,6 +904,7 @@ class MediaDataProcessor(
                    instanceId = instanceId,
                    appUid = appUid,
                    isExplicit = isExplicit,
                    smartspaceId = SmallHash.hash(appUid + systemClock.currentTimeMillis().toInt()),
                )
            )
        }
+6 −0
Original line number Diff line number Diff line
@@ -99,6 +99,12 @@ data class MediaData(

    /** Track progress (0 - 1) to display for players where [resumption] is true */
    val resumeProgress: Double? = null,

    /** Smartspace Id, used for logging. */
    var smartspaceId: Int = -1,

    /** If media card was visible to user, used for logging. */
    var isImpressed: Boolean = false,
) {
    companion object {
        /** Media is playing on the local device */
Loading