Merge "Add logging for media controls" into tm-dev

This commit is contained in:
Beth Thibodeau
2022-03-31 14:36:38 +00:00
committed by Android (Google) Code Review
19 changed files with 690 additions and 272 deletions

View File

@@ -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

View File

@@ -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()

View File

@@ -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()},

View File

@@ -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 */

View File

@@ -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)
}
}
}

View File

@@ -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
}

View File

@@ -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.

View File

@@ -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()
}
}

View File

@@ -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))
}
}

View File

@@ -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);
}

View File

@@ -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)

View File

@@ -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))
}
}

View File

@@ -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)
}

View File

@@ -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
)
}

View File

@@ -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

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -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)
}

View File

@@ -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)