From b2c8605d1760bdd3e5495f943718d67e626787e8 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 Merged-In: I1fc0bc8a372d49b0fb6c8b25fd51fbcf321bab6f --- .../server/audio/AudioDeviceBroker.java | 72 ++++++++++++++++++- .../android/server/audio/AudioService.java | 58 ++++----------- 2 files changed, 84 insertions(+), 46 deletions(-) diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 278c98f45c44b..0589cfc0967b6 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -56,6 +56,7 @@ import android.util.PrintWriterPrinter; import com.android.internal.annotations.GuardedBy; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedList; @@ -434,6 +435,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. @@ -441,7 +484,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; } } @@ -918,8 +983,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; } @@ -936,6 +1001,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 8931ddee74fde..d6ecbc3828b03 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -5846,46 +5846,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) { - 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(); @@ -5897,7 +5867,7 @@ public class AudioService extends IAudioService.Stub Log.w(TAG, "setCommunicationDevice: invalid portID " + portId); return false; } - if (!isValidCommunicationDevice(device)) { + if (!AudioDeviceBroker.isValidCommunicationDevice(device)) { throw new IllegalArgumentException("invalid device type " + device.getType()); } } @@ -5940,13 +5910,15 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#getCommunicationDevice() */ public int getCommunicationDevice() { + int deviceId = 0; final long ident = Binder.clearCallingIdentity(); - AudioDeviceInfo device = mDeviceBroker.getCommunicationDevice(); - Binder.restoreCallingIdentity(ident); - if (device == null) { - return 0; + try { + AudioDeviceInfo device = mDeviceBroker.getCommunicationDevice(); + deviceId = device != null ? device.getId() : 0; + } finally { + Binder.restoreCallingIdentity(ident); } - return device.getId(); + return deviceId; } /** @see AudioManager#addOnCommunicationDeviceChangedListener(