Merge "Add dismiss, impression, click logging for media control card, add dismiss logging for media recommendation card" into sc-dev

This commit is contained in:
Jieru Shi
2021-05-07 16:57:16 +00:00
committed by Android (Google) Code Review
5 changed files with 114 additions and 51 deletions

View File

@@ -106,7 +106,7 @@ class MediaCarouselController @Inject constructor(
private var carouselMeasureHeight: Int = 0 private var carouselMeasureHeight: Int = 0
private var desiredHostState: MediaHostState? = null private var desiredHostState: MediaHostState? = null
private val mediaCarousel: MediaScrollView private val mediaCarousel: MediaScrollView
private val mediaCarouselScrollHandler: MediaCarouselScrollHandler val mediaCarouselScrollHandler: MediaCarouselScrollHandler
val mediaFrame: ViewGroup val mediaFrame: ViewGroup
private lateinit var settingsButton: View private lateinit var settingsButton: View
private val mediaContent: ViewGroup private val mediaContent: ViewGroup
@@ -156,20 +156,13 @@ class MediaCarouselController @Inject constructor(
} }
} }
var visibleToUser: Boolean = false
set(value) {
if (field != value) {
field = value
}
}
init { init {
mediaFrame = inflateMediaCarousel() mediaFrame = inflateMediaCarousel()
mediaCarousel = mediaFrame.requireViewById(R.id.media_carousel_scroller) mediaCarousel = mediaFrame.requireViewById(R.id.media_carousel_scroller)
pageIndicator = mediaFrame.requireViewById(R.id.media_page_indicator) pageIndicator = mediaFrame.requireViewById(R.id.media_page_indicator)
mediaCarouselScrollHandler = MediaCarouselScrollHandler(mediaCarousel, pageIndicator, mediaCarouselScrollHandler = MediaCarouselScrollHandler(mediaCarousel, pageIndicator,
executor, mediaManager::onSwipeToDismiss, this::updatePageIndicatorLocation, executor, this::onSwipeToDismiss, this::updatePageIndicatorLocation,
this::closeGuts, falsingCollector, falsingManager) this::closeGuts, falsingCollector, falsingManager, this::logSmartspaceImpression)
isRtl = context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL isRtl = context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
inflateSettingsButton() inflateSettingsButton()
mediaContent = mediaCarousel.requireViewById(R.id.media_carousel) mediaContent = mediaCarousel.requireViewById(R.id.media_carousel)
@@ -210,7 +203,7 @@ class MediaCarouselController @Inject constructor(
override fun onSmartspaceMediaDataLoaded(key: String, data: SmartspaceTarget) { override fun onSmartspaceMediaDataLoaded(key: String, data: SmartspaceTarget) {
Log.d(TAG, "My Smartspace media update is here") Log.d(TAG, "My Smartspace media update is here")
addSmartspaceMediaRecommendations(key, data) addSmartspaceMediaRecommendations(key, data)
if (visibleToUser) { if (mediaCarouselScrollHandler.visibleToUser) {
logSmartspaceImpression() logSmartspaceImpression()
} }
} }
@@ -580,23 +573,68 @@ class MediaCarouselController @Inject constructor(
* Log the user impression for media card. * Log the user impression for media card.
*/ */
fun logSmartspaceImpression() { fun logSmartspaceImpression() {
MediaPlayerData.players().forEach { val visibleMediaIndex = mediaCarouselScrollHandler.visibleMediaIndex
// Log every impression of media recommendation card since it will only be shown if (MediaPlayerData.players().size > visibleMediaIndex) {
// for 1 minute after each connection. val mediaControlPanel = MediaPlayerData.players().elementAt(visibleMediaIndex)
if (it.recommendationViewHolder?.recommendations?.visibility == View.VISIBLE) { val isMediaActive =
MediaPlayerData.playerKeys().elementAt(visibleMediaIndex).data?.active
val isRecommendationCard = mediaControlPanel.recommendationViewHolder != null
if (!isRecommendationCard && !isMediaActive) {
// Media control card time out or swiped away
return
}
logSmartspaceCardReported(800, // SMARTSPACE_CARD_SEEN
mediaControlPanel.mInstanceId,
isRecommendationCard,
mediaControlPanel.surfaceForSmartspaceLogging)
}
}
@JvmOverloads
fun logSmartspaceCardReported(
eventId: Int,
instanceId: Int,
isRecommendationCard: Boolean,
surface: Int,
rank: Int = mediaCarouselScrollHandler.visibleMediaIndex
) {
/* ktlint-disable max-line-length */ /* ktlint-disable max-line-length */
SysUiStatsLog.write(SysUiStatsLog.SMARTSPACE_CARD_REPORTED, SysUiStatsLog.write(SysUiStatsLog.SMARTSPACE_CARD_REPORTED,
800, // SMARTSPACE_CARD_SEEN eventId,
it.getInstanceId(), instanceId,
SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_MEDIA_RECOMMENDATIONS, if (isRecommendationCard)
it.getSurfaceForSmartspaceLogging(), SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_MEDIA_RECOMMENDATIONS
/* rank */ 0, else
/* cardinality */ 1) SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_RESUME_MEDIA,
surface,
rank,
mediaContent.getChildCount())
/* ktlint-disable max-line-length */ /* ktlint-disable max-line-length */
} }
// TODO(shijieru): add logging for media control card private fun onSwipeToDismiss() {
val recommendation = MediaPlayerData.players().filter {
it.recommendationViewHolder != null
} }
// Use -1 as rank value to indicate user swipe to dismiss the card
if (!recommendation.isEmpty()) {
logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
recommendation.get(0).mInstanceId,
true,
recommendation.get(0).surfaceForSmartspaceLogging,
/* rank */-1)
} else {
val visibleMediaIndex = mediaCarouselScrollHandler.visibleMediaIndex
if (MediaPlayerData.players().size > visibleMediaIndex) {
val player = MediaPlayerData.players().elementAt(visibleMediaIndex)
logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
player.mInstanceId,
false,
player.surfaceForSmartspaceLogging,
/* rank */-1)
}
}
mediaManager.onSwipeToDismiss()
} }
} }
@@ -605,7 +643,7 @@ internal object MediaPlayerData {
private val EMPTY = MediaData(-1, false, 0, null, null, null, null, null, private val EMPTY = MediaData(-1, false, 0, null, null, null, null, null,
emptyList(), emptyList(), "INVALID", null, null, null, false, null) emptyList(), emptyList(), "INVALID", null, null, null, false, null)
private data class MediaSortKey( data class MediaSortKey(
// Is Smartspace media recommendation. When the Smartspace media is present, it should // Is Smartspace media recommendation. When the Smartspace media is present, it should
// always be the first card in carousel. // always be the first card in carousel.
val isSsMediaRec: Boolean, val isSsMediaRec: Boolean,
@@ -663,6 +701,8 @@ internal object MediaPlayerData {
return -1 return -1
} }
fun playerKeys() = mediaPlayers.keys
@VisibleForTesting @VisibleForTesting
fun clear() { fun clear() {
mediaData.clear() mediaData.clear()

View File

@@ -28,13 +28,13 @@ import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.dynamicanimation.animation.SpringForce import androidx.dynamicanimation.animation.SpringForce
import com.android.settingslib.Utils import com.android.settingslib.Utils
import com.android.systemui.Gefingerpoken import com.android.systemui.Gefingerpoken
import com.android.systemui.qs.PageIndicator
import com.android.systemui.R import com.android.systemui.R
import com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS import com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS
import com.android.systemui.classifier.FalsingCollector import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.FalsingManager
import com.android.wm.shell.animation.PhysicsAnimator import com.android.systemui.qs.PageIndicator
import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.wm.shell.animation.PhysicsAnimator
private const val FLING_SLOP = 1000000 private const val FLING_SLOP = 1000000
private const val DISMISS_DELAY = 100L private const val DISMISS_DELAY = 100L
@@ -61,7 +61,8 @@ class MediaCarouselScrollHandler(
private var translationChangedListener: () -> Unit, private var translationChangedListener: () -> Unit,
private val closeGuts: () -> Unit, private val closeGuts: () -> Unit,
private val falsingCollector: FalsingCollector, private val falsingCollector: FalsingCollector,
private val falsingManager: FalsingManager private val falsingManager: FalsingManager,
private val logSmartspaceImpression: () -> Unit
) { ) {
/** /**
* Is the view in RTL * Is the view in RTL
@@ -200,6 +201,13 @@ class MediaCarouselScrollHandler(
} }
} }
var visibleToUser: Boolean = false
set(value) {
if (field != value) {
field = value
}
}
init { init {
gestureDetector = GestureDetectorCompat(scrollView.context, gestureListener) gestureDetector = GestureDetectorCompat(scrollView.context, gestureListener)
scrollView.touchListener = touchListener scrollView.touchListener = touchListener
@@ -460,7 +468,11 @@ class MediaCarouselScrollHandler(
scrollIntoCurrentMedia = scrollInAmount scrollIntoCurrentMedia = scrollInAmount
val nowScrolledIn = scrollIntoCurrentMedia != 0 val nowScrolledIn = scrollIntoCurrentMedia != 0
if (newIndex != visibleMediaIndex || wasScrolledIn != nowScrolledIn) { if (newIndex != visibleMediaIndex || wasScrolledIn != nowScrolledIn) {
val oldIndex = visibleMediaIndex
visibleMediaIndex = newIndex visibleMediaIndex = newIndex
if (oldIndex != visibleMediaIndex && visibleToUser) {
logSmartspaceImpression()
}
closeGuts() closeGuts()
updatePlayerVisibilities() updatePlayerVisibilities()
} }

View File

@@ -105,7 +105,8 @@ public class MediaControlPanel {
private int mDevicePadding; private int mDevicePadding;
private int mAlbumArtSize; private int mAlbumArtSize;
// Instance id for logging purpose. // Instance id for logging purpose.
private int mInstanceId; protected int mInstanceId = -1;
private MediaCarouselController mMediaCarouselController;
private final MediaOutputDialogFactory mMediaOutputDialogFactory; private final MediaOutputDialogFactory mMediaOutputDialogFactory;
/** /**
@@ -119,7 +120,7 @@ public class MediaControlPanel {
ActivityStarter activityStarter, MediaViewController mediaViewController, ActivityStarter activityStarter, MediaViewController mediaViewController,
SeekBarViewModel seekBarViewModel, Lazy<MediaDataManager> lazyMediaDataManager, SeekBarViewModel seekBarViewModel, Lazy<MediaDataManager> lazyMediaDataManager,
KeyguardDismissUtil keyguardDismissUtil, MediaOutputDialogFactory KeyguardDismissUtil keyguardDismissUtil, MediaOutputDialogFactory
mediaOutputDialogFactory) { mediaOutputDialogFactory, MediaCarouselController mediaCarouselController) {
mContext = context; mContext = context;
mBackgroundExecutor = backgroundExecutor; mBackgroundExecutor = backgroundExecutor;
mActivityStarter = activityStarter; mActivityStarter = activityStarter;
@@ -128,6 +129,7 @@ public class MediaControlPanel {
mMediaDataManagerLazy = lazyMediaDataManager; mMediaDataManagerLazy = lazyMediaDataManager;
mKeyguardDismissUtil = keyguardDismissUtil; mKeyguardDismissUtil = keyguardDismissUtil;
mMediaOutputDialogFactory = mediaOutputDialogFactory; mMediaOutputDialogFactory = mediaOutputDialogFactory;
mMediaCarouselController = mediaCarouselController;
loadDimens(); loadDimens();
} }
@@ -251,6 +253,8 @@ public class MediaControlPanel {
} }
mKey = key; mKey = key;
MediaSession.Token token = data.getToken(); MediaSession.Token token = data.getToken();
mInstanceId = data.getPackageName().hashCode();
mBackgroundColor = data.getBackgroundColor(); mBackgroundColor = data.getBackgroundColor();
if (mToken == null || !mToken.equals(token)) { if (mToken == null || !mToken.equals(token)) {
mToken = token; mToken = token;
@@ -270,6 +274,9 @@ public class MediaControlPanel {
if (clickIntent != null) { if (clickIntent != null) {
mPlayerViewHolder.getPlayer().setOnClickListener(v -> { mPlayerViewHolder.getPlayer().setOnClickListener(v -> {
if (mMediaViewController.isGutsVisible()) return; if (mMediaViewController.isGutsVisible()) return;
logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
false);
mActivityStarter.postStartActivityDismissingKeyguard(clickIntent, mActivityStarter.postStartActivityDismissingKeyguard(clickIntent,
buildLaunchAnimatorController(mPlayerViewHolder.getPlayer())); buildLaunchAnimatorController(mPlayerViewHolder.getPlayer()));
}); });
@@ -376,6 +383,8 @@ public class MediaControlPanel {
} else { } else {
button.setEnabled(true); button.setEnabled(true);
button.setOnClickListener(v -> { button.setOnClickListener(v -> {
logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
false);
action.run(); action.run();
}); });
} }
@@ -408,6 +417,9 @@ public class MediaControlPanel {
mPlayerViewHolder.getDismissLabel().setAlpha(isDismissible ? 1 : DISABLED_ALPHA); mPlayerViewHolder.getDismissLabel().setAlpha(isDismissible ? 1 : DISABLED_ALPHA);
mPlayerViewHolder.getDismiss().setEnabled(isDismissible); mPlayerViewHolder.getDismiss().setEnabled(isDismissible);
mPlayerViewHolder.getDismiss().setOnClickListener(v -> { mPlayerViewHolder.getDismiss().setOnClickListener(v -> {
logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
false);
if (mKey != null) { if (mKey != null) {
closeGuts(); closeGuts();
mKeyguardDismissUtil.executeWhenUnlocked(() -> { mKeyguardDismissUtil.executeWhenUnlocked(() -> {
@@ -550,6 +562,8 @@ public class MediaControlPanel {
// Set up long press to show guts setting panel. // Set up long press to show guts setting panel.
mRecommendationViewHolder.getDismiss().setOnClickListener(v -> { mRecommendationViewHolder.getDismiss().setOnClickListener(v -> {
logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
true);
closeGuts(); closeGuts();
mKeyguardDismissUtil.executeWhenUnlocked(() -> { mKeyguardDismissUtil.executeWhenUnlocked(() -> {
mMediaDataManagerLazy.get().dismissSmartspaceRecommendation( mMediaDataManagerLazy.get().dismissSmartspaceRecommendation(
@@ -652,15 +666,9 @@ public class MediaControlPanel {
} }
view.setOnClickListener(v -> { view.setOnClickListener(v -> {
// When media recommendation card is shown, there could be only one card. // When media recommendation card is shown, it will always be the top card.
SysUiStatsLog.write(SysUiStatsLog.SMARTSPACE_CARD_REPORTED, logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
760, // SMARTSPACE_CARD_CLICK true);
mInstanceId,
SysUiStatsLog
.SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_MEDIA_RECOMMENDATIONS,
getSurfaceForSmartspaceLogging(),
/* rank */ 0,
/* cardinality */ 1);
if (shouldSmartspaceRecItemOpenInForeground(action)) { if (shouldSmartspaceRecItemOpenInForeground(action)) {
// Request to unlock the device if the activity needs to be opened in foreground. // Request to unlock the device if the activity needs to be opened in foreground.
@@ -718,7 +726,10 @@ public class MediaControlPanel {
return SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DEFAULT_SURFACE; return SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DEFAULT_SURFACE;
} }
protected int getInstanceId() { private void logSmartspaceCardReported(int eventId, boolean isRecommendationCard) {
return mInstanceId; mMediaCarouselController.logSmartspaceCardReported(eventId,
mInstanceId,
isRecommendationCard,
getSurfaceForSmartspaceLogging());
} }
} }

View File

@@ -171,16 +171,15 @@ class MediaHierarchyManager @Inject constructor(
if (field != value) { if (field != value) {
field = value field = value
} }
// Pull down shade from lock screen (exclude the case when shade is brought out by // qs is expanded on LS shade and HS shade
// tapping twice on lock screen) if (value && (isLockScreenShadeVisibleToUser() || isHomeScreenShadeVisibleToUser())) {
if (value && isLockScreenShadeVisibleToUser()) {
mediaCarouselController.logSmartspaceImpression() mediaCarouselController.logSmartspaceImpression()
} }
// Release shade and back to lock screen // Release shade and back to lock screen
if (isLockScreenVisibleToUser()) { if (isLockScreenVisibleToUser()) {
mediaCarouselController.logSmartspaceImpression() mediaCarouselController.logSmartspaceImpression()
} }
mediaCarouselController.visibleToUser = isVisibleToUser() mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
} }
/** /**
@@ -257,7 +256,7 @@ class MediaHierarchyManager @Inject constructor(
if (newState == StatusBarState.SHADE_LOCKED && isLockScreenShadeVisibleToUser()) { if (newState == StatusBarState.SHADE_LOCKED && isLockScreenShadeVisibleToUser()) {
mediaCarouselController.logSmartspaceImpression() mediaCarouselController.logSmartspaceImpression()
} }
mediaCarouselController.visibleToUser = isVisibleToUser() mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
} }
override fun onDozeAmountChanged(linear: Float, eased: Float) { override fun onDozeAmountChanged(linear: Float, eased: Float) {
@@ -275,7 +274,7 @@ class MediaHierarchyManager @Inject constructor(
updateDesiredLocation() updateDesiredLocation()
qsExpanded = false qsExpanded = false
} }
mediaCarouselController.visibleToUser = isVisibleToUser() mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
} }
override fun onExpandedChanged(isExpanded: Boolean) { override fun onExpandedChanged(isExpanded: Boolean) {
@@ -287,7 +286,7 @@ class MediaHierarchyManager @Inject constructor(
if (isLockScreenVisibleToUser()) { if (isLockScreenVisibleToUser()) {
mediaCarouselController.logSmartspaceImpression() mediaCarouselController.logSmartspaceImpression()
} }
mediaCarouselController.visibleToUser = isVisibleToUser() mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
} }
}) })

View File

@@ -93,6 +93,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Mock private lateinit var expandedSet: ConstraintSet @Mock private lateinit var expandedSet: ConstraintSet
@Mock private lateinit var collapsedSet: ConstraintSet @Mock private lateinit var collapsedSet: ConstraintSet
@Mock private lateinit var mediaOutputDialogFactory: MediaOutputDialogFactory @Mock private lateinit var mediaOutputDialogFactory: MediaOutputDialogFactory
@Mock private lateinit var mediaCarouselController: MediaCarouselController
private lateinit var appIcon: ImageView private lateinit var appIcon: ImageView
private lateinit var albumView: ImageView private lateinit var albumView: ImageView
private lateinit var titleText: TextView private lateinit var titleText: TextView
@@ -129,7 +130,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
player = MediaControlPanel(context, bgExecutor, activityStarter, mediaViewController, player = MediaControlPanel(context, bgExecutor, activityStarter, mediaViewController,
seekBarViewModel, Lazy { mediaDataManager }, keyguardDismissUtil, seekBarViewModel, Lazy { mediaDataManager }, keyguardDismissUtil,
mediaOutputDialogFactory) mediaOutputDialogFactory, mediaCarouselController)
whenever(seekBarViewModel.progress).thenReturn(seekBarData) whenever(seekBarViewModel.progress).thenReturn(seekBarData)
// Mock out a view holder for the player to attach to. // Mock out a view holder for the player to attach to.