[Media SASS] Display about-to-connect device names in the media player

device chip.

Bug: 206614671
Test: atest MediaMuteAwaitConnectionManagerTest
Test: manual: `adb shell am broadcast -a
com.android.systemui.action.SET_FLAG --ei id 904 --ez value 1` to flip
the flag then `adb shell cmd statusbar media-mute-await MuteAwaitDevice
start` and verify that the media player updates to show
"MuteAwaitDevice" as the device with a headphone icon. Video uploaded to
bug showing this interaction.

Change-Id: I4b3dde002904026334f03068dc3574d4b3c5fede
This commit is contained in:
Caitlin Cassidy
2022-02-03 18:02:48 +00:00
parent 2ccaf53cd7
commit bc8c679093
11 changed files with 513 additions and 5 deletions

View File

@@ -21,6 +21,7 @@ import android.app.Notification;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.media.RoutingSessionInfo;
import android.os.Build;
import android.text.TextUtils;
@@ -226,6 +227,18 @@ public class LocalMediaManager implements BluetoothCallback {
}
}
/**
* Dispatch a change in the about-to-connect device. See
* {@link DeviceCallback#onAboutToConnectDeviceChanged} for more information.
*/
public void dispatchAboutToConnectDeviceChanged(
@Nullable String deviceName,
@Nullable Drawable deviceIcon) {
for (DeviceCallback callback : getCallbacks()) {
callback.onAboutToConnectDeviceChanged(deviceName, deviceIcon);
}
}
/**
* Stop scan MediaDevice
*/
@@ -674,6 +687,21 @@ public class LocalMediaManager implements BluetoothCallback {
* {@link android.media.MediaRoute2ProviderService#REASON_INVALID_COMMAND},
*/
default void onRequestFailed(int reason){};
/**
* Callback for notifying that we have a new about-to-connect device.
*
* An about-to-connect device is a device that is not yet connected but is expected to
* connect imminently and should be displayed as the current device in the media player.
* See [AudioManager.muteAwaitConnection] for more details.
*
* @param deviceName the name of the device (displayed to the user).
* @param deviceIcon the icon that should be used with the device.
*/
default void onAboutToConnectDeviceChanged(
@Nullable String deviceName,
@Nullable Drawable deviceIcon
) {}
}
/**

View File

@@ -23,6 +23,7 @@ import com.android.systemui.InitController;
import com.android.systemui.SystemUIAppComponentFactory;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardSliceProvider;
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver;
import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
@@ -144,6 +145,7 @@ public interface SysUIComponent {
getMediaTttChipControllerSender();
getMediaTttChipControllerReceiver();
getMediaTttCommandLineHelper();
getMediaMuteAwaitConnectionCli();
getUnfoldLatencyTracker().init();
getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init);
getFoldStateLogger().ifPresent(FoldStateLogger::init);
@@ -220,6 +222,9 @@ public interface SysUIComponent {
/** */
Optional<MediaTttCommandLineHelper> getMediaTttCommandLineHelper();
/** */
Optional<MediaMuteAwaitConnectionCli> getMediaMuteAwaitConnectionCli();
/**
* Member injection into the supplied argument.
*/

View File

@@ -138,6 +138,7 @@ public class Flags {
public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, false);
public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, true);
public static final BooleanFlag MEDIA_SESSION_LAYOUT = new BooleanFlag(902, false);
public static final BooleanFlag MEDIA_MUTE_AWAIT = new BooleanFlag(904, true);
// Pay no attention to the reflection behind the curtain.
// ========================== Curtain ==========================

View File

