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

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