Loading packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt +34 −0 Original line number Original line Diff line number Diff line Loading @@ -408,6 +408,40 @@ class MediaDataLoaderTest : SysuiTestCase() { verify(mockImageLoader, times(1)).loadBitmap(any(), anyInt(), anyInt(), anyInt()) verify(mockImageLoader, times(1)).loadBitmap(any(), anyInt(), anyInt(), anyInt()) } } @OptIn(ExperimentalCoroutinesApi::class) @Test fun testLoadMediaDataInBg_fromResumeToActive_doesNotCancelResumeToActiveTask() = testScope.runTest { val mockImageLoader = mock<ImageLoader>() val mediaDataLoader = MediaDataLoader( context, testDispatcher, testScope, mediaControllerFactory, mediaFlags, mockImageLoader, statusBarManager, ) metadataBuilder.putString( MediaMetadata.METADATA_KEY_ALBUM_ART_URI, "content://album_art_uri", ) testScope.launch { mediaDataLoader.loadMediaData( KEY, createMediaNotification(), isConvertingToActive = true, ) } testScope.launch { mediaDataLoader.loadMediaData(KEY, createMediaNotification()) } testScope.launch { mediaDataLoader.loadMediaData(KEY, createMediaNotification()) } testScope.advanceUntilIdle() verify(mockImageLoader, times(2)).loadBitmap(any(), anyInt(), anyInt(), anyInt()) } private fun createMediaNotification( private fun createMediaNotification( mediaSession: MediaSession? = session, mediaSession: MediaSession? = session, applicationInfo: ApplicationInfo? = null, applicationInfo: ApplicationInfo? = null, Loading packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt +7 −3 Original line number Original line Diff line number Diff line Loading @@ -417,6 +417,7 @@ class LegacyMediaDataManagerImpl( override fun onNotificationAdded(key: String, sbn: StatusBarNotification) { override fun onNotificationAdded(key: String, sbn: StatusBarNotification) { if (useQsMediaPlayer && isMediaNotification(sbn)) { if (useQsMediaPlayer && isMediaNotification(sbn)) { var isNewlyActiveEntry = false var isNewlyActiveEntry = false var isConvertingToActive = false Assert.isMainThread() Assert.isMainThread() val oldKey = findExistingEntry(key, sbn.packageName) val oldKey = findExistingEntry(key, sbn.packageName) if (oldKey == null) { if (oldKey == null) { Loading @@ -433,9 +434,10 @@ class LegacyMediaDataManagerImpl( // Resume -> active conversion; move to new key // Resume -> active conversion; move to new key val oldData = mediaEntries.remove(oldKey)!! val oldData = mediaEntries.remove(oldKey)!! isNewlyActiveEntry = true isNewlyActiveEntry = true isConvertingToActive = true mediaEntries.put(key, oldData) mediaEntries.put(key, oldData) } } loadMediaData(key, sbn, oldKey, isNewlyActiveEntry) loadMediaData(key, sbn, oldKey, isNewlyActiveEntry, isConvertingToActive) } else { } else { onNotificationRemoved(key) onNotificationRemoved(key) } } Loading Loading @@ -535,10 +537,11 @@ class LegacyMediaDataManagerImpl( sbn: StatusBarNotification, sbn: StatusBarNotification, oldKey: String?, oldKey: String?, isNewlyActiveEntry: Boolean = false, isNewlyActiveEntry: Boolean = false, isConvertingToActive: Boolean = false, ) { ) { if (Flags.mediaLoadMetadataViaMediaDataLoader()) { if (Flags.mediaLoadMetadataViaMediaDataLoader()) { applicationScope.launch { applicationScope.launch { loadMediaDataWithLoader(key, sbn, oldKey, isNewlyActiveEntry) loadMediaDataWithLoader(key, sbn, oldKey, isNewlyActiveEntry, isConvertingToActive) } } } else { } else { backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, isNewlyActiveEntry) } backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, isNewlyActiveEntry) } Loading @@ -550,10 +553,11 @@ class LegacyMediaDataManagerImpl( sbn: StatusBarNotification, sbn: StatusBarNotification, oldKey: String?, oldKey: String?, isNewlyActiveEntry: Boolean = false, isNewlyActiveEntry: Boolean = false, isConvertingToActive: Boolean = false, ) = ) = withContext(backgroundDispatcher) { withContext(backgroundDispatcher) { val lastActive = systemClock.elapsedRealtime() val lastActive = systemClock.elapsedRealtime() val result = mediaDataLoader.get().loadMediaData(key, sbn) val result = mediaDataLoader.get().loadMediaData(key, sbn, isConvertingToActive) if (result == null) { if (result == null) { Log.d(TAG, "No result from loadMediaData") Log.d(TAG, "No result from loadMediaData") return@withContext return@withContext Loading packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt +19 −5 Original line number Original line Diff line number Diff line Loading @@ -111,16 +111,26 @@ constructor( * If a new [loadMediaData] is issued while existing load is in progress, the existing (old) * If a new [loadMediaData] is issued while existing load is in progress, the existing (old) * load will be cancelled. * load will be cancelled. */ */ suspend fun loadMediaData(key: String, sbn: StatusBarNotification): MediaDataLoaderResult? { suspend fun loadMediaData( val loadMediaJob = backgroundScope.async { loadMediaDataInBackground(key, sbn) } key: String, sbn: StatusBarNotification, isConvertingToActive: Boolean = false, ): MediaDataLoaderResult? { val loadMediaJob = backgroundScope.async { loadMediaDataInBackground(key, sbn, isConvertingToActive) } loadMediaJob.invokeOnCompletion { loadMediaJob.invokeOnCompletion { // We need to make sure we're removing THIS job after cancellation, not // We need to make sure we're removing THIS job after cancellation, not // a job that we created later. // a job that we created later. mediaProcessingJobs.remove(key, loadMediaJob) mediaProcessingJobs.remove(key, loadMediaJob) } } val existingJob = mediaProcessingJobs.put(key, loadMediaJob) var existingJob: Job? = null logD(TAG) { "Loading media data for $key... / existing job: $existingJob" } // Do not cancel loading jobs that convert resume players to active. if (!isConvertingToActive) { existingJob = mediaProcessingJobs.put(key, loadMediaJob) existingJob?.cancel("New processing job incoming.") existingJob?.cancel("New processing job incoming.") } logD(TAG) { "Loading media data for $key... / existing job: $existingJob" } return loadMediaJob.await() return loadMediaJob.await() } } Loading @@ -129,12 +139,16 @@ constructor( private suspend fun loadMediaDataInBackground( private suspend fun loadMediaDataInBackground( key: String, key: String, sbn: StatusBarNotification, sbn: StatusBarNotification, isConvertingToActive: Boolean = false, ): MediaDataLoaderResult? = ): MediaDataLoaderResult? = traceCoroutine("MediaDataLoader#loadMediaData") { traceCoroutine("MediaDataLoader#loadMediaData") { // We have apps spamming us with quick notification updates which can cause // We have apps spamming us with quick notification updates which can cause // us to spend significant CPU time loading duplicate data. This debounces // us to spend significant CPU time loading duplicate data. This debounces // those requests at the cost of a bit of latency. // those requests at the cost of a bit of latency. // No delay needed to load jobs converting resume players to active. if (!isConvertingToActive) { delay(DEBOUNCE_DELAY_MS) delay(DEBOUNCE_DELAY_MS) } val token = val token = sbn.notification.extras.getParcelable( sbn.notification.extras.getParcelable( Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt +34 −0 Original line number Original line Diff line number Diff line Loading @@ -408,6 +408,40 @@ class MediaDataLoaderTest : SysuiTestCase() { verify(mockImageLoader, times(1)).loadBitmap(any(), anyInt(), anyInt(), anyInt()) verify(mockImageLoader, times(1)).loadBitmap(any(), anyInt(), anyInt(), anyInt()) } } @OptIn(ExperimentalCoroutinesApi::class) @Test fun testLoadMediaDataInBg_fromResumeToActive_doesNotCancelResumeToActiveTask() = testScope.runTest { val mockImageLoader = mock<ImageLoader>() val mediaDataLoader = MediaDataLoader( context, testDispatcher, testScope, mediaControllerFactory, mediaFlags, mockImageLoader, statusBarManager, ) metadataBuilder.putString( MediaMetadata.METADATA_KEY_ALBUM_ART_URI, "content://album_art_uri", ) testScope.launch { mediaDataLoader.loadMediaData( KEY, createMediaNotification(), isConvertingToActive = true, ) } testScope.launch { mediaDataLoader.loadMediaData(KEY, createMediaNotification()) } testScope.launch { mediaDataLoader.loadMediaData(KEY, createMediaNotification()) } testScope.advanceUntilIdle() verify(mockImageLoader, times(2)).loadBitmap(any(), anyInt(), anyInt(), anyInt()) } private fun createMediaNotification( private fun createMediaNotification( mediaSession: MediaSession? = session, mediaSession: MediaSession? = session, applicationInfo: ApplicationInfo? = null, applicationInfo: ApplicationInfo? = null, Loading
packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt +7 −3 Original line number Original line Diff line number Diff line Loading @@ -417,6 +417,7 @@ class LegacyMediaDataManagerImpl( override fun onNotificationAdded(key: String, sbn: StatusBarNotification) { override fun onNotificationAdded(key: String, sbn: StatusBarNotification) { if (useQsMediaPlayer && isMediaNotification(sbn)) { if (useQsMediaPlayer && isMediaNotification(sbn)) { var isNewlyActiveEntry = false var isNewlyActiveEntry = false var isConvertingToActive = false Assert.isMainThread() Assert.isMainThread() val oldKey = findExistingEntry(key, sbn.packageName) val oldKey = findExistingEntry(key, sbn.packageName) if (oldKey == null) { if (oldKey == null) { Loading @@ -433,9 +434,10 @@ class LegacyMediaDataManagerImpl( // Resume -> active conversion; move to new key // Resume -> active conversion; move to new key val oldData = mediaEntries.remove(oldKey)!! val oldData = mediaEntries.remove(oldKey)!! isNewlyActiveEntry = true isNewlyActiveEntry = true isConvertingToActive = true mediaEntries.put(key, oldData) mediaEntries.put(key, oldData) } } loadMediaData(key, sbn, oldKey, isNewlyActiveEntry) loadMediaData(key, sbn, oldKey, isNewlyActiveEntry, isConvertingToActive) } else { } else { onNotificationRemoved(key) onNotificationRemoved(key) } } Loading Loading @@ -535,10 +537,11 @@ class LegacyMediaDataManagerImpl( sbn: StatusBarNotification, sbn: StatusBarNotification, oldKey: String?, oldKey: String?, isNewlyActiveEntry: Boolean = false, isNewlyActiveEntry: Boolean = false, isConvertingToActive: Boolean = false, ) { ) { if (Flags.mediaLoadMetadataViaMediaDataLoader()) { if (Flags.mediaLoadMetadataViaMediaDataLoader()) { applicationScope.launch { applicationScope.launch { loadMediaDataWithLoader(key, sbn, oldKey, isNewlyActiveEntry) loadMediaDataWithLoader(key, sbn, oldKey, isNewlyActiveEntry, isConvertingToActive) } } } else { } else { backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, isNewlyActiveEntry) } backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, isNewlyActiveEntry) } Loading @@ -550,10 +553,11 @@ class LegacyMediaDataManagerImpl( sbn: StatusBarNotification, sbn: StatusBarNotification, oldKey: String?, oldKey: String?, isNewlyActiveEntry: Boolean = false, isNewlyActiveEntry: Boolean = false, isConvertingToActive: Boolean = false, ) = ) = withContext(backgroundDispatcher) { withContext(backgroundDispatcher) { val lastActive = systemClock.elapsedRealtime() val lastActive = systemClock.elapsedRealtime() val result = mediaDataLoader.get().loadMediaData(key, sbn) val result = mediaDataLoader.get().loadMediaData(key, sbn, isConvertingToActive) if (result == null) { if (result == null) { Log.d(TAG, "No result from loadMediaData") Log.d(TAG, "No result from loadMediaData") return@withContext return@withContext Loading
packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt +19 −5 Original line number Original line Diff line number Diff line Loading @@ -111,16 +111,26 @@ constructor( * If a new [loadMediaData] is issued while existing load is in progress, the existing (old) * If a new [loadMediaData] is issued while existing load is in progress, the existing (old) * load will be cancelled. * load will be cancelled. */ */ suspend fun loadMediaData(key: String, sbn: StatusBarNotification): MediaDataLoaderResult? { suspend fun loadMediaData( val loadMediaJob = backgroundScope.async { loadMediaDataInBackground(key, sbn) } key: String, sbn: StatusBarNotification, isConvertingToActive: Boolean = false, ): MediaDataLoaderResult? { val loadMediaJob = backgroundScope.async { loadMediaDataInBackground(key, sbn, isConvertingToActive) } loadMediaJob.invokeOnCompletion { loadMediaJob.invokeOnCompletion { // We need to make sure we're removing THIS job after cancellation, not // We need to make sure we're removing THIS job after cancellation, not // a job that we created later. // a job that we created later. mediaProcessingJobs.remove(key, loadMediaJob) mediaProcessingJobs.remove(key, loadMediaJob) } } val existingJob = mediaProcessingJobs.put(key, loadMediaJob) var existingJob: Job? = null logD(TAG) { "Loading media data for $key... / existing job: $existingJob" } // Do not cancel loading jobs that convert resume players to active. if (!isConvertingToActive) { existingJob = mediaProcessingJobs.put(key, loadMediaJob) existingJob?.cancel("New processing job incoming.") existingJob?.cancel("New processing job incoming.") } logD(TAG) { "Loading media data for $key... / existing job: $existingJob" } return loadMediaJob.await() return loadMediaJob.await() } } Loading @@ -129,12 +139,16 @@ constructor( private suspend fun loadMediaDataInBackground( private suspend fun loadMediaDataInBackground( key: String, key: String, sbn: StatusBarNotification, sbn: StatusBarNotification, isConvertingToActive: Boolean = false, ): MediaDataLoaderResult? = ): MediaDataLoaderResult? = traceCoroutine("MediaDataLoader#loadMediaData") { traceCoroutine("MediaDataLoader#loadMediaData") { // We have apps spamming us with quick notification updates which can cause // We have apps spamming us with quick notification updates which can cause // us to spend significant CPU time loading duplicate data. This debounces // us to spend significant CPU time loading duplicate data. This debounces // those requests at the cost of a bit of latency. // those requests at the cost of a bit of latency. // No delay needed to load jobs converting resume players to active. if (!isConvertingToActive) { delay(DEBOUNCE_DELAY_MS) delay(DEBOUNCE_DELAY_MS) } val token = val token = sbn.notification.extras.getParcelable( sbn.notification.extras.getParcelable( Loading