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

Commit ef07d450 authored by Michael Mikhail's avatar Michael Mikhail
Browse files

Add smartspace logs for click and dismiss events

Flag: com.android.systemui.scene_container
Bug: 330897926
Test: atest MediaDataFilterImplTest
Test: atest SystemUiRoboTests:MediaControlInteractorTest
Test: atest SystemUiRoboTests:MediaRecommendationsInteractorTest
Change-Id: I275633a086c5ce8888bc2fb38dbf51d6ac875853
parent 23d9ea72
Loading
Loading
Loading
Loading
+90 −7
Original line number Diff line number Diff line
@@ -16,7 +16,9 @@

package com.android.systemui.media.controls.domain.interactor

import android.R
import android.app.PendingIntent
import android.graphics.drawable.Icon
import android.os.Bundle
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -30,6 +32,7 @@ import com.android.systemui.bluetooth.mockBroadcastDialogController
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.MediaTestHelper
import com.android.systemui.media.controls.data.repository.mediaDataRepository
import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl
import com.android.systemui.media.controls.domain.pipeline.MediaDataProcessor
@@ -38,7 +41,12 @@ import com.android.systemui.media.controls.domain.pipeline.interactor.mediaContr
import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter
import com.android.systemui.media.controls.domain.pipeline.mediaDataProcessor
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
import com.android.systemui.media.controls.util.MediaSmartspaceLogger.Companion.SMARTSPACE_CARD_CLICK_EVENT
import com.android.systemui.media.controls.util.MediaSmartspaceLogger.Companion.SMARTSPACE_CARD_DISMISS_EVENT
import com.android.systemui.media.controls.util.mediaInstanceId
import com.android.systemui.media.controls.util.mediaSmartspaceLogger
import com.android.systemui.media.controls.util.mockMediaSmartspaceLogger
import com.android.systemui.media.mediaOutputDialogManager
import com.android.systemui.mockActivityIntentHelper
import com.android.systemui.plugins.activityStarter
@@ -49,6 +57,8 @@ import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
@@ -63,11 +73,23 @@ class MediaControlInteractorTest : SysuiTestCase() {
    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope

    private val mediaDataFilter: MediaDataFilterImpl = kosmos.mediaDataFilter
    private val mediaDataFilter: MediaDataFilterImpl =
        with(kosmos) {
            mediaSmartspaceLogger = mockMediaSmartspaceLogger
            mediaDataFilter
        }
    private val activityStarter = kosmos.activityStarter
    private val keyguardStateController = kosmos.keyguardStateController
    private val instanceId: InstanceId = kosmos.mediaInstanceId
    private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager
    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: MediaControlInteractor =
        with(kosmos) {
@@ -124,13 +146,15 @@ class MediaControlInteractorTest : SysuiTestCase() {
        val clickIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(true) }
        val expandable = mock<Expandable>()

        underTest.startClickIntent(expandable, clickIntent)
        underTest.startClickIntent(expandable, clickIntent, SMARTSPACE_CARD_CLICK_EVENT, 1)

        verify(clickIntent).send(any<Bundle>())
    }

    @Test
    fun startClickIntent_hideOverLockscreen() {
        whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
        whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
        whenever(keyguardStateController.isShowing).thenReturn(false)

        val clickIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(true) }
@@ -138,8 +162,20 @@ class MediaControlInteractorTest : SysuiTestCase() {
        val activityController = mock<ActivityTransitionAnimator.Controller>()
        whenever(expandable.activityTransitionController(any())).thenReturn(activityController)

        underTest.startClickIntent(expandable, clickIntent)

        val mediaData = MediaData(userId = USER_ID, instanceId = instanceId, artist = ARTIST)
        mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, mediaRecommendation, true)
        mediaDataFilter.onMediaDataLoaded(KEY, null, mediaData)
        underTest.startClickIntent(expandable, clickIntent, SMARTSPACE_CARD_CLICK_EVENT, 1)

        verify(smartspaceLogger)
            .logSmartspaceCardUIEvent(
                SMARTSPACE_CARD_CLICK_EVENT,
                mediaData.smartspaceId,
                mediaData.appUid,
                surface = SURFACE,
                cardinality = 2,
                rank = 1
            )
        verify(activityStarter)
            .postStartActivityDismissingKeyguard(eq(clickIntent), eq(activityController))
    }
