Merge "Use same steps to test MBS connection as when actually connecting" into rvc-qpr-dev am: 9746948a58
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/12685369 Change-Id: I236a457960355d55bddf4ada102cb1b387c16fb8
This commit is contained in:
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.media.browse.MediaBrowser;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Testable wrapper around {@link MediaBrowser} constructor
|
||||||
|
*/
|
||||||
|
public class MediaBrowserFactory {
|
||||||
|
private final Context mContext;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public MediaBrowserFactory(Context context) {
|
||||||
|
mContext = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new MediaBrowser
|
||||||
|
*
|
||||||
|
* @param serviceComponent
|
||||||
|
* @param callback
|
||||||
|
* @param rootHints
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public MediaBrowser create(ComponentName serviceComponent,
|
||||||
|
MediaBrowser.ConnectionCallback callback, Bundle rootHints) {
|
||||||
|
return new MediaBrowser(mContext, serviceComponent, callback, rootHints);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ import android.os.UserHandle
|
|||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.service.media.MediaBrowserService
|
import android.service.media.MediaBrowserService
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import com.android.internal.annotations.VisibleForTesting
|
||||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||||
import com.android.systemui.dagger.qualifiers.Background
|
import com.android.systemui.dagger.qualifiers.Background
|
||||||
import com.android.systemui.tuner.TunerService
|
import com.android.systemui.tuner.TunerService
|
||||||
@@ -47,7 +48,8 @@ class MediaResumeListener @Inject constructor(
|
|||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val broadcastDispatcher: BroadcastDispatcher,
|
private val broadcastDispatcher: BroadcastDispatcher,
|
||||||
@Background private val backgroundExecutor: Executor,
|
@Background private val backgroundExecutor: Executor,
|
||||||
private val tunerService: TunerService
|
private val tunerService: TunerService,
|
||||||
|
private val mediaBrowserFactory: ResumeMediaBrowserFactory
|
||||||
) : MediaDataManager.Listener {
|
) : MediaDataManager.Listener {
|
||||||
|
|
||||||
private var useMediaResumption: Boolean = Utils.useMediaResumption(context)
|
private var useMediaResumption: Boolean = Utils.useMediaResumption(context)
|
||||||
@@ -58,7 +60,8 @@ class MediaResumeListener @Inject constructor(
|
|||||||
private var mediaBrowser: ResumeMediaBrowser? = null
|
private var mediaBrowser: ResumeMediaBrowser? = null
|
||||||
private var currentUserId: Int = context.userId
|
private var currentUserId: Int = context.userId
|
||||||
|
|
||||||
private val userChangeReceiver = object : BroadcastReceiver() {
|
@VisibleForTesting
|
||||||
|
val userChangeReceiver = object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
if (Intent.ACTION_USER_UNLOCKED == intent.action) {
|
if (Intent.ACTION_USER_UNLOCKED == intent.action) {
|
||||||
loadMediaResumptionControls()
|
loadMediaResumptionControls()
|
||||||
@@ -142,7 +145,7 @@ class MediaResumeListener @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
resumeComponents.forEach {
|
resumeComponents.forEach {
|
||||||
val browser = ResumeMediaBrowser(context, mediaBrowserCallback, it)
|
val browser = mediaBrowserFactory.create(mediaBrowserCallback, it)
|
||||||
browser.findRecentMedia()
|
browser.findRecentMedia()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,14 +184,10 @@ class MediaResumeListener @Inject constructor(
|
|||||||
private fun tryUpdateResumptionList(key: String, componentName: ComponentName) {
|
private fun tryUpdateResumptionList(key: String, componentName: ComponentName) {
|
||||||
Log.d(TAG, "Testing if we can connect to $componentName")
|
Log.d(TAG, "Testing if we can connect to $componentName")
|
||||||
mediaBrowser?.disconnect()
|
mediaBrowser?.disconnect()
|
||||||
mediaBrowser = ResumeMediaBrowser(context,
|
mediaBrowser = mediaBrowserFactory.create(
|
||||||
object : ResumeMediaBrowser.Callback() {
|
object : ResumeMediaBrowser.Callback() {
|
||||||
override fun onConnected() {
|
override fun onConnected() {
|
||||||
Log.d(TAG, "yes we can resume with $componentName")
|
Log.d(TAG, "Connected to $componentName")
|
||||||
mediaDataManager.setResumeAction(key, getResumeAction(componentName))
|
|
||||||
updateResumptionList(componentName)
|
|
||||||
mediaBrowser?.disconnect()
|
|
||||||
mediaBrowser = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onError() {
|
override fun onError() {
|
||||||
@@ -197,6 +196,19 @@ class MediaResumeListener @Inject constructor(
|
|||||||
mediaBrowser?.disconnect()
|
mediaBrowser?.disconnect()
|
||||||
mediaBrowser = null
|
mediaBrowser = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun addTrack(
|
||||||
|
desc: MediaDescription,
|
||||||
|
component: ComponentName,
|
||||||
|
browser: ResumeMediaBrowser
|
||||||
|
) {
|
||||||
|
// Since this is a test, just save the component for later
|
||||||
|
Log.d(TAG, "Can get resumable media from $componentName")
|
||||||
|
mediaDataManager.setResumeAction(key, getResumeAction(componentName))
|
||||||
|
updateResumptionList(componentName)
|
||||||
|
mediaBrowser?.disconnect()
|
||||||
|
mediaBrowser = null
|
||||||
|
}
|
||||||
},
|
},
|
||||||
componentName)
|
componentName)
|
||||||
mediaBrowser?.testConnection()
|
mediaBrowser?.testConnection()
|
||||||
@@ -233,7 +245,7 @@ class MediaResumeListener @Inject constructor(
|
|||||||
private fun getResumeAction(componentName: ComponentName): Runnable {
|
private fun getResumeAction(componentName: ComponentName): Runnable {
|
||||||
return Runnable {
|
return Runnable {
|
||||||
mediaBrowser?.disconnect()
|
mediaBrowser?.disconnect()
|
||||||
mediaBrowser = ResumeMediaBrowser(context,
|
mediaBrowser = mediaBrowserFactory.create(
|
||||||
object : ResumeMediaBrowser.Callback() {
|
object : ResumeMediaBrowser.Callback() {
|
||||||
override fun onConnected() {
|
override fun onConnected() {
|
||||||
if (mediaBrowser?.token == null) {
|
if (mediaBrowser?.token == null) {
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ import android.service.media.MediaBrowserService;
|
|||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,6 +48,7 @@ public class ResumeMediaBrowser {
|
|||||||
private static final String TAG = "ResumeMediaBrowser";
|
private static final String TAG = "ResumeMediaBrowser";
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final Callback mCallback;
|
private final Callback mCallback;
|
||||||
|
private MediaBrowserFactory mBrowserFactory;
|
||||||
private MediaBrowser mMediaBrowser;
|
private MediaBrowser mMediaBrowser;
|
||||||
private ComponentName mComponentName;
|
private ComponentName mComponentName;
|
||||||
|
|
||||||
@@ -55,10 +58,12 @@ public class ResumeMediaBrowser {
|
|||||||
* @param callback used to report media items found
|
* @param callback used to report media items found
|
||||||
* @param componentName Component name of the MediaBrowserService this browser will connect to
|
* @param componentName Component name of the MediaBrowserService this browser will connect to
|
||||||
*/
|
*/
|
||||||
public ResumeMediaBrowser(Context context, Callback callback, ComponentName componentName) {
|
public ResumeMediaBrowser(Context context, Callback callback, ComponentName componentName,
|
||||||
|
MediaBrowserFactory browserFactory) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mCallback = callback;
|
mCallback = callback;
|
||||||
mComponentName = componentName;
|
mComponentName = componentName;
|
||||||
|
mBrowserFactory = browserFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -74,7 +79,7 @@ public class ResumeMediaBrowser {
|
|||||||
disconnect();
|
disconnect();
|
||||||
Bundle rootHints = new Bundle();
|
Bundle rootHints = new Bundle();
|
||||||
rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
|
rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
|
||||||
mMediaBrowser = new MediaBrowser(mContext,
|
mMediaBrowser = mBrowserFactory.create(
|
||||||
mComponentName,
|
mComponentName,
|
||||||
mConnectionCallback,
|
mConnectionCallback,
|
||||||
rootHints);
|
rootHints);
|
||||||
@@ -88,17 +93,19 @@ public class ResumeMediaBrowser {
|
|||||||
List<MediaBrowser.MediaItem> children) {
|
List<MediaBrowser.MediaItem> children) {
|
||||||
if (children.size() == 0) {
|
if (children.size() == 0) {
|
||||||
Log.d(TAG, "No children found for " + mComponentName);
|
Log.d(TAG, "No children found for " + mComponentName);
|
||||||
return;
|
mCallback.onError();
|
||||||
}
|
|
||||||
// We ask apps to return a playable item as the first child when sending
|
|
||||||
// a request with EXTRA_RECENT; if they don't, no resume controls
|
|
||||||
MediaBrowser.MediaItem child = children.get(0);
|
|
||||||
MediaDescription desc = child.getDescription();
|
|
||||||
if (child.isPlayable() && mMediaBrowser != null) {
|
|
||||||
mCallback.addTrack(desc, mMediaBrowser.getServiceComponent(),
|
|
||||||
ResumeMediaBrowser.this);
|
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "Child found but not playable for " + mComponentName);
|
// We ask apps to return a playable item as the first child when sending
|
||||||
|
// a request with EXTRA_RECENT; if they don't, no resume controls
|
||||||
|
MediaBrowser.MediaItem child = children.get(0);
|
||||||
|
MediaDescription desc = child.getDescription();
|
||||||
|
if (child.isPlayable() && mMediaBrowser != null) {
|
||||||
|
mCallback.addTrack(desc, mMediaBrowser.getServiceComponent(),
|
||||||
|
ResumeMediaBrowser.this);
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Child found but not playable for " + mComponentName);
|
||||||
|
mCallback.onError();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
disconnect();
|
disconnect();
|
||||||
}
|
}
|
||||||
@@ -131,7 +138,7 @@ public class ResumeMediaBrowser {
|
|||||||
Log.d(TAG, "Service connected for " + mComponentName);
|
Log.d(TAG, "Service connected for " + mComponentName);
|
||||||
if (mMediaBrowser != null && mMediaBrowser.isConnected()) {
|
if (mMediaBrowser != null && mMediaBrowser.isConnected()) {
|
||||||
String root = mMediaBrowser.getRoot();
|
String root = mMediaBrowser.getRoot();
|
||||||
if (!TextUtils.isEmpty(root)) {
|
if (!TextUtils.isEmpty(root) && mMediaBrowser != null) {
|
||||||
mCallback.onConnected();
|
mCallback.onConnected();
|
||||||
mMediaBrowser.subscribe(root, mSubscriptionCallback);
|
mMediaBrowser.subscribe(root, mSubscriptionCallback);
|
||||||
return;
|
return;
|
||||||
@@ -182,7 +189,7 @@ public class ResumeMediaBrowser {
|
|||||||
disconnect();
|
disconnect();
|
||||||
Bundle rootHints = new Bundle();
|
Bundle rootHints = new Bundle();
|
||||||
rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
|
rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
|
||||||
mMediaBrowser = new MediaBrowser(mContext, mComponentName,
|
mMediaBrowser = mBrowserFactory.create(mComponentName,
|
||||||
new MediaBrowser.ConnectionCallback() {
|
new MediaBrowser.ConnectionCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void onConnected() {
|
public void onConnected() {
|
||||||
@@ -192,7 +199,7 @@ public class ResumeMediaBrowser {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MediaSession.Token token = mMediaBrowser.getSessionToken();
|
MediaSession.Token token = mMediaBrowser.getSessionToken();
|
||||||
MediaController controller = new MediaController(mContext, token);
|
MediaController controller = createMediaController(token);
|
||||||
controller.getTransportControls();
|
controller.getTransportControls();
|
||||||
controller.getTransportControls().prepare();
|
controller.getTransportControls().prepare();
|
||||||
controller.getTransportControls().play();
|
controller.getTransportControls().play();
|
||||||
@@ -212,6 +219,11 @@ public class ResumeMediaBrowser {
|
|||||||
mMediaBrowser.connect();
|
mMediaBrowser.connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
protected MediaController createMediaController(MediaSession.Token token) {
|
||||||
|
return new MediaController(mContext, token);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the media session token
|
* Get the media session token
|
||||||
* @return the token, or null if the MediaBrowser is null or disconnected
|
* @return the token, or null if the MediaBrowser is null or disconnected
|
||||||
@@ -235,42 +247,19 @@ public class ResumeMediaBrowser {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to test if SystemUI is allowed to connect to the given component as a MediaBrowser.
|
* Used to test if SystemUI is allowed to connect to the given component as a MediaBrowser.
|
||||||
* ResumeMediaBrowser.Callback#onError or ResumeMediaBrowser.Callback#onConnected will be called
|
* If it can connect, ResumeMediaBrowser.Callback#onConnected will be called. If valid media is
|
||||||
* depending on whether it was successful.
|
* found, then ResumeMediaBrowser.Callback#addTrack will also be called. This allows for more
|
||||||
|
* detailed logging if the service has issues. If it cannot connect, or cannot find valid media,
|
||||||
|
* then ResumeMediaBrowser.Callback#onError will be called.
|
||||||
* ResumeMediaBrowser#disconnect should be called after this to ensure the connection is closed.
|
* ResumeMediaBrowser#disconnect should be called after this to ensure the connection is closed.
|
||||||
*/
|
*/
|
||||||
public void testConnection() {
|
public void testConnection() {
|
||||||
disconnect();
|
disconnect();
|
||||||
final MediaBrowser.ConnectionCallback connectionCallback =
|
|
||||||
new MediaBrowser.ConnectionCallback() {
|
|
||||||
@Override
|
|
||||||
public void onConnected() {
|
|
||||||
Log.d(TAG, "connected");
|
|
||||||
if (mMediaBrowser == null || !mMediaBrowser.isConnected()
|
|
||||||
|| TextUtils.isEmpty(mMediaBrowser.getRoot())) {
|
|
||||||
mCallback.onError();
|
|
||||||
} else {
|
|
||||||
mCallback.onConnected();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnectionSuspended() {
|
|
||||||
Log.d(TAG, "suspended");
|
|
||||||
mCallback.onError();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnectionFailed() {
|
|
||||||
Log.d(TAG, "failed");
|
|
||||||
mCallback.onError();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Bundle rootHints = new Bundle();
|
Bundle rootHints = new Bundle();
|
||||||
rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
|
rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
|
||||||
mMediaBrowser = new MediaBrowser(mContext,
|
mMediaBrowser = mBrowserFactory.create(
|
||||||
mComponentName,
|
mComponentName,
|
||||||
connectionCallback,
|
mConnectionCallback,
|
||||||
rootHints);
|
rootHints);
|
||||||
mMediaBrowser.connect();
|
mMediaBrowser.connect();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* 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.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Testable wrapper around {@link ResumeMediaBrowser} constructor
|
||||||
|
*/
|
||||||
|
public class ResumeMediaBrowserFactory {
|
||||||
|
private final Context mContext;
|
||||||
|
private final MediaBrowserFactory mBrowserFactory;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public ResumeMediaBrowserFactory(Context context, MediaBrowserFactory browserFactory) {
|
||||||
|
mContext = context;
|
||||||
|
mBrowserFactory = browserFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new ResumeMediaBrowser.
|
||||||
|
*
|
||||||
|
* @param callback will be called on connection or error, and addTrack when media item found
|
||||||
|
* @param componentName component to browse
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public ResumeMediaBrowser create(ResumeMediaBrowser.Callback callback,
|
||||||
|
ComponentName componentName) {
|
||||||
|
return new ResumeMediaBrowser(mContext, callback, componentName, mBrowserFactory);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,258 @@
|
|||||||
|
/*
|
||||||
|
* 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.app.PendingIntent
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.pm.ResolveInfo
|
||||||
|
import android.content.pm.ServiceInfo
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.media.MediaDescription
|
||||||
|
import android.media.session.MediaSession
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.testing.AndroidTestingRunner
|
||||||
|
import android.testing.TestableLooper
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||||
|
import com.android.systemui.tuner.TunerService
|
||||||
|
import com.android.systemui.util.concurrency.FakeExecutor
|
||||||
|
import com.android.systemui.util.time.FakeSystemClock
|
||||||
|
import org.junit.After
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.ArgumentCaptor
|
||||||
|
import org.mockito.ArgumentMatchers.anyInt
|
||||||
|
import org.mockito.Captor
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.mockito.Mockito.mock
|
||||||
|
import org.mockito.Mockito.never
|
||||||
|
import org.mockito.Mockito.times
|
||||||
|
import org.mockito.Mockito.verify
|
||||||
|
import org.mockito.Mockito.`when` as whenever
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
private const val KEY = "TEST_KEY"
|
||||||
|
private const val OLD_KEY = "RESUME_KEY"
|
||||||
|
private const val APP = "APP"
|
||||||
|
private const val BG_COLOR = Color.RED
|
||||||
|
private const val PACKAGE_NAME = "PKG"
|
||||||
|
private const val CLASS_NAME = "CLASS"
|
||||||
|
private const val ARTIST = "ARTIST"
|
||||||
|
private const val TITLE = "TITLE"
|
||||||
|
private const val USER_ID = 0
|
||||||
|
private const val MEDIA_PREFERENCES = "media_control_prefs"
|
||||||
|
private const val RESUME_COMPONENTS = "package1/class1:package2/class2:package3/class3"
|
||||||
|
|
||||||
|
private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
|
||||||
|
private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
|
||||||
|
private fun <T> any(): T = Mockito.any<T>()
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
@RunWith(AndroidTestingRunner::class)
|
||||||
|
@TestableLooper.RunWithLooper
|
||||||
|
class MediaResumeListenerTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
|
||||||
|
@Mock private lateinit var mediaDataManager: MediaDataManager
|
||||||
|
@Mock private lateinit var device: MediaDeviceData
|
||||||
|
@Mock private lateinit var token: MediaSession.Token
|
||||||
|
@Mock private lateinit var tunerService: TunerService
|
||||||
|
@Mock private lateinit var resumeBrowserFactory: ResumeMediaBrowserFactory
|
||||||
|
@Mock private lateinit var resumeBrowser: ResumeMediaBrowser
|
||||||
|
@Mock private lateinit var sharedPrefs: SharedPreferences
|
||||||
|
@Mock private lateinit var sharedPrefsEditor: SharedPreferences.Editor
|
||||||
|
@Mock private lateinit var mockContext: Context
|
||||||
|
@Mock private lateinit var pendingIntent: PendingIntent
|
||||||
|
|
||||||
|
@Captor lateinit var callbackCaptor: ArgumentCaptor<ResumeMediaBrowser.Callback>
|
||||||
|
|
||||||
|
private lateinit var executor: FakeExecutor
|
||||||
|
private lateinit var data: MediaData
|
||||||
|
private lateinit var resumeListener: MediaResumeListener
|
||||||
|
|
||||||
|
private var originalQsSetting = Settings.Global.getInt(context.contentResolver,
|
||||||
|
Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1)
|
||||||
|
private var originalResumeSetting = Settings.Secure.getInt(context.contentResolver,
|
||||||
|
Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
|
||||||
|
Settings.Global.putInt(context.contentResolver,
|
||||||
|
Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1)
|
||||||
|
Settings.Secure.putInt(context.contentResolver,
|
||||||
|
Settings.Secure.MEDIA_CONTROLS_RESUME, 1)
|
||||||
|
|
||||||
|
whenever(resumeBrowserFactory.create(capture(callbackCaptor), any()))
|
||||||
|
.thenReturn(resumeBrowser)
|
||||||
|
|
||||||
|
// resume components are stored in sharedpreferences
|
||||||
|
whenever(mockContext.getSharedPreferences(eq(MEDIA_PREFERENCES), anyInt()))
|
||||||
|
.thenReturn(sharedPrefs)
|
||||||
|
whenever(sharedPrefs.getString(any(), any())).thenReturn(RESUME_COMPONENTS)
|
||||||
|
whenever(sharedPrefs.edit()).thenReturn(sharedPrefsEditor)
|
||||||
|
whenever(sharedPrefsEditor.putString(any(), any())).thenReturn(sharedPrefsEditor)
|
||||||
|
whenever(mockContext.packageManager).thenReturn(context.packageManager)
|
||||||
|
whenever(mockContext.contentResolver).thenReturn(context.contentResolver)
|
||||||
|
|
||||||
|
executor = FakeExecutor(FakeSystemClock())
|
||||||
|
resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
|
||||||
|
tunerService, resumeBrowserFactory)
|
||||||
|
resumeListener.setManager(mediaDataManager)
|
||||||
|
mediaDataManager.addListener(resumeListener)
|
||||||
|
|
||||||
|
data = MediaData(
|
||||||
|
userId = USER_ID,
|
||||||
|
initialized = true,
|
||||||
|
backgroundColor = BG_COLOR,
|
||||||
|
app = APP,
|
||||||
|
appIcon = null,
|
||||||
|
artist = ARTIST,
|
||||||
|
song = TITLE,
|
||||||
|
artwork = null,
|
||||||
|
actions = emptyList(),
|
||||||
|
actionsToShowInCompact = emptyList(),
|
||||||
|
packageName = PACKAGE_NAME,
|
||||||
|
token = token,
|
||||||
|
clickIntent = null,
|
||||||
|
device = device,
|
||||||
|
active = true,
|
||||||
|
notificationKey = KEY,
|
||||||
|
resumeAction = null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
Settings.Global.putInt(context.contentResolver,
|
||||||
|
Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, originalQsSetting)
|
||||||
|
Settings.Secure.putInt(context.contentResolver,
|
||||||
|
Settings.Secure.MEDIA_CONTROLS_RESUME, originalResumeSetting)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testWhenNoResumption_doesNothing() {
|
||||||
|
Settings.Secure.putInt(context.contentResolver,
|
||||||
|
Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
|
||||||
|
|
||||||
|
// When listener is created, we do NOT register a user change listener
|
||||||
|
val listener = MediaResumeListener(context, broadcastDispatcher, executor, tunerService,
|
||||||
|
resumeBrowserFactory)
|
||||||
|
listener.setManager(mediaDataManager)
|
||||||
|
verify(broadcastDispatcher, never()).registerReceiver(eq(listener.userChangeReceiver),
|
||||||
|
any(), any(), any())
|
||||||
|
|
||||||
|
// When data is loaded, we do NOT execute or update anything
|
||||||
|
listener.onMediaDataLoaded(KEY, OLD_KEY, data)
|
||||||
|
assertThat(executor.numPending()).isEqualTo(0)
|
||||||
|
verify(mediaDataManager, never()).setResumeAction(any(), any())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testOnLoad_checksForResume_noService() {
|
||||||
|
// When media data is loaded that has not been checked yet, and does not have a MBS
|
||||||
|
resumeListener.onMediaDataLoaded(KEY, null, data)
|
||||||
|
|
||||||
|
// Then we report back to the manager
|
||||||
|
verify(mediaDataManager).setResumeAction(KEY, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testOnLoad_checksForResume_hasService() {
|
||||||
|
// Set up mocks to successfully find a MBS that returns valid media
|
||||||
|
val pm = mock(PackageManager::class.java)
|
||||||
|
whenever(mockContext.packageManager).thenReturn(pm)
|
||||||
|
val resolveInfo = ResolveInfo()
|
||||||
|
val serviceInfo = ServiceInfo()
|
||||||
|
serviceInfo.packageName = PACKAGE_NAME
|
||||||
|
resolveInfo.serviceInfo = serviceInfo
|
||||||
|
resolveInfo.serviceInfo.name = CLASS_NAME
|
||||||
|
val resumeInfo = listOf(resolveInfo)
|
||||||
|
whenever(pm.queryIntentServices(any(), anyInt())).thenReturn(resumeInfo)
|
||||||
|
|
||||||
|
val description = MediaDescription.Builder().setTitle(TITLE).build()
|
||||||
|
val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
|
||||||
|
whenever(resumeBrowser.testConnection()).thenAnswer {
|
||||||
|
callbackCaptor.value.addTrack(description, component, resumeBrowser)
|
||||||
|
}
|
||||||
|
|
||||||
|
// When media data is loaded that has not been checked yet, and does have a MBS
|
||||||
|
val dataCopy = data.copy(resumeAction = null, hasCheckedForResume = false)
|
||||||
|
resumeListener.onMediaDataLoaded(KEY, null, dataCopy)
|
||||||
|
|
||||||
|
// Then we test whether the service is valid
|
||||||
|
executor.runAllReady()
|
||||||
|
verify(resumeBrowser).testConnection()
|
||||||
|
|
||||||
|
// And since it is, we report back to the manager
|
||||||
|
verify(mediaDataManager).setResumeAction(eq(KEY), any())
|
||||||
|
|
||||||
|
// But we do not tell it to add new controls
|
||||||
|
verify(mediaDataManager, never())
|
||||||
|
.addResumptionControls(anyInt(), any(), any(), any(), any(), any(), any())
|
||||||
|
|
||||||
|
// Finally, make sure the resume browser disconnected
|
||||||
|
verify(resumeBrowser).disconnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testOnLoad_doesNotCheckAgain() {
|
||||||
|
// When a media data is loaded that has been checked already
|
||||||
|
var dataCopy = data.copy(hasCheckedForResume = true)
|
||||||
|
resumeListener.onMediaDataLoaded(KEY, null, dataCopy)
|
||||||
|
|
||||||
|
// Then we should not check it again
|
||||||
|
verify(resumeBrowser, never()).testConnection()
|
||||||
|
verify(mediaDataManager, never()).setResumeAction(KEY, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testOnUserUnlock_loadsTracks() {
|
||||||
|
// Set up mock service to successfully find valid media
|
||||||
|
val description = MediaDescription.Builder().setTitle(TITLE).build()
|
||||||
|
val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
|
||||||
|
whenever(resumeBrowser.token).thenReturn(token)
|
||||||
|
whenever(resumeBrowser.appIntent).thenReturn(pendingIntent)
|
||||||
|
whenever(resumeBrowser.findRecentMedia()).thenAnswer {
|
||||||
|
callbackCaptor.value.addTrack(description, component, resumeBrowser)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure broadcast receiver is registered
|
||||||
|
resumeListener.setManager(mediaDataManager)
|
||||||
|
verify(broadcastDispatcher).registerReceiver(eq(resumeListener.userChangeReceiver),
|
||||||
|
any(), any(), any())
|
||||||
|
|
||||||
|
// When we get an unlock event
|
||||||
|
val intent = Intent(Intent.ACTION_USER_UNLOCKED)
|
||||||
|
resumeListener.userChangeReceiver.onReceive(context, intent)
|
||||||
|
|
||||||
|
// Then we should attempt to find recent media for each saved component
|
||||||
|
verify(resumeBrowser, times(3)).findRecentMedia()
|
||||||
|
|
||||||
|
// Then since the mock service found media, the manager should be informed
|
||||||
|
verify(mediaDataManager, times(3)).addResumptionControls(anyInt(),
|
||||||
|
any(), any(), any(), any(), any(), eq(PACKAGE_NAME))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,287 @@
|
|||||||
|
/*
|
||||||
|
* 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.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.media.MediaDescription
|
||||||
|
import android.media.browse.MediaBrowser
|
||||||
|
import android.media.session.MediaController
|
||||||
|
import android.media.session.MediaSession
|
||||||
|
import android.service.media.MediaBrowserService
|
||||||
|
import android.testing.AndroidTestingRunner
|
||||||
|
import android.testing.TestableLooper
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.ArgumentCaptor
|
||||||
|
import org.mockito.Captor
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.mockito.Mockito.verify
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
import org.mockito.Mockito.`when` as whenever
|
||||||
|
|
||||||
|
private const val PACKAGE_NAME = "package"
|
||||||
|
private const val CLASS_NAME = "class"
|
||||||
|
private const val TITLE = "song title"
|
||||||
|
private const val MEDIA_ID = "media ID"
|
||||||
|
private const val ROOT = "media browser root"
|
||||||
|
|
||||||
|
private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
|
||||||
|
private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
|
||||||
|
private fun <T> any(): T = Mockito.any<T>()
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
@RunWith(AndroidTestingRunner::class)
|
||||||
|
@TestableLooper.RunWithLooper
|
||||||
|
public class ResumeMediaBrowserTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
private lateinit var resumeBrowser: TestableResumeMediaBrowser
|
||||||
|
private val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
|
||||||
|
private val description = MediaDescription.Builder()
|
||||||
|
.setTitle(TITLE)
|
||||||
|
.setMediaId(MEDIA_ID)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
@Mock lateinit var callback: ResumeMediaBrowser.Callback
|
||||||
|
@Mock lateinit var listener: MediaResumeListener
|
||||||
|
@Mock lateinit var service: MediaBrowserService
|
||||||
|
@Mock lateinit var browserFactory: MediaBrowserFactory
|
||||||
|
@Mock lateinit var browser: MediaBrowser
|
||||||
|
@Mock lateinit var token: MediaSession.Token
|
||||||
|
@Mock lateinit var mediaController: MediaController
|
||||||
|
@Mock lateinit var transportControls: MediaController.TransportControls
|
||||||
|
|
||||||
|
@Captor lateinit var connectionCallback: ArgumentCaptor<MediaBrowser.ConnectionCallback>
|
||||||
|
@Captor lateinit var subscriptionCallback: ArgumentCaptor<MediaBrowser.SubscriptionCallback>
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
|
||||||
|
whenever(browserFactory.create(any(), capture(connectionCallback), any()))
|
||||||
|
.thenReturn(browser)
|
||||||
|
|
||||||
|
whenever(mediaController.transportControls).thenReturn(transportControls)
|
||||||
|
|
||||||
|
resumeBrowser = TestableResumeMediaBrowser(context, callback, component, browserFactory,
|
||||||
|
mediaController)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testConnection_connectionFails_callsOnError() {
|
||||||
|
// When testConnection cannot connect to the service
|
||||||
|
setupBrowserFailed()
|
||||||
|
resumeBrowser.testConnection()
|
||||||
|
|
||||||
|
// Then it calls onError
|
||||||
|
verify(callback).onError()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testConnection_connects_onConnected() {
|
||||||
|
// When testConnection can connect to the service
|
||||||
|
setupBrowserConnection()
|
||||||
|
resumeBrowser.testConnection()
|
||||||
|
|
||||||
|
// Then it calls onConnected
|
||||||
|
verify(callback).onConnected()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testConnection_noValidMedia_error() {
|
||||||
|
// When testConnection can connect to the service, and does not find valid media
|
||||||
|
setupBrowserConnectionNoResults()
|
||||||
|
resumeBrowser.testConnection()
|
||||||
|
|
||||||
|
// Then it calls onError
|
||||||
|
verify(callback).onError()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testConnection_hasValidMedia_addTrack() {
|
||||||
|
// When testConnection can connect to the service, and finds valid media
|
||||||
|
setupBrowserConnectionValidMedia()
|
||||||
|
resumeBrowser.testConnection()
|
||||||
|
|
||||||
|
// Then it calls addTrack
|
||||||
|
verify(callback).onConnected()
|
||||||
|
verify(callback).addTrack(eq(description), eq(component), eq(resumeBrowser))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testFindRecentMedia_connectionFails_error() {
|
||||||
|
// When findRecentMedia is called and we cannot connect
|
||||||
|
setupBrowserFailed()
|
||||||
|
resumeBrowser.findRecentMedia()
|
||||||
|
|
||||||
|
// Then it calls onError
|
||||||
|
verify(callback).onError()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testFindRecentMedia_noRoot_error() {
|
||||||
|
// When findRecentMedia is called and does not get a valid root
|
||||||
|
setupBrowserConnection()
|
||||||
|
whenever(browser.getRoot()).thenReturn(null)
|
||||||
|
resumeBrowser.findRecentMedia()
|
||||||
|
|
||||||
|
// Then it calls onError
|
||||||
|
verify(callback).onError()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testFindRecentMedia_connects_onConnected() {
|
||||||
|
// When findRecentMedia is called and we connect
|
||||||
|
setupBrowserConnection()
|
||||||
|
resumeBrowser.findRecentMedia()
|
||||||
|
|
||||||
|
// Then it calls onConnected
|
||||||
|
verify(callback).onConnected()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testFindRecentMedia_noChildren_error() {
|
||||||
|
// When findRecentMedia is called and we connect, but do not get any results
|
||||||
|
setupBrowserConnectionNoResults()
|
||||||
|
resumeBrowser.findRecentMedia()
|
||||||
|
|
||||||
|
// Then it calls onError
|
||||||
|
verify(callback).onError()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testFindRecentMedia_notPlayable_error() {
|
||||||
|
// When findRecentMedia is called and we connect, but do not get a playable child
|
||||||
|
setupBrowserConnectionNotPlayable()
|
||||||
|
resumeBrowser.findRecentMedia()
|
||||||
|
|
||||||
|
// Then it calls onError
|
||||||
|
verify(callback).onError()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testFindRecentMedia_hasValidMedia_addTrack() {
|
||||||
|
// When findRecentMedia is called and we can connect and get playable media
|
||||||
|
setupBrowserConnectionValidMedia()
|
||||||
|
resumeBrowser.findRecentMedia()
|
||||||
|
|
||||||
|
// Then it calls addTrack
|
||||||
|
verify(callback).addTrack(eq(description), eq(component), eq(resumeBrowser))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testRestart_connectionFails_error() {
|
||||||
|
// When restart is called and we cannot connect
|
||||||
|
setupBrowserFailed()
|
||||||
|
resumeBrowser.restart()
|
||||||
|
|
||||||
|
// Then it calls onError
|
||||||
|
verify(callback).onError()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testRestart_connects() {
|
||||||
|
// When restart is called and we connect successfully
|
||||||
|
setupBrowserConnection()
|
||||||
|
resumeBrowser.restart()
|
||||||
|
|
||||||
|
// Then it creates a new controller and sends play command
|
||||||
|
verify(transportControls).prepare()
|
||||||
|
verify(transportControls).play()
|
||||||
|
|
||||||
|
// Then it calls onConnected
|
||||||
|
verify(callback).onConnected()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to mock a failed connection
|
||||||
|
*/
|
||||||
|
private fun setupBrowserFailed() {
|
||||||
|
whenever(browser.connect()).thenAnswer {
|
||||||
|
connectionCallback.value.onConnectionFailed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to mock a successful connection only
|
||||||
|
*/
|
||||||
|
private fun setupBrowserConnection() {
|
||||||
|
whenever(browser.connect()).thenAnswer {
|
||||||
|
connectionCallback.value.onConnected()
|
||||||
|
}
|
||||||
|
whenever(browser.isConnected()).thenReturn(true)
|
||||||
|
whenever(browser.getRoot()).thenReturn(ROOT)
|
||||||
|
whenever(browser.sessionToken).thenReturn(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to mock a successful connection, but no media results
|
||||||
|
*/
|
||||||
|
private fun setupBrowserConnectionNoResults() {
|
||||||
|
setupBrowserConnection()
|
||||||
|
whenever(browser.subscribe(any(), capture(subscriptionCallback))).thenAnswer {
|
||||||
|
subscriptionCallback.value.onChildrenLoaded(ROOT, emptyList())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to mock a successful connection, but no playable results
|
||||||
|
*/
|
||||||
|
private fun setupBrowserConnectionNotPlayable() {
|
||||||
|
setupBrowserConnection()
|
||||||
|
|
||||||
|
val child = MediaBrowser.MediaItem(description, 0)
|
||||||
|
|
||||||
|
whenever(browser.subscribe(any(), capture(subscriptionCallback))).thenAnswer {
|
||||||
|
subscriptionCallback.value.onChildrenLoaded(ROOT, listOf(child))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to mock a successful connection with playable media
|
||||||
|
*/
|
||||||
|
private fun setupBrowserConnectionValidMedia() {
|
||||||
|
setupBrowserConnection()
|
||||||
|
|
||||||
|
val child = MediaBrowser.MediaItem(description, MediaBrowser.MediaItem.FLAG_PLAYABLE)
|
||||||
|
|
||||||
|
whenever(browser.serviceComponent).thenReturn(component)
|
||||||
|
whenever(browser.subscribe(any(), capture(subscriptionCallback))).thenAnswer {
|
||||||
|
subscriptionCallback.value.onChildrenLoaded(ROOT, listOf(child))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override so media controller use is testable
|
||||||
|
*/
|
||||||
|
private class TestableResumeMediaBrowser(
|
||||||
|
context: Context,
|
||||||
|
callback: Callback,
|
||||||
|
componentName: ComponentName,
|
||||||
|
browserFactory: MediaBrowserFactory,
|
||||||
|
private val fakeController: MediaController
|
||||||
|
) : ResumeMediaBrowser(context, callback, componentName, browserFactory) {
|
||||||
|
|
||||||
|
override fun createMediaController(token: MediaSession.Token): MediaController {
|
||||||
|
return fakeController
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user