diff options
| author | 2024-05-29 19:10:17 +0000 | |
|---|---|---|
| committer | 2024-06-18 13:58:57 +0000 | |
| commit | 3dc09b51a6c5663109526b675edad7469ece02c7 (patch) | |
| tree | 5459e348fb150ed1d0081231dbb5cde32fb7df8d | |
| parent | f74076cb49900c758a3a29c026e7b8cecb50acd0 (diff) | |
Add smartspace logger
This CL adds smartspace logger class and some fields in media data to be
logged when media is loaded.
Flag: com.android.systemui.scene_container
Bug: 330897926
Test: atest SystemUiRoboTests:MediaFilterRepositoryTest
Change-Id: Ice8b7f1847f74a407081b3dcfdcbd7f5d0e182e0
8 files changed, 239 insertions, 17 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt index f78a0f959f3c..31bd4fb78ffa 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt @@ -180,7 +180,13 @@ constructor( mediaData.instanceId ) mediaFilterRepository.addMediaDataLoadingState( - MediaDataLoadingModel.Loaded(lastActiveId) + MediaDataLoadingModel.Loaded( + lastActiveId, + receivedSmartspaceCardLatency = + (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis) + .toInt(), + isSsReactivated = true + ) ) mediaLoadingLogger.logMediaLoaded( mediaData.instanceId, diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt index 37dffd1955d6..adcfba75f498 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt @@ -86,6 +86,7 @@ import com.android.systemui.media.controls.util.MediaControllerFactory import com.android.systemui.media.controls.util.MediaDataUtils import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.media.controls.util.MediaUiEventLogger +import com.android.systemui.media.controls.util.SmallHash import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.BcSmartspaceDataPlugin import com.android.systemui.res.R @@ -721,6 +722,7 @@ class MediaDataProcessor( appUid = appUid, isExplicit = isExplicit, resumeProgress = progress, + smartspaceId = SmallHash.hash(appUid + systemClock.currentTimeMillis().toInt()), ) ) } @@ -902,6 +904,7 @@ class MediaDataProcessor( instanceId = instanceId, appUid = appUid, isExplicit = isExplicit, + smartspaceId = SmallHash.hash(appUid + systemClock.currentTimeMillis().toInt()), ) ) } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaData.kt index 11a562911a85..40b34779151d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaData.kt @@ -99,6 +99,12 @@ data class MediaData( /** Track progress (0 - 1) to display for players where [resumption] is true */ val resumeProgress: Double? = null, + + /** Smartspace Id, used for logging. */ + var smartspaceId: Int = -1, + + /** If media card was visible to user, used for logging. */ + var isImpressed: Boolean = false, ) { companion object { /** Media is playing on the local device */ diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaDataLoadingModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaDataLoadingModel.kt index 170f1f78a5a6..c8a02faea58a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaDataLoadingModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaDataLoadingModel.kt @@ -27,6 +27,8 @@ sealed class MediaDataLoadingModel { data class Loaded( override val instanceId: InstanceId, val immediatelyUpdateUi: Boolean = true, + val receivedSmartspaceCardLatency: Int = 0, + val isSsReactivated: Boolean = false, ) : MediaDataLoadingModel() /** Media data has been removed. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaData.kt index 9e15dbbb64b8..96c3fa8fbc89 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaData.kt @@ -48,6 +48,8 @@ data class SmartspaceMediaData( val instanceId: InstanceId? = null, /** The timestamp in milliseconds indicating when the card should be removed */ val expiryTimeMs: Long = 0L, + /** If recommendation card was visible to user, used for logging. */ + var isImpressed: Boolean = false, ) { /** * Indicates if all the data is valid. diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaSmartspaceLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaSmartspaceLogger.kt new file mode 100644 index 000000000000..01fbf4af7626 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaSmartspaceLogger.kt @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.controls.util + +import android.util.Log +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.shared.system.SysUiStatsLog +import javax.inject.Inject + +/** Logger class for Smartspace logging events. */ +@SysUISingleton +class MediaSmartspaceLogger @Inject constructor() { + /** + * Log Smartspace card received event + * + * @param instanceId id to uniquely identify a card. + * @param uid uid for the application that media comes from. + * @param cardinality number of card in carousel. + * @param isRecommendationCard whether media card being logged is a recommendations card. + * @param isSsReactivated indicates resume media card is reactivated by Smartspace + * recommendation signal + * @param rank the rank for media card in the media carousel, starting from 0 + * @param receivedLatencyMillis latency in milliseconds for card received events. + */ + fun logSmartspaceCardReceived( + instanceId: Int, + uid: Int, + cardinality: Int, + isRecommendationCard: Boolean = false, + isSsReactivated: Boolean = false, + rank: Int = 0, + receivedLatencyMillis: Int = 0, + ) { + logSmartspaceCardReported( + SMARTSPACE_CARD_RECEIVED_EVENT, + instanceId, + uid, + surfaces = + intArrayOf( + SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, + SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN, + SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY, + ), + cardinality, + isRecommendationCard, + isSsReactivated, + rank = rank, + receivedLatencyMillis = receivedLatencyMillis, + ) + } + + /** + * Log Smartspace card UI event + * + * @param eventId id of the event. eg: dismiss, click, or seen. + * @param instanceId id to uniquely identify a card. + * @param uid uid for the application that media comes from. + * @param location location of media carousel holding media card. + * @param cardinality number of card in carousel. + * @param isRecommendationCard whether media card being logged is a recommendations card. + * @param isSsReactivated indicates resume media card is reactivated by Smartspace + * recommendation signal + * @param rank the rank for media card in the media carousel, starting from 0 + * @param isSwipeToDismiss whether is to log swipe-to-dismiss event + */ + fun logSmartspaceCardUIEvent( + eventId: Int, + instanceId: Int, + uid: Int, + location: Int, + cardinality: Int, + isRecommendationCard: Boolean = false, + isSsReactivated: Boolean = false, + rank: Int = 0, + isSwipeToDismiss: Boolean = false, + ) { + logSmartspaceCardReported( + eventId, + instanceId, + uid, + surfaces = intArrayOf(location), + cardinality, + isRecommendationCard, + isSsReactivated, + rank = rank, + isSwipeToDismiss = isSwipeToDismiss, + ) + } + + /** + * Log Smartspace events + * + * @param eventId UI event id (e.g. 800 for SMARTSPACE_CARD_SEEN) + * @param instanceId id to uniquely identify a card, e.g. each headphone generates a new + * instanceId + * @param uid uid for the application that media comes from + * @param surfaces list of display surfaces the media card is on (e.g. lockscreen, shade) when + * the event happened + * @param cardinality number of card in carousel. + * @param isRecommendationCard whether media card being logged is a recommendations card. + * @param isSsReactivated indicates resume media card is reactivated by Smartspace + * recommendation signal + * @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 + * @param isSwipeToDismiss whether is to log swipe-to-dismiss event + */ + private fun logSmartspaceCardReported( + eventId: Int, + instanceId: Int, + uid: Int, + surfaces: IntArray, + cardinality: Int, + isRecommendationCard: Boolean, + isSsReactivated: Boolean, + interactedSubcardRank: Int = 0, + interactedSubcardCardinality: Int = 0, + rank: Int = 0, + receivedLatencyMillis: Int = 0, + isSwipeToDismiss: Boolean = false, + ) { + surfaces.forEach { surface -> + SysUiStatsLog.write( + SysUiStatsLog.SMARTSPACE_CARD_REPORTED, + eventId, + instanceId, + // Deprecated, replaced with AiAi feature type so we don't need to create logging + // card type for each new feature. + SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD, + surface, + // Use -1 as rank value to indicate user swipe to dismiss the card + if (isSwipeToDismiss) -1 else rank, + cardinality, + if (isRecommendationCard) { + 15 // MEDIA_RECOMMENDATION + } else if (isSsReactivated) { + 43 // MEDIA_RESUME_SS_ACTIVATED + } else { + 31 // MEDIA_RESUME + }, + uid, + interactedSubcardRank, + interactedSubcardCardinality, + receivedLatencyMillis, + null, // Media cards cannot have subcards. + null // Media cards don't have dimensions today. + ) + + if (DEBUG) { + Log.d( + TAG, + "Log Smartspace card event id: $eventId instance id: $instanceId" + + " surface: $surface rank: $rank cardinality: $cardinality " + + "isRecommendationCard: $isRecommendationCard " + + "isSsReactivated: $isSsReactivated" + + "uid: $uid " + + "interactedSubcardRank: $interactedSubcardRank " + + "interactedSubcardCardinality: $interactedSubcardCardinality " + + "received_latency_millis: $receivedLatencyMillis" + ) + } + } + } + + companion object { + private const val TAG = "MediaSmartspaceLogger" + private val DEBUG = Log.isLoggable(TAG, Log.DEBUG) + private const val SMARTSPACE_CARD_RECEIVED_EVENT = 759 + const val SMARTSPACE_CARD_CLICK_EVENT = 760 + const val SMARTSPACE_CARD_DISMISS_EVENT = 761 + const val SMARTSPACE_CARD_SEEN_EVENT = 800 + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java index 544350c7e24d..1d4b0903b579 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java @@ -79,7 +79,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { USER_ID, true, APP, null, ARTIST, TITLE, null, new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, true, null, MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L, 0L, - InstanceId.fakeInstanceId(-1), -1, false, null); + InstanceId.fakeInstanceId(-1), -1, false, null, -1, false); mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME, null, false); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt index 4da56b51e423..0a9b4fdf8ad8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt @@ -740,11 +740,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { // WHEN we have media that was recently played, but not currently active val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) - val controlCommonModel = - MediaCommonModel.MediaControl( - MediaDataLoadingModel.Loaded(dataMain.instanceId), - true - ) + val mediaLoadingModel = MediaDataLoadingModel.Loaded(dataMain.instanceId) + var controlCommonModel = MediaCommonModel.MediaControl(mediaLoadingModel, true) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) repository.setOrderedMedia() assertThat(currentMedia).containsExactly(controlCommonModel) @@ -759,6 +756,13 @@ class MediaDataFilterImplTest : SysuiTestCase() { // THEN we should treat the media as active instead val dataCurrentAndActive = dataCurrent.copy(active = true) + controlCommonModel = + controlCommonModel.copy( + mediaLoadingModel.copy( + receivedSmartspaceCardLatency = 100, + isSsReactivated = true + ) + ) assertThat(currentMedia).containsExactly(controlCommonModel) assertThat( hasActiveMediaOrRecommendation( @@ -800,11 +804,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { val currentMedia by collectLastValue(repository.currentMedia) // WHEN we have media that was recently played, but not currently active val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) - val controlCommonModel = - MediaCommonModel.MediaControl( - MediaDataLoadingModel.Loaded(dataMain.instanceId), - true - ) + val mediaLoadingModel = MediaDataLoadingModel.Loaded(dataMain.instanceId) + var controlCommonModel = MediaCommonModel.MediaControl(mediaLoadingModel, true) val recsCommonModel = MediaCommonModel.MediaRecommendations( SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY) @@ -849,6 +850,13 @@ class MediaDataFilterImplTest : SysuiTestCase() { ) .isTrue() // Smartspace update should also be propagated but not prioritized. + controlCommonModel = + controlCommonModel.copy( + mediaLoadingModel.copy( + receivedSmartspaceCardLatency = 100, + isSsReactivated = true + ) + ) assertThat(currentMedia).containsExactly(controlCommonModel, recsCommonModel) verify(listener) .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) @@ -1063,11 +1071,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { MediaCommonModel.MediaRecommendations( SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY) ) - val controlCommonModel = - MediaCommonModel.MediaControl( - MediaDataLoadingModel.Loaded(dataMain.instanceId), - true - ) + val mediaLoadingModel = MediaDataLoadingModel.Loaded(dataMain.instanceId) + var controlCommonModel = MediaCommonModel.MediaControl(mediaLoadingModel, true) // WHEN we have media that was recently played, but not currently active val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) @@ -1087,6 +1092,13 @@ class MediaDataFilterImplTest : SysuiTestCase() { // THEN we should treat the media as active instead val dataCurrentAndActive = dataCurrent.copy(active = true) + controlCommonModel = + controlCommonModel.copy( + mediaLoadingModel.copy( + receivedSmartspaceCardLatency = 100, + isSsReactivated = true + ) + ) verify(listener) .onMediaDataLoaded( eq(KEY), |