@@ -217,17 +253,62 @@ class MediaControlInteractorTest : SysuiTestCase() {
    }

    @Test
    fun removeMediaControl() {
    fun removeMediaControl_noRecommendation() {
        whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
        whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
        val listener = mock<MediaDataProcessor.Listener>()
        kosmos.mediaDataProcessor.addInternalListener(listener)

        var mediaData = MediaData(userId = USER_ID, instanceId = instanceId, artist = ARTIST)
        val mediaData = MediaData(userId = USER_ID, instanceId = instanceId, artist = ARTIST)
        kosmos.mediaDataRepository.addMediaEntry(KEY, mediaData)
        kosmos.mediaDataFilter.onMediaDataLoaded(KEY, null, mediaData)

        underTest.removeMediaControl(null, instanceId, 0L, SMARTSPACE_CARD_DISMISS_EVENT, 1)
        kosmos.fakeExecutor.advanceClockToNext()
        kosmos.fakeExecutor.runAllReady()

        verify(smartspaceLogger, never())
            .logSmartspaceCardUIEvent(
                anyInt(),
                anyInt(),
                anyInt(),
                anyInt(),
                anyInt(),
                anyBoolean(),
                anyBoolean(),
                anyInt(),
                anyInt(),
                anyInt(),
                anyBoolean()
            )
        verify(listener).onMediaDataRemoved(eq(KEY), eq(true))
    }

    @Test
    fun removeMediaControl_recommendationsExist() {
        whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
        whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
        val listener = mock<MediaDataProcessor.Listener>()
        kosmos.mediaDataProcessor.addInternalListener(listener)

        val mediaData = MediaData(userId = USER_ID, instanceId = instanceId, artist = ARTIST)
        kosmos.mediaDataRepository.addMediaEntry(KEY, mediaData)
        mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, mediaRecommendation, true)
        mediaDataFilter.onMediaDataLoaded(KEY, null, mediaData)

        underTest.removeMediaControl(null, instanceId, 0L)
        underTest.removeMediaControl(null, instanceId, 0L, SMARTSPACE_CARD_DISMISS_EVENT, 1)
        kosmos.fakeExecutor.advanceClockToNext()
        kosmos.fakeExecutor.runAllReady()

        verify(smartspaceLogger)
            .logSmartspaceCardUIEvent(
                SMARTSPACE_CARD_DISMISS_EVENT,
                mediaData.smartspaceId,
                mediaData.appUid,
                surface = SURFACE,
                cardinality = 2,
                rank = 1
            )
        verify(listener).onMediaDataRemoved(eq(KEY), eq(true))
    }

@@ -238,5 +319,7 @@ class MediaControlInteractorTest : SysuiTestCase() {
        private const val APP_NAME = "app"
        private const val ARTIST = "artist"
        private const val ARTIST_2 = "artist2"
        private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
        private const val SURFACE = 4
    }
}
+52 −6
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.content.ComponentName
import android.content.Intent
import android.content.applicationContext
import android.graphics.drawable.Icon
import android.os.Process
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -40,10 +41,14 @@ import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter
import com.android.systemui.media.controls.shared.model.MediaRecModel
import com.android.systemui.media.controls.shared.model.MediaRecommendationsModel
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
import com.android.systemui.media.controls.util.MediaSmartspaceLogger.Companion.SMARTSPACE_CARD_CLICK_EVENT
import com.android.systemui.media.controls.util.MediaSmartspaceLogger.Companion.SMARTSPACE_CARD_DISMISS_EVENT
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.plugins.activityStarter
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -53,6 +58,7 @@ import org.junit.runner.RunWith
import org.mockito.Mockito.doNothing
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.kotlin.eq

