diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index df799fdf98b21..33ec46abc4575 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -903,6 +903,18 @@ public class AudioSystem } } + /** + * Returns a human readable name for a given device type + * @param device a native device type, NOT an AudioDeviceInfo type + * @return a string describing the device type + */ + public static @NonNull String getDeviceName(int device) { + if ((device & DEVICE_BIT_IN) != 0) { + return getInputDeviceName(device); + } + return getOutputDeviceName(device); + } + // phone state, match audio_mode??? public static final int PHONE_STATE_OFFCALL = 0; public static final int PHONE_STATE_RINGING = 1; diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 8144a71180964..60f420e17e3b5 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -600,19 +600,11 @@ import java.io.PrintWriter; sendMsgNoDelay(MSG_REPORT_NEW_ROUTES, SENDMSG_NOOP); } - /*package*/ void cancelA2dpDockTimeout() { - mBrokerHandler.removeMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT); - } - /*package*/ void postA2dpActiveDeviceChange( @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) { sendLMsgNoDelay(MSG_L_A2DP_ACTIVE_DEVICE_CHANGE, SENDMSG_QUEUE, btDeviceInfo); } - /*package*/ boolean hasScheduledA2dpDockTimeout() { - return mBrokerHandler.hasMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT); - } - // must be called synchronized on mConnectedDevices /*package*/ boolean hasScheduledA2dpSinkConnectionState(BluetoothDevice btDevice) { return (mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED, @@ -621,8 +613,8 @@ import java.io.PrintWriter; new BtHelper.BluetoothA2dpDeviceInfo(btDevice))); } - /*package*/ void setA2dpDockTimeout(String address, int a2dpCodec, int delayMs) { - sendILMsg(MSG_IL_BTA2DP_DOCK_TIMEOUT, SENDMSG_QUEUE, a2dpCodec, address, delayMs); + /*package*/ void setA2dpTimeout(String address, int a2dpCodec, int delayMs) { + sendILMsg(MSG_IL_BTA2DP_TIMEOUT, SENDMSG_QUEUE, a2dpCodec, address, delayMs); } /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) { @@ -781,7 +773,7 @@ import java.io.PrintWriter; } } break; - case MSG_IL_BTA2DP_DOCK_TIMEOUT: + case MSG_IL_BTA2DP_TIMEOUT: // msg.obj == address of BTA2DP device synchronized (mDeviceStateLock) { mDeviceInventory.onMakeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1); @@ -945,7 +937,7 @@ import java.io.PrintWriter; private static final int MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE = 7; private static final int MSG_IL_SET_HEARING_AID_CONNECTION_STATE = 8; private static final int MSG_BT_HEADSET_CNCT_FAILED = 9; - private static final int MSG_IL_BTA2DP_DOCK_TIMEOUT = 10; + private static final int MSG_IL_BTA2DP_TIMEOUT = 10; private static final int MSG_L_A2DP_DEVICE_CONFIG_CHANGE = 11; private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 12; private static final int MSG_REPORT_NEW_ROUTES = 13; @@ -981,7 +973,7 @@ import java.io.PrintWriter; case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED: case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE: case MSG_IL_SET_HEARING_AID_CONNECTION_STATE: - case MSG_IL_BTA2DP_DOCK_TIMEOUT: + case MSG_IL_BTA2DP_TIMEOUT: case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: case MSG_TOGGLE_HDMI: case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE: @@ -1071,7 +1063,7 @@ import java.io.PrintWriter; case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED: case MSG_IL_SET_HEARING_AID_CONNECTION_STATE: case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: - case MSG_IL_BTA2DP_DOCK_TIMEOUT: + case MSG_IL_BTA2DP_TIMEOUT: case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE: if (sLastDeviceConnectMsgTime >= time) { diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 90615864d1d07..df5600473024e 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -59,10 +59,21 @@ public class AudioDeviceInventory { private static final String TAG = "AS.AudioDeviceInventory"; - // Actual list of connected devices + // lock to synchronize all access to mConnectedDevices and mApmConnectedDevices + private final Object mDevicesLock = new Object(); + + // List of connected devices // Key for map created from DeviceInfo.makeDeviceListKey() + @GuardedBy("mDevicesLock") private final LinkedHashMap mConnectedDevices = new LinkedHashMap<>(); + // List of devices actually connected to AudioPolicy (through AudioSystem), only one + // by device type, which is used as the key, value is the DeviceInfo generated key. + // For the moment only for A2DP sink devices. + // TODO: extend to all device types + @GuardedBy("mDevicesLock") + private final ArrayMap mApmConnectedDevices = new ArrayMap<>(); + // List of preferred devices for strategies private final ArrayMap mPreferredDevices = new ArrayMap<>(); @@ -94,25 +105,30 @@ public class AudioDeviceInventory { */ private static class DeviceInfo { final int mDeviceType; - final String mDeviceName; - final String mDeviceAddress; + final @NonNull String mDeviceName; + final @NonNull String mDeviceAddress; int mDeviceCodecFormat; DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat) { mDeviceType = deviceType; - mDeviceName = deviceName; - mDeviceAddress = deviceAddress; + mDeviceName = deviceName == null ? "" : deviceName; + mDeviceAddress = deviceAddress == null ? "" : deviceAddress; mDeviceCodecFormat = deviceCodecFormat; } @Override public String toString() { return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType) - + " name:" + mDeviceName + + " (" + AudioSystem.getDeviceName(mDeviceType) + + ") name:" + mDeviceName + " addr:" + mDeviceAddress + " codec: " + Integer.toHexString(mDeviceCodecFormat) + "]"; } + String getKey() { + return makeDeviceListKey(mDeviceType, mDeviceAddress); + } + /** * Generate a unique key for the mConnectedDevices List by composing the device "type" * and the "address" associated with a specific instance of that device type @@ -147,6 +163,14 @@ public class AudioDeviceInventory { pw.println("\n" + prefix + "Preferred devices for strategy:"); mPreferredDevices.forEach((strategy, device) -> { pw.println(" " + prefix + "strategy:" + strategy + " device:" + device); }); + pw.println("\n" + prefix + "Connected devices:"); + mConnectedDevices.forEach((key, deviceInfo) -> { + pw.println(" " + prefix + deviceInfo.toString()); }); + pw.println("\n" + prefix + "APM Connected device (A2DP sink only):"); + mApmConnectedDevices.forEach((keyType, valueAddress) -> { + pw.println(" " + prefix + " type:0x" + Integer.toHexString(keyType) + + " (" + AudioSystem.getDeviceName(keyType) + + ") addr:" + valueAddress); }); } //------------------------------------------------------------ @@ -158,7 +182,8 @@ public class AudioDeviceInventory { */ // Always executed on AudioDeviceBroker message queue /*package*/ void onRestoreDevices() { - synchronized (mConnectedDevices) { + synchronized (mDevicesLock) { + //TODO iterate on mApmConnectedDevices instead once it handles all device types for (DeviceInfo di : mConnectedDevices.values()) { AudioSystem.setDeviceConnectionState( di.mDeviceType, @@ -168,7 +193,6 @@ public class AudioDeviceInventory { di.mDeviceCodecFormat); } } - synchronized (mPreferredDevices) { mPreferredDevices.forEach((strategy, device) -> { AudioSystem.setPreferredDeviceForStrategy(strategy, device); }); @@ -187,6 +211,9 @@ public class AudioDeviceInventory { + state + " vol=" + a2dpVolume); } String address = btDevice.getAddress(); + if (address == null) { + address = ""; + } if (!BluetoothAdapter.checkBluetoothAddress(address)) { address = ""; } @@ -198,7 +225,7 @@ public class AudioDeviceInventory { + " codec=" + a2dpCodec + " vol=" + a2dpVolume)); - synchronized (mConnectedDevices) { + synchronized (mDevicesLock) { final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, btDevice.getAddress()); final DeviceInfo di = mConnectedDevices.get(key); @@ -238,7 +265,7 @@ public class AudioDeviceInventory { address = ""; } - synchronized (mConnectedDevices) { + synchronized (mDevicesLock) { final String key = DeviceInfo.makeDeviceListKey( AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address); final DeviceInfo di = mConnectedDevices.get(key); @@ -261,7 +288,7 @@ public class AudioDeviceInventory { AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( "onSetHearingAidConnectionState addr=" + address)); - synchronized (mConnectedDevices) { + synchronized (mDevicesLock) { final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, btDevice.getAddress()); final DeviceInfo di = mConnectedDevices.get(key); @@ -297,7 +324,7 @@ public class AudioDeviceInventory { "onBluetoothA2dpActiveDeviceChange addr=" + address + " event=" + BtHelper.a2dpDeviceEventToString(event))); - synchronized (mConnectedDevices) { + synchronized (mDevicesLock) { if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) { AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( "A2dp config change ignored (scheduled connection change)")); @@ -340,7 +367,7 @@ public class AudioDeviceInventory { } /*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) { - synchronized (mConnectedDevices) { + synchronized (mDevicesLock) { makeA2dpDeviceUnavailableNow(address, a2dpCodec); } } @@ -377,7 +404,7 @@ public class AudioDeviceInventory { AudioDeviceInventory.WiredDeviceConnectionState wdcs) { AudioService.sDeviceLogger.log(new AudioServiceEvents.WiredDevConnectEvent(wdcs)); - synchronized (mConnectedDevices) { + synchronized (mDevicesLock) { if ((wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED) && DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(wdcs.mType)) { mDeviceBroker.setBluetoothA2dpOnInt(true, @@ -405,7 +432,7 @@ public class AudioDeviceInventory { } /*package*/ void onToggleHdmi() { - synchronized (mConnectedDevices) { + synchronized (mDevicesLock) { // Is HDMI connected? final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, ""); final DeviceInfo di = mConnectedDevices.get(key); @@ -472,7 +499,7 @@ public class AudioDeviceInventory { + Integer.toHexString(device) + " address:" + address + " name:" + deviceName + ")"); } - synchronized (mConnectedDevices) { + synchronized (mDevicesLock) { final String deviceKey = DeviceInfo.makeDeviceListKey(device, address); if (AudioService.DEBUG_DEVICES) { Slog.i(TAG, "deviceKey:" + deviceKey); @@ -511,7 +538,7 @@ public class AudioDeviceInventory { /*package*/ void disconnectA2dp() { - synchronized (mConnectedDevices) { + synchronized (mDevicesLock) { final ArraySet toRemove = new ArraySet<>(); // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices mConnectedDevices.values().forEach(deviceInfo -> { @@ -531,7 +558,7 @@ public class AudioDeviceInventory { } /*package*/ void disconnectA2dpSink() { - synchronized (mConnectedDevices) { + synchronized (mDevicesLock) { final ArraySet toRemove = new ArraySet<>(); // Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices mConnectedDevices.values().forEach(deviceInfo -> { @@ -544,7 +571,7 @@ public class AudioDeviceInventory { } /*package*/ void disconnectHearingAid() { - synchronized (mConnectedDevices) { + synchronized (mDevicesLock) { final ArraySet toRemove = new ArraySet<>(); // Disconnect ALL DEVICE_OUT_HEARING_AID devices mConnectedDevices.values().forEach(deviceInfo -> { @@ -568,7 +595,7 @@ public class AudioDeviceInventory { // from AudioSystem /*package*/ int checkSendBecomingNoisyIntent(int device, @AudioService.ConnectionState int state, int musicDevice) { - synchronized (mConnectedDevices) { + synchronized (mDevicesLock) { return checkSendBecomingNoisyIntentInt(device, state, musicDevice); } } @@ -595,7 +622,7 @@ public class AudioDeviceInventory { if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) { throw new IllegalArgumentException("invalid profile " + profile); } - synchronized (mConnectedDevices) { + synchronized (mDevicesLock) { if (profile == BluetoothProfile.A2DP && !suppressNoisyIntent) { @AudioService.ConnectionState int asState = (state == BluetoothA2dp.STATE_CONNECTED) @@ -635,7 +662,7 @@ public class AudioDeviceInventory { /*package*/ int setWiredDeviceConnectionState(int type, @AudioService.ConnectionState int state, String address, String name, String caller) { - synchronized (mConnectedDevices) { + synchronized (mDevicesLock) { int delay = checkSendBecomingNoisyIntentInt(type, state, AudioSystem.DEVICE_NONE); mDeviceBroker.postSetWiredDeviceConnectionState( new WiredDeviceConnectionState(type, state, address, name, caller), @@ -648,7 +675,7 @@ public class AudioDeviceInventory { @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, boolean suppressNoisyIntent, int musicDevice) { int delay; - synchronized (mConnectedDevices) { + synchronized (mDevicesLock) { if (!suppressNoisyIntent) { int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0; delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_HEARING_AID, @@ -665,39 +692,58 @@ public class AudioDeviceInventory { //------------------------------------------------------------------- // Internal utilities - @GuardedBy("mConnectedDevices") + @GuardedBy("mDevicesLock") private void makeA2dpDeviceAvailable(String address, String name, String eventSource, int a2dpCodec) { // enable A2DP before notifying A2DP connection to avoid unnecessary processing in // audio policy manager mDeviceBroker.setBluetoothA2dpOnInt(true, eventSource); + // at this point there could be another A2DP device already connected in APM, but it + // doesn't matter as this new one will overwrite the previous one AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec); // Reset A2DP suspend state each time a new sink is connected AudioSystem.setParameters("A2dpSuspended=false"); - mConnectedDevices.put( - DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address), - new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name, - address, a2dpCodec)); + + final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name, + address, a2dpCodec); + final String diKey = di.getKey(); + mConnectedDevices.put(diKey, di); + // on a connection always overwrite the device seen by AudioPolicy, see comment above when + // calling AudioSystem + mApmConnectedDevices.put(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, diKey); + mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); setCurrentAudioRouteNameIfPossible(name); } - @GuardedBy("mConnectedDevices") + @GuardedBy("mDevicesLock") private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) { if (address == null) { return; } + final String deviceToRemoveKey = + DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); + + mConnectedDevices.remove(deviceToRemoveKey); + if (!mApmConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) + .equals(deviceToRemoveKey)) { + // removing A2DP device not currently used by AudioPolicy, log but don't act on it + AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( + "A2DP device " + address + " made unavailable, was not used")).printLog(TAG)); + return; + } + + // device to remove was visible by APM, update APM mDeviceBroker.setAvrcpAbsoluteVolumeSupported(false); AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec); - mConnectedDevices.remove( - DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address)); + mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); // Remove A2DP routes as well setCurrentAudioRouteNameIfPossible(null); } - @GuardedBy("mConnectedDevices") + @GuardedBy("mDevicesLock") private void makeA2dpDeviceUnavailableLater(String address, int delayMs) { // prevent any activity on the A2DP audio output to avoid unwanted // reconnection of the sink. @@ -711,11 +757,11 @@ public class AudioDeviceInventory { // the device will be made unavailable later, so consider it disconnected right away mConnectedDevices.remove(deviceKey); // send the delayed message to make the device unavailable later - mDeviceBroker.setA2dpDockTimeout(address, a2dpCodec, delayMs); + mDeviceBroker.setA2dpTimeout(address, a2dpCodec, delayMs); } - @GuardedBy("mConnectedDevices") + @GuardedBy("mDevicesLock") private void makeA2dpSrcAvailable(String address) { AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, AudioSystem.DEVICE_STATE_AVAILABLE, address, "", @@ -726,7 +772,7 @@ public class AudioDeviceInventory { address, AudioSystem.AUDIO_FORMAT_DEFAULT)); } - @GuardedBy("mConnectedDevices") + @GuardedBy("mDevicesLock") private void makeA2dpSrcUnavailable(String address) { AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", @@ -735,7 +781,7 @@ public class AudioDeviceInventory { DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address)); } - @GuardedBy("mConnectedDevices") + @GuardedBy("mDevicesLock") private void makeHearingAidDeviceAvailable( String address, String name, int streamType, String eventSource) { final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType, @@ -755,7 +801,7 @@ public class AudioDeviceInventory { setCurrentAudioRouteNameIfPossible(name); } - @GuardedBy("mConnectedDevices") + @GuardedBy("mDevicesLock") private void makeHearingAidDeviceUnavailable(String address) { AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID, AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", @@ -766,7 +812,7 @@ public class AudioDeviceInventory { setCurrentAudioRouteNameIfPossible(null); } - @GuardedBy("mConnectedDevices") + @GuardedBy("mDevicesLock") private void setCurrentAudioRouteNameIfPossible(String name) { synchronized (mCurAudioRoutes) { if (TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) { @@ -779,7 +825,7 @@ public class AudioDeviceInventory { } } - @GuardedBy("mConnectedDevices") + @GuardedBy("mDevicesLock") private boolean isCurrentDeviceConnected() { return mConnectedDevices.values().stream().anyMatch(deviceInfo -> TextUtils.equals(deviceInfo.mDeviceName, mCurAudioRoutes.bluetoothName)); @@ -807,7 +853,7 @@ public class AudioDeviceInventory { // must be called before removing the device from mConnectedDevices // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying // from AudioSystem - @GuardedBy("mConnectedDevices") + @GuardedBy("mDevicesLock") private int checkSendBecomingNoisyIntentInt(int device, @AudioService.ConnectionState int state, int musicDevice) { if (state != AudioService.CONNECTION_STATE_DISCONNECTED) { @@ -1015,7 +1061,7 @@ public class AudioDeviceInventory { public boolean isA2dpDeviceConnected(@NonNull BluetoothDevice device) { final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, device.getAddress()); - synchronized (mConnectedDevices) { + synchronized (mDevicesLock) { return (mConnectedDevices.get(key) != null); } }