Loading packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +116 −71 Original line number Diff line number Diff line Loading @@ -37,7 +37,7 @@ import javax.inject.Provider private const val TAG = "MediaCarouselController" private val settingsIntent = Intent().setAction(ACTION_MEDIA_CONTROLS_SETTINGS) private const val DEBUG = false private val DEBUG = Log.isLoggable(TAG, Log.DEBUG) /** * Class that is responsible for keeping the view carousel up to date. Loading Loading @@ -209,41 +209,56 @@ class MediaCarouselController @Inject constructor( oldKey: String?, data: MediaData, immediately: Boolean, isSsReactivated: Boolean receivedSmartspaceCardLatency: Int ) { if (addOrUpdatePlayer(key, oldKey, data)) { // Log card received if a new resumable media card is added MediaPlayerData.getMediaPlayer(key)?.let { /* ktlint-disable max-line-length */ logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED it.mInstanceId, it.mUid, /* isRecommendationCard */ false, it.surfaceForSmartspaceLogging, intArrayOf( SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN), rank = MediaPlayerData.getMediaPlayerIndex(key)) /* ktlint-disable max-line-length */ } if (mediaCarouselScrollHandler.visibleToUser && mediaCarouselScrollHandler.visibleMediaIndex == MediaPlayerData.getMediaPlayerIndex(key)) { logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded) } if (isSsReactivated) { // If resumable media is reactivated by headphone connection, update instance // id for each card and log a receive event. } else if (receivedSmartspaceCardLatency != 0) { // Log resume card received if resumable media card is reactivated and // resume card is ranked first MediaPlayerData.players().forEachIndexed { index, it -> if (it.recommendationViewHolder == null) { it.mInstanceId = SmallHash.hash(it.mUid + systemClock.currentTimeMillis().toInt()) it.mIsImpressed = false /* ktlint-disable max-line-length */ logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED it.mInstanceId, it.mUid, /* isRecommendationCard */ false, it.surfaceForSmartspaceLogging, rank = index) } intArrayOf( SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN), rank = index, receivedLatencyMillis = receivedSmartspaceCardLatency) /* ktlint-disable max-line-length */ } } // If media container area already visible to the user, log impression for // reactivated card. if (mediaCarouselScrollHandler.visibleToUser && isSsReactivated && !mediaCarouselScrollHandler.qsExpanded) { // It could happen that reactived media player isn't visible to user because // of it is a resumption card. !mediaCarouselScrollHandler.qsExpanded) { logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded) } } val canRemove = data.isPlaying?.let { !it } ?: data.isClearable && !data.active if (canRemove && !Utils.useMediaResumption(context)) { // This view isn't playing, let's remove this! This happens e.g when Loading @@ -262,28 +277,51 @@ class MediaCarouselController @Inject constructor( override fun onSmartspaceMediaDataLoaded( key: String, data: SmartspaceMediaData, shouldPrioritize: Boolean shouldPrioritize: Boolean, isSsReactivated: Boolean ) { if (DEBUG) Log.d(TAG, "Loading Smartspace media update") if (data.isActive) { addSmartspaceMediaRecommendations(key, data, shouldPrioritize) MediaPlayerData.getMediaPlayer(key)?.let { if (isSsReactivated && shouldPrioritize) { // Log resume card received if resumable media card is reactivated and // recommendation card is valid and ranked first MediaPlayerData.players().forEachIndexed { index, it -> if (it.recommendationViewHolder == null) { it.mInstanceId = SmallHash.hash(it.mUid + systemClock.currentTimeMillis().toInt()) it.mIsImpressed = false /* ktlint-disable max-line-length */ logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED it.mInstanceId, it.mUid, /* isRecommendationCard */ true, it.surfaceForSmartspaceLogging, rank = MediaPlayerData.getMediaPlayerIndex(key)) if (mediaCarouselScrollHandler.visibleToUser && mediaCarouselScrollHandler.visibleMediaIndex == MediaPlayerData.getMediaPlayerIndex(key)) { logSmartspaceCardReported(800, // SMARTSPACE_CARD_SEEN /* isRecommendationCard */ false, intArrayOf( SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN), rank = index, receivedLatencyMillis = (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis).toInt()) /* ktlint-disable max-line-length */ } } } addSmartspaceMediaRecommendations(key, data, shouldPrioritize) MediaPlayerData.getMediaPlayer(key)?.let { /* ktlint-disable max-line-length */ logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED it.mInstanceId, it.mUid, /* isRecommendationCard */ true, it.surfaceForSmartspaceLogging) intArrayOf( SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN), rank = MediaPlayerData.getMediaPlayerIndex(key), receivedLatencyMillis = (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis).toInt()) /* ktlint-disable max-line-length */ } if (mediaCarouselScrollHandler.visibleToUser && mediaCarouselScrollHandler.visibleMediaIndex == MediaPlayerData.getMediaPlayerIndex(key)) { logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded) } } else { onSmartspaceMediaDataRemoved(data.targetId, immediately = true) Loading Loading @@ -718,7 +756,8 @@ class MediaCarouselController @Inject constructor( mediaControlPanel.mInstanceId, mediaControlPanel.mUid, isRecommendationCard, mediaControlPanel.surfaceForSmartspaceLogging) intArrayOf(mediaControlPanel.surfaceForSmartspaceLogging)) mediaControlPanel.mIsImpressed = true } } Loading @@ -731,12 +770,15 @@ class MediaCarouselController @Inject constructor( * instanceId * @param uid uid for the application that media comes from * @param isRecommendationCard whether the card is media recommendation * @param surface which display surface the media card is on (e.g. lockscreen, shade) * @param surfaces list of display surfaces the media card is on (e.g. lockscreen, shade) when * the event happened * @param interactedSubcardRank the rank for interacted media item for recommendation card, -1 * for tapping on card but not on any media item, 0 for first media item, 1 for second, etc. * @param interactedSubcardCardinality how many media items were shown to the user when there * is user interaction * @param rank the rank for media card in the media carousel, starting from 0 * @param receivedLatencyMillis latency in milliseconds for card received events. E.g. latency * between headphone connection to sysUI displays media recommendation card * */ fun logSmartspaceCardReported( Loading @@ -744,10 +786,11 @@ class MediaCarouselController @Inject constructor( instanceId: Int, uid: Int, isRecommendationCard: Boolean, surface: Int, surfaces: IntArray, interactedSubcardRank: Int = 0, interactedSubcardCardinality: Int = 0, rank: Int = mediaCarouselScrollHandler.visibleMediaIndex rank: Int = mediaCarouselScrollHandler.visibleMediaIndex, receivedLatencyMillis: Int = 0 ) { // Only log media resume card when Smartspace data is available if (!isRecommendationCard && Loading @@ -756,6 +799,8 @@ class MediaCarouselController @Inject constructor( return } val cardinality = mediaContent.getChildCount() surfaces.forEach { surface -> /* ktlint-disable max-line-length */ SysUiStatsLog.write(SysUiStatsLog.SMARTSPACE_CARD_REPORTED, eventId, Loading @@ -765,7 +810,7 @@ class MediaCarouselController @Inject constructor( SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD, surface, rank, mediaContent.getChildCount(), cardinality, if (isRecommendationCard) 15 // MEDIA_RECOMMENDATION else Loading @@ -773,33 +818,33 @@ class MediaCarouselController @Inject constructor( uid, interactedSubcardRank, interactedSubcardCardinality, 0 // received_latency_millis receivedLatencyMillis ) /* ktlint-disable max-line-length */ if (DEBUG) { Log.d(TAG, "Log Smartspace card event id: $eventId instance id: $instanceId" + " surface: $surface rank: $rank cardinality: $cardinality " + "isRecommendationCard: $isRecommendationCard uid: $uid " + "interactedSubcardRank: $interactedSubcardRank " + "interactedSubcardCardinality: $interactedSubcardCardinality " + "received_latency_millis: $receivedLatencyMillis") } } } 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, recommendation.get(0).mUid, true, recommendation.get(0).surfaceForSmartspaceLogging, rank = -1) } else { val visibleMediaIndex = mediaCarouselScrollHandler.visibleMediaIndex if (MediaPlayerData.players().size > visibleMediaIndex) { val player = MediaPlayerData.players().elementAt(visibleMediaIndex) MediaPlayerData.players().forEachIndexed { index, it -> if (it.mIsImpressed) { logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS player.mInstanceId, player.mUid, false, player.surfaceForSmartspaceLogging, it.mInstanceId, it.mUid, it.recommendationViewHolder != null, intArrayOf(it.surfaceForSmartspaceLogging), // Use -1 as rank value to indicate user swipe to dismiss the card rank = -1) // Reset card impressed state when swipe to dismissed it.mIsImpressed = false } } mediaManager.onSwipeToDismiss() Loading packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +12 −3 Original line number Diff line number Diff line Loading @@ -59,6 +59,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.phone.KeyguardDismissUtil; import com.android.systemui.util.animation.TransitionLayout; import com.android.systemui.util.time.SystemClock; import java.net.URISyntaxException; import java.util.List; Loading Loading @@ -121,6 +122,9 @@ public class MediaControlPanel { private MediaCarouselController mMediaCarouselController; private final MediaOutputDialogFactory mMediaOutputDialogFactory; private final FalsingManager mFalsingManager; // Used for swipe-to-dismiss logging. protected boolean mIsImpressed = false; private SystemClock mSystemClock; /** * Initialize a new control panel Loading @@ -134,7 +138,7 @@ public class MediaControlPanel { SeekBarViewModel seekBarViewModel, Lazy<MediaDataManager> lazyMediaDataManager, KeyguardDismissUtil keyguardDismissUtil, MediaOutputDialogFactory mediaOutputDialogFactory, MediaCarouselController mediaCarouselController, FalsingManager falsingManager) { FalsingManager falsingManager, SystemClock systemClock) { mContext = context; mBackgroundExecutor = backgroundExecutor; mActivityStarter = activityStarter; Loading @@ -145,6 +149,8 @@ public class MediaControlPanel { mMediaOutputDialogFactory = mediaOutputDialogFactory; mMediaCarouselController = mediaCarouselController; mFalsingManager = falsingManager; mSystemClock = systemClock; loadDimens(); mSeekBarViewModel.setLogSmartspaceClick(() -> { Loading Loading @@ -291,7 +297,10 @@ public class MediaControlPanel { } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "Unable to look up package name", e); } mInstanceId = SmallHash.hash(mUid); // Only assigns instance id if it's unassigned. if (mInstanceId == -1) { mInstanceId = SmallHash.hash(mUid + (int) mSystemClock.currentTimeMillis()); } mBackgroundColor = data.getBackgroundColor(); if (mToken == null || !mToken.equals(token)) { Loading Loading @@ -885,7 +894,7 @@ public class MediaControlPanel { mInstanceId, mUid, isRecommendationCard, getSurfaceForSmartspaceLogging(), new int[]{getSurfaceForSmartspaceLogging()}, interactedSubcardRank, interactedSubcardCardinality); } Loading packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt +3 −2 Original line number Diff line number Diff line Loading @@ -32,7 +32,7 @@ class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener, oldKey: String?, data: MediaData, immediately: Boolean, isSsReactivated: Boolean receivedSmartspaceCardLatency: Int ) { if (oldKey != null && oldKey != key && entries.contains(oldKey)) { entries[key] = data to entries.remove(oldKey)?.second Loading @@ -46,7 +46,8 @@ class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener, override fun onSmartspaceMediaDataLoaded( key: String, data: SmartspaceMediaData, shouldPrioritize: Boolean shouldPrioritize: Boolean, isSsReactivated: Boolean ) { listeners.toSet().forEach { it.onSmartspaceMediaDataLoaded(key, data) } } Loading packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt +24 −18 Original line number Diff line number Diff line Loading @@ -56,7 +56,6 @@ internal val SMARTSPACE_MAX_AGE = SystemProperties class MediaDataFilter @Inject constructor( private val context: Context, private val broadcastDispatcher: BroadcastDispatcher, private val mediaResumeListener: MediaResumeListener, private val lockscreenUserManager: NotificationLockscreenUserManager, @Main private val executor: Executor, private val systemClock: SystemClock Loading Loading @@ -88,7 +87,7 @@ class MediaDataFilter @Inject constructor( oldKey: String?, data: MediaData, immediately: Boolean, isSsReactivated: Boolean receivedSmartspaceCardLatency: Int ) { if (oldKey != null && oldKey != key) { allEntries.remove(oldKey) Loading @@ -106,14 +105,15 @@ class MediaDataFilter @Inject constructor( // Notify listeners listeners.forEach { it.onMediaDataLoaded(key, oldKey, data, isSsReactivated = isSsReactivated) it.onMediaDataLoaded(key, oldKey, data) } } override fun onSmartspaceMediaDataLoaded( key: String, data: SmartspaceMediaData, shouldPrioritize: Boolean shouldPrioritize: Boolean, isSsReactivated: Boolean ) { if (!data.isActive) { Log.d(TAG, "Inactive recommendation data. Skip triggering.") Loading @@ -123,8 +123,6 @@ class MediaDataFilter @Inject constructor( // Override the pass-in value here, as the order of Smartspace card is only determined here. var shouldPrioritizeMutable = false smartspaceMediaData = data // Override the pass-in value here, as the Smartspace reactivation could only happen here. var isSsReactivated = false // Before forwarding the smartspace target, first check if we have recently inactive media val sorted = userEntries.toSortedMap(compareBy { Loading @@ -139,18 +137,25 @@ class MediaDataFilter @Inject constructor( smartspaceMaxAgeMillis = TimeUnit.SECONDS.toMillis(smartspaceMaxAgeSeconds) } } val activeMedia = userEntries.filter { (key, value) -> value.active } var isSsReactivatedMutable = activeMedia.isEmpty() && userEntries.isNotEmpty() if (timeSinceActive < smartspaceMaxAgeMillis) { // It could happen there are existing active media resume cards, then we don't need to // reactivate. if (isSsReactivatedMutable) { val lastActiveKey = sorted.lastKey() // most recently active // Notify listeners to consider this media active Log.d(TAG, "reactivating $lastActiveKey instead of smartspace") reactivatedKey = lastActiveKey if (MediaPlayerData.firstActiveMediaIndex() == -1) { isSsReactivated = true } val mediaData = sorted.get(lastActiveKey)!!.copy(active = true) listeners.forEach { it.onMediaDataLoaded(lastActiveKey, lastActiveKey, mediaData, isSsReactivated = isSsReactivated) receivedSmartspaceCardLatency = (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis) .toInt()) } } } else { // Mark to prioritize Smartspace card if no recent media. Loading @@ -161,7 +166,8 @@ class MediaDataFilter @Inject constructor( Log.d(TAG, "Invalid recommendation data. Skip showing the rec card") return } listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) } listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable, isSsReactivatedMutable) } } override fun onMediaDataRemoved(key: String) { Loading packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +14 −8 Original line number Diff line number Diff line Loading @@ -84,7 +84,7 @@ private val LOADING = MediaData(-1, false, 0, null, null, null, null, null, emptyList(), emptyList(), "INVALID", null, null, null, true, null) @VisibleForTesting internal val EMPTY_SMARTSPACE_MEDIA_DATA = SmartspaceMediaData("INVALID", false, false, "INVALID", null, emptyList(), null, 0) "INVALID", null, emptyList(), null, 0, 0) fun isMediaNotification(sbn: StatusBarNotification): Boolean { return sbn.notification.isMediaNotification() Loading Loading @@ -852,15 +852,16 @@ class MediaDataManager( * until the next refresh-round before UI becomes visible. True by default to take in place * immediately. * * @param isSsReactivated indicates transition from a state with no active media players to * a state with active media players upon receiving Smartspace media data. * @param receivedSmartspaceCardLatency is the latency between headphone connects and sysUI * displays Smartspace media targets. Will be 0 if the data is not activated by Smartspace * signal. */ fun onMediaDataLoaded( key: String, oldKey: String?, data: MediaData, immediately: Boolean = true, isSsReactivated: Boolean = false receivedSmartspaceCardLatency: Int = 0 ) {} /** Loading @@ -869,11 +870,15 @@ class MediaDataManager( * @param shouldPrioritize indicates the sorting priority of the Smartspace card. If true, * it will be prioritized as the first card. Otherwise, it will show up as the last card as * default. * * @param isSsReactivated indicates resume media card is reactivated by Smartspace * recommendation signal */ fun onSmartspaceMediaDataLoaded( key: String, data: SmartspaceMediaData, shouldPrioritize: Boolean = false shouldPrioritize: Boolean = false, isSsReactivated: Boolean = false ) {} /** Called whenever a previously existing Media notification was removed. */ Loading Loading @@ -909,12 +914,13 @@ class MediaDataManager( packageName(target)?.let { return SmartspaceMediaData(target.smartspaceTargetId, isActive, true, it, target.baseAction, target.iconGrid, dismissIntent, 0) dismissIntent, 0, target.creationTimeMillis) } return EMPTY_SMARTSPACE_MEDIA_DATA .copy(targetId = target.smartspaceTargetId, isActive = isActive, dismissIntent = dismissIntent) dismissIntent = dismissIntent, headphoneConnectionTimeMillis = target.creationTimeMillis) } private fun packageName(target: SmartspaceTarget): String? { Loading Loading
packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +116 −71 Original line number Diff line number Diff line Loading @@ -37,7 +37,7 @@ import javax.inject.Provider private const val TAG = "MediaCarouselController" private val settingsIntent = Intent().setAction(ACTION_MEDIA_CONTROLS_SETTINGS) private const val DEBUG = false private val DEBUG = Log.isLoggable(TAG, Log.DEBUG) /** * Class that is responsible for keeping the view carousel up to date. Loading Loading @@ -209,41 +209,56 @@ class MediaCarouselController @Inject constructor( oldKey: String?, data: MediaData, immediately: Boolean, isSsReactivated: Boolean receivedSmartspaceCardLatency: Int ) { if (addOrUpdatePlayer(key, oldKey, data)) { // Log card received if a new resumable media card is added MediaPlayerData.getMediaPlayer(key)?.let { /* ktlint-disable max-line-length */ logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED it.mInstanceId, it.mUid, /* isRecommendationCard */ false, it.surfaceForSmartspaceLogging, intArrayOf( SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN), rank = MediaPlayerData.getMediaPlayerIndex(key)) /* ktlint-disable max-line-length */ } if (mediaCarouselScrollHandler.visibleToUser && mediaCarouselScrollHandler.visibleMediaIndex == MediaPlayerData.getMediaPlayerIndex(key)) { logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded) } if (isSsReactivated) { // If resumable media is reactivated by headphone connection, update instance // id for each card and log a receive event. } else if (receivedSmartspaceCardLatency != 0) { // Log resume card received if resumable media card is reactivated and // resume card is ranked first MediaPlayerData.players().forEachIndexed { index, it -> if (it.recommendationViewHolder == null) { it.mInstanceId = SmallHash.hash(it.mUid + systemClock.currentTimeMillis().toInt()) it.mIsImpressed = false /* ktlint-disable max-line-length */ logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED it.mInstanceId, it.mUid, /* isRecommendationCard */ false, it.surfaceForSmartspaceLogging, rank = index) } intArrayOf( SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN), rank = index, receivedLatencyMillis = receivedSmartspaceCardLatency) /* ktlint-disable max-line-length */ } } // If media container area already visible to the user, log impression for // reactivated card. if (mediaCarouselScrollHandler.visibleToUser && isSsReactivated && !mediaCarouselScrollHandler.qsExpanded) { // It could happen that reactived media player isn't visible to user because // of it is a resumption card. !mediaCarouselScrollHandler.qsExpanded) { logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded) } } val canRemove = data.isPlaying?.let { !it } ?: data.isClearable && !data.active if (canRemove && !Utils.useMediaResumption(context)) { // This view isn't playing, let's remove this! This happens e.g when Loading @@ -262,28 +277,51 @@ class MediaCarouselController @Inject constructor( override fun onSmartspaceMediaDataLoaded( key: String, data: SmartspaceMediaData, shouldPrioritize: Boolean shouldPrioritize: Boolean, isSsReactivated: Boolean ) { if (DEBUG) Log.d(TAG, "Loading Smartspace media update") if (data.isActive) { addSmartspaceMediaRecommendations(key, data, shouldPrioritize) MediaPlayerData.getMediaPlayer(key)?.let { if (isSsReactivated && shouldPrioritize) { // Log resume card received if resumable media card is reactivated and // recommendation card is valid and ranked first MediaPlayerData.players().forEachIndexed { index, it -> if (it.recommendationViewHolder == null) { it.mInstanceId = SmallHash.hash(it.mUid + systemClock.currentTimeMillis().toInt()) it.mIsImpressed = false /* ktlint-disable max-line-length */ logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED it.mInstanceId, it.mUid, /* isRecommendationCard */ true, it.surfaceForSmartspaceLogging, rank = MediaPlayerData.getMediaPlayerIndex(key)) if (mediaCarouselScrollHandler.visibleToUser && mediaCarouselScrollHandler.visibleMediaIndex == MediaPlayerData.getMediaPlayerIndex(key)) { logSmartspaceCardReported(800, // SMARTSPACE_CARD_SEEN /* isRecommendationCard */ false, intArrayOf( SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN), rank = index, receivedLatencyMillis = (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis).toInt()) /* ktlint-disable max-line-length */ } } } addSmartspaceMediaRecommendations(key, data, shouldPrioritize) MediaPlayerData.getMediaPlayer(key)?.let { /* ktlint-disable max-line-length */ logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED it.mInstanceId, it.mUid, /* isRecommendationCard */ true, it.surfaceForSmartspaceLogging) intArrayOf( SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN), rank = MediaPlayerData.getMediaPlayerIndex(key), receivedLatencyMillis = (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis).toInt()) /* ktlint-disable max-line-length */ } if (mediaCarouselScrollHandler.visibleToUser && mediaCarouselScrollHandler.visibleMediaIndex == MediaPlayerData.getMediaPlayerIndex(key)) { logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded) } } else { onSmartspaceMediaDataRemoved(data.targetId, immediately = true) Loading Loading @@ -718,7 +756,8 @@ class MediaCarouselController @Inject constructor( mediaControlPanel.mInstanceId, mediaControlPanel.mUid, isRecommendationCard, mediaControlPanel.surfaceForSmartspaceLogging) intArrayOf(mediaControlPanel.surfaceForSmartspaceLogging)) mediaControlPanel.mIsImpressed = true } } Loading @@ -731,12 +770,15 @@ class MediaCarouselController @Inject constructor( * instanceId * @param uid uid for the application that media comes from * @param isRecommendationCard whether the card is media recommendation * @param surface which display surface the media card is on (e.g. lockscreen, shade) * @param surfaces list of display surfaces the media card is on (e.g. lockscreen, shade) when * the event happened * @param interactedSubcardRank the rank for interacted media item for recommendation card, -1 * for tapping on card but not on any media item, 0 for first media item, 1 for second, etc. * @param interactedSubcardCardinality how many media items were shown to the user when there * is user interaction * @param rank the rank for media card in the media carousel, starting from 0 * @param receivedLatencyMillis latency in milliseconds for card received events. E.g. latency * between headphone connection to sysUI displays media recommendation card * */ fun logSmartspaceCardReported( Loading @@ -744,10 +786,11 @@ class MediaCarouselController @Inject constructor( instanceId: Int, uid: Int, isRecommendationCard: Boolean, surface: Int, surfaces: IntArray, interactedSubcardRank: Int = 0, interactedSubcardCardinality: Int = 0, rank: Int = mediaCarouselScrollHandler.visibleMediaIndex rank: Int = mediaCarouselScrollHandler.visibleMediaIndex, receivedLatencyMillis: Int = 0 ) { // Only log media resume card when Smartspace data is available if (!isRecommendationCard && Loading @@ -756,6 +799,8 @@ class MediaCarouselController @Inject constructor( return } val cardinality = mediaContent.getChildCount() surfaces.forEach { surface -> /* ktlint-disable max-line-length */ SysUiStatsLog.write(SysUiStatsLog.SMARTSPACE_CARD_REPORTED, eventId, Loading @@ -765,7 +810,7 @@ class MediaCarouselController @Inject constructor( SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD, surface, rank, mediaContent.getChildCount(), cardinality, if (isRecommendationCard) 15 // MEDIA_RECOMMENDATION else Loading @@ -773,33 +818,33 @@ class MediaCarouselController @Inject constructor( uid, interactedSubcardRank, interactedSubcardCardinality, 0 // received_latency_millis receivedLatencyMillis ) /* ktlint-disable max-line-length */ if (DEBUG) { Log.d(TAG, "Log Smartspace card event id: $eventId instance id: $instanceId" + " surface: $surface rank: $rank cardinality: $cardinality " + "isRecommendationCard: $isRecommendationCard uid: $uid " + "interactedSubcardRank: $interactedSubcardRank " + "interactedSubcardCardinality: $interactedSubcardCardinality " + "received_latency_millis: $receivedLatencyMillis") } } } 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, recommendation.get(0).mUid, true, recommendation.get(0).surfaceForSmartspaceLogging, rank = -1) } else { val visibleMediaIndex = mediaCarouselScrollHandler.visibleMediaIndex if (MediaPlayerData.players().size > visibleMediaIndex) { val player = MediaPlayerData.players().elementAt(visibleMediaIndex) MediaPlayerData.players().forEachIndexed { index, it -> if (it.mIsImpressed) { logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS player.mInstanceId, player.mUid, false, player.surfaceForSmartspaceLogging, it.mInstanceId, it.mUid, it.recommendationViewHolder != null, intArrayOf(it.surfaceForSmartspaceLogging), // Use -1 as rank value to indicate user swipe to dismiss the card rank = -1) // Reset card impressed state when swipe to dismissed it.mIsImpressed = false } } mediaManager.onSwipeToDismiss() Loading
packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +12 −3 Original line number Diff line number Diff line Loading @@ -59,6 +59,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.phone.KeyguardDismissUtil; import com.android.systemui.util.animation.TransitionLayout; import com.android.systemui.util.time.SystemClock; import java.net.URISyntaxException; import java.util.List; Loading Loading @@ -121,6 +122,9 @@ public class MediaControlPanel { private MediaCarouselController mMediaCarouselController; private final MediaOutputDialogFactory mMediaOutputDialogFactory; private final FalsingManager mFalsingManager; // Used for swipe-to-dismiss logging. protected boolean mIsImpressed = false; private SystemClock mSystemClock; /** * Initialize a new control panel Loading @@ -134,7 +138,7 @@ public class MediaControlPanel { SeekBarViewModel seekBarViewModel, Lazy<MediaDataManager> lazyMediaDataManager, KeyguardDismissUtil keyguardDismissUtil, MediaOutputDialogFactory mediaOutputDialogFactory, MediaCarouselController mediaCarouselController, FalsingManager falsingManager) { FalsingManager falsingManager, SystemClock systemClock) { mContext = context; mBackgroundExecutor = backgroundExecutor; mActivityStarter = activityStarter; Loading @@ -145,6 +149,8 @@ public class MediaControlPanel { mMediaOutputDialogFactory = mediaOutputDialogFactory; mMediaCarouselController = mediaCarouselController; mFalsingManager = falsingManager; mSystemClock = systemClock; loadDimens(); mSeekBarViewModel.setLogSmartspaceClick(() -> { Loading Loading @@ -291,7 +297,10 @@ public class MediaControlPanel { } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "Unable to look up package name", e); } mInstanceId = SmallHash.hash(mUid); // Only assigns instance id if it's unassigned. if (mInstanceId == -1) { mInstanceId = SmallHash.hash(mUid + (int) mSystemClock.currentTimeMillis()); } mBackgroundColor = data.getBackgroundColor(); if (mToken == null || !mToken.equals(token)) { Loading Loading @@ -885,7 +894,7 @@ public class MediaControlPanel { mInstanceId, mUid, isRecommendationCard, getSurfaceForSmartspaceLogging(), new int[]{getSurfaceForSmartspaceLogging()}, interactedSubcardRank, interactedSubcardCardinality); } Loading
packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt +3 −2 Original line number Diff line number Diff line Loading @@ -32,7 +32,7 @@ class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener, oldKey: String?, data: MediaData, immediately: Boolean, isSsReactivated: Boolean receivedSmartspaceCardLatency: Int ) { if (oldKey != null && oldKey != key && entries.contains(oldKey)) { entries[key] = data to entries.remove(oldKey)?.second Loading @@ -46,7 +46,8 @@ class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener, override fun onSmartspaceMediaDataLoaded( key: String, data: SmartspaceMediaData, shouldPrioritize: Boolean shouldPrioritize: Boolean, isSsReactivated: Boolean ) { listeners.toSet().forEach { it.onSmartspaceMediaDataLoaded(key, data) } } Loading
packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt +24 −18 Original line number Diff line number Diff line Loading @@ -56,7 +56,6 @@ internal val SMARTSPACE_MAX_AGE = SystemProperties class MediaDataFilter @Inject constructor( private val context: Context, private val broadcastDispatcher: BroadcastDispatcher, private val mediaResumeListener: MediaResumeListener, private val lockscreenUserManager: NotificationLockscreenUserManager, @Main private val executor: Executor, private val systemClock: SystemClock Loading Loading @@ -88,7 +87,7 @@ class MediaDataFilter @Inject constructor( oldKey: String?, data: MediaData, immediately: Boolean, isSsReactivated: Boolean receivedSmartspaceCardLatency: Int ) { if (oldKey != null && oldKey != key) { allEntries.remove(oldKey) Loading @@ -106,14 +105,15 @@ class MediaDataFilter @Inject constructor( // Notify listeners listeners.forEach { it.onMediaDataLoaded(key, oldKey, data, isSsReactivated = isSsReactivated) it.onMediaDataLoaded(key, oldKey, data) } } override fun onSmartspaceMediaDataLoaded( key: String, data: SmartspaceMediaData, shouldPrioritize: Boolean shouldPrioritize: Boolean, isSsReactivated: Boolean ) { if (!data.isActive) { Log.d(TAG, "Inactive recommendation data. Skip triggering.") Loading @@ -123,8 +123,6 @@ class MediaDataFilter @Inject constructor( // Override the pass-in value here, as the order of Smartspace card is only determined here. var shouldPrioritizeMutable = false smartspaceMediaData = data // Override the pass-in value here, as the Smartspace reactivation could only happen here. var isSsReactivated = false // Before forwarding the smartspace target, first check if we have recently inactive media val sorted = userEntries.toSortedMap(compareBy { Loading @@ -139,18 +137,25 @@ class MediaDataFilter @Inject constructor( smartspaceMaxAgeMillis = TimeUnit.SECONDS.toMillis(smartspaceMaxAgeSeconds) } } val activeMedia = userEntries.filter { (key, value) -> value.active } var isSsReactivatedMutable = activeMedia.isEmpty() && userEntries.isNotEmpty() if (timeSinceActive < smartspaceMaxAgeMillis) { // It could happen there are existing active media resume cards, then we don't need to // reactivate. if (isSsReactivatedMutable) { val lastActiveKey = sorted.lastKey() // most recently active // Notify listeners to consider this media active Log.d(TAG, "reactivating $lastActiveKey instead of smartspace") reactivatedKey = lastActiveKey if (MediaPlayerData.firstActiveMediaIndex() == -1) { isSsReactivated = true } val mediaData = sorted.get(lastActiveKey)!!.copy(active = true) listeners.forEach { it.onMediaDataLoaded(lastActiveKey, lastActiveKey, mediaData, isSsReactivated = isSsReactivated) receivedSmartspaceCardLatency = (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis) .toInt()) } } } else { // Mark to prioritize Smartspace card if no recent media. Loading @@ -161,7 +166,8 @@ class MediaDataFilter @Inject constructor( Log.d(TAG, "Invalid recommendation data. Skip showing the rec card") return } listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) } listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable, isSsReactivatedMutable) } } override fun onMediaDataRemoved(key: String) { Loading
packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +14 −8 Original line number Diff line number Diff line Loading @@ -84,7 +84,7 @@ private val LOADING = MediaData(-1, false, 0, null, null, null, null, null, emptyList(), emptyList(), "INVALID", null, null, null, true, null) @VisibleForTesting internal val EMPTY_SMARTSPACE_MEDIA_DATA = SmartspaceMediaData("INVALID", false, false, "INVALID", null, emptyList(), null, 0) "INVALID", null, emptyList(), null, 0, 0) fun isMediaNotification(sbn: StatusBarNotification): Boolean { return sbn.notification.isMediaNotification() Loading Loading @@ -852,15 +852,16 @@ class MediaDataManager( * until the next refresh-round before UI becomes visible. True by default to take in place * immediately. * * @param isSsReactivated indicates transition from a state with no active media players to * a state with active media players upon receiving Smartspace media data. * @param receivedSmartspaceCardLatency is the latency between headphone connects and sysUI * displays Smartspace media targets. Will be 0 if the data is not activated by Smartspace * signal. */ fun onMediaDataLoaded( key: String, oldKey: String?, data: MediaData, immediately: Boolean = true, isSsReactivated: Boolean = false receivedSmartspaceCardLatency: Int = 0 ) {} /** Loading @@ -869,11 +870,15 @@ class MediaDataManager( * @param shouldPrioritize indicates the sorting priority of the Smartspace card. If true, * it will be prioritized as the first card. Otherwise, it will show up as the last card as * default. * * @param isSsReactivated indicates resume media card is reactivated by Smartspace * recommendation signal */ fun onSmartspaceMediaDataLoaded( key: String, data: SmartspaceMediaData, shouldPrioritize: Boolean = false shouldPrioritize: Boolean = false, isSsReactivated: Boolean = false ) {} /** Called whenever a previously existing Media notification was removed. */ Loading Loading @@ -909,12 +914,13 @@ class MediaDataManager( packageName(target)?.let { return SmartspaceMediaData(target.smartspaceTargetId, isActive, true, it, target.baseAction, target.iconGrid, dismissIntent, 0) dismissIntent, 0, target.creationTimeMillis) } return EMPTY_SMARTSPACE_MEDIA_DATA .copy(targetId = target.smartspaceTargetId, isActive = isActive, dismissIntent = dismissIntent) dismissIntent = dismissIntent, headphoneConnectionTimeMillis = target.creationTimeMillis) } private fun packageName(target: SmartspaceTarget): String? { Loading