Merge "Add logging for media recommendation events" into tm-dev am: f30664e60b
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17621785 Change-Id: Ifdb5b2db1959aa5d21e28980c2579e0e775807bc Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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? {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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!!))
|
||||
}
|
||||
}
|
||||
@@ -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()) }
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user