Merge "Add impression logging for media recommendation card" into sc-dev
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user