Merge changes from topic "ccassidy-media-sass"
* changes: [Media SASS] Unregister the muteAwaitConnectionCallback when the Entry stops. [Media SASS] Choose the correct device icon based on the device attributes. [Media SASS] Display about-to-connect device names in the media player device chip.
This commit is contained in:
committed by
Android (Google) Code Review
commit
c7224ea6fc
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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.settingslib.media;
|
||||
|
||||
import android.annotation.DrawableRes;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.media.AudioDeviceInfo;
|
||||
import android.media.MediaRoute2Info;
|
||||
|
||||
import com.android.settingslib.R;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/** A util class to get the appropriate icon for different device types. */
|
||||
public class DeviceIconUtil {
|
||||
// A map from a @AudioDeviceInfo.AudioDeviceType to full device information.
|
||||
private final Map<Integer, Device> mAudioDeviceTypeToIconMap = new HashMap<>();
|
||||
// A map from a @MediaRoute2Info.Type to full device information.
|
||||
private final Map<Integer, Device> mMediaRouteTypeToIconMap = new HashMap<>();
|
||||
// A default icon to use if the type is not present in the map.
|
||||
@DrawableRes private static final int DEFAULT_ICON = R.drawable.ic_smartphone;
|
||||
|
||||
public DeviceIconUtil() {
|
||||
List<Device> deviceList = Arrays.asList(
|
||||
new Device(
|
||||
AudioDeviceInfo.TYPE_USB_DEVICE,
|
||||
MediaRoute2Info.TYPE_USB_DEVICE,
|
||||
R.drawable.ic_headphone),
|
||||
new Device(
|
||||
AudioDeviceInfo.TYPE_USB_HEADSET,
|
||||
MediaRoute2Info.TYPE_USB_HEADSET,
|
||||
R.drawable.ic_headphone),
|
||||
new Device(
|
||||
AudioDeviceInfo.TYPE_USB_ACCESSORY,
|
||||
MediaRoute2Info.TYPE_USB_ACCESSORY,
|
||||
R.drawable.ic_headphone),
|
||||
new Device(
|
||||
AudioDeviceInfo.TYPE_DOCK,
|
||||
MediaRoute2Info.TYPE_DOCK,
|
||||
R.drawable.ic_headphone),
|
||||
new Device(
|
||||
AudioDeviceInfo.TYPE_HDMI,
|
||||
MediaRoute2Info.TYPE_HDMI,
|
||||
R.drawable.ic_headphone),
|
||||
new Device(
|
||||
AudioDeviceInfo.TYPE_WIRED_HEADSET,
|
||||
MediaRoute2Info.TYPE_WIRED_HEADSET,
|
||||
R.drawable.ic_headphone),
|
||||
new Device(
|
||||
AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
|
||||
MediaRoute2Info.TYPE_WIRED_HEADPHONES,
|
||||
R.drawable.ic_headphone),
|
||||
new Device(
|
||||
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
|
||||
MediaRoute2Info.TYPE_BUILTIN_SPEAKER,
|
||||
R.drawable.ic_smartphone));
|
||||
for (int i = 0; i < deviceList.size(); i++) {
|
||||
Device device = deviceList.get(i);
|
||||
mAudioDeviceTypeToIconMap.put(device.mAudioDeviceType, device);
|
||||
mMediaRouteTypeToIconMap.put(device.mMediaRouteType, device);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns a drawable for an icon representing the given audioDeviceType. */
|
||||
public Drawable getIconFromAudioDeviceType(
|
||||
@AudioDeviceInfo.AudioDeviceType int audioDeviceType, Context context) {
|
||||
return context.getDrawable(getIconResIdFromAudioDeviceType(audioDeviceType));
|
||||
}
|
||||
|
||||
/** Returns a drawable res ID for an icon representing the given audioDeviceType. */
|
||||
@DrawableRes
|
||||
public int getIconResIdFromAudioDeviceType(
|
||||
@AudioDeviceInfo.AudioDeviceType int audioDeviceType) {
|
||||
if (mAudioDeviceTypeToIconMap.containsKey(audioDeviceType)) {
|
||||
return mAudioDeviceTypeToIconMap.get(audioDeviceType).mIconDrawableRes;
|
||||
}
|
||||
return DEFAULT_ICON;
|
||||
}
|
||||
|
||||
/** Returns a drawable res ID for an icon representing the given mediaRouteType. */
|
||||
@DrawableRes
|
||||
public int getIconResIdFromMediaRouteType(
|
||||
@MediaRoute2Info.Type int mediaRouteType) {
|
||||
if (mMediaRouteTypeToIconMap.containsKey(mediaRouteType)) {
|
||||
return mMediaRouteTypeToIconMap.get(mediaRouteType).mIconDrawableRes;
|
||||
}
|
||||
return DEFAULT_ICON;
|
||||
}
|
||||
|
||||
private static class Device {
|
||||
@AudioDeviceInfo.AudioDeviceType
|
||||
private final int mAudioDeviceType;
|
||||
|
||||
@MediaRoute2Info.Type
|
||||
private final int mMediaRouteType;
|
||||
|
||||
@DrawableRes
|
||||
private final int mIconDrawableRes;
|
||||
|
||||
Device(@AudioDeviceInfo.AudioDeviceType int audioDeviceType,
|
||||
@MediaRoute2Info.Type int mediaRouteType,
|
||||
@DrawableRes int iconDrawableRes) {
|
||||
mAudioDeviceType = audioDeviceType;
|
||||
mMediaRouteType = mediaRouteType;
|
||||
mIconDrawableRes = iconDrawableRes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
) {}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -47,10 +47,12 @@ public class PhoneMediaDevice extends MediaDevice {
|
||||
|
||||
private String mSummary = "";
|
||||
|
||||
private final DeviceIconUtil mDeviceIconUtil;
|
||||
|
||||
PhoneMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
|
||||
String packageName) {
|
||||
super(context, routerManager, info, packageName);
|
||||
|
||||
mDeviceIconUtil = new DeviceIconUtil();
|
||||
initDeviceRecord();
|
||||
}
|
||||
|
||||
@@ -94,23 +96,7 @@ public class PhoneMediaDevice extends MediaDevice {
|
||||
|
||||
@VisibleForTesting
|
||||
int getDrawableResId() {
|
||||
int resId;
|
||||
switch (mRouteInfo.getType()) {
|
||||
case TYPE_USB_DEVICE:
|
||||
case TYPE_USB_HEADSET:
|
||||
case TYPE_USB_ACCESSORY:
|
||||
case TYPE_DOCK:
|
||||
case TYPE_HDMI:
|
||||
case TYPE_WIRED_HEADSET:
|
||||
case TYPE_WIRED_HEADPHONES:
|
||||
resId = R.drawable.ic_headphone;
|
||||
break;
|
||||
case TYPE_BUILTIN_SPEAKER:
|
||||
default:
|
||||
resId = R.drawable.ic_smartphone;
|
||||
break;
|
||||
}
|
||||
return resId;
|
||||
return mDeviceIconUtil.getIconResIdFromMediaRouteType(mRouteInfo.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright 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.settingslib.media;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.media.AudioDeviceInfo;
|
||||
import android.media.MediaRoute2Info;
|
||||
|
||||
import com.android.settingslib.R;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class DeviceIconUtilTest {
|
||||
private final DeviceIconUtil mDeviceIconUtil = new DeviceIconUtil();
|
||||
|
||||
@Test
|
||||
public void getIconResIdFromMediaRouteType_usbDevice_isHeadphone() {
|
||||
assertThat(mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_USB_DEVICE))
|
||||
.isEqualTo(R.drawable.ic_headphone);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIconResIdFromMediaRouteType_usbHeadset_isHeadphone() {
|
||||
assertThat(mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_USB_HEADSET))
|
||||
.isEqualTo(R.drawable.ic_headphone);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIconResIdFromMediaRouteType_usbAccessory_isHeadphone() {
|
||||
assertThat(
|
||||
mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_USB_ACCESSORY))
|
||||
.isEqualTo(R.drawable.ic_headphone);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIconResIdFromMediaRouteType_dock_isHeadphone() {
|
||||
assertThat(mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_DOCK))
|
||||
.isEqualTo(R.drawable.ic_headphone);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIconResIdFromMediaRouteType_hdmi_isHeadphone() {
|
||||
assertThat(mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_HDMI))
|
||||
.isEqualTo(R.drawable.ic_headphone);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIconResIdFromMediaRouteType_wiredHeadset_isHeadphone() {
|
||||
assertThat(
|
||||
mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_WIRED_HEADSET))
|
||||
.isEqualTo(R.drawable.ic_headphone);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIconResIdFromMediaRouteType_wiredHeadphones_isHeadphone() {
|
||||
assertThat(
|
||||
mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_WIRED_HEADPHONES))
|
||||
.isEqualTo(R.drawable.ic_headphone);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIconResIdFromMediaRouteType_builtinSpeaker_isSmartphone() {
|
||||
assertThat(
|
||||
mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_BUILTIN_SPEAKER))
|
||||
.isEqualTo(R.drawable.ic_smartphone);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIconResIdFromMediaRouteType_unsupportedType_isSmartphone() {
|
||||
assertThat(mDeviceIconUtil.getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_UNKNOWN))
|
||||
.isEqualTo(R.drawable.ic_smartphone);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIconResIdFromAudioDeviceType_usbDevice_isHeadphone() {
|
||||
assertThat(mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_USB_DEVICE))
|
||||
.isEqualTo(R.drawable.ic_headphone);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIconResIdFromAudioDeviceType_usbHeadset_isHeadphone() {
|
||||
assertThat(
|
||||
mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_USB_HEADSET))
|
||||
.isEqualTo(R.drawable.ic_headphone);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIconResIdFromAudioDeviceType_usbAccessory_isHeadphone() {
|
||||
assertThat(
|
||||
mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_USB_ACCESSORY))
|
||||
.isEqualTo(R.drawable.ic_headphone);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIconResIdFromAudioDeviceType_dock_isHeadphone() {
|
||||
assertThat(mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_DOCK))
|
||||
.isEqualTo(R.drawable.ic_headphone);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIconResIdFromAudioDeviceType_hdmi_isHeadphone() {
|
||||
assertThat(mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_HDMI))
|
||||
.isEqualTo(R.drawable.ic_headphone);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIconResIdFromAudioDeviceType_wiredHeadset_isHeadphone() {
|
||||
assertThat(
|
||||
mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_WIRED_HEADSET))
|
||||
.isEqualTo(R.drawable.ic_headphone);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIconResIdFromAudioDeviceType_wiredHeadphones_isHeadphone() {
|
||||
assertThat(
|
||||
mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_WIRED_HEADPHONES))
|
||||
.isEqualTo(R.drawable.ic_headphone);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIconResIdFromAudioDeviceType_builtinSpeaker_isSmartphone() {
|
||||
assertThat(
|
||||
mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER))
|
||||
.isEqualTo(R.drawable.ic_smartphone);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getIconResIdFromAudioDeviceType_unsupportedType_isSmartphone() {
|
||||
assertThat(mDeviceIconUtil.getIconResIdFromAudioDeviceType(AudioDeviceInfo.TYPE_UNKNOWN))
|
||||
.isEqualTo(R.drawable.ic_smartphone);
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import com.android.systemui.SystemUIAppComponentFactory;
|
||||
import com.android.systemui.dagger.qualifiers.PerUser;
|
||||
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;
|
||||
@@ -150,6 +151,7 @@ public interface SysUIComponent {
|
||||
getMediaTttChipControllerSender();
|
||||
getMediaTttChipControllerReceiver();
|
||||
getMediaTttCommandLineHelper();
|
||||
getMediaMuteAwaitConnectionCli();
|
||||
getUnfoldLatencyTracker().init();
|
||||
getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init);
|
||||
getFoldStateLogger().ifPresent(FoldStateLogger::init);
|
||||
@@ -226,6 +228,9 @@ public interface SysUIComponent {
|
||||
/** */
|
||||
Optional<MediaTttCommandLineHelper> getMediaTttCommandLineHelper();
|
||||
|
||||
/** */
|
||||
Optional<MediaMuteAwaitConnectionCli> getMediaMuteAwaitConnectionCli();
|
||||
|
||||
/**
|
||||
* Returns {@link CoreStartable}s that should be started with the application.
|
||||
*/
|
||||
|
||||
@@ -142,6 +142,7 @@ public class Flags {
|
||||
public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, true);
|
||||
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 ==========================
|
||||
|
||||
@@ -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,8 @@ 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.MediaMuteAwaitConnectionManager
|
||||
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory
|
||||
import java.io.FileDescriptor
|
||||
import java.io.PrintWriter
|
||||
import java.util.concurrent.Executor
|
||||
@@ -41,6 +44,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 +84,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)
|
||||
val muteAwaitConnectionManager =
|
||||
muteAwaitConnectionManagerFactory.create(localMediaManager)
|
||||
entry = Entry(
|
||||
key,
|
||||
oldKey,
|
||||
controller,
|
||||
localMediaManager,
|
||||
muteAwaitConnectionManager
|
||||
)
|
||||
entries[key] = entry
|
||||
entry.start()
|
||||
}
|
||||
@@ -126,7 +138,8 @@ class MediaDeviceManager @Inject constructor(
|
||||
val key: String,
|
||||
val oldKey: String?,
|
||||
val controller: MediaController?,
|
||||
val localMediaManager: LocalMediaManager
|
||||
val localMediaManager: LocalMediaManager,
|
||||
val muteAwaitConnectionManager: MediaMuteAwaitConnectionManager?
|
||||
) : LocalMediaManager.DeviceCallback, MediaController.Callback() {
|
||||
|
||||
val token
|
||||
@@ -142,11 +155,15 @@ 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 {
|
||||
localMediaManager.registerCallback(this)
|
||||
localMediaManager.startScan()
|
||||
muteAwaitConnectionManager?.startListening()
|
||||
playbackType = controller?.playbackInfo?.playbackType ?: PLAYBACK_TYPE_UNKNOWN
|
||||
controller?.registerCallback(this)
|
||||
updateCurrent()
|
||||
@@ -159,6 +176,7 @@ class MediaDeviceManager @Inject constructor(
|
||||
controller?.unregisterCallback(this)
|
||||
localMediaManager.stopScan()
|
||||
localMediaManager.unregisterCallback(this)
|
||||
muteAwaitConnectionManager?.stopListening()
|
||||
}
|
||||
|
||||
fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
|
||||
@@ -197,8 +215,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) }
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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.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,
|
||||
/* type= */ Integer.parseInt(args[0]),
|
||||
ADDRESS,
|
||||
/* name= */ args[1],
|
||||
listOf(),
|
||||
listOf(),
|
||||
)
|
||||
val startOrCancel = args[2]
|
||||
|
||||
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`; was $startOrCancel")
|
||||
}
|
||||
}
|
||||
override fun help(pw: PrintWriter) {
|
||||
pw.println("Usage: adb shell cmd statusbar $MEDIA_MUTE_AWAIT_COMMAND " +
|
||||
"[type] [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
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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.DeviceIconUtil
|
||||
import com.android.settingslib.media.LocalMediaManager
|
||||
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 private val mainExecutor: Executor,
|
||||
private val localMediaManager: LocalMediaManager,
|
||||
private val context: Context,
|
||||
private val deviceIconUtil: DeviceIconUtil
|
||||
) {
|
||||
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, device.getIcon())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onUnmutedEvent(
|
||||
@UnmuteEvent unmuteEvent: Int,
|
||||
device: AudioDeviceAttributes,
|
||||
mutedUsages: IntArray
|
||||
) {
|
||||
if (currentMutedDevice == device && USAGE_MEDIA in mutedUsages) {
|
||||
currentMutedDevice = null
|
||||
localMediaManager.dispatchAboutToConnectDeviceChanged(null, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Start listening for mute await events. */
|
||||
fun startListening() {
|
||||
audioManager.registerMuteAwaitConnectionCallback(
|
||||
mainExecutor, muteAwaitConnectionChangeListener
|
||||
)
|
||||
val currentDevice = audioManager.mutingExpectedDevice
|
||||
if (currentDevice != null) {
|
||||
currentMutedDevice = currentDevice
|
||||
localMediaManager.dispatchAboutToConnectDeviceChanged(
|
||||
currentDevice.name, currentDevice.getIcon()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Stop listening for mute await events. */
|
||||
fun stopListening() {
|
||||
audioManager.unregisterMuteAwaitConnectionCallback(muteAwaitConnectionChangeListener)
|
||||
}
|
||||
|
||||
private fun AudioDeviceAttributes.getIcon(): Drawable {
|
||||
return deviceIconUtil.getIconFromAudioDeviceType(this.type, context)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.DeviceIconUtil
|
||||
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
|
||||
) {
|
||||
private val deviceIconUtil = DeviceIconUtil()
|
||||
|
||||
/** Creates a [MediaMuteAwaitConnectionManager]. */
|
||||
fun create(localMediaManager: LocalMediaManager): MediaMuteAwaitConnectionManager? {
|
||||
if (!mediaFlags.areMuteAwaitConnectionsEnabled()) {
|
||||
return null
|
||||
}
|
||||
return MediaMuteAwaitConnectionManager(
|
||||
mainExecutor, localMediaManager, context, deviceIconUtil
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,8 @@ 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.MediaMuteAwaitConnectionManager
|
||||
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory
|
||||
import com.android.systemui.util.concurrency.FakeExecutor
|
||||
import com.android.systemui.util.time.FakeSystemClock
|
||||
|
||||
@@ -44,6 +46,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 +74,8 @@ 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
|
||||
@Mock private lateinit var muteAwaitManager: MediaMuteAwaitConnectionManager
|
||||
private lateinit var fakeFgExecutor: FakeExecutor
|
||||
private lateinit var fakeBgExecutor: FakeExecutor
|
||||
@Mock private lateinit var dumpster: DumpManager
|
||||
@@ -88,14 +93,22 @@ 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.
|
||||
whenever(device.name).thenReturn(DEVICE_NAME)
|
||||
whenever(device.iconWithoutBackground).thenReturn(icon)
|
||||
whenever(lmmFactory.create(PACKAGE)).thenReturn(lmm)
|
||||
whenever(muteAwaitFactory.create(lmm)).thenReturn(muteAwaitManager)
|
||||
whenever(lmm.getCurrentConnectedDevice()).thenReturn(device)
|
||||
whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(route)
|
||||
|
||||
@@ -146,6 +159,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
|
||||
manager.onMediaDataRemoved(KEY)
|
||||
fakeBgExecutor.runAllReady()
|
||||
verify(lmm).unregisterCallback(any())
|
||||
verify(muteAwaitManager).stopListening()
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -169,6 +183,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
|
||||
fakeFgExecutor.runAllReady()
|
||||
// THEN the listener for the old key should removed.
|
||||
verify(lmm).unregisterCallback(any())
|
||||
verify(muteAwaitManager).stopListening()
|
||||
// AND a new device event emitted
|
||||
val data = captureDeviceData(KEY, KEY_OLD)
|
||||
assertThat(data.enabled).isTrue()
|
||||
@@ -240,6 +255,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
|
||||
manager.onMediaDataLoaded(KEY, null, mediaData)
|
||||
fakeBgExecutor.runAllReady()
|
||||
val deviceCallback = captureCallback()
|
||||
verify(muteAwaitManager).startListening()
|
||||
// WHEN the device list changes
|
||||
deviceCallback.onDeviceListUpdate(mutableListOf(device))
|
||||
assertThat(fakeBgExecutor.runAllReady()).isEqualTo(1)
|
||||
@@ -267,6 +283,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)
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* 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.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.DeviceIconUtil
|
||||
import com.android.settingslib.media.LocalMediaManager
|
||||
import com.android.systemui.R
|
||||
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 deviceIconUtil: DeviceIconUtil
|
||||
@Mock
|
||||
private lateinit var localMediaManager: LocalMediaManager
|
||||
private lateinit var icon: Drawable
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
context.addMockSystemService(Context.AUDIO_SERVICE, audioManager)
|
||||
icon = context.getDrawable(R.drawable.ic_cake)!!
|
||||
whenever(deviceIconUtil.getIconFromAudioDeviceType(any(), any())).thenReturn(icon)
|
||||
|
||||
muteAwaitConnectionManager = MediaMuteAwaitConnectionManager(
|
||||
FakeExecutor(FakeSystemClock()),
|
||||
localMediaManager,
|
||||
context,
|
||||
deviceIconUtil
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun constructor_audioManagerCallbackNotRegistered() {
|
||||
verify(audioManager, never()).registerMuteAwaitConnectionCallback(any(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun startListening_audioManagerCallbackRegistered() {
|
||||
muteAwaitConnectionManager.startListening()
|
||||
|
||||
verify(audioManager).registerMuteAwaitConnectionCallback(any(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stopListening_audioManagerCallbackUnregistered() {
|
||||
muteAwaitConnectionManager.stopListening()
|
||||
|
||||
verify(audioManager).unregisterMuteAwaitConnectionCallback(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun startListening_audioManagerHasNoMuteAwaitDevice_localMediaMangerNotNotified() {
|
||||
whenever(audioManager.mutingExpectedDevice).thenReturn(null)
|
||||
|
||||
muteAwaitConnectionManager.startListening()
|
||||
|
||||
verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun startListening_audioManagerHasMuteAwaitDevice_localMediaMangerNotified() {
|
||||
whenever(audioManager.mutingExpectedDevice).thenReturn(DEVICE)
|
||||
|
||||
muteAwaitConnectionManager.startListening()
|
||||
|
||||
verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(DEVICE_NAME), eq(icon))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onMutedUntilConnection_notUsageMedia_localMediaManagerNotNotified() {
|
||||
muteAwaitConnectionManager.startListening()
|
||||
val muteAwaitListener = getMuteAwaitListener()
|
||||
|
||||
muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_UNKNOWN))
|
||||
|
||||
verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onMutedUntilConnection_isUsageMedia_localMediaManagerNotified() {
|
||||
muteAwaitConnectionManager.startListening()
|
||||
val muteAwaitListener = getMuteAwaitListener()
|
||||
|
||||
|
||||
muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_MEDIA))
|
||||
|
||||
verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(DEVICE_NAME), eq(icon))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onUnmutedEvent_noDeviceMutedBefore_localMediaManagerNotNotified() {
|
||||
muteAwaitConnectionManager.startListening()
|
||||
val muteAwaitListener = getMuteAwaitListener()
|
||||
|
||||
muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, DEVICE, intArrayOf(USAGE_MEDIA))
|
||||
|
||||
verify(localMediaManager, never()).dispatchAboutToConnectDeviceChanged(any(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onUnmutedEvent_notSameDevice_localMediaManagerNotNotified() {
|
||||
muteAwaitConnectionManager.startListening()
|
||||
val muteAwaitListener = getMuteAwaitListener()
|
||||
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() {
|
||||
muteAwaitConnectionManager.startListening()
|
||||
val muteAwaitListener = getMuteAwaitListener()
|
||||
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() {
|
||||
muteAwaitConnectionManager.startListening()
|
||||
val muteAwaitListener = getMuteAwaitListener()
|
||||
muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_MEDIA))
|
||||
reset(localMediaManager)
|
||||
|
||||
muteAwaitListener.onUnmutedEvent(EVENT_CONNECTION, DEVICE, intArrayOf(USAGE_MEDIA))
|
||||
|
||||
verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(null), eq(null))
|
||||
}
|
||||
|
||||
private fun getMuteAwaitListener(): AudioManager.MuteAwaitConnectionCallback {
|
||||
val listenerCaptor = ArgumentCaptor.forClass(
|
||||
AudioManager.MuteAwaitConnectionCallback::class.java
|
||||
)
|
||||
verify(audioManager).registerMuteAwaitConnectionCallback(any(), listenerCaptor.capture())
|
||||
return listenerCaptor.value!!
|
||||
}
|
||||
}
|
||||
|
||||
private const val DEVICE_NAME = "DeviceName"
|
||||
private val DEVICE = AudioDeviceAttributes(
|
||||
AudioDeviceAttributes.ROLE_OUTPUT,
|
||||
AudioDeviceInfo.TYPE_USB_HEADSET,
|
||||
"address",
|
||||
DEVICE_NAME,
|
||||
listOf(),
|
||||
listOf(),
|
||||
)
|
||||
Reference in New Issue
Block a user