Add impression logging for media recommendation card

Working sheet to figure out where to log impression and test scenarios https://docs.google.com/spreadsheets/d/1XZbRaS6vCWfSDwZ9prxiv5RFzdSg3a86C0zB-uSgJ-M/edit?resourcekey=0--PdUHvg7ZJ2NgbSkLs3abQ#gid=0

Bug: 181364757
Test: will append statsd_drive logs
Change-Id: I650d7c960b6e26efb39a32e5eacef01339e351a4
This commit is contained in:
Jieru Shi
2021-04-26 14:36:04 -07:00
parent e3e92e26c5
commit 47fa996fb2
5 changed files with 134 additions and 10 deletions

View File

@@ -20,6 +20,7 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PageIndicator
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.Utils
@@ -156,6 +157,13 @@ class MediaCarouselController @Inject constructor(
}
}
var visibleToUser: Boolean = false
set(value) {
if (field != value) {
field = value
}
}
init {
mediaFrame = inflateMediaCarousel()
mediaCarousel = mediaFrame.requireViewById(R.id.media_carousel_scroller)
@@ -203,6 +211,9 @@ class MediaCarouselController @Inject constructor(
override fun onSmartspaceMediaDataLoaded(key: String, data: SmartspaceTarget) {
Log.d(TAG, "My Smartspace media update is here")
addSmartspaceMediaRecommendations(key, data)
if (visibleToUser) {
logSmartspaceImpression()
}
}
override fun onMediaDataRemoved(key: String) {
@@ -567,6 +578,29 @@ class MediaCarouselController @Inject constructor(
mediaCarouselScrollHandler.playerWidthPlusPadding = playerWidthPlusPadding
}
}
/**
* Log the user impression for media card.
*/
fun logSmartspaceImpression() {
MediaPlayerData.players().forEach {
// Log every impression of media recommendation card since it will only be shown
// for 1 minute after each connection.
if (it.recommendationViewHolder?.recommendations?.visibility == View.VISIBLE) {
/* ktlint-disable max-line-length */
SysUiStatsLog.write(SysUiStatsLog.SMARTSPACE_CARD_REPORTED,
800, // SMARTSPACE_CARD_SEEN
it.getInstanceId(),
SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_MEDIA_RECOMMENDATIONS,
it.getSurfaceForSmartspaceLogging(),
/* rank */ 0,
/* cardinality */ 1)
/* ktlint-disable max-line-length */
}
// TODO(shijieru): add logging for media control card
}
}
}
@VisibleForTesting

View File

@@ -104,6 +104,8 @@ public class MediaControlPanel {
private int mBackgroundColor;
private int mDevicePadding;
private int mAlbumArtSize;
// Instance id for logging purpose.
private int mInstanceId;
private final MediaOutputDialogFactory mMediaOutputDialogFactory;
/**
@@ -472,6 +474,7 @@ public class MediaControlPanel {
mRecommendationViewHolder.getCardIcon().setColorFilter(primaryColor);
mRecommendationViewHolder.getCardText().setTextColor(primaryColor);
mInstanceId = target.getSmartspaceTargetId().hashCode();
mRecommendationViewHolder.getRecommendations()
.setBackgroundTintList(ColorStateList.valueOf(backgroundColor));
mBackgroundColor = backgroundColor;
@@ -530,7 +533,6 @@ public class MediaControlPanel {
setSmartspaceRecItemOnClickListener(
mediaCoverImageView,
recommendation,
target.getSmartspaceTargetId(),
null);
if (uiComponentIndex < MEDIA_RECOMMENDATION_ITEMS_PER_ROW) {
@@ -649,7 +651,6 @@ public class MediaControlPanel {
private void setSmartspaceRecItemOnClickListener(
@NonNull View view,
@NonNull SmartspaceAction action,
@NonNull String targetId,
@Nullable View.OnClickListener callback) {
if (view == null || action == null || action.getIntent() == null) {
Log.e(TAG, "No tap action can be set up");
@@ -660,11 +661,11 @@ public class MediaControlPanel {
// When media recommendation card is shown, there could be only one card.
SysUiStatsLog.write(SysUiStatsLog.SMARTSPACE_CARD_REPORTED,
760, // SMARTSPACE_CARD_CLICK
targetId.hashCode(),
mInstanceId,
SysUiStatsLog
.SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_MEDIA_RECOMMENDATIONS,
getSurfaceForSmartspaceLogging(mMediaViewController.getCurrentEndLocation()),
/* rank */ 1,
getSurfaceForSmartspaceLogging(),
/* rank */ 0,
/* cardinality */ 1);
if (shouldSmartspaceRecItemOpenInForeground(action)) {
@@ -708,7 +709,12 @@ public class MediaControlPanel {
return false;
}
private int getSurfaceForSmartspaceLogging(int currentEndLocation) {
/**
* Get the surface given the current end location for MediaViewController
* @return surface used for Smartspace logging
*/
protected int getSurfaceForSmartspaceLogging() {
int currentEndLocation = mMediaViewController.getCurrentEndLocation();
if (currentEndLocation == MediaHierarchyManager.LOCATION_QQS
|| currentEndLocation == MediaHierarchyManager.LOCATION_QS) {
return SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE;
@@ -717,4 +723,8 @@ public class MediaControlPanel {
}
return SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DEFAULT_SURFACE;
}
protected int getInstanceId() {
return mInstanceId;
}
}

View File

@@ -38,6 +38,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.animation.UniqueObjectHostView
import javax.inject.Inject
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
/**
* Similarly to isShown but also excludes views that have 0 alpha
@@ -73,7 +74,8 @@ class MediaHierarchyManager @Inject constructor(
private val bypassController: KeyguardBypassController,
private val mediaCarouselController: MediaCarouselController,
private val notifLockscreenUserManager: NotificationLockscreenUserManager,
wakefulnessLifecycle: WakefulnessLifecycle
wakefulnessLifecycle: WakefulnessLifecycle,
private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager
) {
/**
* The root overlay of the hierarchy. This is where the media notification is attached to
@@ -161,6 +163,26 @@ class MediaHierarchyManager @Inject constructor(
}
}
/**
* Is quick setting expanded?
*/
var qsExpanded: Boolean = false
set(value) {
if (field != value) {
field = value
}
// Pull down shade from lock screen (exclude the case when shade is brought out by
// tapping twice on lock screen)
if (value && isLockScreenShadeVisibleToUser()) {
mediaCarouselController.logSmartspaceImpression()
}
// Release shade and back to lock screen
if (isLockScreenVisibleToUser()) {
mediaCarouselController.logSmartspaceImpression()
}
mediaCarouselController.visibleToUser = isVisibleToUser()
}
/**
* Is the shade currently collapsing from the expanded qs? If we're on the lockscreen and in qs,
* we wouldn't want to transition in that case.
@@ -231,6 +253,11 @@ class MediaHierarchyManager @Inject constructor(
override fun onStateChanged(newState: Int) {
updateTargetState()
// Enters shade from lock screen
if (newState == StatusBarState.SHADE_LOCKED && isLockScreenShadeVisibleToUser()) {
mediaCarouselController.logSmartspaceImpression()
}
mediaCarouselController.visibleToUser = isVisibleToUser()
}
override fun onDozeAmountChanged(linear: Float, eased: Float) {
@@ -240,9 +267,27 @@ class MediaHierarchyManager @Inject constructor(
override fun onDozingChanged(isDozing: Boolean) {
if (!isDozing) {
dozeAnimationRunning = false
// Enters lock screen from screen off
if (isLockScreenVisibleToUser()) {
mediaCarouselController.logSmartspaceImpression()
}
} else {
updateDesiredLocation()
qsExpanded = false
}
mediaCarouselController.visibleToUser = isVisibleToUser()
}
override fun onExpandedChanged(isExpanded: Boolean) {
// Enters shade from home screen
if (isHomeScreenShadeVisibleToUser()) {
mediaCarouselController.logSmartspaceImpression()
}
// Back to lock screen from bouncer
if (isLockScreenVisibleToUser()) {
mediaCarouselController.logSmartspaceImpression()
}
mediaCarouselController.visibleToUser = isVisibleToUser()
}
})
@@ -622,6 +667,36 @@ class MediaHierarchyManager @Inject constructor(
return location
}
/**
* Returns true when the media card could be visible to the user if existed.
*/
private fun isVisibleToUser(): Boolean {
return isLockScreenVisibleToUser() || isLockScreenShadeVisibleToUser() ||
isHomeScreenShadeVisibleToUser()
}
private fun isLockScreenVisibleToUser(): Boolean {
return !statusBarStateController.isDozing &&
!statusBarKeyguardViewManager.isBouncerShowing &&
statusBarStateController.state == StatusBarState.KEYGUARD &&
notifLockscreenUserManager.shouldShowLockscreenNotifications() &&
statusBarStateController.isExpanded &&
!qsExpanded
}
private fun isLockScreenShadeVisibleToUser(): Boolean {
return !statusBarStateController.isDozing &&
!statusBarKeyguardViewManager.isBouncerShowing &&
(statusBarStateController.state == StatusBarState.SHADE_LOCKED ||
(statusBarStateController.state == StatusBarState.KEYGUARD && qsExpanded))
}
private fun isHomeScreenShadeVisibleToUser(): Boolean {
return !statusBarStateController.isDozing &&
statusBarStateController.state == StatusBarState.SHADE &&
statusBarStateController.isExpanded
}
companion object {
/**
* Attached in expanded quick settings

View File

@@ -2698,6 +2698,7 @@ public class NotificationPanelViewController extends PanelViewController {
mConversationNotificationManager.onNotificationPanelExpandStateChanged(isFullyCollapsed());
mIsExpanding = false;
mMediaHierarchyManager.setCollapsingShadeFromQS(false);
mMediaHierarchyManager.setQsExpanded(mQsExpanded);
if (isFullyCollapsed()) {
DejankUtils.postAfterTraversal(new Runnable() {
@Override

View File

@@ -28,6 +28,7 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.animation.UniqueObjectHostView
import org.junit.Assert.assertNotNull
@@ -73,6 +74,8 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
private lateinit var mediaCarouselController: MediaCarouselController
@Mock
private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
@Mock
private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
@Captor
private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)>
@JvmField
@@ -90,7 +93,8 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
bypassController,
mediaCarouselController,
notificationLockscreenUserManager,
wakefulnessLifecycle)
wakefulnessLifecycle,
statusBarKeyguardViewManager)
verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture())
setupHost(lockHost, MediaHierarchyManager.LOCATION_LOCKSCREEN)
setupHost(qsHost, MediaHierarchyManager.LOCATION_QS)
@@ -98,7 +102,6 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
`when`(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
// We'll use the viewmanager to verify a few calls below, let's reset this.
clearInvocations(mediaCarouselController)
}
private fun setupHost(host: MediaHost, location: Int) {
@@ -125,7 +128,8 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
observer.onStartedGoingToSleep()
clearInvocations(mediaCarouselController)
mediaHiearchyManager.qsExpansion = 0.0f
verify(mediaCarouselController, times(0)).onDesiredLocationChanged(ArgumentMatchers.anyInt(),
verify(mediaCarouselController, times(0))
.onDesiredLocationChanged(ArgumentMatchers.anyInt(),
any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
}