diff options
10 files changed, 367 insertions, 60 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index a636dc5e6ae1..8e81831a8dc3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt @@ -508,6 +508,11 @@ class MediaCarouselController @Inject constructor( dismissMediaData: Boolean = true, dismissRecommendation: Boolean = true ) { + if (key == MediaPlayerData.smartspaceMediaKey()) { + MediaPlayerData.smartspaceMediaData?.let { + logger.logRecommendationRemoved(it.packageName, it.instanceId) + } + } val removed = MediaPlayerData.removeMediaPlayer(key) removed?.apply { mediaCarouselScrollHandler.onPrePlayerRemoved(removed) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index 8ddec5c7d9f7..c95678311093 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -331,6 +331,7 @@ public class MediaControlPanel { }); mRecommendationViewHolder.getSettings().setOnClickListener(v -> { if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { + mLogger.logLongPressSettings(mUid, mPackageName, mInstanceId); mActivityStarter.startActivity(SETTINGS_INTENT, true /* dismissShade */); } }); @@ -940,6 +941,8 @@ public class MediaControlPanel { mSmartspaceId = SmallHash.hash(data.getTargetId()); mBackgroundColor = data.getBackgroundColor(); + mPackageName = data.getPackageName(); + mInstanceId = data.getInstanceId(); TransitionLayout recommendationCard = mRecommendationViewHolder.getRecommendations(); recommendationCard.setBackgroundTintList(ColorStateList.valueOf(mBackgroundColor)); @@ -1068,6 +1071,7 @@ public class MediaControlPanel { mRecommendationViewHolder.getDismiss().setOnClickListener(v -> { if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return; + mLogger.logLongPressDismiss(mUid, mPackageName, mInstanceId); logSmartspaceCardReported( 761 // SMARTSPACE_CARD_DISMISS ); @@ -1204,6 +1208,11 @@ public class MediaControlPanel { view.setOnClickListener(v -> { if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return; + if (interactedSubcardRank == -1) { + mLogger.logRecommendationCardTap(mPackageName, mInstanceId); + } else { + mLogger.logRecommendationItemTap(mPackageName, mInstanceId, interactedSubcardRank); + } logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT, interactedSubcardRank, getSmartspaceSubCardCardinality()); diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt index 80a407b9d90f..647d3efa5916 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt @@ -60,7 +60,8 @@ class MediaDataFilter @Inject constructor( private val broadcastSender: BroadcastSender, private val lockscreenUserManager: NotificationLockscreenUserManager, @Main private val executor: Executor, - private val systemClock: SystemClock + private val systemClock: SystemClock, + private val logger: MediaUiEventLogger ) : MediaDataManager.Listener { private val userTracker: CurrentUserTracker private val _listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf() @@ -151,6 +152,8 @@ class MediaDataFilter @Inject constructor( Log.d(TAG, "reactivating $lastActiveKey instead of smartspace") reactivatedKey = lastActiveKey val mediaData = sorted.get(lastActiveKey)!!.copy(active = true) + logger.logRecommendationActivated(mediaData.appUid, mediaData.packageName, + mediaData.instanceId) listeners.forEach { it.onMediaDataLoaded(lastActiveKey, lastActiveKey, mediaData, receivedSmartspaceCardLatency = @@ -167,6 +170,8 @@ class MediaDataFilter @Inject constructor( Log.d(TAG, "Invalid recommendation data. Skip showing the rec card") return } + logger.logRecommendationAdded(smartspaceMediaData.packageName, + smartspaceMediaData.instanceId) listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) } } @@ -197,7 +202,9 @@ class MediaDataFilter @Inject constructor( if (smartspaceMediaData.isActive) { smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy( - targetId = smartspaceMediaData.targetId, isValid = smartspaceMediaData.isValid) + targetId = smartspaceMediaData.targetId, + isValid = smartspaceMediaData.isValid, + instanceId = smartspaceMediaData.instanceId) } listeners.forEach { it.onSmartspaceMediaDataRemoved(key, immediately) } } @@ -252,9 +259,12 @@ class MediaDataFilter @Inject constructor( broadcastSender.sendBroadcast(dismissIntent) } smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy( - targetId = smartspaceMediaData.targetId, isValid = smartspaceMediaData.isValid) + targetId = smartspaceMediaData.targetId, + isValid = smartspaceMediaData.isValid, + instanceId = smartspaceMediaData.instanceId) + mediaDataManager.dismissSmartspaceRecommendation(smartspaceMediaData.targetId, + delay = 0L) } - mediaDataManager.dismissSmartspaceRecommendation(smartspaceMediaData.targetId, delay = 0L) } /** diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index 9a2977d56153..0ad15facee66 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -103,8 +103,17 @@ private val LOADING = MediaData( appUid = Process.INVALID_UID) @VisibleForTesting -internal val EMPTY_SMARTSPACE_MEDIA_DATA = SmartspaceMediaData("INVALID", false, false, - "INVALID", null, emptyList(), null, 0, 0) +internal val EMPTY_SMARTSPACE_MEDIA_DATA = SmartspaceMediaData( + targetId = "INVALID", + isActive = false, + isValid = false, + packageName = "INVALID", + cardAction = null, + recommendations = emptyList(), + dismissIntent = null, + backgroundColor = 0, + headphoneConnectionTimeMillis = 0, + instanceId = InstanceId.fakeInstanceId(-1)) fun isMediaNotification(sbn: StatusBarNotification): Boolean { return sbn.notification.isMediaNotification() @@ -532,14 +541,16 @@ class MediaDataManager( * connection session. */ fun dismissSmartspaceRecommendation(key: String, delay: Long) { - if (smartspaceMediaData.targetId != key) { + if (smartspaceMediaData.targetId != key || !smartspaceMediaData.isValid) { + // If this doesn't match, or we've already invalidated the data, no action needed return } if (DEBUG) Log.d(TAG, "Dismissing Smartspace media target") if (smartspaceMediaData.isActive) { smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy( - targetId = smartspaceMediaData.targetId) + targetId = smartspaceMediaData.targetId, + instanceId = smartspaceMediaData.instanceId) } foregroundExecutor.executeDelayed( { notifySmartspaceMediaDataRemoved( @@ -1035,7 +1046,8 @@ class MediaDataManager( Log.d(TAG, "Set Smartspace media to be inactive for the data update") } smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy( - targetId = smartspaceMediaData.targetId) + targetId = smartspaceMediaData.targetId, + instanceId = smartspaceMediaData.instanceId) notifySmartspaceMediaDataRemoved(smartspaceMediaData.targetId, immediately = false) } 1 -> { @@ -1214,15 +1226,24 @@ class MediaDataManager( .getParcelable(EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY) as Intent? } packageName(target)?.let { - return SmartspaceMediaData(target.smartspaceTargetId, isActive, true, it, - target.baseAction, target.iconGrid, - dismissIntent, 0, target.creationTimeMillis) + return SmartspaceMediaData( + targetId = target.smartspaceTargetId, + isActive = isActive, + isValid = true, + packageName = it, + cardAction = target.baseAction, + recommendations = target.iconGrid, + dismissIntent = dismissIntent, + backgroundColor = 0, + headphoneConnectionTimeMillis = target.creationTimeMillis, + instanceId = logger.getNewInstanceId()) } return EMPTY_SMARTSPACE_MEDIA_DATA .copy(targetId = target.smartspaceTargetId, isActive = isActive, dismissIntent = dismissIntent, - headphoneConnectionTimeMillis = target.creationTimeMillis) + headphoneConnectionTimeMillis = target.creationTimeMillis, + instanceId = logger.getNewInstanceId()) } private fun packageName(target: SmartspaceTarget): String? { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt b/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt index 3eba3b55b7e8..52f5cc568ba4 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt @@ -151,6 +151,31 @@ class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger) } logger.log(event) } + + fun logRecommendationAdded(packageName: String, instanceId: InstanceId) { + logger.logWithInstanceId(MediaUiEvent.MEDIA_RECOMMENDATION_ADDED, 0, packageName, + instanceId) + } + + fun logRecommendationRemoved(packageName: String, instanceId: InstanceId) { + logger.logWithInstanceId(MediaUiEvent.MEDIA_RECOMMENDATION_REMOVED, 0, packageName, + instanceId) + } + + fun logRecommendationActivated(uid: Int, packageName: String, instanceId: InstanceId) { + logger.logWithInstanceId(MediaUiEvent.MEDIA_RECOMMENDATION_ACTIVATED, uid, packageName, + instanceId) + } + + fun logRecommendationItemTap(packageName: String, instanceId: InstanceId, position: Int) { + logger.logWithInstanceIdAndPosition(MediaUiEvent.MEDIA_RECOMMENDATION_ITEM_TAP, 0, + packageName, instanceId, position) + } + + fun logRecommendationCardTap(packageName: String, instanceId: InstanceId) { + logger.logWithInstanceId(MediaUiEvent.MEDIA_RECOMMENDATION_CARD_TAP, 0, packageName, + instanceId) + } } enum class MediaUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum { @@ -233,7 +258,22 @@ enum class MediaUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum { MEDIA_CAROUSEL_LOCATION_LOCKSCREEN(1039), @UiEvent(doc = "The media carousel moved to the dream state") - MEDIA_CAROUSEL_LOCATION_DREAM(1040); + MEDIA_CAROUSEL_LOCATION_DREAM(1040), + + @UiEvent(doc = "A media recommendation card was added to the media carousel") + MEDIA_RECOMMENDATION_ADDED(1041), + + @UiEvent(doc = "A media recommendation card was removed from the media carousel") + MEDIA_RECOMMENDATION_REMOVED(1042), + + @UiEvent(doc = "An existing media control was made active as a recommendation") + MEDIA_RECOMMENDATION_ACTIVATED(1043), + + @UiEvent(doc = "User tapped on an item in a media recommendation card") + MEDIA_RECOMMENDATION_ITEM_TAP(1044), + + @UiEvent(doc = "User tapped on a media recommendation card") + MEDIA_RECOMMENDATION_CARD_TAP(1045); override fun getId() = metricId }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt index 066a6fbfa8be..e161ea7a9c68 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt @@ -18,6 +18,7 @@ package com.android.systemui.media import android.app.smartspace.SmartspaceAction import android.content.Intent +import com.android.internal.logging.InstanceId /** State of a Smartspace media recommendations view. */ data class SmartspaceMediaData( @@ -56,5 +57,9 @@ data class SmartspaceMediaData( /** * The timestamp in milliseconds that headphone is connected. */ - val headphoneConnectionTimeMillis: Long + val headphoneConnectionTimeMillis: Long, + /** + * Instance ID for [MediaUiEventLogger] + */ + val instanceId: InstanceId ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt index 67fe0445e225..1522ee8cd6d5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.media import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest +import com.android.internal.logging.InstanceId import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.dagger.qualifiers.Main @@ -28,6 +29,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.FakeSystemClock import junit.framework.Assert.assertEquals import junit.framework.Assert.assertTrue @@ -225,4 +227,19 @@ class MediaCarouselControllerTest : SysuiTestCase() { animate = false) verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_DREAM_OVERLAY) } + + @Test + fun testRecommendationRemoved_logged() { + val packageName = "smartspace package" + val instanceId = InstanceId.fakeInstanceId(123) + + val smartspaceData = EMPTY_SMARTSPACE_MEDIA_DATA.copy( + packageName = packageName, + instanceId = instanceId + ) + MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, smartspaceData, panel, true, clock) + mediaCarouselController.removePlayer(SMARTSPACE_KEY) + + verify(logger).logRecommendationRemoved(eq(packageName), eq(instanceId!!)) + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt index 14db4bf81e14..a58a28e3920b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -17,16 +17,22 @@ package com.android.systemui.media import android.app.PendingIntent +import android.app.smartspace.SmartspaceAction +import android.content.Context import org.mockito.Mockito.`when` as whenever import android.content.Intent +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager import android.graphics.Color import android.graphics.drawable.Animatable2 import android.graphics.drawable.AnimatedVectorDrawable import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.Icon import android.graphics.drawable.RippleDrawable import android.media.MediaMetadata import android.media.session.MediaSession import android.media.session.PlaybackState +import android.os.Bundle import android.os.Handler import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS import android.testing.AndroidTestingRunner @@ -67,6 +73,7 @@ import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyLong import org.mockito.Mock import org.mockito.Mockito.any +import org.mockito.Mockito.anyString import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.reset @@ -107,6 +114,7 @@ public class MediaControlPanelTest : SysuiTestCase() { @Mock private lateinit var collapsedSet: ConstraintSet @Mock private lateinit var mediaOutputDialogFactory: MediaOutputDialogFactory @Mock private lateinit var mediaCarouselController: MediaCarouselController + @Mock private lateinit var mediaCarouselScrollHandler: MediaCarouselScrollHandler @Mock private lateinit var falsingManager: FalsingManager @Mock private lateinit var transitionParent: ViewGroup private lateinit var appIcon: ImageView @@ -145,6 +153,14 @@ public class MediaControlPanelTest : SysuiTestCase() { private val clock = FakeSystemClock() @Mock private lateinit var logger: MediaUiEventLogger @Mock private lateinit var instanceId: InstanceId + @Mock private lateinit var packageManager: PackageManager + @Mock private lateinit var applicationInfo: ApplicationInfo + + @Mock private lateinit var recommendationViewHolder: RecommendationViewHolder + @Mock private lateinit var smartspaceAction: SmartspaceAction + private lateinit var smartspaceData: SmartspaceMediaData + @Mock private lateinit var coverContainer: ViewGroup + private lateinit var coverItem: ImageView @JvmField @Rule val mockito = MockitoJUnit.rule() @@ -155,6 +171,16 @@ public class MediaControlPanelTest : SysuiTestCase() { whenever(mediaViewController.expandedLayout).thenReturn(expandedSet) whenever(mediaViewController.collapsedLayout).thenReturn(collapsedSet) + // Set up package manager mocks + val icon = context.getDrawable(R.drawable.ic_android) + whenever(packageManager.getApplicationIcon(anyString())).thenReturn(icon) + whenever(packageManager.getApplicationIcon(any(ApplicationInfo::class.java))) + .thenReturn(icon) + whenever(packageManager.getApplicationInfo(eq(PACKAGE), anyInt())) + .thenReturn(applicationInfo) + whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE) + context.setMockPackageManager(packageManager) + player = MediaControlPanel( context, bgExecutor, @@ -170,7 +196,60 @@ public class MediaControlPanelTest : SysuiTestCase() { clock, logger ) + + initMediaViewHolderMocks() + + // Create media session + val metadataBuilder = MediaMetadata.Builder().apply { + putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST) + putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE) + } + val playbackBuilder = PlaybackState.Builder().apply { + setState(PlaybackState.STATE_PAUSED, 6000L, 1f) + setActions(PlaybackState.ACTION_PLAY) + } + session = MediaSession(context, SESSION_KEY).apply { + setMetadata(metadataBuilder.build()) + setPlaybackState(playbackBuilder.build()) + } + session.setActive(true) + + mediaData = MediaTestUtils.emptyMediaData.copy( + backgroundColor = BG_COLOR, + artist = ARTIST, + song = TITLE, + packageName = PACKAGE, + token = session.sessionToken, + device = device, + instanceId = instanceId) + + // Set up recommendation view + initRecommendationViewHolderMocks() + + // Set valid recommendation data + val extras = Bundle() + val intent = Intent().apply { + putExtras(extras) + setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + whenever(smartspaceAction.intent).thenReturn(intent) + whenever(smartspaceAction.extras).thenReturn(extras) + smartspaceData = EMPTY_SMARTSPACE_MEDIA_DATA.copy( + packageName = PACKAGE, + instanceId = instanceId, + recommendations = listOf(smartspaceAction), + cardAction = smartspaceAction + ) + } + + /** + * Initialize elements in media view holder + */ + private fun initMediaViewHolderMocks() { whenever(seekBarViewModel.progress).thenReturn(seekBarData) + whenever(mediaCarouselController.mediaCarouselScrollHandler) + .thenReturn(mediaCarouselScrollHandler) + whenever(mediaCarouselScrollHandler.qsExpanded).thenReturn(false) // Set up mock views for the players appIcon = ImageView(context) @@ -218,37 +297,6 @@ public class MediaControlPanelTest : SysuiTestCase() { action4.id) } - initMediaViewHolderMocks() - - // Create media session - val metadataBuilder = MediaMetadata.Builder().apply { - putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST) - putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE) - } - val playbackBuilder = PlaybackState.Builder().apply { - setState(PlaybackState.STATE_PAUSED, 6000L, 1f) - setActions(PlaybackState.ACTION_PLAY) - } - session = MediaSession(context, SESSION_KEY).apply { - setMetadata(metadataBuilder.build()) - setPlaybackState(playbackBuilder.build()) - } - session.setActive(true) - - mediaData = MediaTestUtils.emptyMediaData.copy( - backgroundColor = BG_COLOR, - artist = ARTIST, - song = TITLE, - packageName = PACKAGE, - token = session.sessionToken, - device = device, - instanceId = instanceId) - } - - /** - * Initialize elements in media view holder - */ - private fun initMediaViewHolderMocks() { whenever(viewHolder.player).thenReturn(view) whenever(viewHolder.appIcon).thenReturn(appIcon) whenever(viewHolder.albumView).thenReturn(albumView) @@ -297,6 +345,38 @@ public class MediaControlPanelTest : SysuiTestCase() { whenever(viewHolder.actionsTopBarrier).thenReturn(actionsTopBarrier) } + /** + * Initialize elements for the recommendation view holder + */ + private fun initRecommendationViewHolderMocks() { + whenever(recommendationViewHolder.recommendations).thenReturn(view) + whenever(recommendationViewHolder.cardIcon).thenReturn(appIcon) + whenever(recommendationViewHolder.cardText).thenReturn(titleText) + + // Add a recommendation item + coverItem = ImageView(context).also { it.setId(R.id.media_cover1) } + whenever(coverContainer.id).thenReturn(R.id.media_cover1_container) + whenever(recommendationViewHolder.mediaCoverItems).thenReturn(listOf(coverItem)) + whenever(recommendationViewHolder.mediaCoverContainers).thenReturn(listOf(coverContainer)) + whenever(recommendationViewHolder.mediaCoverItemsResIds) + .thenReturn(listOf(R.id.media_cover1)) + whenever(recommendationViewHolder.mediaCoverContainersResIds) + .thenReturn(listOf(R.id.media_cover1_container)) + + // Long press menu + whenever(recommendationViewHolder.settings).thenReturn(settings) + whenever(recommendationViewHolder.cancel).thenReturn(cancel) + whenever(recommendationViewHolder.dismiss).thenReturn(dismiss) + + val actionIcon = Icon.createWithResource(context, R.drawable.ic_android) + whenever(smartspaceAction.icon).thenReturn(actionIcon) + + // Needed for card and item action click + val mockContext = mock(Context::class.java) + whenever(view.context).thenReturn(mockContext) + whenever(coverContainer.context).thenReturn(mockContext) + } + @After fun tearDown() { session.release() @@ -923,6 +1003,67 @@ public class MediaControlPanelTest : SysuiTestCase() { verify(logger).logSeek(anyInt(), eq(PACKAGE), eq(instanceId)) } + @Test + fun recommendation_gutsClosed_longPressOpens() { + player.attachRecommendation(recommendationViewHolder) + player.bindRecommendation(smartspaceData) + whenever(mediaViewController.isGutsVisible).thenReturn(false) + + val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java) + verify(recommendationViewHolder.recommendations).setOnLongClickListener(captor.capture()) + + captor.value.onLongClick(recommendationViewHolder.recommendations) + verify(mediaViewController).openGuts() + verify(logger).logLongPressOpen(anyInt(), eq(PACKAGE), eq(instanceId)) + } + + @Test + fun recommendation_settingsButtonClick_isLogged() { + player.attachRecommendation(recommendationViewHolder) + player.bindRecommendation(smartspaceData) + + settings.callOnClick() + verify(logger).logLongPressSettings(anyInt(), eq(PACKAGE), eq(instanceId)) + + val captor = ArgumentCaptor.forClass(Intent::class.java) + verify(activityStarter).startActivity(captor.capture(), eq(true)) + + assertThat(captor.value.action).isEqualTo(ACTION_MEDIA_CONTROLS_SETTINGS) + } + + @Test + fun recommendation_dismissButton_isLogged() { + player.attachRecommendation(recommendationViewHolder) + player.bindRecommendation(smartspaceData) + + dismiss.callOnClick() + verify(logger).logLongPressDismiss(anyInt(), eq(PACKAGE), eq(instanceId)) + } + + @Test + fun recommendation_tapOnCard_isLogged() { + val captor = ArgumentCaptor.forClass(View.OnClickListener::class.java) + player.attachRecommendation(recommendationViewHolder) + player.bindRecommendation(smartspaceData) + + verify(recommendationViewHolder.recommendations).setOnClickListener(captor.capture()) + captor.value.onClick(recommendationViewHolder.recommendations) + + verify(logger).logRecommendationCardTap(eq(PACKAGE), eq(instanceId)) + } + + @Test + fun recommendation_tapOnItem_isLogged() { + val captor = ArgumentCaptor.forClass(View.OnClickListener::class.java) + player.attachRecommendation(recommendationViewHolder) + player.bindRecommendation(smartspaceData) + + verify(coverContainer).setOnClickListener(captor.capture()) + captor.value.onClick(recommendationViewHolder.recommendations) + + verify(logger).logRecommendationItemTap(eq(PACKAGE), eq(instanceId), eq(0)) + } + private fun getScrubbingChangeListener(): SeekBarViewModel.ScrubbingChangeListener = withArgCaptor { verify(seekBarViewModel).setScrubbingChangeListener(capture()) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt index 3b996d4a37d3..1f9490ab3851 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt @@ -20,6 +20,7 @@ import android.app.smartspace.SmartspaceAction import androidx.test.filters.SmallTest import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import com.android.internal.logging.InstanceId import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.broadcast.BroadcastSender @@ -46,7 +47,11 @@ private const val KEY_ALT = "TEST_KEY_2" private const val USER_MAIN = 0 private const val USER_GUEST = 10 private const val PACKAGE = "PKG" +private val INSTANCE_ID = InstanceId.fakeInstanceId(123)!! +private const val APP_UID = 99 private const val SMARTSPACE_KEY = "SMARTSPACE_KEY" +private const val SMARTSPACE_PACKAGE = "SMARTSPACE_PKG" +private val SMARTSPACE_INSTANCE_ID = InstanceId.fakeInstanceId(456)!! @SmallTest @RunWith(AndroidTestingRunner::class) @@ -69,6 +74,8 @@ class MediaDataFilterTest : SysuiTestCase() { private lateinit var smartspaceData: SmartspaceMediaData @Mock private lateinit var smartspaceMediaRecommendationItem: SmartspaceAction + @Mock + private lateinit var logger: MediaUiEventLogger private lateinit var mediaDataFilter: MediaDataFilter private lateinit var dataMain: MediaData @@ -79,8 +86,14 @@ class MediaDataFilterTest : SysuiTestCase() { fun setup() { MockitoAnnotations.initMocks(this) MediaPlayerData.clear() - mediaDataFilter = MediaDataFilter(context, broadcastDispatcher, broadcastSender, - lockscreenUserManager, executor, clock) + mediaDataFilter = MediaDataFilter( + context, + broadcastDispatcher, + broadcastSender, + lockscreenUserManager, + executor, + clock, + logger) mediaDataFilter.mediaDataManager = mediaDataManager mediaDataFilter.addListener(listener) @@ -90,16 +103,19 @@ class MediaDataFilterTest : SysuiTestCase() { // Set up test media data dataMain = MediaTestUtils.emptyMediaData.copy( userId = USER_MAIN, - packageName = PACKAGE) + packageName = PACKAGE, + instanceId = INSTANCE_ID, + appUid = APP_UID) dataGuest = dataMain.copy(userId = USER_GUEST) `when`(smartspaceData.targetId).thenReturn(SMARTSPACE_KEY) `when`(smartspaceData.isActive).thenReturn(true) `when`(smartspaceData.isValid).thenReturn(true) - `when`(smartspaceData.packageName).thenReturn(PACKAGE) + `when`(smartspaceData.packageName).thenReturn(SMARTSPACE_PACKAGE) `when`(smartspaceData.recommendations).thenReturn(listOf(smartspaceMediaRecommendationItem)) `when`(smartspaceData.headphoneConnectionTimeMillis).thenReturn( clock.currentTimeMillis() - 100) + `when`(smartspaceData.instanceId).thenReturn(SMARTSPACE_INSTANCE_ID) } private fun setUser(id: Int) { @@ -241,6 +257,8 @@ class MediaDataFilterTest : SysuiTestCase() { .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true)) assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue() assertThat(mediaDataFilter.hasActiveMedia()).isFalse() + verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID) + verify(logger, never()).logRecommendationActivated(any(), any(), any()) } @Test @@ -254,6 +272,8 @@ class MediaDataFilterTest : SysuiTestCase() { verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean()) assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse() assertThat(mediaDataFilter.hasActiveMedia()).isFalse() + verify(logger, never()).logRecommendationAdded(any(), any()) + verify(logger, never()).logRecommendationActivated(any(), any(), any()) } @Test @@ -267,6 +287,8 @@ class MediaDataFilterTest : SysuiTestCase() { .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true)) assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue() assertThat(mediaDataFilter.hasActiveMedia()).isFalse() + verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID) + verify(logger, never()).logRecommendationActivated(any(), any(), any()) } @Test @@ -281,6 +303,8 @@ class MediaDataFilterTest : SysuiTestCase() { verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean()) assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse() assertThat(mediaDataFilter.hasActiveMedia()).isFalse() + verify(logger, never()).logRecommendationAdded(any(), any()) + verify(logger, never()).logRecommendationActivated(any(), any(), any()) } @Test @@ -302,6 +326,8 @@ class MediaDataFilterTest : SysuiTestCase() { verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean()) assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse() assertThat(mediaDataFilter.hasActiveMedia()).isFalse() + verify(logger, never()).logRecommendationAdded(any(), any()) + verify(logger, never()).logRecommendationActivated(any(), any(), any()) } @Test @@ -324,6 +350,8 @@ class MediaDataFilterTest : SysuiTestCase() { assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse() // Smartspace update shouldn't be propagated for the empty rec list. verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean()) + verify(logger, never()).logRecommendationAdded(any(), any()) + verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID)) } @Test @@ -345,6 +373,8 @@ class MediaDataFilterTest : SysuiTestCase() { // Smartspace update should also be propagated but not prioritized. verify(listener) .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) + verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID) + verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID)) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt index 68eebede1482..858249960a6e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt @@ -19,6 +19,7 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.media.utils.MediaConstants import androidx.test.filters.SmallTest +import com.android.internal.logging.InstanceId import com.android.systemui.InstanceIdSequenceFake import com.android.systemui.R import com.android.systemui.SysuiTestCase @@ -47,6 +48,7 @@ import org.mockito.Mockito import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.junit.MockitoJUnit import org.mockito.Mockito.`when` as whenever @@ -452,11 +454,22 @@ class MediaDataManagerTest : SysuiTestCase() { @Test fun testOnSmartspaceMediaDataLoaded_hasNewValidMediaTarget_callsListener() { smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget)) + verify(logger).getNewInstanceId() + val instanceId = instanceIdSequence.lastInstanceId + verify(listener).onSmartspaceMediaDataLoaded( eq(KEY_MEDIA_SMARTSPACE), - eq(SmartspaceMediaData(KEY_MEDIA_SMARTSPACE, true /* isActive */, true /*isValid */, - PACKAGE_NAME, mediaSmartspaceBaseAction, listOf(mediaRecommendationItem), - DISMISS_INTENT, 0, 1234L)), + eq(SmartspaceMediaData( + targetId = KEY_MEDIA_SMARTSPACE, + isActive = true, + isValid = true, + packageName = PACKAGE_NAME, + cardAction = mediaSmartspaceBaseAction, + recommendations = listOf(mediaRecommendationItem), + dismissIntent = DISMISS_INTENT, + backgroundColor = 0, + headphoneConnectionTimeMillis = 1234L, + instanceId = InstanceId.fakeInstanceId(instanceId))), eq(false)) } @@ -464,12 +477,18 @@ class MediaDataManagerTest : SysuiTestCase() { fun testOnSmartspaceMediaDataLoaded_hasNewInvalidMediaTarget_callsListener() { whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf()) smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget)) + verify(logger).getNewInstanceId() + val instanceId = instanceIdSequence.lastInstanceId + verify(listener).onSmartspaceMediaDataLoaded( eq(KEY_MEDIA_SMARTSPACE), - eq(EMPTY_SMARTSPACE_MEDIA_DATA - .copy(targetId = KEY_MEDIA_SMARTSPACE, isActive = true, - isValid = false, dismissIntent = DISMISS_INTENT, - headphoneConnectionTimeMillis = 1234L)), + eq(EMPTY_SMARTSPACE_MEDIA_DATA.copy( + targetId = KEY_MEDIA_SMARTSPACE, + isActive = true, + isValid = false, + dismissIntent = DISMISS_INTENT, + headphoneConnectionTimeMillis = 1234L, + instanceId = InstanceId.fakeInstanceId(instanceId))), eq(false)) } @@ -484,18 +503,25 @@ class MediaDataManagerTest : SysuiTestCase() { whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf()) smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget)) + verify(logger).getNewInstanceId() + val instanceId = instanceIdSequence.lastInstanceId verify(listener).onSmartspaceMediaDataLoaded( eq(KEY_MEDIA_SMARTSPACE), - eq(EMPTY_SMARTSPACE_MEDIA_DATA - .copy(targetId = KEY_MEDIA_SMARTSPACE, isActive = true, - isValid = false, dismissIntent = null, headphoneConnectionTimeMillis = 1234L)), + eq(EMPTY_SMARTSPACE_MEDIA_DATA.copy( + targetId = KEY_MEDIA_SMARTSPACE, + isActive = true, + isValid = false, + dismissIntent = null, + headphoneConnectionTimeMillis = 1234L, + instanceId = InstanceId.fakeInstanceId(instanceId))), eq(false)) } @Test fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_notCallsListener() { smartspaceMediaDataProvider.onTargetsAvailable(listOf()) + verify(logger, never()).getNewInstanceId() verify(listener, never()) .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean()) } @@ -503,11 +529,14 @@ class MediaDataManagerTest : SysuiTestCase() { @Test fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_callsRemoveListener() { smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget)) + verify(logger).getNewInstanceId() + smartspaceMediaDataProvider.onTargetsAvailable(listOf()) foregroundExecutor.advanceClockToLast() foregroundExecutor.runAllReady() verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false)) + verifyNoMoreInteractions(logger) } @Test |