Loading packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java +20 −3 Original line number Diff line number Diff line Loading @@ -24,6 +24,8 @@ import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.media.MediaMetadata; import android.media.session.MediaController; import android.media.session.MediaSession; import android.util.Log; import android.view.View; import android.widget.ImageButton; Loading @@ -40,6 +42,7 @@ import androidx.palette.graphics.Palette; import com.android.internal.util.ContrastColorUtil; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.media.MediaControllerFactory; import com.android.systemui.statusbar.notification.MediaNotificationProcessor; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.stack.MediaHeaderView; Loading Loading @@ -71,10 +74,11 @@ public class KeyguardMediaPlayer { private KeyguardMediaObserver mObserver; @Inject public KeyguardMediaPlayer(Context context, @Background Executor backgroundExecutor) { public KeyguardMediaPlayer(Context context, MediaControllerFactory factory, @Background Executor backgroundExecutor) { mContext = context; mBackgroundExecutor = backgroundExecutor; mViewModel = new KeyguardMediaViewModel(context); mViewModel = new KeyguardMediaViewModel(context, factory); } /** Binds media controls to a view hierarchy. */ Loading Loading @@ -139,14 +143,16 @@ public class KeyguardMediaPlayer { private static final class KeyguardMediaViewModel { private final Context mContext; private final MediaControllerFactory mMediaControllerFactory; private final MutableLiveData<KeyguardMedia> mMedia = new MutableLiveData<>(); private final Object mActionsLock = new Object(); private List<PendingIntent> mActions; private float mAlbumArtRadius; private int mAlbumArtSize; KeyguardMediaViewModel(Context context) { KeyguardMediaViewModel(Context context, MediaControllerFactory factory) { mContext = context; mMediaControllerFactory = factory; loadDimens(); } Loading @@ -162,6 +168,17 @@ public class KeyguardMediaPlayer { public void updateControls(NotificationEntry entry, Icon appIcon, MediaMetadata mediaMetadata) { // Check the playback state of the media controller. If it is null, then the session was // probably destroyed. Don't update in this case. final MediaSession.Token token = entry.getSbn().getNotification().extras .getParcelable(Notification.EXTRA_MEDIA_SESSION); final MediaController controller = token != null ? mMediaControllerFactory.create(token) : null; if (controller != null && controller.getPlaybackState() == null) { clearControls(); return; } // Foreground and Background colors computed from album art Notification notif = entry.getSbn().getNotification(); int fgColor = notif.color; Loading packages/SystemUI/src/com/android/systemui/media/MediaControllerFactory.java 0 → 100644 +45 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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; import android.content.Context; import android.media.session.MediaController; import android.media.session.MediaSession; import javax.inject.Inject; /** * Testable wrapper around {@link MediaController} constructor. */ public class MediaControllerFactory { private final Context mContext; @Inject public MediaControllerFactory(Context context) { mContext = context; } /** * Creates a new MediaController from a session's token. * * @param token The token for the session. This value must never be null. */ public MediaController create(MediaSession.Token token) { return new MediaController(mContext, token); } } packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt +37 −6 Original line number Diff line number Diff line Loading @@ -16,8 +16,12 @@ package com.android.keyguard import android.app.Notification import android.graphics.drawable.Icon import android.media.MediaMetadata import android.media.session.MediaController import android.media.session.MediaSession import android.media.session.PlaybackState import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View Loading @@ -28,7 +32,9 @@ import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.media.MediaControllerFactory import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat Loading @@ -38,6 +44,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.any import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever Loading @@ -48,9 +55,12 @@ import org.mockito.Mockito.`when` as whenever public class KeyguardMediaPlayerTest : SysuiTestCase() { private lateinit var keyguardMediaPlayer: KeyguardMediaPlayer @Mock private lateinit var mockMediaFactory: MediaControllerFactory @Mock private lateinit var mockMediaController: MediaController private lateinit var playbackState: PlaybackState private lateinit var fakeExecutor: FakeExecutor private lateinit var mediaMetadata: MediaMetadata.Builder private lateinit var entry: NotificationEntryBuilder private lateinit var entry: NotificationEntry @Mock private lateinit var mockView: View private lateinit var songView: TextView private lateinit var artistView: TextView Loading @@ -70,8 +80,16 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() { @Before public fun setup() { playbackState = PlaybackState.Builder().run { build() } mockMediaController = mock(MediaController::class.java) whenever(mockMediaController.getPlaybackState()).thenReturn(playbackState) mockMediaFactory = mock(MediaControllerFactory::class.java) whenever(mockMediaFactory.create(any())).thenReturn(mockMediaController) fakeExecutor = FakeExecutor(FakeSystemClock()) keyguardMediaPlayer = KeyguardMediaPlayer(context, fakeExecutor) keyguardMediaPlayer = KeyguardMediaPlayer(context, mockMediaFactory, fakeExecutor) mockIcon = mock(Icon::class.java) mockView = mock(View::class.java) Loading @@ -81,7 +99,9 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() { whenever<TextView>(mockView.findViewById(R.id.header_artist)).thenReturn(artistView) mediaMetadata = MediaMetadata.Builder() entry = NotificationEntryBuilder() entry = NotificationEntryBuilder().build() entry.getSbn().getNotification().extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, MediaSession.Token(1, null)) ArchTaskExecutor.getInstance().setDelegate(taskExecutor) Loading Loading @@ -109,7 +129,7 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() { @Test public fun testUpdateControls() { keyguardMediaPlayer.updateControls(entry.build(), mockIcon, mediaMetadata.build()) keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build()) FakeExecutor.exhaustExecutors(fakeExecutor) verify(mockView).setVisibility(View.VISIBLE) } Loading @@ -121,12 +141,23 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() { verify(mockView).setVisibility(View.GONE) } @Test public fun testUpdateControlsNullPlaybackState() { // GIVEN that the playback state is null (ie. the media session was destroyed) whenever(mockMediaController.getPlaybackState()).thenReturn(null) // WHEN updated keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build()) FakeExecutor.exhaustExecutors(fakeExecutor) // THEN the controls are cleared (ie. visibility is set to GONE) verify(mockView).setVisibility(View.GONE) } @Test public fun testSongName() { val song: String = "Song" mediaMetadata.putText(MediaMetadata.METADATA_KEY_TITLE, song) keyguardMediaPlayer.updateControls(entry.build(), mockIcon, mediaMetadata.build()) keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build()) assertThat(fakeExecutor.runAllReady()).isEqualTo(1) assertThat(songView.getText()).isEqualTo(song) Loading @@ -137,7 +168,7 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() { val artist: String = "Artist" mediaMetadata.putText(MediaMetadata.METADATA_KEY_ARTIST, artist) keyguardMediaPlayer.updateControls(entry.build(), mockIcon, mediaMetadata.build()) keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build()) assertThat(fakeExecutor.runAllReady()).isEqualTo(1) assertThat(artistView.getText()).isEqualTo(artist) Loading Loading
packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java +20 −3 Original line number Diff line number Diff line Loading @@ -24,6 +24,8 @@ import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.media.MediaMetadata; import android.media.session.MediaController; import android.media.session.MediaSession; import android.util.Log; import android.view.View; import android.widget.ImageButton; Loading @@ -40,6 +42,7 @@ import androidx.palette.graphics.Palette; import com.android.internal.util.ContrastColorUtil; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.media.MediaControllerFactory; import com.android.systemui.statusbar.notification.MediaNotificationProcessor; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.stack.MediaHeaderView; Loading Loading @@ -71,10 +74,11 @@ public class KeyguardMediaPlayer { private KeyguardMediaObserver mObserver; @Inject public KeyguardMediaPlayer(Context context, @Background Executor backgroundExecutor) { public KeyguardMediaPlayer(Context context, MediaControllerFactory factory, @Background Executor backgroundExecutor) { mContext = context; mBackgroundExecutor = backgroundExecutor; mViewModel = new KeyguardMediaViewModel(context); mViewModel = new KeyguardMediaViewModel(context, factory); } /** Binds media controls to a view hierarchy. */ Loading Loading @@ -139,14 +143,16 @@ public class KeyguardMediaPlayer { private static final class KeyguardMediaViewModel { private final Context mContext; private final MediaControllerFactory mMediaControllerFactory; private final MutableLiveData<KeyguardMedia> mMedia = new MutableLiveData<>(); private final Object mActionsLock = new Object(); private List<PendingIntent> mActions; private float mAlbumArtRadius; private int mAlbumArtSize; KeyguardMediaViewModel(Context context) { KeyguardMediaViewModel(Context context, MediaControllerFactory factory) { mContext = context; mMediaControllerFactory = factory; loadDimens(); } Loading @@ -162,6 +168,17 @@ public class KeyguardMediaPlayer { public void updateControls(NotificationEntry entry, Icon appIcon, MediaMetadata mediaMetadata) { // Check the playback state of the media controller. If it is null, then the session was // probably destroyed. Don't update in this case. final MediaSession.Token token = entry.getSbn().getNotification().extras .getParcelable(Notification.EXTRA_MEDIA_SESSION); final MediaController controller = token != null ? mMediaControllerFactory.create(token) : null; if (controller != null && controller.getPlaybackState() == null) { clearControls(); return; } // Foreground and Background colors computed from album art Notification notif = entry.getSbn().getNotification(); int fgColor = notif.color; Loading
packages/SystemUI/src/com/android/systemui/media/MediaControllerFactory.java 0 → 100644 +45 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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; import android.content.Context; import android.media.session.MediaController; import android.media.session.MediaSession; import javax.inject.Inject; /** * Testable wrapper around {@link MediaController} constructor. */ public class MediaControllerFactory { private final Context mContext; @Inject public MediaControllerFactory(Context context) { mContext = context; } /** * Creates a new MediaController from a session's token. * * @param token The token for the session. This value must never be null. */ public MediaController create(MediaSession.Token token) { return new MediaController(mContext, token); } }
packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt +37 −6 Original line number Diff line number Diff line Loading @@ -16,8 +16,12 @@ package com.android.keyguard import android.app.Notification import android.graphics.drawable.Icon import android.media.MediaMetadata import android.media.session.MediaController import android.media.session.MediaSession import android.media.session.PlaybackState import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View Loading @@ -28,7 +32,9 @@ import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.media.MediaControllerFactory import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat Loading @@ -38,6 +44,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.any import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever Loading @@ -48,9 +55,12 @@ import org.mockito.Mockito.`when` as whenever public class KeyguardMediaPlayerTest : SysuiTestCase() { private lateinit var keyguardMediaPlayer: KeyguardMediaPlayer @Mock private lateinit var mockMediaFactory: MediaControllerFactory @Mock private lateinit var mockMediaController: MediaController private lateinit var playbackState: PlaybackState private lateinit var fakeExecutor: FakeExecutor private lateinit var mediaMetadata: MediaMetadata.Builder private lateinit var entry: NotificationEntryBuilder private lateinit var entry: NotificationEntry @Mock private lateinit var mockView: View private lateinit var songView: TextView private lateinit var artistView: TextView Loading @@ -70,8 +80,16 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() { @Before public fun setup() { playbackState = PlaybackState.Builder().run { build() } mockMediaController = mock(MediaController::class.java) whenever(mockMediaController.getPlaybackState()).thenReturn(playbackState) mockMediaFactory = mock(MediaControllerFactory::class.java) whenever(mockMediaFactory.create(any())).thenReturn(mockMediaController) fakeExecutor = FakeExecutor(FakeSystemClock()) keyguardMediaPlayer = KeyguardMediaPlayer(context, fakeExecutor) keyguardMediaPlayer = KeyguardMediaPlayer(context, mockMediaFactory, fakeExecutor) mockIcon = mock(Icon::class.java) mockView = mock(View::class.java) Loading @@ -81,7 +99,9 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() { whenever<TextView>(mockView.findViewById(R.id.header_artist)).thenReturn(artistView) mediaMetadata = MediaMetadata.Builder() entry = NotificationEntryBuilder() entry = NotificationEntryBuilder().build() entry.getSbn().getNotification().extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, MediaSession.Token(1, null)) ArchTaskExecutor.getInstance().setDelegate(taskExecutor) Loading Loading @@ -109,7 +129,7 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() { @Test public fun testUpdateControls() { keyguardMediaPlayer.updateControls(entry.build(), mockIcon, mediaMetadata.build()) keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build()) FakeExecutor.exhaustExecutors(fakeExecutor) verify(mockView).setVisibility(View.VISIBLE) } Loading @@ -121,12 +141,23 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() { verify(mockView).setVisibility(View.GONE) } @Test public fun testUpdateControlsNullPlaybackState() { // GIVEN that the playback state is null (ie. the media session was destroyed) whenever(mockMediaController.getPlaybackState()).thenReturn(null) // WHEN updated keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build()) FakeExecutor.exhaustExecutors(fakeExecutor) // THEN the controls are cleared (ie. visibility is set to GONE) verify(mockView).setVisibility(View.GONE) } @Test public fun testSongName() { val song: String = "Song" mediaMetadata.putText(MediaMetadata.METADATA_KEY_TITLE, song) keyguardMediaPlayer.updateControls(entry.build(), mockIcon, mediaMetadata.build()) keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build()) assertThat(fakeExecutor.runAllReady()).isEqualTo(1) assertThat(songView.getText()).isEqualTo(song) Loading @@ -137,7 +168,7 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() { val artist: String = "Artist" mediaMetadata.putText(MediaMetadata.METADATA_KEY_ARTIST, artist) keyguardMediaPlayer.updateControls(entry.build(), mockIcon, mediaMetadata.build()) keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build()) assertThat(fakeExecutor.runAllReady()).isEqualTo(1) assertThat(artistView.getText()).isEqualTo(artist) Loading