Merge "Close LS media player when session is destroyed" into rvc-dev

This commit is contained in:
TreeHugger Robot
2020-04-08 05:23:37 +00:00
committed by Android (Google) Code Review
3 changed files with 102 additions and 9 deletions

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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)