Close LS media player when session is destroyed
When updating media player, check the playing state. It is null after the media session has been destroyed. This is necessary since the NotificationMediaManager is used as the source of information about the "current" session. It still sends updates after a media session has been destroyed. Fixes: 152821122 Test: play podcast, pause and dismiss app. Go to lock screen. Verify that LS media player has closed. Change-Id: I52445d18eb945127f6c9af0908e154ac983e88cf
This commit is contained in:
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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. */
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user