diff options
27 files changed, 590 insertions, 125 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt index f43fa5048298..24672ebe6134 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt @@ -183,7 +183,10 @@ class MediaFilterRepositoryTest : SysuiTestCase() { SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true) ) underTest.addSelectedUserMediaEntry(playingData) - underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId)) + underTest.addMediaDataLoadingState( + MediaDataLoadingModel.Loaded(playingInstanceId), + false + ) verify(smartspaceLogger) .logSmartspaceCardReceived( @@ -193,7 +196,10 @@ class MediaFilterRepositoryTest : SysuiTestCase() { ) underTest.addSelectedUserMediaEntry(remoteData) - underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(remoteInstanceId)) + underTest.addMediaDataLoadingState( + MediaDataLoadingModel.Loaded(remoteInstanceId), + false + ) verify(smartspaceLogger) .logSmartspaceCardReceived( @@ -442,7 +448,7 @@ class MediaFilterRepositoryTest : SysuiTestCase() { reset(smartspaceLogger) underTest.addSelectedUserMediaEntry(data) - underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId)) + underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId), false) verify(smartspaceLogger) .logSmartspaceCardReceived(data.smartspaceId, data.appUid, cardinality = 2) @@ -451,7 +457,8 @@ class MediaFilterRepositoryTest : SysuiTestCase() { underTest.addSelectedUserMediaEntry(data) underTest.addMediaDataLoadingState( - MediaDataLoadingModel.Loaded(instanceId, receivedSmartspaceCardLatency = 123) + MediaDataLoadingModel.Loaded(instanceId, receivedSmartspaceCardLatency = 123), + true ) verify(smartspaceLogger) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt index c62195fafd8c..414974cc2941 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.media.controls.domain.interactor import android.R import android.graphics.drawable.Icon +import android.os.Process import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId @@ -38,12 +39,19 @@ 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.media.controls.util.mediaSmartspaceLogger +import com.android.systemui.media.controls.util.mockMediaSmartspaceLogger import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito.reset +import org.mockito.kotlin.never +import org.mockito.kotlin.verify @SmallTest @RunWith(AndroidJUnit4::class) @@ -52,7 +60,11 @@ class MediaCarouselInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val mediaFilterRepository: MediaFilterRepository = kosmos.mediaFilterRepository + private val mediaFilterRepository: MediaFilterRepository = + with(kosmos) { + mediaSmartspaceLogger = mockMediaSmartspaceLogger + mediaFilterRepository + } private val mediaRecommendationsInteractor: MediaRecommendationsInteractor = kosmos.mediaRecommendationsInteractor val icon = Icon.createWithResource(context, R.drawable.ic_media_play) @@ -63,6 +75,7 @@ class MediaCarouselInteractorTest : SysuiTestCase() { packageName = PACKAGE_NAME, recommendations = MediaTestHelper.getValidRecommendationList(icon), ) + private val smartspaceLogger = kosmos.mockMediaSmartspaceLogger private val underTest: MediaCarouselInteractor = kosmos.mediaCarouselInteractor @@ -153,6 +166,18 @@ class MediaCarouselInteractorTest : SysuiTestCase() { MediaCommonModel.MediaControl(mediaLoadingModel, true) ) .inOrder() + + underTest.logSmartspaceSeenCard(0, 1, false) + + verify(smartspaceLogger) + .logSmartspaceCardUIEvent( + MediaSmartspaceLogger.SMARTSPACE_CARD_SEEN_EVENT, + SmallHash.hash(mediaRecommendation.targetId), + Process.INVALID_UID, + surface = SURFACE, + 2, + true + ) } @Test @@ -239,7 +264,7 @@ class MediaCarouselInteractorTest : SysuiTestCase() { .inOrder() mediaFilterRepository.addSelectedUserMediaEntry(data.copy(isPlaying = true)) - mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId)) + mediaFilterRepository.addMediaDataLoadingState(mediaLoadingModel) assertThat(currentMedia) .containsExactly( @@ -249,9 +274,83 @@ class MediaCarouselInteractorTest : SysuiTestCase() { .inOrder() } + @Test + fun loadMediaAndRecommendation_logSmartspaceSeenCard() { + val instanceId = InstanceId.fakeInstanceId(123) + val data = + MediaData( + active = true, + instanceId = instanceId, + packageName = PACKAGE_NAME, + notificationKey = KEY + ) + val smartspaceLoadingModel = SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE) + val mediaLoadingModel = MediaDataLoadingModel.Loaded(instanceId) + + mediaFilterRepository.addSelectedUserMediaEntry(data) + mediaFilterRepository.addMediaDataLoadingState(mediaLoadingModel) + underTest.logSmartspaceSeenCard(0, 1, false) + + verify(smartspaceLogger) + .logSmartspaceCardUIEvent( + MediaSmartspaceLogger.SMARTSPACE_CARD_SEEN_EVENT, + data.smartspaceId, + data.appUid, + surface = SURFACE, + 1 + ) + + reset(smartspaceLogger) + mediaFilterRepository.addSelectedUserMediaEntry(data) + mediaFilterRepository.addMediaDataLoadingState(mediaLoadingModel) + underTest.logSmartspaceSeenCard(0, 1, true) + + verify(smartspaceLogger, never()) + .logSmartspaceCardUIEvent( + MediaSmartspaceLogger.SMARTSPACE_CARD_SEEN_EVENT, + data.smartspaceId, + data.appUid, + surface = SURFACE, + 2 + ) + + reset(smartspaceLogger) + mediaFilterRepository.setRecommendation(mediaRecommendation) + mediaFilterRepository.setRecommendationsLoadingState(smartspaceLoadingModel) + underTest.logSmartspaceSeenCard(1, 1, true) + + verify(smartspaceLogger) + .logSmartspaceCardUIEvent( + MediaSmartspaceLogger.SMARTSPACE_CARD_SEEN_EVENT, + SmallHash.hash(mediaRecommendation.targetId), + Process.INVALID_UID, + surface = SURFACE, + 2, + true, + rank = 1 + ) + + reset(smartspaceLogger) + mediaFilterRepository.addSelectedUserMediaEntry(data) + mediaFilterRepository.addMediaDataLoadingState( + mediaLoadingModel.copy(receivedSmartspaceCardLatency = 1) + ) + underTest.logSmartspaceSeenCard(0, 1, true) + + verify(smartspaceLogger) + .logSmartspaceCardUIEvent( + MediaSmartspaceLogger.SMARTSPACE_CARD_SEEN_EVENT, + data.smartspaceId, + data.appUid, + surface = SURFACE, + 2 + ) + } + companion object { private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID" private const val PACKAGE_NAME = "com.android.example" private const val KEY = "key" + private const val SURFACE = 4 } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt index 856c3fe19d73..d594f3a2f932 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt @@ -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) + + 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) - 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) + 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 } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaRecommendationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaRecommendationsInteractorTest.kt index 9656511817dc..8af7e1dbe59b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaRecommendationsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaRecommendationsInteractorTest.kt @@ -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 } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/util/MediaDiffUtilTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/util/MediaDiffUtilTest.kt index 71685a4354bf..005424ba599e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/util/MediaDiffUtilTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/util/MediaDiffUtilTest.kt @@ -47,7 +47,7 @@ class MediaDiffUtilTest : SysuiTestCase() { oldList, newList, { commonViewModel, _ -> assertThat(commonViewModel).isEqualTo(mediaControl) }, - { fail("Unexpected to update $it") }, + { commonViewModel, _ -> fail("Unexpected to update $commonViewModel") }, { fail("Unexpected to remove $it") }, { commonViewModel, _, _ -> fail("Unexpected to move $commonViewModel ") }, ) @@ -66,7 +66,7 @@ class MediaDiffUtilTest : SysuiTestCase() { oldList, newList, { commonViewModel, _ -> assertThat(commonViewModel).isEqualTo(mediaRecs) }, - { fail("Unexpected to update $it") }, + { commonViewModel, _ -> fail("Unexpected to update $commonViewModel") }, { fail("Unexpected to remove $it") }, { commonViewModel, _, _ -> fail("Unexpected to move $commonViewModel ") }, ) @@ -85,7 +85,7 @@ class MediaDiffUtilTest : SysuiTestCase() { oldList, newList, { commonViewModel, _ -> fail("Unexpected to add $commonViewModel") }, - { commonViewModel -> assertThat(commonViewModel).isNotEqualTo(mediaControl) }, + { commonViewModel, _ -> assertThat(commonViewModel).isNotEqualTo(mediaControl) }, { fail("Unexpected to remove $it") }, { commonViewModel, _, _ -> fail("Unexpected to move $commonViewModel ") }, ) @@ -104,7 +104,7 @@ class MediaDiffUtilTest : SysuiTestCase() { oldList, newList, { commonViewModel, _ -> fail("Unexpected to add $commonViewModel") }, - { commonViewModel -> assertThat(commonViewModel).isNotEqualTo(mediaRecs) }, + { commonViewModel, _ -> assertThat(commonViewModel).isNotEqualTo(mediaRecs) }, { fail("Unexpected to remove $it") }, { commonViewModel, _, _ -> fail("Unexpected to move $commonViewModel ") }, ) @@ -124,7 +124,7 @@ class MediaDiffUtilTest : SysuiTestCase() { oldList, newList, { commonViewModel, _ -> fail("Unexpected to add $commonViewModel") }, - { fail("Unexpected to update $it") }, + { commonViewModel, _ -> fail("Unexpected to update $commonViewModel") }, { fail("Unexpected to remove $it") }, { commonViewModel, _, _ -> assertThat(commonViewModel).isEqualTo(mediaControl1) }, ) @@ -145,7 +145,7 @@ class MediaDiffUtilTest : SysuiTestCase() { oldList, newList, { commonViewModel, _ -> fail("Unexpected to add $commonViewModel") }, - { fail("Unexpected to update $it") }, + { commonViewModel, _ -> fail("Unexpected to update $commonViewModel") }, { fail("Unexpected to remove $it") }, { commonViewModel, _, _ -> assertThat(commonViewModel).isEqualTo(mediaRecs) }, ) @@ -164,7 +164,7 @@ class MediaDiffUtilTest : SysuiTestCase() { oldList, newList, { commonViewModel, _ -> fail("Unexpected to add $commonViewModel") }, - { fail("Unexpected to update $it") }, + { commonViewModel, _ -> fail("Unexpected to update $commonViewModel") }, { commonViewModel -> assertThat(commonViewModel).isEqualTo(mediaControl) }, { commonViewModel, _, _ -> fail("Unexpected to move $commonViewModel ") }, ) @@ -183,7 +183,7 @@ class MediaDiffUtilTest : SysuiTestCase() { oldList, newList, { commonViewModel, _ -> fail("Unexpected to add $commonViewModel") }, - { fail("Unexpected to update $it") }, + { commonViewModel, _ -> fail("Unexpected to update $commonViewModel") }, { commonViewModel -> assertThat(commonViewModel).isEqualTo(mediaRecs) }, { commonViewModel, _, _ -> fail("Unexpected to move $commonViewModel ") }, ) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt index e8a23347c3c3..341b8d87eeef 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt @@ -28,6 +28,7 @@ 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.MediaSmartspaceLogger.Companion.SMARTSPACE_CARD_DISMISS_EVENT +import com.android.systemui.media.controls.util.MediaSmartspaceLogger.Companion.SMARTSPACE_CARD_SEEN_EVENT import com.android.systemui.media.controls.util.SmallHash import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.time.SystemClock @@ -137,10 +138,13 @@ constructor( return mediaData } - fun addSelectedUserMediaEntry(data: MediaData) { + /** @return whether the added media data already exists. */ + fun addSelectedUserMediaEntry(data: MediaData): Boolean { val entries = LinkedHashMap<InstanceId, MediaData>(_selectedUserEntries.value) + val update = _selectedUserEntries.value.containsKey(data.instanceId) entries[data.instanceId] = data _selectedUserEntries.value = entries + return update } /** @@ -184,7 +188,10 @@ constructor( _reactivatedId.value = instanceId } - fun addMediaDataLoadingState(mediaDataLoadingModel: MediaDataLoadingModel) { + fun addMediaDataLoadingState( + mediaDataLoadingModel: MediaDataLoadingModel, + isUpdate: Boolean = true + ) { val sortedMap = TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator) sortedMap.putAll( sortedMedia.filter { (_, commonModel) -> @@ -212,15 +219,10 @@ constructor( MediaCommonModel.MediaControl( mediaDataLoadingModel, canBeRemoved(it), - isMediaFromRec(it) + isMediaFromRec(it), + if (isUpdate) systemClock.currentTimeMillis() else 0, ) 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) { @@ -359,14 +361,51 @@ constructor( return _selectedUserEntries.value.entries.isNotEmpty() } + fun hasActiveMediaOrRecommendation(): Boolean { + return _selectedUserEntries.value.any { it.value.active } || + (isRecommendationActive() && + (_smartspaceMediaData.value.isValid() || _reactivatedId.value != null)) + } + fun isRecommendationActive(): Boolean { return _smartspaceMediaData.value.isActive } + /** Log visible card given [visibleIndex]. */ + fun logSmartspaceCardSeen(surface: Int, visibleIndex: Int, isMediaCardUpdate: Boolean) { + if (_currentMedia.value.size <= visibleIndex) return + + when (val mediaCommonModel = _currentMedia.value[visibleIndex]) { + is MediaCommonModel.MediaControl -> { + if ( + !isMediaCardUpdate || + mediaCommonModel.mediaLoadedModel.receivedSmartspaceCardLatency != 0 + ) { + logSmartspaceMediaCardUserEvent( + mediaCommonModel.mediaLoadedModel.instanceId, + visibleIndex, + SMARTSPACE_CARD_SEEN_EVENT, + surface, + mediaCommonModel.mediaLoadedModel.isSsReactivated, + ) + } + } + is MediaCommonModel.MediaRecommendations -> { + if (isRecommendationActive()) { + logSmarspaceRecommendationCardUserEvent( + SMARTSPACE_CARD_SEEN_EVENT, + surface, + visibleIndex + ) + } + } + } + } + /** 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 +420,7 @@ constructor( instanceId, index, eventId, - location, + surface, mediaCommonModel.mediaLoadedModel.isSsReactivated, interactedSubCardRank, interactedSubCardCardinality @@ -395,7 +434,7 @@ constructor( if (isSmartspaceLoggingEnabled(mediaCommonModel, index)) { logSmarspaceRecommendationCardUserEvent( eventId, - location, + surface, index, interactedSubCardRank, interactedSubCardCardinality @@ -409,7 +448,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 +457,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 +509,7 @@ constructor( instanceId: InstanceId, index: Int, eventId: Int, - location: Int, + surface: Int, isReactivated: Boolean, interactedSubCardRank: Int = 0, interactedSubCardCardinality: Int = 0, @@ -481,7 +520,7 @@ constructor( eventId, it.smartspaceId, it.appUid, - location, + surface, _currentMedia.value.size, isSsReactivated = isReactivated, interactedSubcardRank = interactedSubCardRank, @@ -494,7 +533,7 @@ constructor( private fun logSmarspaceRecommendationCardUserEvent( eventId: Int, - location: Int, + surface: Int, index: Int, interactedSubCardRank: Int = 0, interactedSubCardCardinality: Int = 0, @@ -504,7 +543,7 @@ constructor( eventId, SmallHash.hash(_smartspaceMediaData.value.targetId), _smartspaceMediaData.value.getUid(applicationContext), - location, + surface, _currentMedia.value.size, isRecommendationCard = true, interactedSubcardRank = interactedSubCardRank, diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt index 31bd4fb78ffa..803e7efa7f60 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt @@ -116,11 +116,12 @@ constructor( return } - mediaFilterRepository.addSelectedUserMediaEntry(data) + val isUpdate = mediaFilterRepository.addSelectedUserMediaEntry(data) mediaLoadingLogger.logMediaLoaded(data.instanceId, data.active, "loading media") mediaFilterRepository.addMediaDataLoadingState( - MediaDataLoadingModel.Loaded(data.instanceId) + MediaDataLoadingModel.Loaded(data.instanceId), + isUpdate ) // Notify listeners @@ -323,9 +324,10 @@ constructor( mediaFilterRepository.allUserEntries.value.forEach { (key, data) -> if (lockscreenUserManager.isCurrentProfile(data.userId)) { - mediaFilterRepository.addSelectedUserMediaEntry(data) + val isUpdate = mediaFilterRepository.addSelectedUserMediaEntry(data) mediaFilterRepository.addMediaDataLoadingState( - MediaDataLoadingModel.Loaded(data.instanceId) + MediaDataLoadingModel.Loaded(data.instanceId), + isUpdate ) mediaLoadingLogger.logMediaLoaded( data.instanceId, @@ -338,8 +340,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)) { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt index 0630cbd3f3be..9d7160cbaffc 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt @@ -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,11 +205,14 @@ 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 + override fun hasActiveMediaOrRecommendation() = + mediaFilterRepository.hasActiveMediaOrRecommendation() override fun hasAnyMediaOrRecommendation() = hasAnyMediaOrRecommendation.value @@ -222,6 +226,14 @@ constructor( mediaFilterRepository.setOrderedMedia() } + fun logSmartspaceSeenCard(visibleIndex: Int, location: Int, isMediaCardUpdate: Boolean) { + mediaFilterRepository.logSmartspaceCardSeen( + MediaSmartspaceLogger.getSurface(location), + visibleIndex, + isMediaCardUpdate + ) + } + /** Add a listener for internal events. */ private fun addInternalListener(listener: MediaDataManager.Listener) = mediaDataProcessor.addInternalListener(listener) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt index 3f75938a91ea..245f6f8bf2f6 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt @@ -19,7 +19,6 @@ package com.android.systemui.media.controls.domain.pipeline.interactor import android.app.ActivityOptions import android.app.BroadcastOptions import android.app.PendingIntent -import android.content.Context import android.content.Intent import android.media.session.MediaSession import android.provider.Settings @@ -31,11 +30,11 @@ import com.android.systemui.animation.DialogCuj import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.animation.Expandable import com.android.systemui.bluetooth.BroadcastDialogController -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.media.controls.data.repository.MediaFilterRepository import com.android.systemui.media.controls.domain.pipeline.MediaDataProcessor import com.android.systemui.media.controls.shared.model.MediaControlModel import com.android.systemui.media.controls.shared.model.MediaData +import com.android.systemui.media.controls.util.MediaSmartspaceLogger import com.android.systemui.media.dialog.MediaOutputDialogManager import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.NotificationLockscreenUserManager @@ -50,9 +49,8 @@ import kotlinx.coroutines.flow.map class MediaControlInteractor @AssistedInject constructor( - @Application applicationContext: Context, @Assisted private val instanceId: InstanceId, - repository: MediaFilterRepository, + private val repository: MediaFilterRepository, private val mediaDataProcessor: MediaDataProcessor, private val keyguardStateController: KeyguardStateController, private val activityStarter: ActivityStarter, @@ -72,8 +70,11 @@ constructor( fun removeMediaControl( token: MediaSession.Token?, instanceId: InstanceId, - delayMs: Long + delayMs: Long, + eventId: Int, + location: Int ): Boolean { + logSmartspaceUserEvent(eventId, location) val dismissed = mediaDataProcessor.dismissMediaData(instanceId, delayMs, userInitiated = true) if (!dismissed) { @@ -114,7 +115,13 @@ constructor( activityStarter.startActivity(SETTINGS_INTENT, /* dismissShade= */ true) } - fun startClickIntent(expandable: Expandable, clickIntent: PendingIntent) { + fun startClickIntent( + expandable: Expandable, + clickIntent: PendingIntent, + eventId: Int, + location: Int + ) { + logSmartspaceUserEvent(eventId, location) if (!launchOverLockscreen(clickIntent)) { activityStarter.postStartActivityDismissingKeyguard( clickIntent, @@ -176,6 +183,14 @@ constructor( ) } + fun logSmartspaceUserEvent(eventId: Int, location: Int) { + repository.logSmartspaceCardUserEvent( + eventId, + MediaSmartspaceLogger.getSurface(location), + instanceId = instanceId + ) + } + private fun Expandable.dialogController(): DialogTransitionAnimator.Controller? { return dialogTransitionController( cuj = diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt index dd6b2646afd1..c3a36b258842 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaRecommendationsInteractor.kt @@ -31,6 +31,7 @@ import com.android.systemui.media.controls.domain.pipeline.MediaDataProcessor 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 import com.android.systemui.plugins.ActivityStarter import java.net.URISyntaxException import javax.inject.Inject @@ -67,7 +68,14 @@ constructor( val onAnyMediaConfigurationChange: Flow<Unit> = repository.onAnyMediaConfigurationChange - fun removeMediaRecommendations(key: String, dismissIntent: Intent?, delayMs: Long) { + fun removeMediaRecommendations( + key: String, + dismissIntent: Intent?, + delayMs: Long, + eventId: Int, + location: Int + ) { + logSmartspaceCardUserEvent(eventId, location) mediaDataProcessor.dismissSmartspaceRecommendation(key, delayMs) if (dismissIntent == null) { Log.w(TAG, "Cannot create dismiss action click action: extras missing dismiss_intent.") @@ -87,7 +95,25 @@ constructor( activityStarter.startActivity(SETTINGS_INTENT, /* dismissShade= */ true) } - fun startClickIntent(expandable: Expandable, intent: Intent) { + fun startClickIntent( + expandable: Expandable, + intent: Intent, + eventId: Int, + location: Int, + interactedSubCardRank: Int, + interactedSubCardCardinality: Int + ) { + if (interactedSubCardRank == -1) { + logSmartspaceCardUserEvent(eventId, MediaSmartspaceLogger.getSurface(location)) + } else { + repository.logSmartspaceCardUserEvent( + eventId, + MediaSmartspaceLogger.getSurface(location), + interactedSubCardRank = interactedSubCardRank, + interactedSubCardCardinality = interactedSubCardCardinality, + isRec = true + ) + } if (shouldActivityOpenInForeground(intent)) { // Request to unlock the device if the activity needs to be opened in foreground. activityStarter.postStartActivityDismissingKeyguard( @@ -103,6 +129,14 @@ constructor( } } + private fun logSmartspaceCardUserEvent(eventId: Int, location: Int) { + repository.logSmartspaceCardUserEvent( + eventId, + MediaSmartspaceLogger.getSurface(location), + isRec = true + ) + } + /** Returns if the action will open the activity in foreground. */ private fun shouldActivityOpenInForeground(intent: Intent): Boolean { val intentString = intent.extras?.getString(EXTRAS_SMARTSPACE_INTENT) ?: return false diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaCommonModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaCommonModel.kt index 56cc618eb61c..3d5d47b30860 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaCommonModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaCommonModel.kt @@ -22,6 +22,7 @@ sealed class MediaCommonModel { val mediaLoadedModel: MediaDataLoadingModel.Loaded, val canBeRemoved: Boolean = false, val isMediaFromRec: Boolean = false, + val updateTime: Long = 0L, ) : MediaCommonModel() data class MediaRecommendations(val recsLoadingModel: SmartspaceMediaLoadingModel) : diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt index 72fb218865b2..62759a4843d9 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt @@ -109,6 +109,10 @@ object MediaControlViewBinder { mainDispatcher: CoroutineDispatcher, mediaFlags: MediaFlags, ) { + // Set up media control location and its listener. + viewModel.onLocationChanged(viewController.currentEndLocation) + viewController.locationChangeListener = viewModel.onLocationChanged + with(viewHolder) { // AlbumView uses a hardware layer so that clipping of the foreground is handled with // clipping the album art. Otherwise album art shows through at the edges. @@ -221,7 +225,7 @@ object MediaControlViewBinder { dismiss.isEnabled = model.isDismissEnabled dismiss.setOnClickListener { if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { - model.onDismissClicked.invoke() + model.onDismissClicked() } } cancelText.background = model.cancelTextBackground @@ -349,7 +353,7 @@ object MediaControlViewBinder { if (actionViewModel.isEnabled) { button.setOnClickListener { if (!falsingManager.isFalseTap(FalsingManager.MODERATE_PENALTY)) { - actionViewModel.onClicked.invoke(it.id) + actionViewModel.onClicked(it.id) viewController.multiRippleController.play( createTouchRippleAnimation( @@ -469,8 +473,7 @@ object MediaControlViewBinder { transitionDrawable.startTransition( if (viewModel.shouldAddGradient) 333 else 80 ) - } - ?: albumView.setImageDrawable(artwork) + } ?: albumView.setImageDrawable(artwork) } viewController.isArtworkBound = viewModel.shouldAddGradient viewController.prevArtwork = artwork diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaRecommendationsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaRecommendationsViewBinder.kt index bd4d43531339..5e8a879adbb4 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaRecommendationsViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaRecommendationsViewBinder.kt @@ -71,14 +71,18 @@ object MediaRecommendationsViewBinder { fun bindRecsCard( viewHolder: RecommendationViewHolder, viewModel: MediaRecsCardViewModel, - mediaViewController: MediaViewController, + viewController: MediaViewController, falsingManager: FalsingManager, ) { + // Set up media control location and its listener. + viewModel.onLocationChanged(viewController.currentEndLocation) + viewController.locationChangeListener = viewModel.onLocationChanged + // Bind main card. viewHolder.cardTitle.setTextColor(viewModel.cardTitleColor) viewHolder.recommendations.backgroundTintList = ColorStateList.valueOf(viewModel.cardColor) viewHolder.recommendations.contentDescription = - viewModel.contentDescription.invoke(mediaViewController.isGutsVisible) + viewModel.contentDescription.invoke(viewController.isGutsVisible) viewHolder.recommendations.setOnClickListener { if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@setOnClickListener @@ -88,21 +92,21 @@ object MediaRecommendationsViewBinder { viewHolder.recommendations.setOnLongClickListener { if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) return@setOnLongClickListener true - if (!mediaViewController.isGutsVisible) { - openGuts(viewHolder, viewModel, mediaViewController) + if (!viewController.isGutsVisible) { + openGuts(viewHolder, viewModel, viewController) } else { - closeGuts(viewHolder, viewModel, mediaViewController) + closeGuts(viewHolder, viewModel, viewController) } return@setOnLongClickListener true } // Bind all recommendations. bindRecommendationsList(viewHolder, viewModel.mediaRecs, falsingManager) - updateRecommendationsVisibility(mediaViewController, viewHolder.recommendations) + updateRecommendationsVisibility(viewController, viewHolder.recommendations) // Set visibility of recommendations. - val expandedSet: ConstraintSet = mediaViewController.expandedLayout - val collapsedSet: ConstraintSet = mediaViewController.collapsedLayout + val expandedSet: ConstraintSet = viewController.expandedLayout + val collapsedSet: ConstraintSet = viewController.collapsedLayout viewHolder.mediaTitles.forEach { setVisibleAndAlpha(expandedSet, it.id, viewModel.areTitlesVisible) setVisibleAndAlpha(collapsedSet, it.id, viewModel.areTitlesVisible) @@ -112,15 +116,15 @@ object MediaRecommendationsViewBinder { setVisibleAndAlpha(collapsedSet, it.id, viewModel.areSubtitlesVisible) } - bindRecommendationsGuts(viewHolder, viewModel, mediaViewController, falsingManager) + bindRecommendationsGuts(viewHolder, viewModel, viewController, falsingManager) - mediaViewController.refreshState() + viewController.refreshState() } private fun bindRecommendationsGuts( viewHolder: RecommendationViewHolder, viewModel: MediaRecsCardViewModel, - mediaViewController: MediaViewController, + viewController: MediaViewController, falsingManager: FalsingManager, ) { val gutsViewHolder = viewHolder.gutsViewHolder @@ -131,14 +135,14 @@ object MediaRecommendationsViewBinder { gutsViewHolder.dismiss.isEnabled = true gutsViewHolder.dismiss.setOnClickListener { if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@setOnClickListener - closeGuts(viewHolder, viewModel, mediaViewController) - gutsViewModel.onDismissClicked.invoke() + closeGuts(viewHolder, viewModel, viewController) + gutsViewModel.onDismissClicked() } gutsViewHolder.cancelText.background = gutsViewModel.cancelTextBackground gutsViewHolder.cancel.setOnClickListener { if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { - closeGuts(viewHolder, viewModel, mediaViewController) + closeGuts(viewHolder, viewModel, viewController) } } @@ -173,7 +177,7 @@ object MediaRecommendationsViewBinder { val mediaCoverContainer = viewHolder.mediaCoverContainers[index] mediaCoverContainer.setOnClickListener { if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@setOnClickListener - mediaRecViewModel.onClicked.invoke(Expandable.fromView(it), index) + mediaRecViewModel.onClicked(Expandable.fromView(it), index) } mediaCoverContainer.setOnLongClickListener { if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt index 886cb7f85b82..125f97375fea 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt @@ -768,6 +768,7 @@ constructor( mediaContent.addView(viewHolder.recommendations, position) } } + onAddOrUpdateVisibleToUserCard(position, isMediaCardUpdate = false) viewController.setListening(mediaCarouselScrollHandler.visibleToUser && currentlyExpanded) controllerByViewModel[commonViewModel] = viewController updateViewControllerToState(viewController, noAnimation = true) @@ -785,10 +786,14 @@ constructor( commonViewModel.onAdded(commonViewModel) } - private fun onUpdated(commonViewModel: MediaCommonViewModel) { + private fun onUpdated(commonViewModel: MediaCommonViewModel, position: Int) { commonViewModel.onUpdated(commonViewModel) updatePageIndicator() mediaCarouselScrollHandler.onPlayersChanged() + onAddOrUpdateVisibleToUserCard( + position, + commonViewModel is MediaCommonViewModel.MediaControl + ) } private fun onRemoved(commonViewModel: MediaCommonViewModel) { @@ -825,6 +830,20 @@ constructor( mediaCarouselScrollHandler.onPlayersChanged() } + private fun onAddOrUpdateVisibleToUserCard(position: Int, isMediaCardUpdate: Boolean) { + if ( + mediaCarouselScrollHandler.visibleToUser && + mediaCarouselScrollHandler.visibleMediaIndex == position + ) { + mediaCarouselViewModel.onCardVisibleToUser( + mediaCarouselScrollHandler.qsExpanded, + mediaCarouselScrollHandler.visibleMediaIndex, + currentEndLocation, + isMediaCardUpdate + ) + } + } + private fun setNewViewModelsList(viewModels: List<MediaCommonViewModel>) { commonViewModels.clear() commonViewModels.addAll(viewModels) @@ -1438,6 +1457,14 @@ constructor( /** Log the user impression for media card at visibleMediaIndex. */ fun logSmartspaceImpression(qsExpanded: Boolean) { + if (SceneContainerFlag.isEnabled) { + mediaCarouselViewModel.onCardVisibleToUser( + qsExpanded, + mediaCarouselScrollHandler.visibleMediaIndex, + currentEndLocation + ) + return + } val visibleMediaIndex = mediaCarouselScrollHandler.visibleMediaIndex if (MediaPlayerData.players().size > visibleMediaIndex) { val mediaControlPanel = MediaPlayerData.getMediaControlPanel(visibleMediaIndex) @@ -1550,7 +1577,7 @@ constructor( @VisibleForTesting fun onSwipeToDismiss() { if (mediaFlags.isSceneContainerEnabled()) { - mediaCarouselViewModel.onSwipeToDismiss() + mediaCarouselViewModel.onSwipeToDismiss(currentEndLocation) return } MediaPlayerData.players().forEachIndexed { index, it -> diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt index 9d0723211d4b..681bf390e3e9 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt @@ -103,6 +103,7 @@ constructor( lateinit var sizeChangedListener: () -> Unit lateinit var configurationChangeListener: () -> Unit lateinit var recsConfigurationChangeListener: (MediaViewController, TransitionLayout) -> Unit + var locationChangeListener: (Int) -> Unit = {} private var firstRefresh: Boolean = true @VisibleForTesting private var transitionLayout: TransitionLayout? = null private val layoutController = TransitionLayoutController() @@ -119,7 +120,15 @@ constructor( * The ending location of the view where it ends when all animations and transitions have * finished */ - @MediaLocation var currentEndLocation: Int = -1 + @MediaLocation + var currentEndLocation: Int = -1 + set(value) { + if (field != value) { + field = value + if (!mediaFlags.isSceneContainerEnabled()) return + locationChangeListener(value) + } + } /** The starting location of the view where it starts for all animations and transitions */ @MediaLocation private var currentStartLocation: Int = -1 @@ -799,7 +808,7 @@ constructor( fun bindSeekBar(onSeek: () -> Unit, onBindSeekBar: (SeekBarViewModel) -> Unit) { if (!mediaFlags.isSceneContainerEnabled()) return seekBarViewModel.logSeek = onSeek - onBindSeekBar.invoke(seekBarViewModel) + onBindSeekBar(seekBarViewModel) } fun setUpTurbulenceNoise() { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelCallback.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelCallback.kt index 952b134c5e16..f28edd638b10 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelCallback.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelCallback.kt @@ -54,7 +54,8 @@ class MediaViewModelCallback( oldItem is MediaCommonViewModel.MediaControl && newItem is MediaCommonViewModel.MediaControl ) { - oldItem.immediatelyUpdateUi == newItem.immediatelyUpdateUi + oldItem.immediatelyUpdateUi == newItem.immediatelyUpdateUi && + oldItem.updateTime == newItem.updateTime } else if ( oldItem is MediaCommonViewModel.MediaRecommendations && newItem is MediaCommonViewModel.MediaRecommendations diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelListUpdateCallback.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelListUpdateCallback.kt index bd81e44d091c..709723fa9480 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelListUpdateCallback.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelListUpdateCallback.kt @@ -24,7 +24,7 @@ class MediaViewModelListUpdateCallback( private val old: List<MediaCommonViewModel>, private val new: List<MediaCommonViewModel>, private val onAdded: (MediaCommonViewModel, Int) -> Unit, - private val onUpdated: (MediaCommonViewModel) -> Unit, + private val onUpdated: (MediaCommonViewModel, Int) -> Unit, private val onRemoved: (MediaCommonViewModel) -> Unit, private val onMoved: (MediaCommonViewModel, Int, Int) -> Unit, ) : ListUpdateCallback { @@ -47,7 +47,7 @@ class MediaViewModelListUpdateCallback( override fun onChanged(position: Int, count: Int, payload: Any?) { for (i in position until position + count) { - onUpdated(new[i]) + onUpdated(new[i], position) } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt index f0d8df511c73..c453a212a3cd 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt @@ -98,9 +98,9 @@ constructor( private var allowReorder = false - fun onSwipeToDismiss() { + fun onSwipeToDismiss(location: Int) { logger.logSwipeDismiss() - interactor.onSwipeToDismiss() + interactor.onSwipeToDismiss(location) } fun onReorderingAllowed() { @@ -108,12 +108,24 @@ constructor( interactor.reorderMedia() } + fun onCardVisibleToUser( + qsExpanded: Boolean, + visibleIndex: Int, + location: Int, + isUpdate: Boolean = false + ) { + // Skip logging if on LS or QQS, and there is no active media card + if (!qsExpanded && !interactor.hasActiveMediaOrRecommendation()) return + interactor.logSmartspaceSeenCard(visibleIndex, location, isUpdate) + } + private fun toViewModel( commonModel: MediaCommonModel.MediaControl ): MediaCommonViewModel.MediaControl { val instanceId = commonModel.mediaLoadedModel.instanceId return mediaControlByInstanceId[instanceId]?.copy( - immediatelyUpdateUi = commonModel.mediaLoadedModel.immediatelyUpdateUi + immediatelyUpdateUi = commonModel.mediaLoadedModel.immediatelyUpdateUi, + updateTime = commonModel.updateTime ) ?: MediaCommonViewModel.MediaControl( instanceId = instanceId, @@ -125,7 +137,8 @@ constructor( mediaControlByInstanceId.remove(instanceId) }, onUpdated = { onMediaControlAddedOrUpdated(it, commonModel) }, - isMediaFromRec = commonModel.isMediaFromRec + isMediaFromRec = commonModel.isMediaFromRec, + updateTime = commonModel.updateTime ) .also { mediaControlByInstanceId[instanceId] = it } } @@ -175,7 +188,6 @@ constructor( commonViewModel: MediaCommonViewModel, commonModel: MediaCommonModel.MediaControl ) { - // TODO (b/330897926) log smartspace card reported (SMARTSPACE_CARD_RECEIVED) if (commonModel.canBeRemoved && !Utils.useMediaResumption(applicationContext)) { // This media control is due for removal as it is now paused + timed out, and resumption // setting is off. @@ -196,8 +208,6 @@ constructor( if (!mediaFlags.isPersistentSsCardEnabled()) { commonViewModel.onRemoved(true) } - } else { - // TODO (b/330897926) log smartspace card reported (SMARTSPACE_CARD_RECEIVED) } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt index a96d75c9ed30..52cb173b39cb 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt @@ -33,6 +33,7 @@ sealed class MediaCommonViewModel { override val onRemoved: (Boolean) -> Unit, override val onUpdated: (MediaCommonViewModel) -> Unit, val isMediaFromRec: Boolean = false, + val updateTime: Long = 0, ) : MediaCommonViewModel() data class MediaRecommendations( diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt index 099991d7c671..64820e0d0ced 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt @@ -37,6 +37,8 @@ import com.android.systemui.media.controls.ui.animation.accentPrimaryFromScheme import com.android.systemui.media.controls.ui.animation.surfaceFromScheme import com.android.systemui.media.controls.ui.animation.textPrimaryFromScheme import com.android.systemui.media.controls.ui.util.MediaArtworkHelper +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.MediaUiEventLogger import com.android.systemui.monet.ColorScheme import com.android.systemui.monet.Style @@ -72,6 +74,7 @@ class MediaControlViewModel( private var isPlaying = false private var isAnyButtonClicked = false + private var location = -1 private fun onDismissMediaData( token: Token?, @@ -80,7 +83,13 @@ class MediaControlViewModel( instanceId: InstanceId ) { logger.logLongPressDismiss(uid, packageName, instanceId) - interactor.removeMediaControl(token, instanceId, MEDIA_PLAYER_ANIMATION_DELAY) + interactor.removeMediaControl( + token, + instanceId, + MEDIA_PLAYER_ANIMATION_DELAY, + SMARTSPACE_CARD_DISMISS_EVENT, + location + ) } private suspend fun toViewModel(model: MediaControlModel): MediaPlayerViewModel? { @@ -100,7 +109,7 @@ class MediaControlViewModel( TAG, Style.CONTENT ) - ?: return null + ?: return null val gutsViewModel = toGutsViewModel(model, scheme) @@ -144,8 +153,12 @@ class MediaControlViewModel( onClicked = { expandable -> model.clickIntent?.let { clickIntent -> logger.logTapContentView(model.uid, model.packageName, model.instanceId) - // TODO (b/330897926) log smartspace card reported (SMARTSPACE_CARD_CLICK_EVENT) - interactor.startClickIntent(expandable, clickIntent) + interactor.startClickIntent( + expandable, + clickIntent, + SMARTSPACE_CARD_CLICK_EVENT, + location + ) } }, onLongClicked = { @@ -153,7 +166,7 @@ class MediaControlViewModel( }, onSeek = { logger.logSeek(model.uid, model.packageName, model.instanceId) - // TODO (b/330897926) log smartspace card reported (SMARTSPACE_CARD_CLICK_EVENT) + interactor.logSmartspaceUserEvent(SMARTSPACE_CARD_CLICK_EVENT, location) }, onBindSeekbar = { seekBarViewModel -> if (model.isResume && model.resumeProgress != null) { @@ -163,7 +176,8 @@ class MediaControlViewModel( seekBarViewModel.updateController(mediaController) } } - } + }, + onLocationChanged = { location = it } ) } @@ -179,8 +193,7 @@ class MediaControlViewModel( it, applicationContext.getString(R.string.broadcasting_description_is_broadcasting) ) - } - ?: false + } ?: false val useDisabledAlpha = if (showBroadcastButton) { !isCurrentBroadcastApp @@ -197,7 +210,8 @@ class MediaControlViewModel( return MediaOutputSwitcherViewModel( isTapEnabled = showBroadcastButton || !useDisabledAlpha, deviceString = deviceString, - deviceIcon = device?.icon?.let { Icon.Loaded(it, null) } + deviceIcon = + device?.icon?.let { Icon.Loaded(it, null) } ?: if (showBroadcastButton) { Icon.Resource(R.drawable.settings_input_antenna, null) } else { @@ -364,7 +378,7 @@ class MediaControlViewModel( action: Runnable ) { logger.logTapAction(id, uid, packageName, instanceId) - // TODO (b/330897926) log smartspace card reported (SMARTSPACE_CARD_CLICK_EVENT) + interactor.logSmartspaceUserEvent(SMARTSPACE_CARD_CLICK_EVENT, location) isAnyButtonClicked = true action.run() } @@ -385,8 +399,7 @@ class MediaControlViewModel( SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING.stream().allMatch { id: Int -> semanticActions.getActionById(id) != null } - } - ?: false + } ?: false } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt index 433434129b96..96e7fc79c8eb 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt @@ -42,4 +42,5 @@ data class MediaPlayerViewModel( val onLongClicked: () -> Unit, val onSeek: () -> Unit, val onBindSeekbar: (SeekBarViewModel) -> Unit, + val onLocationChanged: (Int) -> Unit, ) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt index 52c4bc54eab4..1fd9c4f014ee 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt @@ -37,6 +37,7 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.media.controls.domain.pipeline.interactor.MediaRecommendationsInteractor 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.NUM_REQUIRED_RECOMMENDATIONS import com.android.systemui.media.controls.ui.animation.accentPrimaryFromScheme import com.android.systemui.media.controls.ui.animation.surfaceFromScheme import com.android.systemui.media.controls.ui.animation.textPrimaryFromScheme @@ -44,6 +45,8 @@ import com.android.systemui.media.controls.ui.animation.textSecondaryFromScheme import com.android.systemui.media.controls.ui.controller.MediaViewController.Companion.GUTS_ANIMATION_DURATION import com.android.systemui.media.controls.ui.util.MediaArtworkHelper import com.android.systemui.media.controls.util.MediaDataUtils +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.MediaUiEventLogger import com.android.systemui.monet.ColorScheme import com.android.systemui.monet.Style @@ -78,6 +81,8 @@ constructor( .distinctUntilChanged() .flowOn(backgroundDispatcher) + private var location = -1 + /** * Called whenever the recommendation has been expired or removed by the user. This method * removes the recommendation card entirely from the carousel. @@ -89,9 +94,14 @@ constructor( dismissIntent: Intent?, instanceId: InstanceId? ) { - // TODO (b/330897926) log smartspace card reported (SMARTSPACE_CARD_DISMISS_EVENT). logger.logLongPressDismiss(uid, packageName, instanceId) - interactor.removeMediaRecommendations(key, dismissIntent, GUTS_DISMISS_DELAY_MS_DURATION) + interactor.removeMediaRecommendations( + key, + dismissIntent, + GUTS_DISMISS_DELAY_MS_DURATION, + SMARTSPACE_CARD_DISMISS_EVENT, + location + ) } private fun onClicked( @@ -111,12 +121,18 @@ constructor( } else { logger.logRecommendationItemTap(packageName, instanceId, index) } - // TODO (b/330897926) log smartspace card reported (SMARTSPACE_CARD_CLICK_EVENT). // set the package name of the player added by recommendation once the media is loaded. interactor.switchToMediaControl(packageName) - interactor.startClickIntent(expandable, intent) + interactor.startClickIntent( + expandable, + intent, + SMARTSPACE_CARD_CLICK_EVENT, + location, + index, + NUM_REQUIRED_RECOMMENDATIONS + ) } private suspend fun toRecsViewModel(model: MediaRecommendationsModel): MediaRecsCardViewModel? { @@ -212,6 +228,7 @@ constructor( areTitlesVisible = areTitlesVisible, areSubtitlesVisible = areSubtitlesVisible, gutsMenu = toGutsViewModel(model, scheme), + onLocationChanged = { location = it } ) } @@ -259,8 +276,7 @@ constructor( width, height ) - } - ?: ColorDrawable(Color.TRANSPARENT) + } ?: ColorDrawable(Color.TRANSPARENT) } private fun addGradientToRecommendationAlbum( diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecsCardViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecsCardViewModel.kt index d1713b5cd2fd..5ecbcb20a58a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecsCardViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecsCardViewModel.kt @@ -30,4 +30,5 @@ data class MediaRecsCardViewModel( val areTitlesVisible: Boolean, val areSubtitlesVisible: Boolean, val gutsMenu: GutsViewModel, + val onLocationChanged: (Int) -> Unit, ) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaSmartspaceLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaSmartspaceLogger.kt index d1184b615c95..9c59aa2729b1 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaSmartspaceLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaSmartspaceLogger.kt @@ -71,7 +71,7 @@ class MediaSmartspaceLogger @Inject constructor() { * @param eventId id of the event. eg: dismiss, click, or seen. * @param instanceId id to uniquely identify a card. * @param uid uid for the application that media comes from. - * @param location location of media carousel holding media card. + * @param surface location of media carousel holding media card. * @param cardinality number of card in carousel. * @param isRecommendationCard whether media card being logged is a recommendations card. * @param isSsReactivated indicates resume media card is reactivated by Smartspace @@ -83,7 +83,7 @@ class MediaSmartspaceLogger @Inject constructor() { eventId: Int, instanceId: Int, uid: Int, - location: Int, + surface: Int, cardinality: Int, isRecommendationCard: Boolean = false, isSsReactivated: Boolean = false, @@ -96,7 +96,7 @@ class MediaSmartspaceLogger @Inject constructor() { eventId, instanceId, uid, - surfaces = intArrayOf(location), + surfaces = intArrayOf(surface), cardinality, isRecommendationCard, isSsReactivated, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt index c974e0dde5e6..850e2e014c57 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt @@ -16,8 +16,11 @@ package com.android.systemui.media.controls.domain.pipeline +import android.R import android.app.smartspace.SmartspaceAction +import android.graphics.drawable.Icon import android.os.Bundle +import android.os.Process import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -36,7 +39,11 @@ import com.android.systemui.media.controls.shared.model.SmartspaceMediaData import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel import com.android.systemui.media.controls.ui.controller.MediaPlayerData import com.android.systemui.media.controls.util.MediaFlags +import com.android.systemui.media.controls.util.MediaSmartspaceLogger import com.android.systemui.media.controls.util.MediaUiEventLogger +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.settings.UserTracker import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.testKosmos @@ -101,7 +108,12 @@ class MediaDataFilterImplTest : SysuiTestCase() { private lateinit var dataGuest: MediaData private lateinit var dataPrivateProfile: MediaData private val clock = FakeSystemClock() - private val repository: MediaFilterRepository = kosmos.mediaFilterRepository + private val smartspaceLogger = kosmos.mockMediaSmartspaceLogger + private val repository: MediaFilterRepository = + with(kosmos) { + mediaSmartspaceLogger = mockMediaSmartspaceLogger + mediaFilterRepository + } private val mediaLoadingLogger = kosmos.mockMediaLoadingLogger @Before @@ -146,6 +158,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { whenever(smartspaceData.packageName).thenReturn(SMARTSPACE_PACKAGE) whenever(smartspaceData.recommendations) .thenReturn(listOf(smartspaceMediaRecommendationItem)) + whenever(smartspaceMediaRecommendationItem.icon) + .thenReturn(Icon.createWithResource(context, R.drawable.ic_media_play)) whenever(smartspaceData.headphoneConnectionTimeMillis) .thenReturn(clock.currentTimeMillis() - 100) whenever(smartspaceData.instanceId).thenReturn(SMARTSPACE_INSTANCE_ID) @@ -533,8 +547,22 @@ class MediaDataFilterImplTest : SysuiTestCase() { @Test fun onSwipeToDismiss_setsTimedOut() { mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain) - mediaDataFilter.onSwipeToDismiss() - + mediaDataFilter.onSwipeToDismiss(1) + + verify(smartspaceLogger, never()) + .logSmartspaceCardUIEvent( + eq(MediaSmartspaceLogger.SMARTSPACE_CARD_DISMISS_EVENT), + anyInt(), + anyInt(), + anyInt(), + anyInt(), + anyBoolean(), + anyBoolean(), + anyInt(), + anyInt(), + anyInt(), + eq(true) + ) verify(mediaDataProcessor).setInactive(eq(KEY), eq(true), eq(true)) } @@ -1053,11 +1081,27 @@ class MediaDataFilterImplTest : SysuiTestCase() { targetId = SMARTSPACE_KEY, isActive = true, packageName = SMARTSPACE_PACKAGE, - recommendations = listOf(smartspaceMediaRecommendationItem), + recommendations = + listOf( + smartspaceMediaRecommendationItem, + smartspaceMediaRecommendationItem, + smartspaceMediaRecommendationItem + ), ) mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, data) - mediaDataFilter.onSwipeToDismiss() + mediaDataFilter.onSwipeToDismiss(1) + + verify(smartspaceLogger) + .logSmartspaceCardUIEvent( + MediaSmartspaceLogger.SMARTSPACE_CARD_DISMISS_EVENT, + SmallHash.hash(data.targetId), + Process.INVALID_UID, + surface = 1, + cardinality = 1, + isRecommendationCard = true, + isSwipeToDismiss = true + ) verify(mediaDataProcessor).setRecommendationInactive(eq(SMARTSPACE_KEY)) verify(mediaDataProcessor, never()) .dismissSmartspaceRecommendation(eq(SMARTSPACE_KEY), anyLong()) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractorKosmos.kt index 81adefa11c9b..6e650a3c5391 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractorKosmos.kt @@ -16,7 +16,6 @@ package com.android.systemui.media.controls.domain.pipeline.interactor -import android.content.applicationContext import com.android.systemui.activityIntentHelper import com.android.systemui.bluetooth.mockBroadcastDialogController import com.android.systemui.kosmos.Kosmos @@ -31,7 +30,6 @@ import com.android.systemui.statusbar.policy.keyguardStateController val Kosmos.mediaControlInteractor by Kosmos.Fixture { MediaControlInteractor( - applicationContext = applicationContext, instanceId = mediaInstanceId, repository = mediaFilterRepository, mediaDataProcessor = mediaDataProcessor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/factory/MediaControlInteractorFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/factory/MediaControlInteractorFactoryKosmos.kt index 461eaa24426f..e490b7502894 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/factory/MediaControlInteractorFactoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/interactor/factory/MediaControlInteractorFactoryKosmos.kt @@ -16,7 +16,6 @@ package com.android.systemui.media.controls.domain.pipeline.interactor.factory -import android.content.applicationContext import com.android.internal.logging.InstanceId import com.android.systemui.activityIntentHelper import com.android.systemui.bluetooth.mockBroadcastDialogController @@ -34,7 +33,6 @@ val Kosmos.mediaControlInteractorFactory by object : MediaControlInteractorFactory { override fun create(instanceId: InstanceId): MediaControlInteractor { return MediaControlInteractor( - applicationContext = applicationContext, instanceId = instanceId, repository = mediaFilterRepository, mediaDataProcessor = mediaDataProcessor, |