[Media SASS] Choose the correct device icon based on the device

attributes.

Bug: 206614671
Test: verify `adb shell cmd statusbar media-mute-await 9
MuteAwaitDevice start` shows the headphone icon
Test: verify `adb shell cmd statusbar media-mute-await 2
MuteAwaitDevice start` shows the smartphone icon
Test: atest DeviceIconUtilTest, PhoneMediaDeviceTest
Test: atest MediaMuteAwaitConnectionManagerTest

Change-Id: I7122828bd65a730b94d20d553509886fd166dcb2
This commit is contained in:
Caitlin Cassidy
2022-02-07 21:50:21 +00:00
parent bc8c679093
commit 1400c7a35f
7 changed files with 313 additions and 35 deletions

View File

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

View File

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

View File

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

View File

@@ -19,7 +19,6 @@ 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
@@ -42,13 +41,13 @@ class MediaMuteAwaitConnectionCli @Inject constructor(
override fun execute(pw: PrintWriter, args: List<String>) {
val device = AudioDeviceAttributes(
AudioDeviceAttributes.ROLE_OUTPUT,
AudioDeviceInfo.TYPE_USB_HEADSET,
/* type= */ Integer.parseInt(args[0]),
ADDRESS,
/* name= */ args[0],
/* name= */ args[1],
listOf(),
listOf(),
)
val startOrCancel = args[1]
val startOrCancel = args[2]
val audioManager: AudioManager =
context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
@@ -58,12 +57,12 @@ class MediaMuteAwaitConnectionCli @Inject constructor(
intArrayOf(USAGE_MEDIA), device, TIMEOUT, TIMEOUT_UNITS
)
CANCEL -> audioManager.cancelMuteAwaitConnection(device)
else -> pw.println("Must specify $START or $CANCEL")
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 " +
"[name] [$START|$CANCEL]")
"[type] [name] [$START|$CANCEL]")
}
}
}

View File

@@ -21,8 +21,8 @@ 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.R
import com.android.systemui.dagger.qualifiers.Main
import java.util.concurrent.Executor
@@ -39,7 +39,8 @@ import java.util.concurrent.Executor
class MediaMuteAwaitConnectionManager constructor(
@Main mainExecutor: Executor,
localMediaManager: LocalMediaManager,
private val context: Context
private val context: Context,
private val deviceIconUtil: DeviceIconUtil
) {
var currentMutedDevice: AudioDeviceAttributes? = null
@@ -51,7 +52,7 @@ class MediaMuteAwaitConnectionManager constructor(
// 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())
localMediaManager.dispatchAboutToConnectDeviceChanged(device.name, device.getIcon())
}
}
@@ -75,12 +76,13 @@ class MediaMuteAwaitConnectionManager constructor(
val currentDevice = audioManager.mutingExpectedDevice
if (currentDevice != null) {
currentMutedDevice = currentDevice
localMediaManager.dispatchAboutToConnectDeviceChanged(currentDevice.name, getIcon())
localMediaManager.dispatchAboutToConnectDeviceChanged(
currentDevice.name, currentDevice.getIcon()
)
}
}
private fun getIcon(): Drawable {
// TODO(b/206614671): Choose the icon based on device type.
return context.getDrawable(R.drawable.ic_headphone)!!
private fun AudioDeviceAttributes.getIcon(): Drawable {
return deviceIconUtil.getIconFromAudioDeviceType(this.type, context)
}
}

View File

@@ -17,6 +17,7 @@
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
@@ -31,11 +32,15 @@ class MediaMuteAwaitConnectionManagerFactory @Inject constructor(
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)
return MediaMuteAwaitConnectionManager(
mainExecutor, localMediaManager, context, deviceIconUtil
)
}
}

View File

@@ -16,7 +16,9 @@
package com.android.systemui.media.muteawait
import android.annotation.DrawableRes
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
@@ -24,7 +26,9 @@ 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
@@ -47,13 +51,18 @@ class MediaMuteAwaitConnectionManagerTest : SysuiTestCase() {
@Mock
private lateinit var audioManager: AudioManager
@Mock
private lateinit var deviceIconUtil: DeviceIconUtil
@Mock
private lateinit var localMediaManager: LocalMediaManager
private lateinit var muteAwaitListener: AudioManager.MuteAwaitConnectionCallback
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)
}
@Test
@@ -71,7 +80,7 @@ class MediaMuteAwaitConnectionManagerTest : SysuiTestCase() {
instantiateManager()
verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(DEVICE_NAME), any())
verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(DEVICE_NAME), eq(icon))
}
@Test
@@ -89,7 +98,7 @@ class MediaMuteAwaitConnectionManagerTest : SysuiTestCase() {
muteAwaitListener.onMutedUntilConnection(DEVICE, intArrayOf(USAGE_MEDIA))
verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(DEVICE_NAME), any())
verify(localMediaManager).dispatchAboutToConnectDeviceChanged(eq(DEVICE_NAME), eq(icon))
}
@Test
@@ -146,7 +155,8 @@ class MediaMuteAwaitConnectionManagerTest : SysuiTestCase() {
muteAwaitConnectionManager = MediaMuteAwaitConnectionManager(
FakeExecutor(FakeSystemClock()),
localMediaManager,
context
context,
deviceIconUtil
)
val listenerCaptor = ArgumentCaptor.forClass(