Merge "Close LS media player when session is destroyed" into rvc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
0dcd72e1de
@@ -24,6 +24,8 @@ import android.graphics.Bitmap;
|
|||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.graphics.drawable.Icon;
|
import android.graphics.drawable.Icon;
|
||||||
import android.media.MediaMetadata;
|
import android.media.MediaMetadata;
|
||||||
|
import android.media.session.MediaController;
|
||||||
|
import android.media.session.MediaSession;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
@@ -40,6 +42,7 @@ import androidx.palette.graphics.Palette;
|
|||||||
import com.android.internal.util.ContrastColorUtil;
|
import com.android.internal.util.ContrastColorUtil;
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
import com.android.systemui.dagger.qualifiers.Background;
|
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.MediaNotificationProcessor;
|
||||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||||
import com.android.systemui.statusbar.notification.stack.MediaHeaderView;
|
import com.android.systemui.statusbar.notification.stack.MediaHeaderView;
|
||||||
@@ -71,10 +74,11 @@ public class KeyguardMediaPlayer {
|
|||||||
private KeyguardMediaObserver mObserver;
|
private KeyguardMediaObserver mObserver;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public KeyguardMediaPlayer(Context context, @Background Executor backgroundExecutor) {
|
public KeyguardMediaPlayer(Context context, MediaControllerFactory factory,
|
||||||
|
@Background Executor backgroundExecutor) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mBackgroundExecutor = backgroundExecutor;
|
mBackgroundExecutor = backgroundExecutor;
|
||||||
mViewModel = new KeyguardMediaViewModel(context);
|
mViewModel = new KeyguardMediaViewModel(context, factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Binds media controls to a view hierarchy. */
|
/** Binds media controls to a view hierarchy. */
|
||||||
@@ -139,14 +143,16 @@ public class KeyguardMediaPlayer {
|
|||||||
private static final class KeyguardMediaViewModel {
|
private static final class KeyguardMediaViewModel {
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
|
private final MediaControllerFactory mMediaControllerFactory;
|
||||||
private final MutableLiveData<KeyguardMedia> mMedia = new MutableLiveData<>();
|
private final MutableLiveData<KeyguardMedia> mMedia = new MutableLiveData<>();
|
||||||
private final Object mActionsLock = new Object();
|
private final Object mActionsLock = new Object();
|
||||||
private List<PendingIntent> mActions;
|
private List<PendingIntent> mActions;
|
||||||
private float mAlbumArtRadius;
|
private float mAlbumArtRadius;
|
||||||
private int mAlbumArtSize;
|
private int mAlbumArtSize;
|
||||||
|
|
||||||
KeyguardMediaViewModel(Context context) {
|
KeyguardMediaViewModel(Context context, MediaControllerFactory factory) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
|
mMediaControllerFactory = factory;
|
||||||
loadDimens();
|
loadDimens();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,6 +168,17 @@ public class KeyguardMediaPlayer {
|
|||||||
public void updateControls(NotificationEntry entry, Icon appIcon,
|
public void updateControls(NotificationEntry entry, Icon appIcon,
|
||||||
MediaMetadata mediaMetadata) {
|
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
|
// Foreground and Background colors computed from album art
|
||||||
Notification notif = entry.getSbn().getNotification();
|
Notification notif = entry.getSbn().getNotification();
|
||||||
int fgColor = notif.color;
|
int fgColor = notif.color;
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,8 +16,12 @@
|
|||||||
|
|
||||||
package com.android.keyguard
|
package com.android.keyguard
|
||||||
|
|
||||||
|
import android.app.Notification
|
||||||
import android.graphics.drawable.Icon
|
import android.graphics.drawable.Icon
|
||||||
import android.media.MediaMetadata
|
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.AndroidTestingRunner
|
||||||
import android.testing.TestableLooper
|
import android.testing.TestableLooper
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -28,7 +32,9 @@ import androidx.test.filters.SmallTest
|
|||||||
|
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
import com.android.systemui.SysuiTestCase
|
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.statusbar.notification.collection.NotificationEntryBuilder
|
||||||
|
import com.android.systemui.media.MediaControllerFactory
|
||||||
import com.android.systemui.util.concurrency.FakeExecutor
|
import com.android.systemui.util.concurrency.FakeExecutor
|
||||||
import com.android.systemui.util.time.FakeSystemClock
|
import com.android.systemui.util.time.FakeSystemClock
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
@@ -38,6 +44,7 @@ import org.junit.Before
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito.any
|
||||||
import org.mockito.Mockito.mock
|
import org.mockito.Mockito.mock
|
||||||
import org.mockito.Mockito.verify
|
import org.mockito.Mockito.verify
|
||||||
import org.mockito.Mockito.`when` as whenever
|
import org.mockito.Mockito.`when` as whenever
|
||||||
@@ -48,9 +55,12 @@ import org.mockito.Mockito.`when` as whenever
|
|||||||
public class KeyguardMediaPlayerTest : SysuiTestCase() {
|
public class KeyguardMediaPlayerTest : SysuiTestCase() {
|
||||||
|
|
||||||
private lateinit var keyguardMediaPlayer: KeyguardMediaPlayer
|
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 fakeExecutor: FakeExecutor
|
||||||
private lateinit var mediaMetadata: MediaMetadata.Builder
|
private lateinit var mediaMetadata: MediaMetadata.Builder
|
||||||
private lateinit var entry: NotificationEntryBuilder
|
private lateinit var entry: NotificationEntry
|
||||||
@Mock private lateinit var mockView: View
|
@Mock private lateinit var mockView: View
|
||||||
private lateinit var songView: TextView
|
private lateinit var songView: TextView
|
||||||
private lateinit var artistView: TextView
|
private lateinit var artistView: TextView
|
||||||
@@ -70,8 +80,16 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public fun setup() {
|
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())
|
fakeExecutor = FakeExecutor(FakeSystemClock())
|
||||||
keyguardMediaPlayer = KeyguardMediaPlayer(context, fakeExecutor)
|
keyguardMediaPlayer = KeyguardMediaPlayer(context, mockMediaFactory, fakeExecutor)
|
||||||
mockIcon = mock(Icon::class.java)
|
mockIcon = mock(Icon::class.java)
|
||||||
|
|
||||||
mockView = mock(View::class.java)
|
mockView = mock(View::class.java)
|
||||||
@@ -81,7 +99,9 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() {
|
|||||||
whenever<TextView>(mockView.findViewById(R.id.header_artist)).thenReturn(artistView)
|
whenever<TextView>(mockView.findViewById(R.id.header_artist)).thenReturn(artistView)
|
||||||
|
|
||||||
mediaMetadata = MediaMetadata.Builder()
|
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)
|
ArchTaskExecutor.getInstance().setDelegate(taskExecutor)
|
||||||
|
|
||||||
@@ -109,7 +129,7 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public fun testUpdateControls() {
|
public fun testUpdateControls() {
|
||||||
keyguardMediaPlayer.updateControls(entry.build(), mockIcon, mediaMetadata.build())
|
keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build())
|
||||||
FakeExecutor.exhaustExecutors(fakeExecutor)
|
FakeExecutor.exhaustExecutors(fakeExecutor)
|
||||||
verify(mockView).setVisibility(View.VISIBLE)
|
verify(mockView).setVisibility(View.VISIBLE)
|
||||||
}
|
}
|
||||||
@@ -121,12 +141,23 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() {
|
|||||||
verify(mockView).setVisibility(View.GONE)
|
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
|
@Test
|
||||||
public fun testSongName() {
|
public fun testSongName() {
|
||||||
val song: String = "Song"
|
val song: String = "Song"
|
||||||
mediaMetadata.putText(MediaMetadata.METADATA_KEY_TITLE, 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(fakeExecutor.runAllReady()).isEqualTo(1)
|
||||||
assertThat(songView.getText()).isEqualTo(song)
|
assertThat(songView.getText()).isEqualTo(song)
|
||||||
@@ -137,7 +168,7 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() {
|
|||||||
val artist: String = "Artist"
|
val artist: String = "Artist"
|
||||||
mediaMetadata.putText(MediaMetadata.METADATA_KEY_ARTIST, 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(fakeExecutor.runAllReady()).isEqualTo(1)
|
||||||
assertThat(artistView.getText()).isEqualTo(artist)
|
assertThat(artistView.getText()).isEqualTo(artist)
|
||||||
|
|||||||
Reference in New Issue
Block a user