Merge "Add dismiss, impression, click logging for media control card, add dismiss logging for media recommendation card" into sc-dev

This commit is contained in:
Jieru Shi
2021-05-07 16:57:16 +00:00
committed by Android (Google) Code Review
5 changed files with 114 additions and 51 deletions

View File

@@ -106,7 +106,7 @@ class MediaCarouselController @Inject constructor(
private var carouselMeasureHeight: Int = 0
private var desiredHostState: MediaHostState? = null
private val mediaCarousel: MediaScrollView
private val mediaCarouselScrollHandler: MediaCarouselScrollHandler
val mediaCarouselScrollHandler: MediaCarouselScrollHandler
val mediaFrame: ViewGroup
private lateinit var settingsButton: View
private val mediaContent: ViewGroup
@@ -156,20 +156,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)
pageIndicator = mediaFrame.requireViewById(R.id.media_page_indicator)
mediaCarouselScrollHandler = MediaCarouselScrollHandler(mediaCarousel, pageIndicator,
executor, mediaManager::onSwipeToDismiss, this::updatePageIndicatorLocation,
this::closeGuts, falsingCollector, falsingManager)
executor, this::onSwipeToDismiss, this::updatePageIndicatorLocation,
this::closeGuts, falsingCollector, falsingManager, this::logSmartspaceImpression)
isRtl = context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
inflateSettingsButton()
mediaContent = mediaCarousel.requireViewById(R.id.media_carousel)
@@ -210,7 +203,7 @@ 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) {
if (mediaCarouselScrollHandler.visibleToUser) {
logSmartspaceImpression()
}
}
@@ -580,24 +573,69 @@ class MediaCarouselController @Inject constructor(
* 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 */
val visibleMediaIndex = mediaCarouselScrollHandler.visibleMediaIndex
if (MediaPlayerData.players().size > visibleMediaIndex) {
val mediaControlPanel = MediaPlayerData.players().elementAt(visibleMediaIndex)
val isMediaActive =
MediaPlayerData.playerKeys().elementAt(visibleMediaIndex).data?.active
val isRecommendationCard = mediaControlPanel.recommendationViewHolder != null
if (!isRecommendationCard && !isMediaActive) {
// Media control card time out or swiped away
return
}
// TODO(shijieru): add logging for media control card
logSmartspaceCardReported(800, // SMARTSPACE_CARD_SEEN
mediaControlPanel.mInstanceId,
isRecommendationCard,
mediaControlPanel.surfaceForSmartspaceLogging)
}
}
@JvmOverloads
fun logSmartspaceCardReported(
eventId: Int,
instanceId: Int,
isRecommendationCard: Boolean,
surface: Int,
rank: Int = mediaCarouselScrollHandler.visibleMediaIndex
) {
/* ktlint-disable max-line-length */
SysUiStatsLog.write(SysUiStatsLog.SMARTSPACE_CARD_REPORTED,
eventId,
instanceId,
if (isRecommendationCard)
SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_MEDIA_RECOMMENDATIONS
else
SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_RESUME_MEDIA,
surface,
rank,
mediaContent.getChildCount())
/* ktlint-disable max-line-length */
}
private fun onSwipeToDismiss() {
val recommendation = MediaPlayerData.players().filter {
it.recommendationViewHolder != null
}
// Use -1 as rank value to indicate user swipe to dismiss the card
if (!recommendation.isEmpty()) {
logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
recommendation.get(0).mInstanceId,
true,
recommendation.get(0).surfaceForSmartspaceLogging,
/* rank */-1)
} else {
val visibleMediaIndex = mediaCarouselScrollHandler.visibleMediaIndex
if (MediaPlayerData.players().size > visibleMediaIndex) {
val player = MediaPlayerData.players().elementAt(visibleMediaIndex)
logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
player.mInstanceId,
false,
player.surfaceForSmartspaceLogging,
/* rank */-1)
}
}
mediaManager.onSwipeToDismiss()
}
}
@VisibleForTesting
@@ -605,7 +643,7 @@ internal object MediaPlayerData {
private val EMPTY = MediaData(-1, false, 0, null, null, null, null, null,
emptyList(), emptyList(), "INVALID", null, null, null, false, null)
private data class MediaSortKey(
data class MediaSortKey(
// Is Smartspace media recommendation. When the Smartspace media is present, it should
// always be the first card in carousel.
val isSsMediaRec: Boolean,
@@ -663,6 +701,8 @@ internal object MediaPlayerData {
return -1
}
fun playerKeys() = mediaPlayers.keys
@VisibleForTesting
fun clear() {
mediaData.clear()

View File

@@ -28,13 +28,13 @@ import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.dynamicanimation.animation.SpringForce
import com.android.settingslib.Utils
import com.android.systemui.Gefingerpoken
import com.android.systemui.qs.PageIndicator
import com.android.systemui.R
import com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.plugins.FalsingManager
import com.android.wm.shell.animation.PhysicsAnimator
import com.android.systemui.qs.PageIndicator
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.wm.shell.animation.PhysicsAnimator
private const val FLING_SLOP = 1000000
private const val DISMISS_DELAY = 100L
@@ -61,7 +61,8 @@ class MediaCarouselScrollHandler(
private var translationChangedListener: () -> Unit,
private val closeGuts: () -> Unit,
private val falsingCollector: FalsingCollector,
private val falsingManager: FalsingManager
private val falsingManager: FalsingManager,
private val logSmartspaceImpression: () -> Unit
) {
/**
* Is the view in RTL
@@ -200,6 +201,13 @@ class MediaCarouselScrollHandler(
}
}
var visibleToUser: Boolean = false
set(value) {
if (field != value) {
field = value
}
}
init {
gestureDetector = GestureDetectorCompat(scrollView.context, gestureListener)
scrollView.touchListener = touchListener
@@ -460,7 +468,11 @@ class MediaCarouselScrollHandler(
scrollIntoCurrentMedia = scrollInAmount
val nowScrolledIn = scrollIntoCurrentMedia != 0
if (newIndex != visibleMediaIndex || wasScrolledIn != nowScrolledIn) {
val oldIndex = visibleMediaIndex
visibleMediaIndex = newIndex
if (oldIndex != visibleMediaIndex && visibleToUser) {
logSmartspaceImpression()
}
closeGuts()
updatePlayerVisibilities()
}

View File

@@ -105,7 +105,8 @@ public class MediaControlPanel {
private int mDevicePadding;
private int mAlbumArtSize;
// Instance id for logging purpose.
private int mInstanceId;
protected int mInstanceId = -1;
private MediaCarouselController mMediaCarouselController;
private final MediaOutputDialogFactory mMediaOutputDialogFactory;
/**
@@ -119,7 +120,7 @@ public class MediaControlPanel {
ActivityStarter activityStarter, MediaViewController mediaViewController,
SeekBarViewModel seekBarViewModel, Lazy<MediaDataManager> lazyMediaDataManager,
KeyguardDismissUtil keyguardDismissUtil, MediaOutputDialogFactory
mediaOutputDialogFactory) {
mediaOutputDialogFactory, MediaCarouselController mediaCarouselController) {
mContext = context;
mBackgroundExecutor = backgroundExecutor;
mActivityStarter = activityStarter;
@@ -128,6 +129,7 @@ public class MediaControlPanel {
mMediaDataManagerLazy = lazyMediaDataManager;
mKeyguardDismissUtil = keyguardDismissUtil;
mMediaOutputDialogFactory = mediaOutputDialogFactory;
mMediaCarouselController = mediaCarouselController;
loadDimens();
}
@@ -251,6 +253,8 @@ public class MediaControlPanel {
}
mKey = key;
MediaSession.Token token = data.getToken();
mInstanceId = data.getPackageName().hashCode();
mBackgroundColor = data.getBackgroundColor();
if (mToken == null || !mToken.equals(token)) {
mToken = token;
@@ -270,6 +274,9 @@ public class MediaControlPanel {
if (clickIntent != null) {
mPlayerViewHolder.getPlayer().setOnClickListener(v -> {
if (mMediaViewController.isGutsVisible()) return;
logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
false);
mActivityStarter.postStartActivityDismissingKeyguard(clickIntent,
buildLaunchAnimatorController(mPlayerViewHolder.getPlayer()));
});
@@ -376,6 +383,8 @@ public class MediaControlPanel {
} else {
button.setEnabled(true);
button.setOnClickListener(v -> {
logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
false);
action.run();
});
}
@@ -408,6 +417,9 @@ public class MediaControlPanel {
mPlayerViewHolder.getDismissLabel().setAlpha(isDismissible ? 1 : DISABLED_ALPHA);
mPlayerViewHolder.getDismiss().setEnabled(isDismissible);
mPlayerViewHolder.getDismiss().setOnClickListener(v -> {
logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
false);
if (mKey != null) {
closeGuts();
mKeyguardDismissUtil.executeWhenUnlocked(() -> {
@@ -550,6 +562,8 @@ public class MediaControlPanel {
// Set up long press to show guts setting panel.
mRecommendationViewHolder.getDismiss().setOnClickListener(v -> {
logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
true);
closeGuts();
mKeyguardDismissUtil.executeWhenUnlocked(() -> {
mMediaDataManagerLazy.get().dismissSmartspaceRecommendation(
@@ -652,15 +666,9 @@ public class MediaControlPanel {
}
view.setOnClickListener(v -> {
// When media recommendation card is shown, there could be only one card.
SysUiStatsLog.write(SysUiStatsLog.SMARTSPACE_CARD_REPORTED,
760, // SMARTSPACE_CARD_CLICK
mInstanceId,
SysUiStatsLog
.SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_MEDIA_RECOMMENDATIONS,
getSurfaceForSmartspaceLogging(),
/* rank */ 0,
/* cardinality */ 1);
// When media recommendation card is shown, it will always be the top card.
logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
true);
if (shouldSmartspaceRecItemOpenInForeground(action)) {
// Request to unlock the device if the activity needs to be opened in foreground.
@@ -718,7 +726,10 @@ public class MediaControlPanel {
return SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DEFAULT_SURFACE;
}
protected int getInstanceId() {
return mInstanceId;
private void logSmartspaceCardReported(int eventId, boolean isRecommendationCard) {
mMediaCarouselController.logSmartspaceCardReported(eventId,
mInstanceId,
isRecommendationCard,
getSurfaceForSmartspaceLogging());
}
}

View File

@@ -171,16 +171,15 @@ class MediaHierarchyManager @Inject constructor(
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()) {
// qs is expanded on LS shade and HS shade
if (value && (isLockScreenShadeVisibleToUser() || isHomeScreenShadeVisibleToUser())) {
mediaCarouselController.logSmartspaceImpression()
}
// Release shade and back to lock screen
if (isLockScreenVisibleToUser()) {
mediaCarouselController.logSmartspaceImpression()
}
mediaCarouselController.visibleToUser = isVisibleToUser()
mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
}
/**
@@ -257,7 +256,7 @@ class MediaHierarchyManager @Inject constructor(
if (newState == StatusBarState.SHADE_LOCKED && isLockScreenShadeVisibleToUser()) {
mediaCarouselController.logSmartspaceImpression()
}
mediaCarouselController.visibleToUser = isVisibleToUser()
mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
}
override fun onDozeAmountChanged(linear: Float, eased: Float) {
@@ -275,7 +274,7 @@ class MediaHierarchyManager @Inject constructor(
updateDesiredLocation()
qsExpanded = false
}
mediaCarouselController.visibleToUser = isVisibleToUser()
mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
}
override fun onExpandedChanged(isExpanded: Boolean) {
@@ -287,7 +286,7 @@ class MediaHierarchyManager @Inject constructor(
if (isLockScreenVisibleToUser()) {
mediaCarouselController.logSmartspaceImpression()
}
mediaCarouselController.visibleToUser = isVisibleToUser()
mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
}
})

View File

@@ -93,6 +93,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Mock private lateinit var expandedSet: ConstraintSet
@Mock private lateinit var collapsedSet: ConstraintSet
@Mock private lateinit var mediaOutputDialogFactory: MediaOutputDialogFactory
@Mock private lateinit var mediaCarouselController: MediaCarouselController
private lateinit var appIcon: ImageView
private lateinit var albumView: ImageView
private lateinit var titleText: TextView
@@ -129,7 +130,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
player = MediaControlPanel(context, bgExecutor, activityStarter, mediaViewController,
seekBarViewModel, Lazy { mediaDataManager }, keyguardDismissUtil,
mediaOutputDialogFactory)
mediaOutputDialogFactory, mediaCarouselController)
whenever(seekBarViewModel.progress).thenReturn(seekBarData)
// Mock out a view holder for the player to attach to.