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.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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user