@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -62,7 +68,11 @@ class MediaRecommendationsInteractorTest : SysuiTestCase() {
    private val kosmos = testKosmos().apply { applicationContext = spyContext }
    private val testScope = kosmos.testScope

    private val mediaDataFilter: MediaDataFilterImpl = kosmos.mediaDataFilter
    private val mediaDataFilter: MediaDataFilterImpl =
        with(kosmos) {
            mediaSmartspaceLogger = mockMediaSmartspaceLogger
            mediaDataFilter
        }
    private val activityStarter = kosmos.activityStarter
    private val icon: Icon = Icon.createWithResource(context, R.drawable.ic_media_play)
    private val smartspaceMediaData: SmartspaceMediaData =
@@ -72,6 +82,7 @@ class MediaRecommendationsInteractorTest : SysuiTestCase() {
            packageName = PACKAGE_NAME,
            recommendations = MediaTestHelper.getValidRecommendationList(icon),
        )
    private val smartspaceLogger = kosmos.mockMediaSmartspaceLogger

    private val underTest: MediaRecommendationsInteractor =
        with(kosmos) {
@@ -138,8 +149,24 @@ class MediaRecommendationsInteractorTest : SysuiTestCase() {

        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)

        underTest.removeMediaRecommendations(KEY_MEDIA_SMARTSPACE, intent, 0)
        mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, smartspaceMediaData)
        underTest.removeMediaRecommendations(
            KEY_MEDIA_SMARTSPACE,
            intent,
            0,
            SMARTSPACE_CARD_DISMISS_EVENT,
            1
        )

        verify(smartspaceLogger)
            .logSmartspaceCardUIEvent(
                SMARTSPACE_CARD_DISMISS_EVENT,
                SmallHash.hash(smartspaceMediaData.targetId),
                Process.INVALID_UID,
                surface = SURFACE,
                cardinality = 1,
                isRecommendationCard = true,
            )
        verify(kosmos.mockBroadcastSender).sendBroadcast(eq(intent))
    }

@@ -151,7 +178,13 @@ class MediaRecommendationsInteractorTest : SysuiTestCase() {
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        intent.component = ComponentName(PACKAGE_NAME, EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME)

        underTest.removeMediaRecommendations(KEY_MEDIA_SMARTSPACE, intent, 0)
        underTest.removeMediaRecommendations(
            KEY_MEDIA_SMARTSPACE,
            intent,
            0,
            SMARTSPACE_CARD_DISMISS_EVENT,
            1
        )

        verify(spyContext).startActivity(eq(intent))
    }