@@ -16,6 +16,7 @@
package com.android.systemui.media
import android.graphics.drawable.Drawable
import android.media.MediaRouter2Manager
import android.media.session.MediaController
import androidx.annotation.AnyThread
@@ -27,6 +28,7 @@ import com.android.systemui.Dumpable
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory
import java.io.FileDescriptor
import java.io.PrintWriter
import java.util.concurrent.Executor
@@ -41,6 +43,7 @@ class MediaDeviceManager @Inject constructor(
private val controllerFactory: MediaControllerFactory,
private val localMediaManagerFactory: LocalMediaManagerFactory,
private val mr2manager: MediaRouter2Manager,
private val muteAwaitConnectionManagerFactory: MediaMuteAwaitConnectionManagerFactory,
@Main private val fgExecutor: Executor,
@Background private val bgExecutor: Executor,
dumpManager: DumpManager
@@ -80,8 +83,16 @@ class MediaDeviceManager @Inject constructor(
val controller = data.token?.let {
controllerFactory.create(it)
}
entry = Entry(key, oldKey, controller,
localMediaManagerFactory.create(data.packageName))
val localMediaManager = localMediaManagerFactory.create(data.packageName)
// We don't need to set this muteAwaitConnectionManager anywhere; it will just notify
// [localMediaManager] on the appropriate events.
muteAwaitConnectionManagerFactory.create(localMediaManager)
entry = Entry(
key,
oldKey,
controller,
localMediaManager
)
entries[key] = entry
entry.start()
}
@@ -142,6 +153,9 @@ class MediaDeviceManager @Inject constructor(
}
}
}
// A device that is not yet connected but is expected to connect imminently. Because it's
// expected to connect imminently, it should be displayed as the current device.
private var aboutToConnectDeviceOverride: MediaDeviceData? = null
@AnyThread
fun start() = bgExecutor.execute {
@@ -197,8 +211,21 @@ class MediaDeviceManager @Inject constructor(
}
}
override fun onAboutToConnectDeviceChanged(deviceName: String?, deviceIcon: Drawable?) {
aboutToConnectDeviceOverride = if (deviceName == null || deviceIcon == null) {
null
} else {
MediaDeviceData(enabled = true, deviceIcon, deviceName)
}
updateCurrent()
}
@WorkerThread
private fun updateCurrent() {
if (aboutToConnectDeviceOverride != null) {
current = aboutToConnectDeviceOverride
return
}
val device = localMediaManager.currentConnectedDevice
val route = controller?.let { mr2manager.getRoutingSessionForMediaController(it) }

View File

@@ -37,4 +37,9 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) {
return featureFlags.isEnabled(Flags.MEDIA_SESSION_ACTIONS) &&
featureFlags.isEnabled(Flags.MEDIA_SESSION_LAYOUT)
}
}
/**
* Check whether we support displaying information about mute await connections.
*/
fun areMuteAwaitConnectionsEnabled() = featureFlags.isEnabled(Flags.MEDIA_MUTE_AWAIT)
}

View File

@@ -23,10 +23,12 @@ import android.view.WindowManager;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.media.MediaFlags;
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.media.MediaHost;
import com.android.systemui.media.MediaHostStatesManager;
import com.android.systemui.media.dream.dagger.MediaComplicationComponent;
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
import com.android.systemui.media.nearby.NearbyMediaDevicesService;
import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
import com.android.systemui.media.taptotransfer.MediaTttFlags;
@@ -140,6 +142,20 @@ public interface MediaModule {
new MediaTttCommandLineHelper(commandRegistry, context, mainExecutor));
}
/** */
@Provides
@SysUISingleton
static Optional<MediaMuteAwaitConnectionCli> providesMediaMuteAwaitConnectionCli(
MediaFlags mediaFlags,
CommandRegistry commandRegistry,
Context context
) {
if (!mediaFlags.areMuteAwaitConnectionsEnabled()) {
return Optional.empty();
}
return Optional.of(new MediaMuteAwaitConnectionCli(commandRegistry, context));
}
/** Inject into NearbyMediaDevicesService. */
@Binds
@IntoMap

View File

