Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d8539d87 authored by Jieru Shi's avatar Jieru Shi Committed by Automerger Merge Worker
Browse files

Merge "Add impression logging for media recommendation card" into sc-dev am: 9fb45cc8

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/14344088

Change-Id: Ic62573df649c7f5c4d1186433e9bebf2b3b2391c
parents 245d0d59 9fb45cc8
Loading
Loading
Loading
Loading
+34 −0
Original line number Original line Diff line number Diff line
@@ -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
+16 −6
Original line number Original line Diff line number Diff line
@@ -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;
    }
}
}
+76 −1
Original line number Original line Diff line number Diff line
@@ -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
+1 −0
Original line number Original line Diff line number Diff line
@@ -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
+7 −3
Original line number Original line Diff line number Diff line
@@ -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())
    }
    }