@@ -171,13 +204,26 @@ class MediaRecommendationsInteractorTest : SysuiTestCase() {

        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)

        underTest.startClickIntent(expandable, intent)

        mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, smartspaceMediaData)
        underTest.startClickIntent(expandable, intent, SMARTSPACE_CARD_CLICK_EVENT, 1, 2, 3)

        verify(smartspaceLogger)
            .logSmartspaceCardUIEvent(
                SMARTSPACE_CARD_CLICK_EVENT,
                SmallHash.hash(smartspaceMediaData.targetId),
                Process.INVALID_UID,
                surface = SURFACE,
                cardinality = 1,
                isRecommendationCard = true,
                interactedSubcardRank = 2,
                interactedSubcardCardinality = 3
            )
        verify(spyContext).startActivity(eq(intent))
    }

    companion object {
        private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
        private const val PACKAGE_NAME = "com.example.app"
        private const val SURFACE = 4
    }
}
+10 −10
Original line number Diff line number Diff line
@@ -366,7 +366,7 @@ constructor(
    /** Log user event on media card if smartspace logging is enabled. */
    fun logSmartspaceCardUserEvent(
        eventId: Int,
        location: Int,
        surface: Int,
        interactedSubCardRank: Int = 0,
        interactedSubCardCardinality: Int = 0,
        instanceId: InstanceId? = null,
@@ -381,7 +381,7 @@ constructor(
                                instanceId,
                                index,
                                eventId,
                                location,
                                surface,
                                mediaCommonModel.mediaLoadedModel.isSsReactivated,
                                interactedSubCardRank,
                                interactedSubCardCardinality
@@ -395,7 +395,7 @@ constructor(
                        if (isSmartspaceLoggingEnabled(mediaCommonModel, index)) {
                            logSmarspaceRecommendationCardUserEvent(
                                eventId,
                                location,
                                surface,
                                index,
                                interactedSubCardRank,
                                interactedSubCardCardinality
@@ -409,7 +409,7 @@ constructor(
    }

    /** Log media and recommendation cards dismissal if smartspace logging is enabled for each. */
    fun logSmartspaceCardsOnSwipeToDismiss(location: Int) {
    fun logSmartspaceCardsOnSwipeToDismiss(surface: Int) {
        _currentMedia.value.forEachIndexed { index, mediaCommonModel ->
            if (isSmartspaceLoggingEnabled(mediaCommonModel, index)) {
                when (mediaCommonModel) {
@@ -418,14 +418,14 @@ constructor(
                            mediaCommonModel.mediaLoadedModel.instanceId,
                            index,
                            SMARTSPACE_CARD_DISMISS_EVENT,
                            location,
                            surface,
                            mediaCommonModel.mediaLoadedModel.isSsReactivated,
                            isSwipeToDismiss = true
                        )
                    is MediaCommonModel.MediaRecommendations ->
                        logSmarspaceRecommendationCardUserEvent(
                            SMARTSPACE_CARD_DISMISS_EVENT,
                            location,
                            surface,
                            index,
                            isSwipeToDismiss = true
                        )
@@ -470,7 +470,7 @@ constructor(
        instanceId: InstanceId,
        index: Int,
        eventId: Int,
        location: Int,
        surface: Int,
        isReactivated: Boolean,
        interactedSubCardRank: Int = 0,
        interactedSubCardCardinality: Int = 0,
@@ -481,7 +481,7 @@ constructor(
                eventId,
                it.smartspaceId,
                it.appUid,
                location,
                surface,
                _currentMedia.value.size,
                isSsReactivated = isReactivated,
                interactedSubcardRank = interactedSubCardRank,
@@ -494,7 +494,7 @@ constructor(

    private fun logSmarspaceRecommendationCardUserEvent(
        eventId: Int,
        location: Int,
        surface: Int,
        index: Int,
        interactedSubCardRank: Int = 0,
        interactedSubCardCardinality: Int = 0,
@@ -504,7 +504,7 @@ constructor(
            eventId,
            SmallHash.hash(_smartspaceMediaData.value.targetId),
            _smartspaceMediaData.value.getUid(applicationContext),
            location,
            surface,
            _currentMedia.value.size,
            isRecommendationCard = true,
            interactedSubcardRank = interactedSubCardRank,
+2 −1
Original line number Diff line number Diff line
@@ -338,8 +338,9 @@ constructor(
    }

    /** Invoked when the user has dismissed the media carousel */
    fun onSwipeToDismiss() {
    fun onSwipeToDismiss(surface: Int) {
        if (DEBUG) Log.d(TAG, "Media carousel swiped away")
        mediaFilterRepository.logSmartspaceCardsOnSwipeToDismiss(surface)
        val mediaEntries = mediaFilterRepository.allUserEntries.value.entries
        mediaEntries.forEach { (key, data) ->
            if (mediaFilterRepository.selectedUserEntries.value.containsKey(data.instanceId)) {
+5 −2
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import com.android.systemui.media.controls.domain.pipeline.MediaTimeoutListener
import com.android.systemui.media.controls.domain.resume.MediaResumeListener
import com.android.systemui.media.controls.shared.model.MediaCommonModel
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaSmartspaceLogger
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import java.io.PrintWriter
import javax.inject.Inject
@@ -204,8 +205,10 @@ constructor(
        mediaDataProcessor.setMediaResumptionEnabled(isEnabled)
    }

    override fun onSwipeToDismiss() {
        mediaDataFilter.onSwipeToDismiss()
    override fun onSwipeToDismiss() = unsupported

    fun onSwipeToDismiss(location: Int) {
        mediaDataFilter.onSwipeToDismiss(MediaSmartspaceLogger.getSurface(location))
    }

    override fun hasActiveMediaOrRecommendation() = hasActiveMediaOrRecommendation.value
Loading