@@ -0,0 +1,76 @@
/*
* Copyright (C) 2022 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.muteawait
import android.content.Context
import android.media.AudioAttributes.USAGE_MEDIA
import android.media.AudioDeviceAttributes
import android.media.AudioDeviceInfo
import android.media.AudioManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import java.io.PrintWriter
import java.util.concurrent.TimeUnit
import javax.inject.Inject
/** A command line interface to manually test [MediaMuteAwaitConnectionManager]. */
@SysUISingleton
class MediaMuteAwaitConnectionCli @Inject constructor(
commandRegistry: CommandRegistry,
private val context: Context
) {
init {
commandRegistry.registerCommand(MEDIA_MUTE_AWAIT_COMMAND) { MuteAwaitCommand() }
}
inner class MuteAwaitCommand : Command {
override fun execute(pw: PrintWriter, args: List<String>) {
val device = AudioDeviceAttributes(
AudioDeviceAttributes.ROLE_OUTPUT,
AudioDeviceInfo.TYPE_USB_HEADSET,
ADDRESS,
/* name= */ args[0],
listOf(),
listOf(),
)
val startOrCancel = args[1]
val audioManager: AudioManager =
context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
when (startOrCancel) {
START ->
audioManager.muteAwaitConnection(
intArrayOf(USAGE_MEDIA), device, TIMEOUT, TIMEOUT_UNITS
)
CANCEL -> audioManager.cancelMuteAwaitConnection(device)
else -> pw.println("Must specify $START or $CANCEL")
}
}
override fun help(pw: PrintWriter) {
pw.println("Usage: adb shell cmd statusbar $MEDIA_MUTE_AWAIT_COMMAND " +
"[name] [$START|$CANCEL]")
}
}
}
private const val MEDIA_MUTE_AWAIT_COMMAND = "media-mute-await"
private const val START = "start"
private const val CANCEL = "cancel"
private const val ADDRESS = "address"
private const val TIMEOUT = 5L
private val TIMEOUT_UNITS = TimeUnit.SECONDS

View File

