diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index 72ae16e607c82..1714f48f66578 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -71,6 +71,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.Utils; import com.android.systemui.util.concurrency.DelayableExecutor; +import dagger.Lazy; + import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -80,8 +82,6 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import dagger.Lazy; - /** * Handles tasks and state related to media notifications. For example, there is a 'current' media * notification, which this class keeps track of. @@ -161,11 +161,48 @@ public class NotificationMediaManager implements Dumpable { Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata); } mMediaArtworkProcessor.clearCache(); - mMediaMetadata = metadata; + mMediaMetadata = cleanMetadata(metadata); dispatchUpdateMediaMetaData(true /* changed */, true /* allowAnimation */); } }; + /** + * If this build is not configured for lockscreen artwork, clear artwork references from the + * metadata to avoid excess memory usage. Otherwise, return as is. + * @param data Original metadata + * @return a copy without artwork data, or original + */ + private MediaMetadata cleanMetadata(MediaMetadata data) { + if (SHOW_LOCKSCREEN_MEDIA_ARTWORK) { + return data; + } + if (data == null) { + return null; + } + if (DEBUG_MEDIA) { + String[] artKeys = new String[] { + MediaMetadata.METADATA_KEY_ART, + MediaMetadata.METADATA_KEY_ALBUM_ART, + MediaMetadata.METADATA_KEY_DISPLAY_ICON, + MediaMetadata.METADATA_KEY_ALBUM_ART_URI, + MediaMetadata.METADATA_KEY_ART_URI, + MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI + }; + Log.v(TAG, "DEBUG_MEDIA: removing artwork from metadata"); + for (String key: artKeys) { + Log.v(TAG, " " + key + ": " + data.containsKey(key)); + } + } + return new MediaMetadata.Builder(data) + .putBitmap(MediaMetadata.METADATA_KEY_ART, null) + .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, null) + .putBitmap(MediaMetadata.METADATA_KEY_DISPLAY_ICON, null) + .putString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI, null) + .putString(MediaMetadata.METADATA_KEY_ART_URI, null) + .putString(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI, null) + .build(); + } + /** * Injected constructor. See {@link CentralSurfacesModule}. */ @@ -313,6 +350,7 @@ public class NotificationMediaManager implements Dumpable { return mMediaNotificationKey; } + @VisibleForTesting public MediaMetadata getMediaMetadata() { return mMediaMetadata; } @@ -350,7 +388,7 @@ public class NotificationMediaManager implements Dumpable { * update this manager's internal state. * @return whether the current MediaMetadata changed (and needs to be announced to listeners). */ - boolean findPlayingMediaNotification( + private boolean findPlayingMediaNotification( @NonNull Collection allNotifications) { boolean metaDataChanged = false; // Promote the media notification with a controller in 'playing' state, if any. @@ -383,7 +421,7 @@ public class NotificationMediaManager implements Dumpable { clearCurrentMediaNotificationSession(); mMediaController = controller; mMediaController.registerCallback(mMediaListener); - mMediaMetadata = mMediaController.getMetadata(); + mMediaMetadata = cleanMetadata(mMediaController.getMetadata()); if (DEBUG_MEDIA) { Log.v(TAG, "DEBUG_MEDIA: insert listener, found new controller: " + mMediaController + ", receive metadata: " + mMediaMetadata); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index f579d304b2684..b15d241adac2c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -75,7 +75,7 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn boolean DEBUG_WAKEUP_DELAY = Compile.IS_DEBUG; // additional instrumentation for testing purposes; intended to be left on during development boolean CHATTY = DEBUG; - boolean SHOW_LOCKSCREEN_MEDIA_ARTWORK = true; + boolean SHOW_LOCKSCREEN_MEDIA_ARTWORK = false; String ACTION_FAKE_ARTWORK = "fake_artwork"; int FADE_KEYGUARD_START_DELAY = 100; int FADE_KEYGUARD_DURATION = 300; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt index 9d6ea857fefc1..b997f64476909 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt @@ -16,11 +16,34 @@ package com.android.systemui.statusbar +import android.app.Notification +import android.app.WallpaperManager +import android.graphics.Bitmap +import android.media.MediaMetadata +import android.media.session.MediaSession +import android.media.session.PlaybackState import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.colorextraction.SysuiColorExtractor +import com.android.systemui.dump.DumpManager +import com.android.systemui.media.controls.pipeline.MediaDataManager +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.statusbar.notification.collection.NotifCollection +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider +import com.android.systemui.statusbar.phone.CentralSurfaces +import com.android.systemui.statusbar.phone.KeyguardBypassController +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.whenever -import org.junit.After +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import dagger.Lazy +import java.util.Optional import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -32,39 +55,121 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -/** - * Temporary test for the lock screen live wallpaper project. - * - * TODO(b/273443374): remove this test - */ @RunWith(AndroidTestingRunner::class) @SmallTest +@RunWithLooper class NotificationMediaManagerTest : SysuiTestCase() { - @Mock private lateinit var notificationMediaManager: NotificationMediaManager + @Mock private lateinit var centralSurfaces: CentralSurfaces + @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController + @Mock private lateinit var visibilityProvider: NotificationVisibilityProvider + @Mock private lateinit var mediaArtworkProcessor: MediaArtworkProcessor + @Mock private lateinit var keyguardBypassController: KeyguardBypassController + @Mock private lateinit var notifPipeline: NotifPipeline + @Mock private lateinit var notifCollection: NotifCollection + @Mock private lateinit var mediaDataManager: MediaDataManager + @Mock private lateinit var statusBarStateController: StatusBarStateController + @Mock private lateinit var colorExtractor: SysuiColorExtractor + @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock private lateinit var dumpManager: DumpManager + @Mock private lateinit var wallpaperManager: WallpaperManager + @Mock private lateinit var notificationEntry: NotificationEntry + + lateinit var manager: NotificationMediaManager + val clock = FakeSystemClock() + val mainExecutor: DelayableExecutor = FakeExecutor(clock) + + @Mock private lateinit var mockManager: NotificationMediaManager @Mock private lateinit var mockBackDropView: BackDropView @Before fun setUp() { MockitoAnnotations.initMocks(this) - doCallRealMethod() - .whenever(notificationMediaManager) - .updateMediaMetaData(anyBoolean(), anyBoolean()) - doReturn(mockBackDropView).whenever(notificationMediaManager).backDropView + doCallRealMethod().whenever(mockManager).updateMediaMetaData(anyBoolean(), anyBoolean()) + doReturn(mockBackDropView).whenever(mockManager).backDropView + + manager = + NotificationMediaManager( + context, + Lazy { Optional.of(centralSurfaces) }, + Lazy { notificationShadeWindowController }, + visibilityProvider, + mediaArtworkProcessor, + keyguardBypassController, + notifPipeline, + notifCollection, + mainExecutor, + mediaDataManager, + statusBarStateController, + colorExtractor, + keyguardStateController, + dumpManager, + wallpaperManager, + ) } - @After fun tearDown() {} - - /** Check that updateMediaMetaData is a no-op with mIsLockscreenLiveWallpaperEnabled = true */ + /** + * Check that updateMediaMetaData is a no-op with mIsLockscreenLiveWallpaperEnabled = true + * Temporary test for the lock screen live wallpaper project. + * + * TODO(b/273443374): remove this test + */ @Test fun testUpdateMediaMetaDataDisabled() { - notificationMediaManager.mIsLockscreenLiveWallpaperEnabled = true + mockManager.mIsLockscreenLiveWallpaperEnabled = true for (metaDataChanged in listOf(true, false)) { for (allowEnterAnimation in listOf(true, false)) { - notificationMediaManager.updateMediaMetaData(metaDataChanged, allowEnterAnimation) - verify(notificationMediaManager, never()).mediaMetadata + mockManager.updateMediaMetaData(metaDataChanged, allowEnterAnimation) + verify(mockManager, never()).mediaMetadata } } } + + @Test + fun testMetadataUpdated_doesNotRetainArtwork() { + val artBmp = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888) + val artUri = "content://example" + val inputMetadata = + MediaMetadata.Builder() + .putBitmap(MediaMetadata.METADATA_KEY_ART, artBmp) + .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, artBmp) + .putBitmap(MediaMetadata.METADATA_KEY_DISPLAY_ICON, artBmp) + .putString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI, artUri) + .putString(MediaMetadata.METADATA_KEY_ART_URI, artUri) + .putString(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI, artUri) + .build() + + // Create a playing media notification + val state = PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0L, 1f).build() + val session = MediaSession(context, "NotificationMediaManagerTest") + session.setMetadata(inputMetadata) + session.setPlaybackState(state) + val sbn = + SbnBuilder().run { + modifyNotification(context).also { + it.setSmallIcon(android.R.drawable.ic_media_play) + it.setStyle( + Notification.MediaStyle().apply { setMediaSession(session.sessionToken) } + ) + } + build() + } + whenever(notificationEntry.sbn).thenReturn(sbn) + val collection = ArrayList() + collection.add(notificationEntry) + whenever(notifPipeline.allNotifs).thenReturn(collection) + + // Trigger update in NotificationMediaManager + manager.findAndUpdateMediaNotifications() + + // Verify that there is no artwork data retained + val metadata = manager.mediaMetadata + assertThat(metadata.getBitmap(MediaMetadata.METADATA_KEY_ART)).isNull() + assertThat(metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART)).isNull() + assertThat(metadata.getBitmap(MediaMetadata.METADATA_KEY_DISPLAY_ICON)).isNull() + assertThat(metadata.getString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI)).isNull() + assertThat(metadata.getString(MediaMetadata.METADATA_KEY_ART_URI)).isNull() + assertThat(metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI)).isNull() + } }