Merge "Add impression logging for media recommendation card" into sc-dev

This commit is contained in:
Jieru Shi
2021-05-04 03:34:55 +00:00
committed by Android (Google) Code Review
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.ActivityStarter
import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PageIndicator 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.notification.collection.legacy.VisualStabilityManager
import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.Utils 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 { init {
mediaFrame = inflateMediaCarousel() mediaFrame = inflateMediaCarousel()
mediaCarousel = mediaFrame.requireViewById(R.id.media_carousel_scroller) mediaCarousel = mediaFrame.requireViewById(R.id.media_carousel_scroller)
@@ -203,6 +211,9 @@ 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) {
logSmartspaceImpression()
}
} }
override fun onMediaDataRemoved(key: String) { override fun onMediaDataRemoved(key: String) {
@@ -567,6 +578,29 @@ class MediaCarouselController @Inject constructor(
mediaCarouselScrollHandler.playerWidthPlusPadding = playerWidthPlusPadding 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 @VisibleForTesting

View File

@@ -104,6 +104,8 @@ public class MediaControlPanel {
private int mBackgroundColor; private int mBackgroundColor;
private int mDevicePadding; private int mDevicePadding;
private int mAlbumArtSize; private int mAlbumArtSize;
// Instance id for logging purpose.
private int mInstanceId;
private final MediaOutputDialogFactory mMediaOutputDialogFactory; private final MediaOutputDialogFactory mMediaOutputDialogFactory;
/** /**
@@ -472,6 +474,7 @@ public class MediaControlPanel {
mRecommendationViewHolder.getCardIcon().setColorFilter(primaryColor); mRecommendationViewHolder.getCardIcon().setColorFilter(primaryColor);
mRecommendationViewHolder.getCardText().setTextColor(primaryColor); mRecommendationViewHolder.getCardText().setTextColor(primaryColor);
mInstanceId = target.getSmartspaceTargetId().hashCode();
mRecommendationViewHolder.getRecommendations() mRecommendationViewHolder.getRecommendations()
.setBackgroundTintList(ColorStateList.valueOf(backgroundColor)); .setBackgroundTintList(ColorStateList.valueOf(backgroundColor));
mBackgroundColor = backgroundColor; mBackgroundColor = backgroundColor;
@@ -530,7 +533,6 @@ public class MediaControlPanel {
setSmartspaceRecItemOnClickListener( setSmartspaceRecItemOnClickListener(
mediaCoverImageView, mediaCoverImageView,
recommendation, recommendation,
target.getSmartspaceTargetId(),
null); null);
if (uiComponentIndex < MEDIA_RECOMMENDATION_ITEMS_PER_ROW) { if (uiComponentIndex < MEDIA_RECOMMENDATION_ITEMS_PER_ROW) {
@@ -649,7 +651,6 @@ public class MediaControlPanel {
private void setSmartspaceRecItemOnClickListener( private void setSmartspaceRecItemOnClickListener(
@NonNull View view, @NonNull View view,
@NonNull SmartspaceAction action, @NonNull SmartspaceAction action,
@NonNull String targetId,
@Nullable View.OnClickListener callback) { @Nullable View.OnClickListener callback) {
if (view == null || action == null || action.getIntent() == null) { if (view == null || action == null || action.getIntent() == null) {
Log.e(TAG, "No tap action can be set up"); 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. // When media recommendation card is shown, there could be only one card.
SysUiStatsLog.write(SysUiStatsLog.SMARTSPACE_CARD_REPORTED, SysUiStatsLog.write(SysUiStatsLog.SMARTSPACE_CARD_REPORTED,
760, // SMARTSPACE_CARD_CLICK 760, // SMARTSPACE_CARD_CLICK
targetId.hashCode(), mInstanceId,
SysUiStatsLog SysUiStatsLog
.SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_MEDIA_RECOMMENDATIONS, .SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_MEDIA_RECOMMENDATIONS,
getSurfaceForSmartspaceLogging(mMediaViewController.getCurrentEndLocation()), getSurfaceForSmartspaceLogging(),
/* rank */ 1, /* rank */ 0,
/* cardinality */ 1); /* cardinality */ 1);
if (shouldSmartspaceRecItemOpenInForeground(action)) { if (shouldSmartspaceRecItemOpenInForeground(action)) {
@@ -708,7 +709,12 @@ public class MediaControlPanel {
return false; 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 if (currentEndLocation == MediaHierarchyManager.LOCATION_QQS
|| currentEndLocation == MediaHierarchyManager.LOCATION_QS) { || currentEndLocation == MediaHierarchyManager.LOCATION_QS) {
return SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE; 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; 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.statusbar.policy.KeyguardStateController
import com.android.systemui.util.animation.UniqueObjectHostView import com.android.systemui.util.animation.UniqueObjectHostView
import javax.inject.Inject import javax.inject.Inject
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
/** /**
* Similarly to isShown but also excludes views that have 0 alpha * 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 bypassController: KeyguardBypassController,
private val mediaCarouselController: MediaCarouselController, private val mediaCarouselController: MediaCarouselController,
private val notifLockscreenUserManager: NotificationLockscreenUserManager, 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 * 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, * 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. * we wouldn't want to transition in that case.
@@ -231,6 +253,11 @@ class MediaHierarchyManager @Inject constructor(
override fun onStateChanged(newState: Int) { override fun onStateChanged(newState: Int) {
updateTargetState() updateTargetState()
// Enters shade from lock screen
if (newState == StatusBarState.SHADE_LOCKED && isLockScreenShadeVisibleToUser()) {
mediaCarouselController.logSmartspaceImpression()
}
mediaCarouselController.visibleToUser = isVisibleToUser()
} }
override fun onDozeAmountChanged(linear: Float, eased: Float) { override fun onDozeAmountChanged(linear: Float, eased: Float) {
@@ -240,9 +267,27 @@ class MediaHierarchyManager @Inject constructor(
override fun onDozingChanged(isDozing: Boolean) { override fun onDozingChanged(isDozing: Boolean) {
if (!isDozing) { if (!isDozing) {
dozeAnimationRunning = false dozeAnimationRunning = false
// Enters lock screen from screen off
if (isLockScreenVisibleToUser()) {
mediaCarouselController.logSmartspaceImpression()
}
} else { } else {
updateDesiredLocation() 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 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 { companion object {
/** /**
* Attached in expanded quick settings * Attached in expanded quick settings

View File

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

View File

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