@@ -0,0 +1,86 @@
/*
* Copyright (C) 2022 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.muteawait
import android.content.Context
import android.graphics.drawable.Drawable
import android.media.AudioAttributes.USAGE_MEDIA
import android.media.AudioDeviceAttributes
import android.media.AudioManager
import com.android.settingslib.media.LocalMediaManager
import com.android.systemui.R
import com.android.systemui.dagger.qualifiers.Main
import java.util.concurrent.Executor
/**
* A class responsible for keeping track of devices that have muted audio playback until the device
* is connected. The device connection expected to happen imminently, so we'd like to display the
* device name in the media player. When the about-to-connect device changes, [localMediaManager]
* will be notified.
*
* See [AudioManager.muteAwaitConnection] and b/206614671 for more details.
*
* TODO(b/206614671): Add logging.
*/
class MediaMuteAwaitConnectionManager constructor(
@Main mainExecutor: Executor,
localMediaManager: LocalMediaManager,
private val context: Context
) {
var currentMutedDevice: AudioDeviceAttributes? = null
val audioManager: AudioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
val muteAwaitConnectionChangeListener = object : AudioManager.MuteAwaitConnectionCallback() {
override fun onMutedUntilConnection(device: AudioDeviceAttributes, mutedUsages: IntArray) {
if (USAGE_MEDIA in mutedUsages) {
// There should only be one device that's mutedUntilConnection at a time, so we can
// safely override any previous value.
currentMutedDevice = device
localMediaManager.dispatchAboutToConnectDeviceChanged(device.name, getIcon())
}
}
override fun onUnmutedEvent(
@UnmuteEvent unmuteEvent: Int,
device: AudioDeviceAttributes,
mutedUsages: IntArray
) {
if (currentMutedDevice == device && USAGE_MEDIA in mutedUsages) {
currentMutedDevice = null
localMediaManager.dispatchAboutToConnectDeviceChanged(null, null)
}
}
}
init {
// TODO(b/206614671): Unregister this callback (likely on [MediaDeviceManager.Entry.Stop]).
audioManager.registerMuteAwaitConnectionCallback(
mainExecutor, muteAwaitConnectionChangeListener
)
val currentDevice = audioManager.mutingExpectedDevice
if (currentDevice != null) {
currentMutedDevice = currentDevice
localMediaManager.dispatchAboutToConnectDeviceChanged(currentDevice.name, getIcon())
}
}
private fun getIcon(): Drawable {
// TODO(b/206614671): Choose the icon based on device type.
return context.getDrawable(R.drawable.ic_headphone)!!
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (C) 2022 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.muteawait
import android.content.Context
import com.android.settingslib.media.LocalMediaManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.MediaFlags
import java.util.concurrent.Executor
import javax.inject.Inject
/** Factory class to create [MediaMuteAwaitConnectionManager] instances. */
@SysUISingleton
class MediaMuteAwaitConnectionManagerFactory @Inject constructor(
private val mediaFlags: MediaFlags,
private val context: Context,
@Main private val mainExecutor: Executor
) {
/** Creates a [MediaMuteAwaitConnectionManager]. */
fun create(localMediaManager: LocalMediaManager): MediaMuteAwaitConnectionManager? {
if (!mediaFlags.areMuteAwaitConnectionsEnabled()) {
return null
}
return MediaMuteAwaitConnectionManager(mainExecutor, localMediaManager, context)
}
}

View File

@@ -30,6 +30,7 @@ import com.android.settingslib.media.LocalMediaManager
import com.android.settingslib.media.MediaDevice
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
@@ -44,6 +45,7 @@ import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.any
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
@@ -71,6 +73,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
@Mock private lateinit var lmmFactory: LocalMediaManagerFactory
@Mock private lateinit var lmm: LocalMediaManager
@Mock private lateinit var mr2: MediaRouter2Manager
@Mock private lateinit var muteAwaitFactory: MediaMuteAwaitConnectionManagerFactory
private lateinit var fakeFgExecutor: FakeExecutor
private lateinit var fakeBgExecutor: FakeExecutor
@Mock private lateinit var dumpster: DumpManager
@@ -88,8 +91,15 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
fun setUp() {
fakeFgExecutor = FakeExecutor(FakeSystemClock())
fakeBgExecutor = FakeExecutor(FakeSystemClock())
manager = MediaDeviceManager(controllerFactory, lmmFactory, mr2, fakeFgExecutor,
fakeBgExecutor, dumpster)
manager = MediaDeviceManager(
controllerFactory,
lmmFactory,
mr2,
muteAwaitFactory,
fakeFgExecutor,
fakeBgExecutor,
dumpster
)
manager.addListener(listener)
// Configure mocks.
@@ -267,6 +277,51 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
assertThat(data.icon).isEqualTo(icon)
}
@Test
fun onAboutToConnectDeviceChangedWithNonNullParams() {
manager.onMediaDataLoaded(KEY, null, mediaData)
// Run and reset the executors and listeners so we only focus on new events.
fakeBgExecutor.runAllReady()
fakeFgExecutor.runAllReady()
reset(listener)
val deviceCallback = captureCallback()
// WHEN the about-to-connect device changes to non-null
val name = "AboutToConnectDeviceName"
val mockIcon = mock(Drawable::class.java)
deviceCallback.onAboutToConnectDeviceChanged(name, mockIcon)
assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1)
// THEN the about-to-connect device is returned
val data = captureDeviceData(KEY)
assertThat(data.enabled).isTrue()
assertThat(data.name).isEqualTo(name)
assertThat(data.icon).isEqualTo(mockIcon)
}
@Test
fun onAboutToConnectDeviceChangedWithNullParams() {
manager.onMediaDataLoaded(KEY, null, mediaData)
fakeBgExecutor.runAllReady()
val deviceCallback = captureCallback()
// First set a non-null about-to-connect device
deviceCallback.onAboutToConnectDeviceChanged(
"AboutToConnectDeviceName", mock(Drawable::class.java)
)
// Run and reset the executors and listeners so we only focus on new events.
fakeBgExecutor.runAllReady()
fakeFgExecutor.runAllReady()
reset(listener)
// WHEN the about-to-connect device changes to null
deviceCallback.onAboutToConnectDeviceChanged(null, null)
assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1)
// THEN the normal device is returned
val data = captureDeviceData(KEY)
assertThat(data.enabled).isTrue()
assertThat(data.name).isEqualTo(DEVICE_NAME)
assertThat(data.icon).isEqualTo(icon)
}
@Test
fun listenerReceivesKeyRemoved() {
manager.onMediaDataLoaded(KEY, null, mediaData)

View File

@@ -0,0 +1,168 @@
/*
* Copyright (C) 2022 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.muteawait
import android.content.Context
import android.media.AudioAttributes.USAGE_MEDIA
import android.media.AudioAttributes.USAGE_UNKNOWN
import android.media.AudioDeviceAttributes
import android.media.AudioDeviceInfo
import android.media.AudioManager
import android.media.AudioManager.MuteAwaitConnectionCallback.EVENT_CONNECTION
import android.test.suitebuilder.annotation.SmallTest
import com.android.settingslib.media.LocalMediaManager
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@SmallTest
class MediaMuteAwaitConnectionManagerTest : SysuiTestCase() {
private lateinit var muteAwaitConnectionManager: MediaMuteAwaitConnectionManager
@Mock
private lateinit var audioManager: AudioManager
@Mock
private lateinit var localMediaManager: LocalMediaManager
private lateinit var muteAwaitListener: AudioManager.MuteAwaitConnectionCallback
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
context.addMockSystemService(Context.AUDIO_SERVICE, audioManager)
}
@Test
fun constructor_audioManagerHasNoMuteAwaitDevice_localMediaMangerNotNotified() {
whenever(audioManager.mutingExpectedDevice).thenReturn(null)
instantiateManager()
verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
}
@Test
fun constructor_audioManagerHasMuteAwaitDevice_localMediaMangerNotified() {
whenever(audioManager.mutingExpectedDevice).thenReturn(DEVICE)
instantiateManager()
verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(DEVICE_NAME), any())
}
@Test
fun onMutedUntilConnection_notUsageMedia_localMediaManagerNotNotified() {
instantiateManager()
muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_UNKNOWN))
verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
}
@Test
fun onMutedUntilConnection_isUsageMedia_localMediaManagerNotified() {
instantiateManager()
muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_MEDIA))
verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(DEVICE_NAME), any())
}
@Test
fun onUnmutedEvent_noDeviceMutedBefore_localMediaManagerNotNotified() {
instantiateManager()
muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, DEVICE, intArrayOf(USAGE_MEDIA))
verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
}
@Test
fun onUnmutedEvent_notSameDevice_localMediaManagerNotNotified() {
instantiateManager()
muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_MEDIA))
reset(localMediaManager)
val otherDevice = AudioDeviceAttributes(
AudioDeviceAttributes.ROLE_OUTPUT,
AudioDeviceInfo.TYPE_USB_HEADSET,
"address",
"DifferentName",
listOf(),
listOf(),
)
muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, otherDevice, intArrayOf(USAGE_MEDIA))
verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
}
@Test
fun onUnmutedEvent_notUsageMedia_localMediaManagerNotNotified() {
instantiateManager()
muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_MEDIA))
reset(localMediaManager)
muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, DEVICE, intArrayOf(USAGE_UNKNOWN))
verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
}
@Test
fun onUnmutedEvent_sameDeviceAndUsageMedia_localMediaManagerNotified() {
instantiateManager()
muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_MEDIA))
reset(localMediaManager)
muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, DEVICE, intArrayOf(USAGE_MEDIA))
verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(null), eq(null))
}
// Some classes test the constructor, so don't instantiate the manager in @SetUp.
private fun instantiateManager() {
muteAwaitConnectionManager = MediaMuteAwaitConnectionManager(
FakeExecutor(FakeSystemClock()),
localMediaManager,
context
)
val listenerCaptor = ArgumentCaptor.forClass(
AudioManager.MuteAwaitConnectionCallback::class.java
)
verify(audioManager).registerMuteAwaitConnectionCallback(any(), listenerCaptor.capture())
muteAwaitListener = listenerCaptor.value!!
}
}
private const val DEVICE_NAME = "DeviceName"
private val DEVICE = AudioDeviceAttributes(
AudioDeviceAttributes.ROLE_OUTPUT,
AudioDeviceInfo.TYPE_USB_HEADSET,
"address",
DEVICE_NAME,
listOf(),
listOf(),
)