From 1dea0dab31b2609a34d5fa6f7eaf436b20dc1059 Mon Sep 17 00:00:00 2001 From: Eric Laurent Date: Wed, 25 Jan 2023 14:54:00 +0100 Subject: [PATCH] AudioService: fix communication device APIs make sure getAvailableCommunicationDevices() and getCommunicationDevice() always return null or a valid communication device, Bug: 265613261 Bug: 266067856 Test: atest AudioCommunicationDeviceTest Change-Id: I1fc0bc8a372d49b0fb6c8b25fd51fbcf321bab6f --- .../server/audio/AudioDeviceBroker.java | 72 ++++++++++++++++++- .../android/server/audio/AudioService.java | 59 ++++----------- 2 files changed, 81 insertions(+), 50 deletions(-) diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 21bd7bc05bea5..b001f3d0c892d 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -58,6 +58,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.server.utils.EventLogger; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedList; @@ -453,6 +454,48 @@ import java.util.concurrent.atomic.AtomicBoolean; return device; } + private static final int[] VALID_COMMUNICATION_DEVICE_TYPES = { + AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, + AudioDeviceInfo.TYPE_BLUETOOTH_SCO, + AudioDeviceInfo.TYPE_WIRED_HEADSET, + AudioDeviceInfo.TYPE_USB_HEADSET, + AudioDeviceInfo.TYPE_BUILTIN_EARPIECE, + AudioDeviceInfo.TYPE_WIRED_HEADPHONES, + AudioDeviceInfo.TYPE_HEARING_AID, + AudioDeviceInfo.TYPE_BLE_HEADSET, + AudioDeviceInfo.TYPE_USB_DEVICE, + AudioDeviceInfo.TYPE_BLE_SPEAKER, + AudioDeviceInfo.TYPE_LINE_ANALOG, + AudioDeviceInfo.TYPE_HDMI, + AudioDeviceInfo.TYPE_AUX_LINE + }; + + /*package */ static boolean isValidCommunicationDevice(AudioDeviceInfo device) { + for (int type : VALID_COMMUNICATION_DEVICE_TYPES) { + if (device.getType() == type) { + return true; + } + } + return false; + } + + /* package */ static List getAvailableCommunicationDevices() { + ArrayList commDevices = new ArrayList<>(); + AudioDeviceInfo[] allDevices = + AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_OUTPUTS); + for (AudioDeviceInfo device : allDevices) { + if (isValidCommunicationDevice(device)) { + commDevices.add(device); + } + } + return commDevices; + } + + private @Nullable AudioDeviceInfo getCommunicationDeviceOfType(int type) { + return getAvailableCommunicationDevices().stream().filter(d -> d.getType() == type) + .findFirst().orElse(null); + } + /** * Returns the device currently requested for communication use case. * @return AudioDeviceInfo the requested device for communication. @@ -460,7 +503,29 @@ import java.util.concurrent.atomic.AtomicBoolean; /* package */ AudioDeviceInfo getCommunicationDevice() { synchronized (mDeviceStateLock) { updateActiveCommunicationDevice(); - return mActiveCommunicationDevice; + AudioDeviceInfo device = mActiveCommunicationDevice; + // make sure we return a valid communication device (i.e. a device that is allowed by + // setCommunicationDevice()) for consistency. + if (device != null) { + // a digital dock is used instead of the speaker in speakerphone mode and should + // be reflected as such + if (device.getType() == AudioDeviceInfo.TYPE_DOCK) { + device = getCommunicationDeviceOfType(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER); + } + } + // Try to default to earpiece when current communication device is not valid. This can + // happen for instance if no call is active. If no earpiece device is available take the + // first valid communication device + if (device == null || !AudioDeviceBroker.isValidCommunicationDevice(device)) { + device = getCommunicationDeviceOfType(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE); + if (device == null) { + List commDevices = getAvailableCommunicationDevices(); + if (!commDevices.isEmpty()) { + device = commDevices.get(0); + } + } + } + return device; } } @@ -942,8 +1007,8 @@ import java.util.concurrent.atomic.AtomicBoolean; @GuardedBy("mDeviceStateLock") private void dispatchCommunicationDevice() { - int portId = (mActiveCommunicationDevice == null) ? 0 - : mActiveCommunicationDevice.getId(); + AudioDeviceInfo device = getCommunicationDevice(); + int portId = device != null ? device.getId() : 0; if (portId == mCurCommunicationPortId) { return; } @@ -960,6 +1025,7 @@ import java.util.concurrent.atomic.AtomicBoolean; mCommDevDispatchers.finishBroadcast(); } + //--------------------------------------------------------------------- // Communication with (to) AudioService //TODO check whether the AudioService methods are candidates to move here diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 5c44cea77ae83..93a155334e09c 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -6067,49 +6067,16 @@ public class AudioService extends IAudioService.Stub restoreDeviceVolumeBehavior(); } - private static final int[] VALID_COMMUNICATION_DEVICE_TYPES = { - AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, - AudioDeviceInfo.TYPE_BLUETOOTH_SCO, - AudioDeviceInfo.TYPE_WIRED_HEADSET, - AudioDeviceInfo.TYPE_USB_HEADSET, - AudioDeviceInfo.TYPE_BUILTIN_EARPIECE, - AudioDeviceInfo.TYPE_WIRED_HEADPHONES, - AudioDeviceInfo.TYPE_HEARING_AID, - AudioDeviceInfo.TYPE_BLE_HEADSET, - AudioDeviceInfo.TYPE_USB_DEVICE, - AudioDeviceInfo.TYPE_BLE_SPEAKER, - AudioDeviceInfo.TYPE_LINE_ANALOG, - AudioDeviceInfo.TYPE_HDMI, - AudioDeviceInfo.TYPE_AUX_LINE - }; - - private boolean isValidCommunicationDevice(AudioDeviceInfo device) { - if (!device.isSink()) { - return false; - } - for (int type : VALID_COMMUNICATION_DEVICE_TYPES) { - if (device.getType() == type) { - return true; - } - } - return false; - } - /** @see AudioManager#getAvailableCommunicationDevices(int) */ public int[] getAvailableCommunicationDeviceIds() { - ArrayList deviceIds = new ArrayList<>(); - AudioDeviceInfo[] devices = AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_OUTPUTS); - for (AudioDeviceInfo device : devices) { - if (isValidCommunicationDevice(device)) { - deviceIds.add(device.getId()); - } - } - return deviceIds.stream().mapToInt(Integer::intValue).toArray(); + List commDevices = AudioDeviceBroker.getAvailableCommunicationDevices(); + return commDevices.stream().mapToInt(AudioDeviceInfo::getId).toArray(); } - /** - * @see AudioManager#setCommunicationDevice(int) - * @see AudioManager#clearCommunicationDevice() - */ + + /** + * @see AudioManager#setCommunicationDevice(int) + * @see AudioManager#clearCommunicationDevice() + */ public boolean setCommunicationDevice(IBinder cb, int portId) { final int uid = Binder.getCallingUid(); final int pid = Binder.getCallingPid(); @@ -6120,7 +6087,7 @@ public class AudioService extends IAudioService.Stub if (device == null) { throw new IllegalArgumentException("invalid portID " + portId); } - if (!isValidCommunicationDevice(device)) { + if (!AudioDeviceBroker.isValidCommunicationDevice(device)) { if (!device.isSink()) { throw new IllegalArgumentException("device must have sink role"); } else { @@ -6168,17 +6135,15 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#getCommunicationDevice() */ public int getCommunicationDevice() { - AudioDeviceInfo device = null; + int deviceId = 0; final long ident = Binder.clearCallingIdentity(); try { - device = mDeviceBroker.getCommunicationDevice(); + AudioDeviceInfo device = mDeviceBroker.getCommunicationDevice(); + deviceId = device != null ? device.getId() : 0; } finally { Binder.restoreCallingIdentity(ident); } - if (device == null) { - return 0; - } - return device.getId(); + return deviceId; } /** @see AudioManager#addOnCommunicationDeviceChangedListener(