diff options
| author | 2022-03-28 22:16:28 -0400 | |
|---|---|---|
| committer | 2022-03-30 19:27:45 -0400 | |
| commit | 21169ec3f309c73a5643aca91fb7756dedbbdab3 (patch) | |
| tree | 12f5d77501975e1276cc64b04da45493de9ab349 | |
| parent | 90f2f63ca556ea488eeb3b3b46a4edb746f35423 (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
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) |