diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index 8d9a4be30ab62..6a2d0afa813b1 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index 1124ded3f32c7..ed16777110c90 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -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; + } } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt index 30bc8c1733bc4..a80a410d32dbe 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index c79e503379bda..da6f1de67e461 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -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 diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt index d86dfa5fa5f73..406f40c75735c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt @@ -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()) }