summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Beth Thibodeau <ethibodeau@google.com> 2022-03-28 22:16:28 -0400
committer Beth Thibodeau <ethibodeau@google.com> 2022-03-30 19:27:45 -0400
commit21169ec3f309c73a5643aca91fb7756dedbbdab3 (patch)
tree12f5d77501975e1276cc64b04da45493de9ab349
parent90f2f63ca556ea488eeb3b3b46a4edb746f35423 (diff)
Add logging for media controls
Adds logging for interactions and events with the media controls and carousel, with instance IDs for events relating to individual controls Bug: 216488338 Test: atest Change-Id: I9bb312c572ce7c1a0817595444279afc8d5cfb6b
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java41
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaData.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt90
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt207
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt37
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt174
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt33
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt167
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt27
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt8
19 files changed, 691 insertions, 273 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 20029fec4dd1..d4e2214f8b38 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -12,6 +12,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.annotation.VisibleForTesting
+import com.android.internal.logging.InstanceId
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.classifier.FalsingCollector
@@ -59,7 +60,7 @@ class MediaCarouselController @Inject constructor(
falsingCollector: FalsingCollector,
falsingManager: FalsingManager,
dumpManager: DumpManager,
- private val mediaFlags: MediaFlags
+ private val logger: MediaUiEventLogger
) : Dumpable {
/**
* The current width of the carousel
@@ -119,7 +120,9 @@ class MediaCarouselController @Inject constructor(
private val mediaCarousel: MediaScrollView
val mediaCarouselScrollHandler: MediaCarouselScrollHandler
val mediaFrame: ViewGroup
- private lateinit var settingsButton: View
+ @VisibleForTesting
+ lateinit var settingsButton: View
+ private set
private val mediaContent: ViewGroup
private val pageIndicator: PageIndicator
private val visualStabilityCallback: OnReorderingAllowedListener
@@ -183,7 +186,8 @@ class MediaCarouselController @Inject constructor(
pageIndicator = mediaFrame.requireViewById(R.id.media_page_indicator)
mediaCarouselScrollHandler = MediaCarouselScrollHandler(mediaCarousel, pageIndicator,
executor, this::onSwipeToDismiss, this::updatePageIndicatorLocation,
- this::closeGuts, falsingCollector, falsingManager, this::logSmartspaceImpression)
+ this::closeGuts, falsingCollector, falsingManager, this::logSmartspaceImpression,
+ logger)
isRtl = context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
inflateSettingsButton()
mediaContent = mediaCarousel.requireViewById(R.id.media_carousel)
@@ -220,7 +224,7 @@ class MediaCarouselController @Inject constructor(
MediaPlayerData.getMediaPlayer(key)?.let {
/* ktlint-disable max-line-length */
logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
- it.mInstanceId,
+ it.mSmartspaceId,
it.mUid,
/* isRecommendationCard */ false,
intArrayOf(
@@ -239,12 +243,12 @@ class MediaCarouselController @Inject constructor(
// resume card is ranked first
MediaPlayerData.players().forEachIndexed { index, it ->
if (it.recommendationViewHolder == null) {
- it.mInstanceId = SmallHash.hash(it.mUid +
+ it.mSmartspaceId = SmallHash.hash(it.mUid +
systemClock.currentTimeMillis().toInt())
it.mIsImpressed = false
/* ktlint-disable max-line-length */
logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
- it.mInstanceId,
+ it.mSmartspaceId,
it.mUid,
/* isRecommendationCard */ false,
intArrayOf(
@@ -291,12 +295,12 @@ class MediaCarouselController @Inject constructor(
// recommendation card is valid and ranked first
MediaPlayerData.players().forEachIndexed { index, it ->
if (it.recommendationViewHolder == null) {
- it.mInstanceId = SmallHash.hash(it.mUid +
+ it.mSmartspaceId = SmallHash.hash(it.mUid +
systemClock.currentTimeMillis().toInt())
it.mIsImpressed = false
/* ktlint-disable max-line-length */
logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
- it.mInstanceId,
+ it.mSmartspaceId,
it.mUid,
/* isRecommendationCard */ false,
intArrayOf(
@@ -312,7 +316,7 @@ class MediaCarouselController @Inject constructor(
MediaPlayerData.getMediaPlayer(key)?.let {
/* ktlint-disable max-line-length */
logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
- it.mInstanceId,
+ it.mSmartspaceId,
it.mUid,
/* isRecommendationCard */ true,
intArrayOf(
@@ -369,6 +373,7 @@ class MediaCarouselController @Inject constructor(
mediaFrame.addView(settingsButton)
mediaCarouselScrollHandler.onSettingsButtonUpdated(settings)
settingsButton.setOnClickListener {
+ logger.logCarouselSettings()
activityStarter.startActivity(settingsIntent, true /* dismissShade */)
}
}
@@ -752,7 +757,7 @@ class MediaCarouselController @Inject constructor(
return
}
logSmartspaceCardReported(800, // SMARTSPACE_CARD_SEEN
- mediaControlPanel.mInstanceId,
+ mediaControlPanel.mSmartspaceId,
mediaControlPanel.mUid,
isRecommendationCard,
intArrayOf(mediaControlPanel.surfaceForSmartspaceLogging))
@@ -836,7 +841,7 @@ class MediaCarouselController @Inject constructor(
index, it ->
if (it.mIsImpressed) {
logSmartspaceCardReported(SMARTSPACE_CARD_DISMISS_EVENT,
- it.mInstanceId,
+ it.mSmartspaceId,
it.mUid,
it.recommendationViewHolder != null,
intArrayOf(it.surfaceForSmartspaceLogging),
@@ -846,6 +851,7 @@ class MediaCarouselController @Inject constructor(
it.mIsImpressed = false
}
}
+ logger.logSwipeDismiss()
mediaManager.onSwipeToDismiss()
}
@@ -881,7 +887,9 @@ internal object MediaPlayerData {
clickIntent = null,
device = null,
active = true,
- resumeAction = null)
+ resumeAction = null,
+ instanceId = InstanceId.fakeInstanceId(-1),
+ appUid = -1)
// Whether should prioritize Smartspace card.
internal var shouldPrioritizeSs: Boolean = false
private set
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
index 5dc4bcfa92ff..ef49fd35d703 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
@@ -57,12 +57,13 @@ class MediaCarouselScrollHandler(
private val scrollView: MediaScrollView,
private val pageIndicator: PageIndicator,
private val mainExecutor: DelayableExecutor,
- private val dismissCallback: () -> Unit,
+ val dismissCallback: () -> Unit,
private var translationChangedListener: () -> Unit,
private val closeGuts: (immediate: Boolean) -> Unit,
private val falsingCollector: FalsingCollector,
private val falsingManager: FalsingManager,
- private val logSmartspaceImpression: (Boolean) -> Unit
+ private val logSmartspaceImpression: (Boolean) -> Unit,
+ private val logger: MediaUiEventLogger
) {
/**
* Is the view in RTL
@@ -476,6 +477,7 @@ class MediaCarouselScrollHandler(
visibleMediaIndex = newIndex
if (oldIndex != visibleMediaIndex && visibleToUser) {
logSmartspaceImpression(qsExpanded)
+ logger.logMediaCarouselPage(newIndex)
}
closeGuts(false)
updatePlayerVisibilities()
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index b815f0c447d0..7ac70bd78953 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -53,6 +53,7 @@ import androidx.annotation.UiThread;
import androidx.constraintlayout.widget.ConstraintSet;
import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.logging.InstanceId;
import com.android.settingslib.widget.AdaptiveIcon;
import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
@@ -131,8 +132,6 @@ public class MediaControlPanel {
private MediaController mController;
private Lazy<MediaDataManager> mMediaDataManagerLazy;
private int mBackgroundColor;
- // Instance id for logging purpose.
- protected int mInstanceId = -1;
// Uid for the media app.
protected int mUid = Process.INVALID_UID;
private int mSmartspaceMediaItemsCount;
@@ -140,9 +139,13 @@ public class MediaControlPanel {
private final MediaOutputDialogFactory mMediaOutputDialogFactory;
private final FalsingManager mFalsingManager;
- // Used for swipe-to-dismiss logging.
+ // Used for logging.
protected boolean mIsImpressed = false;
private SystemClock mSystemClock;
+ private MediaUiEventLogger mLogger;
+ private InstanceId mInstanceId;
+ protected int mSmartspaceId = -1;
+ private String mPackageName;
/**
* Initialize a new control panel
@@ -157,7 +160,7 @@ public class MediaControlPanel {
Lazy<MediaDataManager> lazyMediaDataManager,
MediaOutputDialogFactory mediaOutputDialogFactory,
MediaCarouselController mediaCarouselController,
- FalsingManager falsingManager, SystemClock systemClock) {
+ FalsingManager falsingManager, SystemClock systemClock, MediaUiEventLogger logger) {
mContext = context;
mBackgroundExecutor = backgroundExecutor;
mActivityStarter = activityStarter;
@@ -169,8 +172,12 @@ public class MediaControlPanel {
mMediaCarouselController = mediaCarouselController;
mFalsingManager = falsingManager;
mSystemClock = systemClock;
+ mLogger = logger;
- mSeekBarViewModel.setLogSmartspaceClick(() -> {
+ mSeekBarViewModel.setLogSeek(() -> {
+ if (mPackageName != null && mInstanceId != null) {
+ mLogger.logSeek(mUid, mPackageName, mInstanceId);
+ }
logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT,
/* isRecommendationCard */ false);
return Unit.INSTANCE;
@@ -261,6 +268,7 @@ public class MediaControlPanel {
});
vh.getSettings().setOnClickListener(v -> {
if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ mLogger.logLongPressSettings(mUid, mPackageName, mInstanceId);
mActivityStarter.startActivity(SETTINGS_INTENT, true /* dismissShade */);
}
});
@@ -301,16 +309,13 @@ public class MediaControlPanel {
}
mKey = key;
MediaSession.Token token = data.getToken();
- PackageManager packageManager = mContext.getPackageManager();
- try {
- mUid = packageManager.getApplicationInfo(data.getPackageName(), 0 /* flags */).uid;
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Unable to look up package name", e);
- }
+ mPackageName = data.getPackageName();
+ mUid = data.getAppUid();
// Only assigns instance id if it's unassigned.
- if (mInstanceId == -1) {
- mInstanceId = SmallHash.hash(mUid + (int) mSystemClock.currentTimeMillis());
+ if (mSmartspaceId == -1) {
+ mSmartspaceId = SmallHash.hash(mUid + (int) mSystemClock.currentTimeMillis());
}
+ mInstanceId = data.getInstanceId();
mBackgroundColor = data.getBackgroundColor();
if (mToken == null || !mToken.equals(token)) {
@@ -401,6 +406,7 @@ public class MediaControlPanel {
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return;
}
+ mLogger.logOpenOutputSwitcher(mUid, mPackageName, mInstanceId);
if (device.getIntent() != null) {
if (device.getIntent().isActivity()) {
mActivityStarter.startActivity(
@@ -413,7 +419,7 @@ public class MediaControlPanel {
}
}
} else {
- mMediaOutputDialogFactory.create(data.getPackageName(), true,
+ mMediaOutputDialogFactory.create(mPackageName, true,
mMediaViewHolder.getSeamlessButton());
}
});
@@ -437,6 +443,7 @@ public class MediaControlPanel {
logSmartspaceCardReported(SMARTSPACE_CARD_DISMISS_EVENT,
/* isRecommendationCard */ false);
+ mLogger.logLongPressDismiss(mUid, mPackageName, mInstanceId);
if (mKey != null) {
closeGuts();
@@ -675,6 +682,7 @@ public class MediaControlPanel {
button.setEnabled(true);
button.setOnClickListener(v -> {
if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ mLogger.logTapAction(button.getId(), mUid, mPackageName, mInstanceId);
logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT,
/* isRecommendationCard */ false);
action.run();
@@ -794,7 +802,7 @@ public class MediaControlPanel {
return;
}
- mInstanceId = SmallHash.hash(data.getTargetId());
+ mSmartspaceId = SmallHash.hash(data.getTargetId());
mBackgroundColor = data.getBackgroundColor();
TransitionLayout recommendationCard = mRecommendationViewHolder.getRecommendations();
recommendationCard.setBackgroundTintList(ColorStateList.valueOf(mBackgroundColor));
@@ -976,6 +984,7 @@ public class MediaControlPanel {
mRecommendationViewHolder.marquee(true, mMediaViewController.GUTS_ANIMATION_DURATION);
}
mMediaViewController.openGuts();
+ mLogger.logLongPressOpen(mUid, mPackageName, mInstanceId);
}
/**
@@ -1138,7 +1147,7 @@ public class MediaControlPanel {
private void logSmartspaceCardReported(int eventId, boolean isRecommendationCard,
int interactedSubcardRank, int interactedSubcardCardinality) {
mMediaCarouselController.logSmartspaceCardReported(eventId,
- mInstanceId,
+ mSmartspaceId,
mUid,
isRecommendationCard,
new int[]{getSurfaceForSmartspaceLogging()},
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
index 47a0991d3d1e..a4d2f7bc96c4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -20,6 +20,7 @@ import android.app.PendingIntent
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.media.session.MediaSession
+import com.android.internal.logging.InstanceId
import com.android.systemui.R
/** State of a media view. */
@@ -115,7 +116,17 @@ data class MediaData(
/**
* Timestamp when this player was last active.
*/
- var lastActive: Long = 0L
+ var lastActive: Long = 0L,
+
+ /**
+ * Instance ID for logging purposes
+ */
+ val instanceId: InstanceId,
+
+ /**
+ * The UID of the app, used for logging
+ */
+ val appUid: Int
) {
companion object {
/** Media is playing on the local device */
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 029ea9ad99f0..08c3395b4528 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -27,6 +27,7 @@ import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.ImageDecoder
import android.graphics.drawable.Icon
@@ -37,6 +38,7 @@ import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.net.Uri
import android.os.Parcelable
+import android.os.Process
import android.os.UserHandle
import android.provider.Settings
import android.service.notification.StatusBarNotification
@@ -44,6 +46,7 @@ import android.text.TextUtils
import android.util.Log
import androidx.media.utils.MediaConstants
import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.logging.InstanceId
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -94,7 +97,9 @@ private val LOADING = MediaData(
clickIntent = null,
device = null,
active = true,
- resumeAction = null)
+ resumeAction = null,
+ instanceId = InstanceId.fakeInstanceId(-1),
+ appUid = Process.INVALID_UID)
@VisibleForTesting
internal val EMPTY_SMARTSPACE_MEDIA_DATA = SmartspaceMediaData("INVALID", false, false,
@@ -138,7 +143,8 @@ class MediaDataManager(
private val useQsMediaPlayer: Boolean,
private val systemClock: SystemClock,
private val tunerService: TunerService,
- private val mediaFlags: MediaFlags
+ private val mediaFlags: MediaFlags,
+ private val logger: MediaUiEventLogger
) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
companion object {
@@ -202,12 +208,13 @@ class MediaDataManager(
smartspaceMediaDataProvider: SmartspaceMediaDataProvider,
clock: SystemClock,
tunerService: TunerService,
- mediaFlags: MediaFlags
+ mediaFlags: MediaFlags,
+ logger: MediaUiEventLogger
) : this(context, backgroundExecutor, foregroundExecutor, mediaControllerFactory,
broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener,
mediaSessionBasedFilter, mediaDeviceManager, mediaDataCombineLatest, mediaDataFilter,
activityStarter, smartspaceMediaDataProvider, Utils.useMediaResumption(context),
- Utils.useQsMediaPlayer(context), clock, tunerService, mediaFlags)
+ Utils.useQsMediaPlayer(context), clock, tunerService, mediaFlags, logger)
private val appChangeReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
@@ -298,17 +305,24 @@ class MediaDataManager(
fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
if (useQsMediaPlayer && isMediaNotification(sbn)) {
+ var logEvent = false
Assert.isMainThread()
val oldKey = findExistingEntry(key, sbn.packageName)
if (oldKey == null) {
- val temp = LOADING.copy(packageName = sbn.packageName)
+ val instanceId = logger.getNewInstanceId()
+ val temp = LOADING.copy(
+ packageName = sbn.packageName,
+ instanceId = instanceId
+ )
mediaEntries.put(key, temp)
+ logEvent = true
} else if (oldKey != key) {
- // Move to new key
+ // Resume -> active conversion; move to new key
val oldData = mediaEntries.remove(oldKey)!!
+ logEvent = true
mediaEntries.put(key, oldData)
}
- loadMediaData(key, sbn, oldKey)
+ loadMediaData(key, sbn, oldKey, logEvent)
} else {
onNotificationRemoved(key)
}
@@ -340,9 +354,23 @@ class MediaDataManager(
) {
// Resume controls don't have a notification key, so store by package name instead
if (!mediaEntries.containsKey(packageName)) {
- val resumeData = LOADING.copy(packageName = packageName, resumeAction = action,
- hasCheckedForResume = true)
+ val instanceId = logger.getNewInstanceId()
+ val appUid = try {
+ context.packageManager.getApplicationInfo(packageName, 0)?.uid!!
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(TAG, "Could not get app UID for $packageName", e)
+ Process.INVALID_UID
+ }
+
+ val resumeData = LOADING.copy(
+ packageName = packageName,
+ resumeAction = action,
+ hasCheckedForResume = true,
+ instanceId = instanceId,
+ appUid = appUid
+ )
mediaEntries.put(packageName, resumeData)
+ logger.logResumeMediaAdded(appUid, packageName, instanceId)
}
backgroundExecutor.execute {
loadMediaDataInBgForResumption(userId, desc, action, token, appName, appIntent,
@@ -368,10 +396,11 @@ class MediaDataManager(
private fun loadMediaData(
key: String,
sbn: StatusBarNotification,
- oldKey: String?
+ oldKey: String?,
+ logEvent: Boolean = false
) {
backgroundExecutor.execute {
- loadMediaDataInBg(key, sbn, oldKey)
+ loadMediaDataInBg(key, sbn, oldKey, logEvent)
}
}
@@ -449,6 +478,10 @@ class MediaDataManager(
*/
internal fun setTimedOut(key: String, timedOut: Boolean, forceUpdate: Boolean = false) {
mediaEntries[key]?.let {
+ if (timedOut && !forceUpdate) {
+ // Only log this event when media expires on its own
+ logger.logMediaTimeout(it.appUid, it.packageName, it.instanceId)
+ }
if (it.active == !timedOut && !forceUpdate) {
if (it.resumption) {
if (DEBUG) Log.d(TAG, "timing out resume player $key")
@@ -463,7 +496,9 @@ class MediaDataManager(
}
private fun removeEntry(key: String) {
- mediaEntries.remove(key)
+ mediaEntries.remove(key)?.let {
+ logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId)
+ }
notifyMediaDataRemoved(key)
}
@@ -537,6 +572,10 @@ class MediaDataManager(
null
}
+ val currentEntry = mediaEntries.get(packageName)
+ val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
+ val appUid = currentEntry?.appUid ?: Process.INVALID_UID
+
val mediaAction = getResumeMediaAction(resumeAction)
val lastActive = systemClock.elapsedRealtime()
foregroundExecutor.execute {
@@ -545,14 +584,16 @@ class MediaDataManager(
MediaButton(playOrPause = mediaAction), packageName, token, appIntent,
device = null, active = false,
resumeAction = resumeAction, resumption = true, notificationKey = packageName,
- hasCheckedForResume = true, lastActive = lastActive))
+ hasCheckedForResume = true, lastActive = lastActive, instanceId = instanceId,
+ appUid = appUid))
}
}
private fun loadMediaDataInBg(
key: String,
sbn: StatusBarNotification,
- oldKey: String?
+ oldKey: String?,
+ logEvent: Boolean = false
) {
val token = sbn.notification.extras.getParcelable(Notification.EXTRA_MEDIA_SESSION)
as MediaSession.Token?
@@ -636,6 +677,22 @@ class MediaDataManager(
MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL) MediaData.PLAYBACK_LOCAL
else MediaData.PLAYBACK_CAST_LOCAL
val isPlaying = mediaController.playbackState?.let { isPlayingState(it.state) } ?: null
+
+ val currentEntry = mediaEntries.get(key)
+ val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
+ val appUid = try {
+ context.packageManager.getApplicationInfo(sbn.packageName, 0)?.uid!!
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(TAG, "Could not get app UID for ${sbn.packageName}", e)
+ Process.INVALID_UID
+ }
+
+ if (logEvent) {
+ logger.logActiveMediaAdded(appUid, sbn.packageName, instanceId, playbackLocation)
+ } else if (playbackLocation != currentEntry?.playbackLocation) {
+ logger.logPlaybackLocationChange(appUid, sbn.packageName, instanceId, playbackLocation)
+ }
+
val lastActive = systemClock.elapsedRealtime()
foregroundExecutor.execute {
val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
@@ -647,7 +704,7 @@ class MediaDataManager(
active, resumeAction = resumeAction, playbackLocation = playbackLocation,
notificationKey = key, hasCheckedForResume = hasCheckedForResume,
isPlaying = isPlaying, isClearable = sbn.isClearable(),
- lastActive = lastActive))
+ lastActive = lastActive, instanceId = instanceId, appUid = appUid))
}
}
@@ -989,10 +1046,12 @@ class MediaDataManager(
notifyMediaDataRemoved(key)
notifyMediaDataLoaded(pkg, pkg, updated)
}
+ logger.logActiveConvertedToResume(updated.appUid, pkg, updated.instanceId)
return
}
if (removed != null) {
notifyMediaDataRemoved(key)
+ logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId)
}
}
@@ -1009,6 +1068,7 @@ class MediaDataManager(
filtered.forEach {
mediaEntries.remove(it.key)
notifyMediaDataRemoved(it.key)
+ logger.logMediaRemoved(it.value.appUid, it.value.packageName, it.value.instanceId)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt b/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt
new file mode 100644
index 000000000000..862b2797b77c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media
+
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.InstanceIdSequence
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import java.lang.IllegalArgumentException
+import javax.inject.Inject
+
+private const val INSTANCE_ID_MAX = 1 shl 20
+
+/**
+ * A helper class to log events related to the media controls
+ */
+@SysUISingleton
+class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger) {
+
+ private val instanceIdSequence = InstanceIdSequence(INSTANCE_ID_MAX)
+
+ /**
+ * Get a new instance ID for a new media control
+ */
+ fun getNewInstanceId(): InstanceId {
+ return instanceIdSequence.newInstanceId()
+ }
+
+ fun logActiveMediaAdded(
+ uid: Int,
+ packageName: String,
+ instanceId: InstanceId,
+ playbackLocation: Int
+ ) {
+ val event = when (playbackLocation) {
+ MediaData.PLAYBACK_LOCAL -> MediaUiEvent.LOCAL_MEDIA_ADDED
+ MediaData.PLAYBACK_CAST_LOCAL -> MediaUiEvent.CAST_MEDIA_ADDED
+ MediaData.PLAYBACK_CAST_REMOTE -> MediaUiEvent.REMOTE_MEDIA_ADDED
+ else -> throw IllegalArgumentException("Unknown playback location")
+ }
+ logger.logWithInstanceId(event, uid, packageName, instanceId)
+ }
+
+ fun logPlaybackLocationChange(
+ uid: Int,
+ packageName: String,
+ instanceId: InstanceId,
+ playbackLocation: Int
+ ) {
+ val event = when (playbackLocation) {
+ MediaData.PLAYBACK_LOCAL -> MediaUiEvent.TRANSFER_TO_LOCAL
+ MediaData.PLAYBACK_CAST_LOCAL -> MediaUiEvent.TRANSFER_TO_CAST
+ MediaData.PLAYBACK_CAST_REMOTE -> MediaUiEvent.TRANSFER_TO_REMOTE
+ else -> throw IllegalArgumentException("Unknown playback location")
+ }
+ logger.logWithInstanceId(event, uid, packageName, instanceId)
+ }
+
+ fun logResumeMediaAdded(uid: Int, packageName: String, instanceId: InstanceId) {
+ logger.logWithInstanceId(MediaUiEvent.RESUME_MEDIA_ADDED, uid, packageName, instanceId)
+ }
+
+ fun logActiveConvertedToResume(uid: Int, packageName: String, instanceId: InstanceId) {
+ logger.logWithInstanceId(MediaUiEvent.ACTIVE_TO_RESUME, uid, packageName, instanceId)
+ }
+
+ fun logMediaTimeout(uid: Int, packageName: String, instanceId: InstanceId) {
+ logger.logWithInstanceId(MediaUiEvent.MEDIA_TIMEOUT, uid, packageName, instanceId)
+ }
+
+ fun logMediaRemoved(uid: Int, packageName: String, instanceId: InstanceId) {
+ logger.logWithInstanceId(MediaUiEvent.MEDIA_REMOVED, uid, packageName, instanceId)
+ }
+
+ fun logMediaCarouselPage(position: Int) {
+ // Since this operation is on the carousel, we don't include package information
+ logger.logWithPosition(MediaUiEvent.CAROUSEL_PAGE, 0, null, position)
+ }
+
+ fun logSwipeDismiss() {
+ // Since this operation is on the carousel, we don't include package information
+ logger.log(MediaUiEvent.DISMISS_SWIPE)
+ }
+
+ fun logLongPressOpen(uid: Int, packageName: String, instanceId: InstanceId) {
+ logger.logWithInstanceId(MediaUiEvent.OPEN_LONG_PRESS, uid, packageName, instanceId)
+ }
+
+ fun logLongPressDismiss(uid: Int, packageName: String, instanceId: InstanceId) {
+ logger.logWithInstanceId(MediaUiEvent.DISMISS_LONG_PRESS, uid, packageName, instanceId)
+ }
+
+ fun logLongPressSettings(uid: Int, packageName: String, instanceId: InstanceId) {
+ logger.logWithInstanceId(MediaUiEvent.OPEN_SETTINGS_LONG_PRESS, uid, packageName,
+ instanceId)
+ }
+
+ fun logCarouselSettings() {
+ // Since this operation is on the carousel, we don't include package information
+ logger.log(MediaUiEvent.OPEN_SETTINGS_CAROUSEL)
+ }
+
+ fun logTapAction(buttonId: Int, uid: Int, packageName: String, instanceId: InstanceId) {
+ val event = when (buttonId) {
+ R.id.actionPlayPause -> MediaUiEvent.TAP_ACTION_PLAY_PAUSE
+ R.id.actionPrev -> MediaUiEvent.TAP_ACTION_PREV
+ R.id.actionNext -> MediaUiEvent.TAP_ACTION_NEXT
+ else -> MediaUiEvent.TAP_ACTION_OTHER
+ }
+
+ logger.logWithInstanceId(event, uid, packageName, instanceId)
+ }
+
+ fun logSeek(uid: Int, packageName: String, instanceId: InstanceId) {
+ logger.logWithInstanceId(MediaUiEvent.ACTION_SEEK, uid, packageName, instanceId)
+ }
+
+ fun logOpenOutputSwitcher(uid: Int, packageName: String, instanceId: InstanceId) {
+ logger.logWithInstanceId(MediaUiEvent.OPEN_OUTPUT_SWITCHER, uid, packageName, instanceId)
+ }
+}
+
+enum class MediaUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "A new media control was added for media playing locally on the device")
+ LOCAL_MEDIA_ADDED(1029),
+
+ @UiEvent(doc = "A new media control was added for media cast from the device")
+ CAST_MEDIA_ADDED(1030),
+
+ @UiEvent(doc = "A new media control was added for media playing remotely")
+ REMOTE_MEDIA_ADDED(1031),
+
+ @UiEvent(doc = "The media for an existing control was transferred to local playback")
+ TRANSFER_TO_LOCAL(1032),
+
+ @UiEvent(doc = "The media for an existing control was transferred to a cast device")
+ TRANSFER_TO_CAST(1033),
+
+ @UiEvent(doc = "The media for an existing control was transferred to a remote device")
+ TRANSFER_TO_REMOTE(1034),
+
+ @UiEvent(doc = "A new resumable media control was added")
+ RESUME_MEDIA_ADDED(1013),
+
+ @UiEvent(doc = "An existing active media control was converted into resumable media")
+ ACTIVE_TO_RESUME(1014),
+
+ @UiEvent(doc = "Media timed out")
+ MEDIA_TIMEOUT(1015),
+
+ @UiEvent(doc = "A media control was removed from the carousel")
+ MEDIA_REMOVED(1016),
+
+ @UiEvent(doc = "User swiped to another control within the media carousel")
+ CAROUSEL_PAGE(1017),
+
+ @UiEvent(doc = "The user swiped away the media carousel")
+ DISMISS_SWIPE(1018),
+
+ @UiEvent(doc = "The user opened the long press menu")
+ OPEN_LONG_PRESS(1019),
+
+ @UiEvent(doc = "The user dismissed via long press menu")
+ DISMISS_LONG_PRESS(1020),
+
+ @UiEvent(doc = "The user opened settings from long press menu")
+ OPEN_SETTINGS_LONG_PRESS(1021),
+
+ @UiEvent(doc = "The user opened settings from the carousel")
+ OPEN_SETTINGS_CAROUSEL(1022),
+
+ @UiEvent(doc = "The play/pause button was tapped")
+ TAP_ACTION_PLAY_PAUSE(1023),
+
+ @UiEvent(doc = "The previous button was tapped")
+ TAP_ACTION_PREV(1024),
+
+ @UiEvent(doc = "The next button was tapped")
+ TAP_ACTION_NEXT(1025),
+
+ @UiEvent(doc = "A custom or generic action button was tapped")
+ TAP_ACTION_OTHER(1026),
+
+ @UiEvent(doc = "The user seeked using the seekbar")
+ ACTION_SEEK(1027),
+
+ @UiEvent(doc = "The user opened the output switcher from a media control")
+ OPEN_OUTPUT_SWITCHER(1028);
+
+ override fun getId() = metricId
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
index a9a8fd1c0e9d..8c1845ac1ae0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
@@ -131,7 +131,7 @@ class SeekBarViewModel @Inject constructor(
}
}
- lateinit var logSmartspaceClick: () -> Unit
+ lateinit var logSeek: () -> Unit
fun getEnabled() = _data.enabled
@@ -175,7 +175,7 @@ class SeekBarViewModel @Inject constructor(
scrubbing = false
checkPlaybackPosition()
} else {
- logSmartspaceClick()
+ logSeek()
controller?.transportControls?.seekTo(position)
// Invalidate the cached playbackState to avoid the thumb jumping back to the previous
// position.
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 daf81bdc6e82..46e675684782 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
@@ -35,26 +35,11 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import javax.inject.Provider
-private val DATA = MediaData(
- userId = -1,
- initialized = false,
- backgroundColor = 0,
- app = null,
- appIcon = null,
- artist = null,
- song = null,
- artwork = null,
- actions = emptyList(),
- actionsToShowInCompact = emptyList(),
- packageName = "INVALID",
- token = null,
- clickIntent = null,
- device = null,
- active = true,
- resumeAction = null)
+private val DATA = MediaTestUtils.emptyMediaData
private val SMARTSPACE_KEY = "smartspace"
@@ -74,7 +59,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
@Mock lateinit var falsingCollector: FalsingCollector
@Mock lateinit var falsingManager: FalsingManager
@Mock lateinit var dumpManager: DumpManager
- @Mock lateinit var mediaFlags: MediaFlags
+ @Mock lateinit var logger: MediaUiEventLogger
private val clock = FakeSystemClock()
private lateinit var mediaCarouselController: MediaCarouselController
@@ -95,7 +80,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
falsingCollector,
falsingManager,
dumpManager,
- mediaFlags
+ logger
)
MediaPlayerData.clear()
@@ -189,4 +174,18 @@ class MediaCarouselControllerTest : SysuiTestCase() {
val size = MediaPlayerData.playerKeys().size
assertTrue(MediaPlayerData.playerKeys().elementAt(size - 1).isSsMediaRec)
}
+
+ @Test
+ fun testSwipeDismiss_logged() {
+ mediaCarouselController.mediaCarouselScrollHandler.dismissCallback.invoke()
+
+ verify(logger).logSwipeDismiss()
+ }
+
+ @Test
+ fun testSettingsButton_logged() {
+ mediaCarouselController.settingsButton.callOnClick()
+
+ verify(logger).logCarouselSettings()
+ }
} \ 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 63b3a7db40d3..f2e3edbc0761 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -41,6 +41,7 @@ import androidx.constraintlayout.widget.Barrier
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.LiveData
import androidx.test.filters.SmallTest
+import com.android.internal.logging.InstanceId
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastSender
@@ -49,6 +50,7 @@ import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.KotlinArgumentCaptor
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -59,6 +61,7 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.Mock
import org.mockito.Mockito.any
@@ -69,7 +72,6 @@ import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
private const val KEY = "TEST_KEY"
-private const val APP = "APP"
private const val BG_COLOR = Color.RED
private const val PACKAGE = "PKG"
private const val ARTIST = "ARTIST"
@@ -78,7 +80,6 @@ private const val DEVICE_NAME = "DEVICE_NAME"
private const val SESSION_KEY = "SESSION_KEY"
private const val SESSION_ARTIST = "SESSION_ARTIST"
private const val SESSION_TITLE = "SESSION_TITLE"
-private const val USER_ID = 0
private const val DISABLED_DEVICE_NAME = "DISABLED_DEVICE_NAME"
@SmallTest
@@ -137,6 +138,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
private val disabledDevice = MediaDeviceData(false, null, DISABLED_DEVICE_NAME)
private lateinit var mediaData: MediaData
private val clock = FakeSystemClock()
+ @Mock private lateinit var logger: MediaUiEventLogger
+ @Mock private lateinit var instanceId: InstanceId
@JvmField @Rule val mockito = MockitoJUnit.rule()
@@ -148,7 +151,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
player = MediaControlPanel(context, bgExecutor, activityStarter, broadcastSender,
mediaViewController, seekBarViewModel, Lazy { mediaDataManager },
- mediaOutputDialogFactory, mediaCarouselController, falsingManager, clock)
+ mediaOutputDialogFactory, mediaCarouselController, falsingManager, clock, logger)
whenever(seekBarViewModel.progress).thenReturn(seekBarData)
// Set up mock views for the players
@@ -212,23 +215,14 @@ public class MediaControlPanelTest : SysuiTestCase() {
}
session.setActive(true)
- mediaData = MediaData(
- userId = USER_ID,
- initialized = true,
+ mediaData = MediaTestUtils.emptyMediaData.copy(
backgroundColor = BG_COLOR,
- app = APP,
- appIcon = null,
artist = ARTIST,
song = TITLE,
- artwork = null,
- actions = emptyList(),
- actionsToShowInCompact = emptyList(),
packageName = PACKAGE,
token = session.sessionToken,
- clickIntent = null,
device = device,
- active = true,
- resumeAction = null)
+ instanceId = instanceId)
}
/**
@@ -534,6 +528,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun longClick_gutsClosed() {
player.attachPlayer(viewHolder)
+ player.bindPlayer(mediaData, KEY)
whenever(mediaViewController.isGutsVisible).thenReturn(false)
val captor = ArgumentCaptor.forClass(View.OnLongClickListener::class.java)
@@ -541,6 +536,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
captor.value.onLongClick(viewHolder.player)
verify(mediaViewController).openGuts()
+ verify(logger).logLongPressOpen(anyInt(), eq(PACKAGE), eq(instanceId))
}
@Test
@@ -568,8 +564,10 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun settingsButtonClick() {
player.attachPlayer(viewHolder)
+ player.bindPlayer(mediaData, KEY)
settings.callOnClick()
+ verify(logger).logLongPressSettings(anyInt(), eq(PACKAGE), eq(instanceId))
val captor = ArgumentCaptor.forClass(Intent::class.java)
verify(activityStarter).startActivity(captor.capture(), eq(true))
@@ -586,6 +584,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
assertThat(dismiss.isEnabled).isEqualTo(true)
dismiss.callOnClick()
+ verify(logger).logLongPressDismiss(anyInt(), eq(PACKAGE), eq(instanceId))
verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong())
}
@@ -614,4 +613,151 @@ public class MediaControlPanelTest : SysuiTestCase() {
verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong())
verify(mediaCarouselController).removePlayer(eq(mediaKey), eq(false), eq(false))
}
+
+ @Test
+ fun actionPlayPauseClick_isLogged() {
+ val semanticActions = MediaButton(
+ playOrPause = MediaAction(null, Runnable {}, "play", null)
+ )
+ val data = mediaData.copy(semanticActions = semanticActions)
+
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(data, KEY)
+
+ viewHolder.actionPlayPause.callOnClick()
+ verify(logger).logTapAction(eq(R.id.actionPlayPause), anyInt(), eq(PACKAGE), eq(instanceId))
+ }
+
+ @Test
+ fun actionPrevClick_isLogged() {
+ val semanticActions = MediaButton(
+ prevOrCustom = MediaAction(null, Runnable {}, "previous", null)
+ )
+ val data = mediaData.copy(semanticActions = semanticActions)
+
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(data, KEY)
+
+ viewHolder.actionPrev.callOnClick()
+ verify(logger).logTapAction(eq(R.id.actionPrev), anyInt(), eq(PACKAGE), eq(instanceId))
+ }
+
+ @Test
+ fun actionNextClick_isLogged() {
+ val semanticActions = MediaButton(
+ nextOrCustom = MediaAction(null, Runnable {}, "next", null)
+ )
+ val data = mediaData.copy(semanticActions = semanticActions)
+
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(data, KEY)
+
+ viewHolder.actionNext.callOnClick()
+ verify(logger).logTapAction(eq(R.id.actionNext), anyInt(), eq(PACKAGE), eq(instanceId))
+ }
+
+ @Test
+ fun actionCustom0Click_isLogged() {
+ val semanticActions = MediaButton(
+ custom0 = MediaAction(null, Runnable {}, "custom 0", null)
+ )
+ val data = mediaData.copy(semanticActions = semanticActions)
+
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(data, KEY)
+
+ viewHolder.action0.callOnClick()
+ verify(logger).logTapAction(eq(R.id.action0), anyInt(), eq(PACKAGE), eq(instanceId))
+ }
+
+ @Test
+ fun actionCustom1Click_isLogged() {
+ val semanticActions = MediaButton(
+ custom1 = MediaAction(null, Runnable {}, "custom 1", null)
+ )
+ val data = mediaData.copy(semanticActions = semanticActions)
+
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(data, KEY)
+
+ viewHolder.action1.callOnClick()
+ verify(logger).logTapAction(eq(R.id.action1), anyInt(), eq(PACKAGE), eq(instanceId))
+ }
+
+ @Test
+ fun actionCustom2Click_isLogged() {
+ val actions = listOf(
+ MediaAction(null, Runnable {}, "action 0", null),
+ MediaAction(null, Runnable {}, "action 1", null),
+ MediaAction(null, Runnable {}, "action 2", null),
+ MediaAction(null, Runnable {}, "action 3", null),
+ MediaAction(null, Runnable {}, "action 4", null)
+ )
+ val data = mediaData.copy(actions = actions)
+
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(data, KEY)
+
+ viewHolder.action2.callOnClick()
+ verify(logger).logTapAction(eq(R.id.action2), anyInt(), eq(PACKAGE), eq(instanceId))
+ }
+
+ @Test
+ fun actionCustom3Click_isLogged() {
+ val actions = listOf(
+ MediaAction(null, Runnable {}, "action 0", null),
+ MediaAction(null, Runnable {}, "action 1", null),
+ MediaAction(null, Runnable {}, "action 2", null),
+ MediaAction(null, Runnable {}, "action 3", null),
+ MediaAction(null, Runnable {}, "action 4", null)
+ )
+ val data = mediaData.copy(actions = actions)
+
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(data, KEY)
+
+ viewHolder.action1.callOnClick()
+ verify(logger).logTapAction(eq(R.id.action1), anyInt(), eq(PACKAGE), eq(instanceId))
+ }
+
+ @Test
+ fun actionCustom4Click_isLogged() {
+ val actions = listOf(
+ MediaAction(null, Runnable {}, "action 0", null),
+ MediaAction(null, Runnable {}, "action 1", null),
+ MediaAction(null, Runnable {}, "action 2", null),
+ MediaAction(null, Runnable {}, "action 3", null),
+ MediaAction(null, Runnable {}, "action 4", null)
+ )
+ val data = mediaData.copy(actions = actions)
+
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(data, KEY)
+
+ viewHolder.action1.callOnClick()
+ verify(logger).logTapAction(eq(R.id.action1), anyInt(), eq(PACKAGE), eq(instanceId))
+ }
+
+ @Test
+ fun openOutputSwitcher_isLogged() {
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(mediaData, KEY)
+
+ seamless.callOnClick()
+
+ verify(logger).logOpenOutputSwitcher(anyInt(), eq(PACKAGE), eq(instanceId))
+ }
+
+ @Test
+ fun logSeek() {
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(mediaData, KEY)
+
+ val callback: () -> Unit = {}
+ val captor = KotlinArgumentCaptor(callback::class.java)
+ verify(seekBarViewModel).logSeek = captor.capture()
+ captor.value.invoke()
+
+ verify(logger).logSeek(anyInt(), eq(PACKAGE), eq(instanceId))
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
index 7a487b871d9d..b0f6a80bc8a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
@@ -32,6 +32,7 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
+import com.android.internal.logging.InstanceId;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
@@ -77,7 +78,8 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
mMediaData = new MediaData(
USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null,
new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, true, null,
- MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L);
+ MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L,
+ InstanceId.fakeInstanceId(-1), -1);
mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME);
}
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 82a48efafa3a..b8e249fcfeb1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
@@ -17,7 +17,6 @@
package com.android.systemui.media
import android.app.smartspace.SmartspaceAction
-import android.graphics.Color
import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -25,6 +24,8 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -33,7 +34,6 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.Mockito.`when`
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
@@ -45,17 +45,9 @@ private const val KEY = "TEST_KEY"
private const val KEY_ALT = "TEST_KEY_2"
private const val USER_MAIN = 0
private const val USER_GUEST = 10
-private const val APP = "APP"
-private const val BG_COLOR = Color.RED
private const val PACKAGE = "PKG"
-private const val ARTIST = "ARTIST"
-private const val TITLE = "TITLE"
-private const val DEVICE_NAME = "DEVICE_NAME"
private const val SMARTSPACE_KEY = "SMARTSPACE_KEY"
-private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
-private fun <T> any(): T = Mockito.any()
-
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
@@ -68,8 +60,6 @@ class MediaDataFilterTest : SysuiTestCase() {
@Mock
private lateinit var broadcastSender: BroadcastSender
@Mock
- private lateinit var mediaResumeListener: MediaResumeListener
- @Mock
private lateinit var mediaDataManager: MediaDataManager
@Mock
private lateinit var lockscreenUserManager: NotificationLockscreenUserManager
@@ -83,7 +73,6 @@ class MediaDataFilterTest : SysuiTestCase() {
private lateinit var mediaDataFilter: MediaDataFilter
private lateinit var dataMain: MediaData
private lateinit var dataGuest: MediaData
- private val device = MediaDeviceData(true, null, DEVICE_NAME)
private val clock = FakeSystemClock()
@Before
@@ -99,23 +88,9 @@ class MediaDataFilterTest : SysuiTestCase() {
setUser(USER_MAIN)
// Set up test media data
- dataMain = MediaData(
+ dataMain = MediaTestUtils.emptyMediaData.copy(
userId = USER_MAIN,
- initialized = true,
- backgroundColor = BG_COLOR,
- app = APP,
- appIcon = null,
- artist = ARTIST,
- song = TITLE,
- artwork = null,
- actions = emptyList(),
- actionsToShowInCompact = emptyList(),
- packageName = PACKAGE,
- token = null,
- clickIntent = null,
- device = device,
- active = true,
- resumeAction = null)
+ packageName = PACKAGE)
dataGuest = dataMain.copy(userId = USER_GUEST)
`when`(smartspaceData.targetId).thenReturn(SMARTSPACE_KEY)
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 066f49a16f19..ccd8ef1b7c6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -18,6 +18,7 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.media.utils.MediaConstants
import androidx.test.filters.SmallTest
+import com.android.systemui.InstanceIdSequenceFake
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -38,10 +39,10 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
-import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
@@ -51,7 +52,8 @@ import org.mockito.Mockito.`when` as whenever
private const val KEY = "KEY"
private const val KEY_2 = "KEY_2"
private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
-private const val PACKAGE_NAME = "com.android.systemui"
+private const val PACKAGE_NAME = "com.example.app"
+private const val SYSTEM_PACKAGE_NAME = "com.android.systemui"
private const val APP_NAME = "SystemUI"
private const val SESSION_ARTIST = "artist"
private const val SESSION_TITLE = "title"
@@ -92,6 +94,7 @@ class MediaDataManagerTest : SysuiTestCase() {
@Mock private lateinit var mediaRecommendationItem: SmartspaceAction
@Mock private lateinit var mediaSmartspaceBaseAction: SmartspaceAction
@Mock private lateinit var mediaFlags: MediaFlags
+ @Mock private lateinit var logger: MediaUiEventLogger
lateinit var mediaDataManager: MediaDataManager
lateinit var mediaNotification: StatusBarNotification
@Captor lateinit var mediaDataCaptor: ArgumentCaptor<MediaData>
@@ -99,6 +102,8 @@ class MediaDataManagerTest : SysuiTestCase() {
@Mock private lateinit var tunerService: TunerService
@Captor lateinit var tunableCaptor: ArgumentCaptor<TunerService.Tunable>
+ private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
+
private val originalSmartspaceSetting = Settings.Secure.getInt(context.contentResolver,
Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 1)
@@ -128,7 +133,8 @@ class MediaDataManagerTest : SysuiTestCase() {
useQsMediaPlayer = true,
systemClock = clock,
tunerService = tunerService,
- mediaFlags = mediaFlags
+ mediaFlags = mediaFlags,
+ logger = logger
)
verify(tunerService).addTunable(capture(tunableCaptor),
eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
@@ -169,6 +175,7 @@ class MediaDataManagerTest : SysuiTestCase() {
whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf(mediaRecommendationItem))
whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(1234L)
whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(false)
+ whenever(logger.getNewInstanceId()).thenReturn(instanceIdSequence.newInstanceId())
}
@After
@@ -181,15 +188,13 @@ class MediaDataManagerTest : SysuiTestCase() {
@Test
fun testSetTimedOut_active_deactivatesMedia() {
- val data = MediaData(userId = USER_ID, initialized = true, backgroundColor = 0, app = null,
- appIcon = null, artist = null, song = null, artwork = null, actions = emptyList(),
- actionsToShowInCompact = emptyList(), packageName = "INVALID", token = null,
- clickIntent = null, device = null, active = true, resumeAction = null)
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = data)
+ addNotificationAndLoad()
+ val data = mediaDataCaptor.value
+ assertThat(data.active).isTrue()
mediaDataManager.setTimedOut(KEY, timedOut = true)
assertThat(data.active).isFalse()
+ verify(logger).logMediaTimeout(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
}
@Test
@@ -201,9 +206,15 @@ class MediaDataManagerTest : SysuiTestCase() {
}
mediaDataManager.addResumptionControls(USER_ID, desc, Runnable {}, session.sessionToken,
APP_NAME, pendingIntent, PACKAGE_NAME)
+
backgroundExecutor.runAllReady()
foregroundExecutor.runAllReady()
+ verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(null), capture(mediaDataCaptor),
+ eq(true), eq(0))
+
mediaDataManager.setTimedOut(PACKAGE_NAME, timedOut = true)
+ verify(logger).logMediaTimeout(anyInt(), eq(PACKAGE_NAME),
+ eq(mediaDataCaptor.value.instanceId))
// THEN it is removed and listeners are informed
foregroundExecutor.advanceClockToLast()
@@ -219,10 +230,9 @@ class MediaDataManagerTest : SysuiTestCase() {
@Test
fun testOnMetaDataLoaded_callsListener() {
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java))
- verify(listener).onMediaDataLoaded(eq(KEY), eq(null), anyObject(), eq(true),
- eq(0))
+ addNotificationAndLoad()
+ verify(logger).logActiveMediaAdded(anyInt(), eq(PACKAGE_NAME),
+ eq(mediaDataCaptor.value.instanceId), eq(MediaData.PLAYBACK_LOCAL))
}
@Test
@@ -241,7 +251,7 @@ class MediaDataManagerTest : SysuiTestCase() {
@Test
fun testOnNotificationAdded_isRcn_markedRemote() {
val rcn = SbnBuilder().run {
- setPkg("com.android.systemui") // System package
+ setPkg(SYSTEM_PACKAGE_NAME)
modifyNotification(context).also {
it.setSmallIcon(android.R.drawable.ic_media_pause)
it.setStyle(MediaStyle().apply {
@@ -259,25 +269,24 @@ class MediaDataManagerTest : SysuiTestCase() {
eq(0))
assertThat(mediaDataCaptor.value!!.playbackLocation).isEqualTo(
MediaData.PLAYBACK_CAST_REMOTE)
+ verify(logger).logActiveMediaAdded(anyInt(), eq(SYSTEM_PACKAGE_NAME),
+ eq(mediaDataCaptor.value.instanceId), eq(MediaData.PLAYBACK_CAST_REMOTE))
}
@Test
fun testOnNotificationRemoved_callsListener() {
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java))
+ addNotificationAndLoad()
+ val data = mediaDataCaptor.value
mediaDataManager.onNotificationRemoved(KEY)
verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
}
@Test
fun testOnNotificationRemoved_withResumption() {
// GIVEN that the manager has a notification with a resume action
whenever(controller.metadata).thenReturn(metadataBuilder.build())
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
- eq(0))
+ addNotificationAndLoad()
val data = mediaDataCaptor.value
assertThat(data.resumption).isFalse()
mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
@@ -289,6 +298,7 @@ class MediaDataManagerTest : SysuiTestCase() {
eq(0))
assertThat(mediaDataCaptor.value.resumption).isTrue()
assertThat(mediaDataCaptor.value.isPlaying).isFalse()
+ verify(logger).logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
}
@Test
@@ -333,15 +343,13 @@ class MediaDataManagerTest : SysuiTestCase() {
whenever(controller.metadata).thenReturn(metadataBuilder.build())
whenever(playbackInfo.playbackType).thenReturn(
MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE)
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
- eq(0))
+ addNotificationAndLoad()
val data = mediaDataCaptor.value
val dataRemoteWithResume = data.copy(resumeAction = Runnable {},
playbackLocation = MediaData.PLAYBACK_CAST_LOCAL)
mediaDataManager.onMediaDataLoaded(KEY, null, dataRemoteWithResume)
+ verify(logger).logActiveMediaAdded(anyInt(), eq(PACKAGE_NAME),
+ eq(mediaDataCaptor.value.instanceId), eq(MediaData.PLAYBACK_CAST_LOCAL))
// WHEN the notification is removed
mediaDataManager.onNotificationRemoved(KEY)
@@ -373,12 +381,34 @@ class MediaDataManagerTest : SysuiTestCase() {
assertThat(data.actions).hasSize(1)
assertThat(data.semanticActions!!.playOrPause).isNotNull()
assertThat(data.lastActive).isAtLeast(currentTime)
+ verify(logger).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
+ }
+
+ @Test
+ fun testResumptionDisabled_dismissesResumeControls() {
+ // WHEN there are resume controls and resumption is switched off
+ val desc = MediaDescription.Builder().run {
+ setTitle(SESSION_TITLE)
+ build()
+ }
+ mediaDataManager.addResumptionControls(USER_ID, desc, Runnable {}, session.sessionToken,
+ APP_NAME, pendingIntent, PACKAGE_NAME)
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(null), capture(mediaDataCaptor),
+ eq(true), eq(0))
+ val data = mediaDataCaptor.value
+ mediaDataManager.setMediaResumptionEnabled(false)
+
+ // THEN the resume controls are dismissed
+ verify(listener).onMediaDataRemoved(eq(PACKAGE_NAME))
+ verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
}
@Test
fun testDismissMedia_listenerCalled() {
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = mock(MediaData::class.java))
+ addNotificationAndLoad()
+ val data = mediaDataCaptor.value
val removed = mediaDataManager.dismissMediaData(KEY, 0L)
assertThat(removed).isTrue()
@@ -386,6 +416,7 @@ class MediaDataManagerTest : SysuiTestCase() {
foregroundExecutor.runAllReady()
verify(listener).onMediaDataRemoved(eq(KEY))
+ verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
}
@Test
@@ -513,11 +544,7 @@ class MediaDataManagerTest : SysuiTestCase() {
@Test
fun testOnMediaDataChanged_updatesLastActiveTime() {
val currentTime = clock.elapsedRealtime()
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
- eq(0))
+ addNotificationAndLoad()
assertThat(mediaDataCaptor.value!!.lastActive).isAtLeast(currentTime)
}
@@ -543,12 +570,9 @@ class MediaDataManagerTest : SysuiTestCase() {
fun testOnActiveMediaConverted_doesNotUpdateLastActiveTime() {
// GIVEN that the manager has a notification with a resume action
whenever(controller.metadata).thenReturn(metadataBuilder.build())
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
- eq(0))
+ addNotificationAndLoad()
val data = mediaDataCaptor.value
+ val instanceId = data.instanceId
assertThat(data.resumption).isFalse()
mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
@@ -563,6 +587,10 @@ class MediaDataManagerTest : SysuiTestCase() {
eq(0))
assertThat(mediaDataCaptor.value.resumption).isTrue()
assertThat(mediaDataCaptor.value.lastActive).isLessThan(currentTime)
+
+ // Log as a conversion event, not as a new resume control
+ verify(logger).logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(instanceId))
+ verify(logger, never()).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), any())
}
@Test
@@ -633,11 +661,7 @@ class MediaDataManagerTest : SysuiTestCase() {
}
whenever(controller.playbackState).thenReturn(stateBuilder.build())
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
- eq(0))
+ addNotificationAndLoad()
assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull()
val actions = mediaDataCaptor.value!!.semanticActions!!
@@ -679,11 +703,7 @@ class MediaDataManagerTest : SysuiTestCase() {
}
whenever(controller.playbackState).thenReturn(stateBuilder.build())
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
- eq(0))
+ addNotificationAndLoad()
assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull()
val actions = mediaDataCaptor.value!!.semanticActions!!
@@ -722,11 +742,7 @@ class MediaDataManagerTest : SysuiTestCase() {
whenever(controller.playbackState).thenReturn(stateBuilder.build())
whenever(controller.extras).thenReturn(extras)
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
- verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
- eq(0))
+ addNotificationAndLoad()
assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull()
val actions = mediaDataCaptor.value!!.semanticActions!!
@@ -744,4 +760,49 @@ class MediaDataManagerTest : SysuiTestCase() {
assertThat(actions.custom1).isNotNull()
assertThat(actions.custom1!!.contentDescription).isEqualTo(customDesc[1])
}
+
+ @Test
+ fun testPlaybackLocationChange_isLogged() {
+ // Media control added for local playback
+ addNotificationAndLoad()
+ val instanceId = mediaDataCaptor.value.instanceId
+
+ // Location is updated to local cast
+ whenever(controller.metadata).thenReturn(metadataBuilder.build())
+ whenever(playbackInfo.playbackType).thenReturn(
+ MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE)
+ addNotificationAndLoad()
+ verify(logger).logPlaybackLocationChange(anyInt(), eq(PACKAGE_NAME),
+ eq(instanceId), eq(MediaData.PLAYBACK_CAST_LOCAL))
+
+ // update to remote cast
+ val rcn = SbnBuilder().run {
+ setPkg(SYSTEM_PACKAGE_NAME) // System package
+ modifyNotification(context).also {
+ it.setSmallIcon(android.R.drawable.ic_media_pause)
+ it.setStyle(MediaStyle().apply {
+ setMediaSession(session.sessionToken)
+ setRemotePlaybackInfo("Remote device", 0, null)
+ })
+ }
+ build()
+ }
+
+ mediaDataManager.onNotificationAdded(KEY, rcn)
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ verify(logger).logPlaybackLocationChange(anyInt(), eq(SYSTEM_PACKAGE_NAME),
+ eq(instanceId), eq(MediaData.PLAYBACK_CAST_REMOTE))
+ }
+
+ /**
+ * Helper function to add a media notification and capture the resulting MediaData
+ */
+ private fun addNotificationAndLoad() {
+ mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
+ eq(0))
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
index d912a8906ab3..e6f48ecd37de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
@@ -33,6 +33,7 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -44,7 +45,6 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.Mockito.any
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
@@ -57,12 +57,8 @@ private const val KEY = "TEST_KEY"
private const val KEY_OLD = "TEST_KEY_OLD"
private const val PACKAGE = "PKG"
private const val SESSION_KEY = "SESSION_KEY"
-private const val SESSION_TITLE = "SESSION_TITLE"
private const val DEVICE_NAME = "DEVICE_NAME"
private const val REMOTE_DEVICE_NAME = "REMOTE_DEVICE_NAME"
-private const val USER_ID = 0
-
-private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -115,24 +111,9 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
// Create a media sesssion and notification for testing.
session = MediaSession(context, SESSION_KEY)
- mediaData = MediaData(
- userId = USER_ID,
- initialized = true,
- backgroundColor = 0,
- app = PACKAGE,
- appIcon = null,
- artist = null,
- song = SESSION_TITLE,
- artwork = null,
- actions = emptyList(),
- actionsToShowInCompact = emptyList(),
+ mediaData = MediaTestUtils.emptyMediaData.copy(
packageName = PACKAGE,
- token = session.sessionToken,
- clickIntent = null,
- device = null,
- active = true,
- resumeAction = null)
-
+ token = session.sessionToken)
whenever(controllerFactory.create(session.sessionToken))
.thenReturn(controller)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
index ceeb0dbb159e..7bd210d762f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
@@ -163,27 +163,12 @@ public class MediaPlayerDataTest : SysuiTestCase() {
isPlaying: Boolean?,
location: Int,
resumption: Boolean
- ) = MediaData(
- userId = 0,
- initialized = false,
- backgroundColor = 0,
+ ) = MediaTestUtils.emptyMediaData.copy(
app = app,
- appIcon = null,
- artist = null,
- song = null,
- artwork = null,
- actions = emptyList(),
- actionsToShowInCompact = emptyList(),
packageName = "package: $app",
- token = null,
- clickIntent = null,
- device = null,
- active = true,
- resumeAction = null,
playbackLocation = location,
resumption = resumption,
notificationKey = "key: $app",
- hasCheckedForResume = false,
isPlaying = isPlaying
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
index 30ee2e4d3431..2d34913d3467 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
@@ -24,7 +24,6 @@ import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.content.pm.ServiceInfo
-import android.graphics.Color
import android.media.MediaDescription
import android.media.session.MediaSession
import android.provider.Settings
@@ -57,13 +56,9 @@ import org.mockito.MockitoAnnotations
private const val KEY = "TEST_KEY"
private const val OLD_KEY = "RESUME_KEY"
-private const val APP = "APP"
-private const val BG_COLOR = Color.RED
private const val PACKAGE_NAME = "PKG"
private const val CLASS_NAME = "CLASS"
-private const val ARTIST = "ARTIST"
private const val TITLE = "TITLE"
-private const val USER_ID = 0
private const val MEDIA_PREFERENCES = "media_control_prefs"
private const val RESUME_COMPONENTS = "package1/class1:package2/class2:package3/class3"
@@ -130,24 +125,10 @@ class MediaResumeListenerTest : SysuiTestCase() {
resumeListener.setManager(mediaDataManager)
mediaDataManager.addListener(resumeListener)
- data = MediaData(
- userId = USER_ID,
- initialized = true,
- backgroundColor = BG_COLOR,
- app = APP,
- appIcon = null,
- artist = ARTIST,
+ data = MediaTestUtils.emptyMediaData.copy(
song = TITLE,
- artwork = null,
- actions = emptyList(),
- actionsToShowInCompact = emptyList(),
packageName = PACKAGE_NAME,
- token = token,
- clickIntent = null,
- device = device,
- active = true,
- notificationKey = KEY,
- resumeAction = null)
+ token = token)
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt
index 5d53181c8345..ee4f8db48ae3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.media
-import android.graphics.Color
import android.media.session.MediaController
import android.media.session.MediaController.PlaybackInfo
import android.media.session.MediaSession
@@ -39,7 +38,6 @@ import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.Mockito.any
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
@@ -50,35 +48,12 @@ import org.mockito.Mockito.`when` as whenever
private const val PACKAGE = "PKG"
private const val KEY = "TEST_KEY"
private const val NOTIF_KEY = "TEST_KEY"
-private const val SESSION_ARTIST = "SESSION_ARTIST"
-private const val SESSION_TITLE = "SESSION_TITLE"
-private const val APP_NAME = "APP_NAME"
-private const val USER_ID = 0
-
-private val info = MediaData(
- userId = USER_ID,
- initialized = true,
- backgroundColor = Color.DKGRAY,
- app = APP_NAME,
- appIcon = null,
- artist = SESSION_ARTIST,
- song = SESSION_TITLE,
- artwork = null,
- actions = emptyList(),
- actionsToShowInCompact = emptyList(),
+
+private val info = MediaTestUtils.emptyMediaData.copy(
packageName = PACKAGE,
- token = null,
- clickIntent = null,
- device = null,
- active = true,
- resumeAction = null,
- resumption = false,
- notificationKey = NOTIF_KEY,
- hasCheckedForResume = false
+ notificationKey = NOTIF_KEY
)
-private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
-
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt
new file mode 100644
index 000000000000..c7ef94eb6a64
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt
@@ -0,0 +1,27 @@
+package com.android.systemui.media
+
+import com.android.internal.logging.InstanceId
+
+class MediaTestUtils {
+ companion object {
+ val emptyMediaData = MediaData(
+ userId = 0,
+ initialized = true,
+ backgroundColor = 0,
+ app = null,
+ appIcon = null,
+ artist = null,
+ song = null,
+ artwork = null,
+ actions = emptyList(),
+ actionsToShowInCompact = emptyList(),
+ packageName = "",
+ token = null,
+ clickIntent = null,
+ device = null,
+ active = true,
+ resumeAction = null,
+ instanceId = InstanceId.fakeInstanceId(-1),
+ appUid = -1)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
index 8c2fed5bd2ed..91169834752e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
@@ -25,6 +25,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -50,9 +51,7 @@ private const val PACKAGE = "PKG"
private const val SESSION_KEY = "SESSION_KEY"
private const val SESSION_ARTIST = "SESSION_ARTIST"
private const val SESSION_TITLE = "SESSION_TITLE"
-private const val USER_ID = 0
-private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
private fun <T> anyObject(): T {
return Mockito.anyObject<T>()
}
@@ -96,23 +95,11 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
}
session.setActive(true)
- mediaData = MediaData(
- userId = USER_ID,
- initialized = true,
- backgroundColor = 0,
- app = PACKAGE,
- appIcon = null,
- artist = null,
- song = SESSION_TITLE,
- artwork = null,
- actions = emptyList(),
- actionsToShowInCompact = emptyList(),
- packageName = PACKAGE,
- token = session.sessionToken,
- clickIntent = null,
- device = null,
- active = true,
- resumeAction = null)
+ mediaData = MediaTestUtils.emptyMediaData.copy(
+ app = PACKAGE,
+ packageName = PACKAGE,
+ token = session.sessionToken
+ )
resumeData = mediaData.copy(token = null, active = false, resumption = true)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
index 399c89373d63..20f5e4c19402 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
@@ -36,6 +36,7 @@ import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Ignore
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
@@ -46,6 +47,7 @@ import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
@SmallTest
@@ -71,14 +73,14 @@ public class SeekBarViewModelTest : SysuiTestCase() {
private val token1 = MediaSession.Token(1, null)
private val token2 = MediaSession.Token(2, null)
+ @JvmField @Rule val mockito = MockitoJUnit.rule()
+
@Before
fun setUp() {
fakeExecutor = FakeExecutor(FakeSystemClock())
viewModel = SeekBarViewModel(FakeRepeatableExecutor(fakeExecutor))
- viewModel.logSmartspaceClick = { }
- mockController = mock(MediaController::class.java)
+ viewModel.logSeek = { }
whenever(mockController.sessionToken).thenReturn(token1)
- mockTransport = mock(MediaController.TransportControls::class.java)
// LiveData to run synchronously
ArchTaskExecutor.getInstance().setDelegate(taskExecutor)