Merge "AudioService: BT dual mode support" into udc-dev am: b44e83134d am: 9d2fee7d40

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/22887817

Change-Id: I2d65902fc6b9bb30bb7b814b26da7c05b871326b
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Eric Laurent
2023-05-02 15:37:18 +00:00
committed by Automerger Merge Worker
4 changed files with 513 additions and 184 deletions

View File

@@ -1237,6 +1237,9 @@ public class AudioSystem
public static final Set<Integer> DEVICE_IN_ALL_SCO_SET; public static final Set<Integer> DEVICE_IN_ALL_SCO_SET;
/** @hide */ /** @hide */
public static final Set<Integer> DEVICE_IN_ALL_USB_SET; public static final Set<Integer> DEVICE_IN_ALL_USB_SET;
/** @hide */
public static final Set<Integer> DEVICE_IN_ALL_BLE_SET;
static { static {
DEVICE_IN_ALL_SET = new HashSet<>(); DEVICE_IN_ALL_SET = new HashSet<>();
DEVICE_IN_ALL_SET.add(DEVICE_IN_COMMUNICATION); DEVICE_IN_ALL_SET.add(DEVICE_IN_COMMUNICATION);
@@ -1276,6 +1279,66 @@ public class AudioSystem
DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_ACCESSORY); DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_ACCESSORY);
DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_DEVICE); DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_DEVICE);
DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_HEADSET); DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_HEADSET);
DEVICE_IN_ALL_BLE_SET = new HashSet<>();
DEVICE_IN_ALL_BLE_SET.add(DEVICE_IN_BLE_HEADSET);
}
/** @hide */
public static boolean isBluetoothDevice(int deviceType) {
return isBluetoothA2dpOutDevice(deviceType)
|| isBluetoothScoDevice(deviceType)
|| isBluetoothLeDevice(deviceType);
}
/** @hide */
public static boolean isBluetoothOutDevice(int deviceType) {
return isBluetoothA2dpOutDevice(deviceType)
|| isBluetoothScoOutDevice(deviceType)
|| isBluetoothLeOutDevice(deviceType);
}
/** @hide */
public static boolean isBluetoothInDevice(int deviceType) {
return isBluetoothScoInDevice(deviceType)
|| isBluetoothLeInDevice(deviceType);
}
/** @hide */
public static boolean isBluetoothA2dpOutDevice(int deviceType) {
return DEVICE_OUT_ALL_A2DP_SET.contains(deviceType);
}
/** @hide */
public static boolean isBluetoothScoOutDevice(int deviceType) {
return DEVICE_OUT_ALL_SCO_SET.contains(deviceType);
}
/** @hide */
public static boolean isBluetoothScoInDevice(int deviceType) {
return DEVICE_IN_ALL_SCO_SET.contains(deviceType);
}
/** @hide */
public static boolean isBluetoothScoDevice(int deviceType) {
return isBluetoothScoOutDevice(deviceType)
|| isBluetoothScoInDevice(deviceType);
}
/** @hide */
public static boolean isBluetoothLeOutDevice(int deviceType) {
return DEVICE_OUT_ALL_BLE_SET.contains(deviceType);
}
/** @hide */
public static boolean isBluetoothLeInDevice(int deviceType) {
return DEVICE_IN_ALL_BLE_SET.contains(deviceType);
}
/** @hide */
public static boolean isBluetoothLeDevice(int deviceType) {
return isBluetoothLeOutDevice(deviceType)
|| isBluetoothLeInDevice(deviceType);
} }
/** @hide */ /** @hide */

View File

@@ -88,14 +88,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
private final @NonNull AudioSystemAdapter mAudioSystem; private final @NonNull AudioSystemAdapter mAudioSystem;
/** ID for Communication strategy retrieved form audio policy manager */ /** ID for Communication strategy retrieved form audio policy manager */
private int mCommunicationStrategyId = -1; /*package*/ int mCommunicationStrategyId = -1;
/** ID for Accessibility strategy retrieved form audio policy manager */ /** ID for Accessibility strategy retrieved form audio policy manager */
private int mAccessibilityStrategyId = -1; private int mAccessibilityStrategyId = -1;
/** Active communication device reported by audio policy manager */ /** Active communication device reported by audio policy manager */
private AudioDeviceInfo mActiveCommunicationDevice; /*package*/ AudioDeviceInfo mActiveCommunicationDevice;
/** Last preferred device set for communication strategy */ /** Last preferred device set for communication strategy */
private AudioDeviceAttributes mPreferredCommunicationDevice; private AudioDeviceAttributes mPreferredCommunicationDevice;
@@ -755,6 +755,19 @@ import java.util.concurrent.atomic.AtomicBoolean;
mIsLeOutput = false; mIsLeOutput = false;
} }
BtDeviceInfo(@NonNull BtDeviceInfo src, int state) {
mDevice = src.mDevice;
mState = state;
mProfile = src.mProfile;
mSupprNoisy = src.mSupprNoisy;
mVolume = src.mVolume;
mIsLeOutput = src.mIsLeOutput;
mEventSource = src.mEventSource;
mAudioSystemDevice = src.mAudioSystemDevice;
mMusicDevice = src.mMusicDevice;
mCodec = src.mCodec;
}
// redefine equality op so we can match messages intended for this device // redefine equality op so we can match messages intended for this device
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
@@ -821,7 +834,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
* @param info struct with the (dis)connection information * @param info struct with the (dis)connection information
*/ */
/*package*/ void queueOnBluetoothActiveDeviceChanged(@NonNull BtDeviceChangedData data) { /*package*/ void queueOnBluetoothActiveDeviceChanged(@NonNull BtDeviceChangedData data) {
if (data.mInfo.getProfile() == BluetoothProfile.A2DP && data.mPreviousDevice != null if (data.mPreviousDevice != null
&& data.mPreviousDevice.equals(data.mNewDevice)) { && data.mPreviousDevice.equals(data.mNewDevice)) {
final String name = TextUtils.emptyIfNull(data.mNewDevice.getName()); final String name = TextUtils.emptyIfNull(data.mNewDevice.getName());
new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE + MediaMetrics.SEPARATOR new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE + MediaMetrics.SEPARATOR
@@ -830,7 +843,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
.set(MediaMetrics.Property.STATUS, data.mInfo.getProfile()) .set(MediaMetrics.Property.STATUS, data.mInfo.getProfile())
.record(); .record();
synchronized (mDeviceStateLock) { synchronized (mDeviceStateLock) {
postBluetoothA2dpDeviceConfigChange(data.mNewDevice); postBluetoothDeviceConfigChange(createBtDeviceInfo(data, data.mNewDevice,
BluetoothProfile.STATE_CONNECTED));
} }
} else { } else {
synchronized (mDeviceStateLock) { synchronized (mDeviceStateLock) {
@@ -1064,8 +1078,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
new AudioModeInfo(mode, pid, uid)); new AudioModeInfo(mode, pid, uid));
} }
/*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) { /*package*/ void postBluetoothDeviceConfigChange(@NonNull BtDeviceInfo info) {
sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, device); sendLMsgNoDelay(MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, info);
} }
/*package*/ void startBluetoothScoForClient(IBinder cb, int pid, int scoAudioMode, /*package*/ void startBluetoothScoForClient(IBinder cb, int pid, int scoAudioMode,
@@ -1322,6 +1336,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
sendIMsgNoDelay(MSG_I_SCO_AUDIO_STATE_CHANGED, SENDMSG_QUEUE, state); sendIMsgNoDelay(MSG_I_SCO_AUDIO_STATE_CHANGED, SENDMSG_QUEUE, state);
} }
/*package*/ void postNotifyPreferredAudioProfileApplied(BluetoothDevice btDevice) {
sendLMsgNoDelay(MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED, SENDMSG_QUEUE, btDevice);
}
/*package*/ static final class CommunicationDeviceInfo { /*package*/ static final class CommunicationDeviceInfo {
final @NonNull IBinder mCb; // Identifies the requesting client for death handler final @NonNull IBinder mCb; // Identifies the requesting client for death handler
final int mPid; // Requester process ID final int mPid; // Requester process ID
@@ -1397,9 +1415,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
} }
} }
/*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, boolean connect) { /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes,
boolean connect, @Nullable BluetoothDevice btDevice) {
synchronized (mDeviceStateLock) { synchronized (mDeviceStateLock) {
return mDeviceInventory.handleDeviceConnection(attributes, connect, false /*for test*/); return mDeviceInventory.handleDeviceConnection(
attributes, connect, false /*for test*/, btDevice);
} }
} }
@@ -1640,13 +1660,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
(String) msg.obj, msg.arg1); (String) msg.obj, msg.arg1);
} }
break; break;
case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE:
final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
synchronized (mDeviceStateLock) { synchronized (mDeviceStateLock) {
final int a2dpCodec = mBtHelper.getA2dpCodec(btDevice); mDeviceInventory.onBluetoothDeviceConfigChange(
mDeviceInventory.onBluetoothA2dpDeviceConfigChange( (BtDeviceInfo) msg.obj, BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
new BtHelper.BluetoothA2dpDeviceInfo(btDevice, -1, a2dpCodec),
BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
} }
break; break;
case MSG_BROADCAST_AUDIO_BECOMING_NOISY: case MSG_BROADCAST_AUDIO_BECOMING_NOISY:
@@ -1810,6 +1827,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
case MSG_IL_SET_LEAUDIO_SUSPENDED: { case MSG_IL_SET_LEAUDIO_SUSPENDED: {
setLeAudioSuspended((msg.arg1 == 1), false /*internal*/, (String) msg.obj); setLeAudioSuspended((msg.arg1 == 1), false /*internal*/, (String) msg.obj);
} break; } break;
case MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED: {
final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
BtHelper.onNotifyPreferredAudioProfileApplied(btDevice);
} break;
default: default:
Log.wtf(TAG, "Invalid message " + msg.what); Log.wtf(TAG, "Invalid message " + msg.what);
} }
@@ -1845,7 +1866,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
private static final int MSG_IL_BTA2DP_TIMEOUT = 10; private static final int MSG_IL_BTA2DP_TIMEOUT = 10;
// process change of A2DP device configuration, obj is BluetoothDevice // process change of A2DP device configuration, obj is BluetoothDevice
private static final int MSG_L_A2DP_DEVICE_CONFIG_CHANGE = 11; private static final int MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE = 11;
private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 12; private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 12;
private static final int MSG_REPORT_NEW_ROUTES = 13; private static final int MSG_REPORT_NEW_ROUTES = 13;
@@ -1887,13 +1908,15 @@ import java.util.concurrent.atomic.AtomicBoolean;
private static final int MSG_IL_SET_A2DP_SUSPENDED = 50; private static final int MSG_IL_SET_A2DP_SUSPENDED = 50;
private static final int MSG_IL_SET_LEAUDIO_SUSPENDED = 51; private static final int MSG_IL_SET_LEAUDIO_SUSPENDED = 51;
private static final int MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED = 52;
private static boolean isMessageHandledUnderWakelock(int msgId) { private static boolean isMessageHandledUnderWakelock(int msgId) {
switch(msgId) { switch(msgId) {
case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
case MSG_L_SET_BT_ACTIVE_DEVICE: case MSG_L_SET_BT_ACTIVE_DEVICE:
case MSG_IL_BTA2DP_TIMEOUT: case MSG_IL_BTA2DP_TIMEOUT:
case MSG_IL_BTLEAUDIO_TIMEOUT: case MSG_IL_BTLEAUDIO_TIMEOUT:
case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE:
case MSG_TOGGLE_HDMI: case MSG_TOGGLE_HDMI:
case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT: case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT:
case MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT: case MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT:
@@ -1985,7 +2008,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
case MSG_IL_BTA2DP_TIMEOUT: case MSG_IL_BTA2DP_TIMEOUT:
case MSG_IL_BTLEAUDIO_TIMEOUT: case MSG_IL_BTLEAUDIO_TIMEOUT:
case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE:
if (sLastDeviceConnectMsgTime >= time) { if (sLastDeviceConnectMsgTime >= time) {
// add a little delay to make sure messages are ordered as expected // add a little delay to make sure messages are ordered as expected
time = sLastDeviceConnectMsgTime + 30; time = sLastDeviceConnectMsgTime + 30;
@@ -2005,7 +2028,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
static { static {
MESSAGES_MUTE_MUSIC = new HashSet<>(); MESSAGES_MUTE_MUSIC = new HashSet<>();
MESSAGES_MUTE_MUSIC.add(MSG_L_SET_BT_ACTIVE_DEVICE); MESSAGES_MUTE_MUSIC.add(MSG_L_SET_BT_ACTIVE_DEVICE);
MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_DEVICE_CONFIG_CHANGE); MESSAGES_MUTE_MUSIC.add(MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE);
MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT); MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT);
MESSAGES_MUTE_MUSIC.add(MSG_IIL_SET_FORCE_BT_A2DP_USE); MESSAGES_MUTE_MUSIC.add(MSG_IIL_SET_FORCE_BT_A2DP_USE);
} }
@@ -2026,7 +2049,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
// Do not mute on bluetooth event if music is playing on a wired headset. // Do not mute on bluetooth event if music is playing on a wired headset.
if ((message == MSG_L_SET_BT_ACTIVE_DEVICE if ((message == MSG_L_SET_BT_ACTIVE_DEVICE
|| message == MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT || message == MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT
|| message == MSG_L_A2DP_DEVICE_CONFIG_CHANGE) || message == MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE)
&& AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) && AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)
&& hasIntersection(mDeviceInventory.DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET, && hasIntersection(mDeviceInventory.DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET,
mAudioService.getDeviceSetForStream(AudioSystem.STREAM_MUSIC))) { mAudioService.getDeviceSetForStream(AudioSystem.STREAM_MUSIC))) {
@@ -2173,6 +2196,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
mDeviceInventory.removePreferredDevicesForStrategy(mCommunicationStrategyId); mDeviceInventory.removePreferredDevicesForStrategy(mCommunicationStrategyId);
mDeviceInventory.removePreferredDevicesForStrategy(mAccessibilityStrategyId); mDeviceInventory.removePreferredDevicesForStrategy(mAccessibilityStrategyId);
} }
mDeviceInventory.applyConnectedDevicesRoles();
} else { } else {
mDeviceInventory.setPreferredDevicesForStrategy( mDeviceInventory.setPreferredDevicesForStrategy(
mCommunicationStrategyId, Arrays.asList(preferredCommunicationDevice)); mCommunicationStrategyId, Arrays.asList(preferredCommunicationDevice));

View File

@@ -34,11 +34,15 @@ import android.media.ICapturePresetDevicesRoleDispatcher;
import android.media.IStrategyNonDefaultDevicesDispatcher; import android.media.IStrategyNonDefaultDevicesDispatcher;
import android.media.IStrategyPreferredDevicesDispatcher; import android.media.IStrategyPreferredDevicesDispatcher;
import android.media.MediaMetrics; import android.media.MediaMetrics;
import android.media.MediaRecorder.AudioSource;
import android.media.audiopolicy.AudioProductStrategy;
import android.media.permission.ClearCallingIdentityContext; import android.media.permission.ClearCallingIdentityContext;
import android.media.permission.SafeCloseable; import android.media.permission.SafeCloseable;
import android.os.Binder; import android.os.Binder;
import android.os.Bundle;
import android.os.RemoteCallbackList; import android.os.RemoteCallbackList;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.SystemProperties;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.ArrayMap; import android.util.ArrayMap;
import android.util.ArraySet; import android.util.ArraySet;
@@ -50,6 +54,8 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.server.utils.EventLogger; import com.android.server.utils.EventLogger;
import com.google.android.collect.Sets;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -179,6 +185,8 @@ public class AudioDeviceInventory {
final RemoteCallbackList<ICapturePresetDevicesRoleDispatcher> mDevRoleCapturePresetDispatchers = final RemoteCallbackList<ICapturePresetDevicesRoleDispatcher> mDevRoleCapturePresetDispatchers =
new RemoteCallbackList<ICapturePresetDevicesRoleDispatcher>(); new RemoteCallbackList<ICapturePresetDevicesRoleDispatcher>();
final List<AudioProductStrategy> mStrategies;
/*package*/ AudioDeviceInventory(@NonNull AudioDeviceBroker broker) { /*package*/ AudioDeviceInventory(@NonNull AudioDeviceBroker broker) {
this(broker, AudioSystemAdapter.getDefaultAdapter()); this(broker, AudioSystemAdapter.getDefaultAdapter());
} }
@@ -193,8 +201,10 @@ public class AudioDeviceInventory {
@Nullable AudioSystemAdapter audioSystem) { @Nullable AudioSystemAdapter audioSystem) {
mDeviceBroker = broker; mDeviceBroker = broker;
mAudioSystem = audioSystem; mAudioSystem = audioSystem;
mStrategies = AudioProductStrategy.getAudioProductStrategies();
mBluetoothDualModeEnabled = SystemProperties.getBoolean(
"persist.bluetooth.enable_dual_mode_audio", false);
} }
/*package*/ void setDeviceBroker(@NonNull AudioDeviceBroker broker) { /*package*/ void setDeviceBroker(@NonNull AudioDeviceBroker broker) {
mDeviceBroker = broker; mDeviceBroker = broker;
} }
@@ -211,8 +221,13 @@ public class AudioDeviceInventory {
int mDeviceCodecFormat; int mDeviceCodecFormat;
final UUID mSensorUuid; final UUID mSensorUuid;
/** Disabled operating modes for this device. Use a negative logic so that by default
* an empty list means all modes are allowed.
* See BluetoothAdapter.AUDIO_MODE_DUPLEX and BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY */
@NonNull ArraySet<String> mDisabledModes = new ArraySet(0);
DeviceInfo(int deviceType, String deviceName, String deviceAddress, DeviceInfo(int deviceType, String deviceName, String deviceAddress,
int deviceCodecFormat, UUID sensorUuid) { int deviceCodecFormat, @Nullable UUID sensorUuid) {
mDeviceType = deviceType; mDeviceType = deviceType;
mDeviceName = deviceName == null ? "" : deviceName; mDeviceName = deviceName == null ? "" : deviceName;
mDeviceAddress = deviceAddress == null ? "" : deviceAddress; mDeviceAddress = deviceAddress == null ? "" : deviceAddress;
@@ -220,11 +235,31 @@ public class AudioDeviceInventory {
mSensorUuid = sensorUuid; mSensorUuid = sensorUuid;
} }
void setModeDisabled(String mode) {
mDisabledModes.add(mode);
}
void setModeEnabled(String mode) {
mDisabledModes.remove(mode);
}
boolean isModeEnabled(String mode) {
return !mDisabledModes.contains(mode);
}
boolean isOutputOnlyModeEnabled() {
return isModeEnabled(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY);
}
boolean isDuplexModeEnabled() {
return isModeEnabled(BluetoothAdapter.AUDIO_MODE_DUPLEX);
}
DeviceInfo(int deviceType, String deviceName, String deviceAddress, DeviceInfo(int deviceType, String deviceName, String deviceAddress,
int deviceCodecFormat) { int deviceCodecFormat) {
this(deviceType, deviceName, deviceAddress, deviceCodecFormat, null); this(deviceType, deviceName, deviceAddress, deviceCodecFormat, null);
} }
DeviceInfo(int deviceType, String deviceName, String deviceAddress) {
this(deviceType, deviceName, deviceAddress, AudioSystem.AUDIO_FORMAT_DEFAULT);
}
@Override @Override
public String toString() { public String toString() {
return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType) return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType)
@@ -232,7 +267,8 @@ public class AudioDeviceInventory {
+ ") name:" + mDeviceName + ") name:" + mDeviceName
+ " addr:" + mDeviceAddress + " addr:" + mDeviceAddress
+ " codec: " + Integer.toHexString(mDeviceCodecFormat) + " codec: " + Integer.toHexString(mDeviceCodecFormat)
+ " sensorUuid: " + Objects.toString(mSensorUuid) + "]"; + " sensorUuid: " + Objects.toString(mSensorUuid)
+ " disabled modes: " + mDisabledModes + "]";
} }
@NonNull String getKey() { @NonNull String getKey() {
@@ -317,6 +353,7 @@ public class AudioDeviceInventory {
di.mDeviceCodecFormat); di.mDeviceCodecFormat);
} }
mAppliedStrategyRoles.clear(); mAppliedStrategyRoles.clear();
applyConnectedDevicesRoles_l();
} }
synchronized (mPreferredDevices) { synchronized (mPreferredDevices) {
mPreferredDevices.forEach((strategy, devices) -> { mPreferredDevices.forEach((strategy, devices) -> {
@@ -397,8 +434,7 @@ public class AudioDeviceInventory {
btInfo.mVolume * 10, btInfo.mAudioSystemDevice, btInfo.mVolume * 10, btInfo.mAudioSystemDevice,
"onSetBtActiveDevice"); "onSetBtActiveDevice");
} }
makeA2dpDeviceAvailable(address, BtHelper.getName(btInfo.mDevice), makeA2dpDeviceAvailable(btInfo, "onSetBtActiveDevice");
"onSetBtActiveDevice", btInfo.mCodec);
} }
break; break;
case BluetoothProfile.HEARING_AID: case BluetoothProfile.HEARING_AID:
@@ -414,10 +450,7 @@ public class AudioDeviceInventory {
if (switchToUnavailable) { if (switchToUnavailable) {
makeLeAudioDeviceUnavailableNow(address, btInfo.mAudioSystemDevice); makeLeAudioDeviceUnavailableNow(address, btInfo.mAudioSystemDevice);
} else if (switchToAvailable) { } else if (switchToAvailable) {
makeLeAudioDeviceAvailable(address, BtHelper.getName(btInfo.mDevice), makeLeAudioDeviceAvailable(btInfo, streamType, "onSetBtActiveDevice");
streamType, btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10,
btInfo.mAudioSystemDevice,
"onSetBtActiveDevice");
} }
break; break;
default: throw new IllegalArgumentException("Invalid profile " default: throw new IllegalArgumentException("Invalid profile "
@@ -428,30 +461,30 @@ public class AudioDeviceInventory {
@GuardedBy("AudioDeviceBroker.mDeviceStateLock") @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
/*package*/ void onBluetoothA2dpDeviceConfigChange( /*package*/ void onBluetoothDeviceConfigChange(
@NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int event) { @NonNull AudioDeviceBroker.BtDeviceInfo btInfo, int event) {
MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId
+ "onBluetoothA2dpDeviceConfigChange") + "onBluetoothDeviceConfigChange")
.set(MediaMetrics.Property.EVENT, BtHelper.a2dpDeviceEventToString(event)); .set(MediaMetrics.Property.EVENT, BtHelper.deviceEventToString(event));
final BluetoothDevice btDevice = btInfo.getBtDevice(); final BluetoothDevice btDevice = btInfo.mDevice;
if (btDevice == null) { if (btDevice == null) {
mmi.set(MediaMetrics.Property.EARLY_RETURN, "btDevice null").record(); mmi.set(MediaMetrics.Property.EARLY_RETURN, "btDevice null").record();
return; return;
} }
if (AudioService.DEBUG_DEVICES) { if (AudioService.DEBUG_DEVICES) {
Log.d(TAG, "onBluetoothA2dpDeviceConfigChange btDevice=" + btDevice); Log.d(TAG, "onBluetoothDeviceConfigChange btDevice=" + btDevice);
} }
int a2dpVolume = btInfo.getVolume(); int volume = btInfo.mVolume;
@AudioSystem.AudioFormatNativeEnumForBtCodec final int a2dpCodec = btInfo.getCodec(); @AudioSystem.AudioFormatNativeEnumForBtCodec final int audioCodec = btInfo.mCodec;
String address = btDevice.getAddress(); String address = btDevice.getAddress();
if (!BluetoothAdapter.checkBluetoothAddress(address)) { if (!BluetoothAdapter.checkBluetoothAddress(address)) {
address = ""; address = "";
} }
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"onBluetoothA2dpDeviceConfigChange addr=" + address "onBluetoothDeviceConfigChange addr=" + address
+ " event=" + BtHelper.a2dpDeviceEventToString(event))); + " event=" + BtHelper.deviceEventToString(event)));
synchronized (mDevicesLock) { synchronized (mDevicesLock) {
if (mDeviceBroker.hasScheduledA2dpConnection(btDevice)) { if (mDeviceBroker.hasScheduledA2dpConnection(btDevice)) {
@@ -466,53 +499,53 @@ public class AudioDeviceInventory {
AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
final DeviceInfo di = mConnectedDevices.get(key); final DeviceInfo di = mConnectedDevices.get(key);
if (di == null) { if (di == null) {
Log.e(TAG, "invalid null DeviceInfo in onBluetoothA2dpDeviceConfigChange"); Log.e(TAG, "invalid null DeviceInfo in onBluetoothDeviceConfigChange");
mmi.set(MediaMetrics.Property.EARLY_RETURN, "null DeviceInfo").record(); mmi.set(MediaMetrics.Property.EARLY_RETURN, "null DeviceInfo").record();
return; return;
} }
mmi.set(MediaMetrics.Property.ADDRESS, address) mmi.set(MediaMetrics.Property.ADDRESS, address)
.set(MediaMetrics.Property.ENCODING, .set(MediaMetrics.Property.ENCODING,
AudioSystem.audioFormatToString(a2dpCodec)) AudioSystem.audioFormatToString(audioCodec))
.set(MediaMetrics.Property.INDEX, a2dpVolume) .set(MediaMetrics.Property.INDEX, volume)
.set(MediaMetrics.Property.NAME, di.mDeviceName); .set(MediaMetrics.Property.NAME, di.mDeviceName);
if (event == BtHelper.EVENT_ACTIVE_DEVICE_CHANGE) {
// Device is connected if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) {
if (a2dpVolume != -1) { boolean a2dpCodecChange = false;
mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC, if (btInfo.mProfile == BluetoothProfile.A2DP) {
// convert index to internal representation in VolumeStreamState if (di.mDeviceCodecFormat != audioCodec) {
a2dpVolume * 10, di.mDeviceCodecFormat = audioCodec;
AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
"onBluetoothA2dpDeviceConfigChange");
}
} else if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) {
if (di.mDeviceCodecFormat != a2dpCodec) {
di.mDeviceCodecFormat = a2dpCodec;
mConnectedDevices.replace(key, di); mConnectedDevices.replace(key, di);
} a2dpCodecChange = true;
} }
final int res = mAudioSystem.handleDeviceConfigChange( final int res = mAudioSystem.handleDeviceConfigChange(
AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, btInfo.mAudioSystemDevice, address,
BtHelper.getName(btDevice), a2dpCodec); BtHelper.getName(btDevice), audioCodec);
if (res != AudioSystem.AUDIO_STATUS_OK) { if (res != AudioSystem.AUDIO_STATUS_OK) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"APM handleDeviceConfigChange failed for A2DP device addr=" + address "APM handleDeviceConfigChange failed for A2DP device addr="
+ " codec=" + AudioSystem.audioFormatToString(a2dpCodec)) + address + " codec="
+ AudioSystem.audioFormatToString(audioCodec))
.printLog(TAG)); .printLog(TAG));
int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC); // force A2DP device disconnection in case of error so that AudioService
// force A2DP device disconnection in case of error so that AudioService state is // state is consistent with audio policy manager state
// consistent with audio policy manager state setBluetoothActiveDevice(new AudioDeviceBroker.BtDeviceInfo(btInfo,
setBluetoothActiveDevice(new AudioDeviceBroker.BtDeviceInfo(btDevice, BluetoothProfile.STATE_DISCONNECTED));
BluetoothProfile.A2DP, BluetoothProfile.STATE_DISCONNECTED,
musicDevice, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
} else { } else {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"APM handleDeviceConfigChange success for A2DP device addr=" + address "APM handleDeviceConfigChange success for A2DP device addr="
+ " codec=" + AudioSystem.audioFormatToString(a2dpCodec)) + address
+ " codec=" + AudioSystem.audioFormatToString(audioCodec))
.printLog(TAG)); .printLog(TAG));
}
}
if (!a2dpCodecChange) {
updateBluetoothPreferredModes_l(btDevice /*connectedDevice*/);
}
} }
} }
mmi.record(); mmi.record();
@@ -595,7 +628,7 @@ public class AudioDeviceInventory {
} }
if (!handleDeviceConnection(wdcs.mAttributes, if (!handleDeviceConnection(wdcs.mAttributes,
wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED, wdcs.mForTest)) { wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED, wdcs.mForTest, null)) {
// change of connection state failed, bailout // change of connection state failed, bailout
mmi.set(MediaMetrics.Property.EARLY_RETURN, "change of connection state failed") mmi.set(MediaMetrics.Property.EARLY_RETURN, "change of connection state failed")
.record(); .record();
@@ -1134,10 +1167,11 @@ public class AudioDeviceInventory {
* @param connect true if connection * @param connect true if connection
* @param isForTesting if true, not calling AudioSystem for the connection as this is * @param isForTesting if true, not calling AudioSystem for the connection as this is
* just for testing * just for testing
* @param btDevice the corresponding Bluetooth device when relevant.
* @return false if an error was reported by AudioSystem * @return false if an error was reported by AudioSystem
*/ */
/*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, boolean connect, /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, boolean connect,
boolean isForTesting) { boolean isForTesting, @Nullable BluetoothDevice btDevice) {
int device = attributes.getInternalType(); int device = attributes.getInternalType();
String address = attributes.getAddress(); String address = attributes.getAddress();
String deviceName = attributes.getName(); String deviceName = attributes.getName();
@@ -1152,6 +1186,7 @@ public class AudioDeviceInventory {
.set(MediaMetrics.Property.MODE, connect .set(MediaMetrics.Property.MODE, connect
? MediaMetrics.Value.CONNECT : MediaMetrics.Value.DISCONNECT) ? MediaMetrics.Value.CONNECT : MediaMetrics.Value.DISCONNECT)
.set(MediaMetrics.Property.NAME, deviceName); .set(MediaMetrics.Property.NAME, deviceName);
boolean status = false;
synchronized (mDevicesLock) { synchronized (mDevicesLock) {
final String deviceKey = DeviceInfo.makeDeviceListKey(device, address); final String deviceKey = DeviceInfo.makeDeviceListKey(device, address);
if (AudioService.DEBUG_DEVICES) { if (AudioService.DEBUG_DEVICES) {
@@ -1179,25 +1214,31 @@ public class AudioDeviceInventory {
.record(); .record();
return false; return false;
} }
mConnectedDevices.put(deviceKey, new DeviceInfo( mConnectedDevices.put(deviceKey, new DeviceInfo(device, deviceName, address));
device, deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
mDeviceBroker.postAccessoryPlugMediaUnmute(device); mDeviceBroker.postAccessoryPlugMediaUnmute(device);
mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record(); status = true;
return true;
} else if (!connect && isConnected) { } else if (!connect && isConnected) {
mAudioSystem.setDeviceConnectionState(attributes, mAudioSystem.setDeviceConnectionState(attributes,
AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT);
// always remove even if disconnection failed // always remove even if disconnection failed
mConnectedDevices.remove(deviceKey); mConnectedDevices.remove(deviceKey);
purgeDevicesRoles_l(); status = true;
mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
return true;
} }
if (status) {
if (AudioSystem.isBluetoothScoDevice(device)) {
updateBluetoothPreferredModes_l(connect ? btDevice : null /*connectedDevice*/);
if (!connect) {
purgeDevicesRoles_l();
}
}
mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
} else {
Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey
+ ", deviceSpec=" + di + ", connect=" + connect); + ", deviceSpec=" + di + ", connect=" + connect);
}
mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.DISCONNECTED).record(); mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.DISCONNECTED).record();
return false; }
}
return status;
} }
@@ -1406,15 +1447,20 @@ public class AudioDeviceInventory {
// Internal utilities // Internal utilities
@GuardedBy("mDevicesLock") @GuardedBy("mDevicesLock")
private void makeA2dpDeviceAvailable(String address, String name, String eventSource, private void makeA2dpDeviceAvailable(AudioDeviceBroker.BtDeviceInfo btInfo,
int a2dpCodec) { String eventSource) {
final String address = btInfo.mDevice.getAddress();
final String name = BtHelper.getName(btInfo.mDevice);
final int a2dpCodec = btInfo.mCodec;
// enable A2DP before notifying A2DP connection to avoid unnecessary processing in // enable A2DP before notifying A2DP connection to avoid unnecessary processing in
// audio policy manager // audio policy manager
mDeviceBroker.setBluetoothA2dpOnInt(true, true /*fromA2dp*/, eventSource); mDeviceBroker.setBluetoothA2dpOnInt(true, true /*fromA2dp*/, eventSource);
// at this point there could be another A2DP device already connected in APM, but it // 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 // doesn't matter as this new one will overwrite the previous one
final int res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( AudioDeviceAttributes ada = new AudioDeviceAttributes(
AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, name), AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, name);
final int res = mAudioSystem.setDeviceConnectionState(ada,
AudioSystem.DEVICE_STATE_AVAILABLE, a2dpCodec); AudioSystem.DEVICE_STATE_AVAILABLE, a2dpCodec);
// TODO: log in MediaMetrics once distinction between connection failure and // TODO: log in MediaMetrics once distinction between connection failure and
@@ -1436,8 +1482,7 @@ public class AudioDeviceInventory {
// The convention for head tracking sensors associated with A2DP devices is to // The convention for head tracking sensors associated with A2DP devices is to
// use a UUID derived from the MAC address as follows: // use a UUID derived from the MAC address as follows:
// time_low = 0, time_mid = 0, time_hi = 0, clock_seq = 0, node = MAC Address // time_low = 0, time_mid = 0, time_hi = 0, clock_seq = 0, node = MAC Address
UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes( UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(ada);
new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name, final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
address, a2dpCodec, sensorUuid); address, a2dpCodec, sensorUuid);
final String diKey = di.getKey(); final String diKey = di.getKey();
@@ -1448,6 +1493,208 @@ public class AudioDeviceInventory {
mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
setCurrentAudioRouteNameIfPossible(name, true /*fromA2dp*/); setCurrentAudioRouteNameIfPossible(name, true /*fromA2dp*/);
updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/);
}
static final int[] CAPTURE_PRESETS = new int[] {AudioSource.MIC, AudioSource.CAMCORDER,
AudioSource.VOICE_RECOGNITION, AudioSource.VOICE_COMMUNICATION,
AudioSource.UNPROCESSED, AudioSource.VOICE_PERFORMANCE, AudioSource.HOTWORD};
// reflects system property persist.bluetooth.enable_dual_mode_audio
final boolean mBluetoothDualModeEnabled;
/**
* Goes over all connected Bluetooth devices and set the audio policy device role to DISABLED
* or not according to their own and other devices modes.
* The top priority is given to LE devices, then SCO ,then A2DP.
*/
@GuardedBy("mDevicesLock")
private void applyConnectedDevicesRoles_l() {
if (!mBluetoothDualModeEnabled) {
return;
}
DeviceInfo leOutDevice =
getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_BLE_SET);
DeviceInfo leInDevice =
getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_IN_ALL_BLE_SET);
DeviceInfo a2dpDevice =
getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_A2DP_SET);
DeviceInfo scoOutDevice =
getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_SCO_SET);
DeviceInfo scoInDevice =
getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_IN_ALL_SCO_SET);
boolean disableA2dp = (leOutDevice != null && leOutDevice.isOutputOnlyModeEnabled());
boolean disableSco = (leOutDevice != null && leOutDevice.isDuplexModeEnabled())
|| (leInDevice != null && leInDevice.isDuplexModeEnabled());
AudioDeviceAttributes communicationDevice =
mDeviceBroker.mActiveCommunicationDevice == null
? null : ((mDeviceBroker.isInCommunication()
&& mDeviceBroker.mActiveCommunicationDevice != null)
? new AudioDeviceAttributes(mDeviceBroker.mActiveCommunicationDevice)
: null);
if (AudioService.DEBUG_DEVICES) {
Log.i(TAG, "applyConnectedDevicesRoles_l\n - leOutDevice: " + leOutDevice
+ "\n - leInDevice: " + leInDevice
+ "\n - a2dpDevice: " + a2dpDevice
+ "\n - scoOutDevice: " + scoOutDevice
+ "\n - scoInDevice: " + scoInDevice
+ "\n - disableA2dp: " + disableA2dp
+ ", disableSco: " + disableSco);
}
for (DeviceInfo di : mConnectedDevices.values()) {
if (!AudioSystem.isBluetoothDevice(di.mDeviceType)) {
continue;
}
AudioDeviceAttributes ada =
new AudioDeviceAttributes(di.mDeviceType, di.mDeviceAddress, di.mDeviceName);
if (AudioService.DEBUG_DEVICES) {
Log.i(TAG, " + checking Device: " + ada);
}
if (ada.equalTypeAddress(communicationDevice)) {
continue;
}
if (AudioSystem.isBluetoothOutDevice(di.mDeviceType)) {
for (AudioProductStrategy strategy : mStrategies) {
boolean disable = false;
if (strategy.getId() == mDeviceBroker.mCommunicationStrategyId) {
if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) {
disable = disableSco || !di.isDuplexModeEnabled();
} else if (AudioSystem.isBluetoothLeDevice(di.mDeviceType)) {
disable = !di.isDuplexModeEnabled();
}
} else {
if (AudioSystem.isBluetoothA2dpOutDevice(di.mDeviceType)) {
disable = disableA2dp || !di.isOutputOnlyModeEnabled();
} else if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) {
disable = disableSco || !di.isOutputOnlyModeEnabled();
} else if (AudioSystem.isBluetoothLeDevice(di.mDeviceType)) {
disable = !di.isOutputOnlyModeEnabled();
}
}
if (AudioService.DEBUG_DEVICES) {
Log.i(TAG, " - strategy: " + strategy.getId()
+ ", disable: " + disable);
}
if (disable) {
addDevicesRoleForStrategy(strategy.getId(),
AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
} else {
removeDevicesRoleForStrategy(strategy.getId(),
AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
}
}
}
if (AudioSystem.isBluetoothInDevice(di.mDeviceType)) {
for (int capturePreset : CAPTURE_PRESETS) {
boolean disable = false;
if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) {
disable = disableSco || !di.isDuplexModeEnabled();
} else if (AudioSystem.isBluetoothLeDevice(di.mDeviceType)) {
disable = !di.isDuplexModeEnabled();
}
if (AudioService.DEBUG_DEVICES) {
Log.i(TAG, " - capturePreset: " + capturePreset
+ ", disable: " + disable);
}
if (disable) {
addDevicesRoleForCapturePreset(capturePreset,
AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
} else {
removeDevicesRoleForCapturePreset(capturePreset,
AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
}
}
}
}
}
/* package */ void applyConnectedDevicesRoles() {
synchronized (mDevicesLock) {
applyConnectedDevicesRoles_l();
}
}
@GuardedBy("mDevicesLock")
int checkProfileIsConnected(int profile) {
switch (profile) {
case BluetoothProfile.HEADSET:
if (getFirstConnectedDeviceOfTypes(
AudioSystem.DEVICE_OUT_ALL_SCO_SET) != null
|| getFirstConnectedDeviceOfTypes(
AudioSystem.DEVICE_IN_ALL_SCO_SET) != null) {
return profile;
}
break;
case BluetoothProfile.A2DP:
if (getFirstConnectedDeviceOfTypes(
AudioSystem.DEVICE_OUT_ALL_A2DP_SET) != null) {
return profile;
}
break;
case BluetoothProfile.LE_AUDIO:
case BluetoothProfile.LE_AUDIO_BROADCAST:
if (getFirstConnectedDeviceOfTypes(
AudioSystem.DEVICE_OUT_ALL_BLE_SET) != null
|| getFirstConnectedDeviceOfTypes(
AudioSystem.DEVICE_IN_ALL_BLE_SET) != null) {
return profile;
}
break;
default:
break;
}
return 0;
}
@GuardedBy("mDevicesLock")
private void updateBluetoothPreferredModes_l(BluetoothDevice connectedDevice) {
if (!mBluetoothDualModeEnabled) {
return;
}
HashSet<String> processedAddresses = new HashSet<>(0);
for (DeviceInfo di : mConnectedDevices.values()) {
if (!AudioSystem.isBluetoothDevice(di.mDeviceType)
|| processedAddresses.contains(di.mDeviceAddress)) {
continue;
}
Bundle preferredProfiles = BtHelper.getPreferredAudioProfiles(di.mDeviceAddress);
if (AudioService.DEBUG_DEVICES) {
Log.i(TAG, "updateBluetoothPreferredModes_l processing device address: "
+ di.mDeviceAddress + ", preferredProfiles: " + preferredProfiles);
}
for (DeviceInfo di2 : mConnectedDevices.values()) {
if (!AudioSystem.isBluetoothDevice(di2.mDeviceType)
|| !di.mDeviceAddress.equals(di2.mDeviceAddress)) {
continue;
}
int profile = BtHelper.getProfileFromType(di2.mDeviceType);
if (profile == 0) {
continue;
}
int preferredProfile = checkProfileIsConnected(
preferredProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX));
if (preferredProfile == profile || preferredProfile == 0) {
di2.setModeEnabled(BluetoothAdapter.AUDIO_MODE_DUPLEX);
} else {
di2.setModeDisabled(BluetoothAdapter.AUDIO_MODE_DUPLEX);
}
preferredProfile = checkProfileIsConnected(
preferredProfiles.getInt(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY));
if (preferredProfile == profile || preferredProfile == 0) {
di2.setModeEnabled(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY);
} else {
di2.setModeDisabled(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY);
}
}
processedAddresses.add(di.mDeviceAddress);
}
applyConnectedDevicesRoles_l();
if (connectedDevice != null) {
mDeviceBroker.postNotifyPreferredAudioProfileApplied(connectedDevice);
}
} }
@GuardedBy("mDevicesLock") @GuardedBy("mDevicesLock")
@@ -1495,6 +1742,7 @@ public class AudioDeviceInventory {
// Remove A2DP routes as well // Remove A2DP routes as well
setCurrentAudioRouteNameIfPossible(null, true /*fromA2dp*/); setCurrentAudioRouteNameIfPossible(null, true /*fromA2dp*/);
mmi.record(); mmi.record();
updateBluetoothPreferredModes_l(null /*connectedDevice*/);
purgeDevicesRoles_l(); purgeDevicesRoles_l();
} }
@@ -1525,8 +1773,7 @@ public class AudioDeviceInventory {
AudioSystem.AUDIO_FORMAT_DEFAULT); AudioSystem.AUDIO_FORMAT_DEFAULT);
mConnectedDevices.put( mConnectedDevices.put(
DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address), DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "", new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "", address));
address, AudioSystem.AUDIO_FORMAT_DEFAULT));
} }
@GuardedBy("mDevicesLock") @GuardedBy("mDevicesLock")
@@ -1552,8 +1799,7 @@ public class AudioDeviceInventory {
AudioSystem.AUDIO_FORMAT_DEFAULT); AudioSystem.AUDIO_FORMAT_DEFAULT);
mConnectedDevices.put( mConnectedDevices.put(
DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address), DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address),
new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name, new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name, address));
address, AudioSystem.AUDIO_FORMAT_DEFAULT));
mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_HEARING_AID); mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_HEARING_AID);
mDeviceBroker.postApplyVolumeOnDevice(streamType, mDeviceBroker.postApplyVolumeOnDevice(streamType,
AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable"); AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable");
@@ -1591,29 +1837,56 @@ public class AudioDeviceInventory {
* @return true if a DEVICE_OUT_HEARING_AID is connected, false otherwise. * @return true if a DEVICE_OUT_HEARING_AID is connected, false otherwise.
*/ */
boolean isHearingAidConnected() { boolean isHearingAidConnected() {
return getFirstConnectedDeviceOfTypes(
Sets.newHashSet(AudioSystem.DEVICE_OUT_HEARING_AID)) != null;
}
/**
* Returns a DeviceInfo for the first connected device matching one of the supplied types
*/
private DeviceInfo getFirstConnectedDeviceOfTypes(Set<Integer> internalTypes) {
List<DeviceInfo> devices = getConnectedDevicesOfTypes(internalTypes);
return devices.isEmpty() ? null : devices.get(0);
}
/**
* Returns a list of connected devices matching one of the supplied types
*/
private List<DeviceInfo> getConnectedDevicesOfTypes(Set<Integer> internalTypes) {
ArrayList<DeviceInfo> devices = new ArrayList<>();
synchronized (mDevicesLock) { synchronized (mDevicesLock) {
for (DeviceInfo di : mConnectedDevices.values()) { for (DeviceInfo di : mConnectedDevices.values()) {
if (di.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) { if (internalTypes.contains(di.mDeviceType)) {
return true; devices.add(di);
} }
} }
return false;
} }
return devices;
}
/* package */ AudioDeviceAttributes getDeviceOfType(int type) {
DeviceInfo di = getFirstConnectedDeviceOfTypes(Sets.newHashSet(type));
return di == null ? null : new AudioDeviceAttributes(
di.mDeviceType, di.mDeviceAddress, di.mDeviceName);
} }
@GuardedBy("mDevicesLock") @GuardedBy("mDevicesLock")
private void makeLeAudioDeviceAvailable(String address, String name, int streamType, private void makeLeAudioDeviceAvailable(
int volumeIndex, int device, String eventSource) { AudioDeviceBroker.BtDeviceInfo btInfo, int streamType, String eventSource) {
final String address = btInfo.mDevice.getAddress();
final String name = BtHelper.getName(btInfo.mDevice);
final int volumeIndex = btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10;
final int device = btInfo.mAudioSystemDevice;
if (device != AudioSystem.DEVICE_NONE) { if (device != AudioSystem.DEVICE_NONE) {
/* Audio Policy sees Le Audio similar to A2DP. Let's make sure /* Audio Policy sees Le Audio similar to A2DP. Let's make sure
* AUDIO_POLICY_FORCE_NO_BT_A2DP is not set * AUDIO_POLICY_FORCE_NO_BT_A2DP is not set
*/ */
mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, eventSource); mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, eventSource);
final int res = AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( AudioDeviceAttributes ada = new AudioDeviceAttributes(device, address, name);
device, address, name), final int res = AudioSystem.setDeviceConnectionState(ada,
AudioSystem.DEVICE_STATE_AVAILABLE, AudioSystem.DEVICE_STATE_AVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT);
AudioSystem.AUDIO_FORMAT_DEFAULT);
if (res != AudioSystem.AUDIO_STATUS_OK) { if (res != AudioSystem.AUDIO_STATUS_OK) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"APM failed to make available LE Audio device addr=" + address "APM failed to make available LE Audio device addr=" + address
@@ -1624,12 +1897,13 @@ public class AudioDeviceInventory {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"LE Audio device addr=" + address + " now available").printLog(TAG)); "LE Audio device addr=" + address + " now available").printLog(TAG));
} }
// Reset LEA suspend state each time a new sink is connected // Reset LEA suspend state each time a new sink is connected
mDeviceBroker.clearLeAudioSuspended(); mDeviceBroker.clearLeAudioSuspended();
UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(ada);
mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address), mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address),
new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT)); new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT,
sensorUuid));
mDeviceBroker.postAccessoryPlugMediaUnmute(device); mDeviceBroker.postAccessoryPlugMediaUnmute(device);
setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false); setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false);
} }
@@ -1645,6 +1919,8 @@ public class AudioDeviceInventory {
final int maxIndex = mDeviceBroker.getMaxVssVolumeForStream(streamType); final int maxIndex = mDeviceBroker.getMaxVssVolumeForStream(streamType);
mDeviceBroker.postSetLeAudioVolumeIndex(leAudioVolIndex, maxIndex, streamType); mDeviceBroker.postSetLeAudioVolumeIndex(leAudioVolIndex, maxIndex, streamType);
mDeviceBroker.postApplyVolumeOnDevice(streamType, device, "makeLeAudioDeviceAvailable"); mDeviceBroker.postApplyVolumeOnDevice(streamType, device, "makeLeAudioDeviceAvailable");
updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/);
} }
@GuardedBy("mDevicesLock") @GuardedBy("mDevicesLock")
@@ -1669,6 +1945,7 @@ public class AudioDeviceInventory {
} }
setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/); setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/);
updateBluetoothPreferredModes_l(null /*connectedDevice*/);
purgeDevicesRoles_l(); purgeDevicesRoles_l();
} }
@@ -2005,18 +2282,6 @@ public class AudioDeviceInventory {
} }
} }
/* package */ AudioDeviceAttributes getDeviceOfType(int type) {
synchronized (mDevicesLock) {
for (DeviceInfo di : mConnectedDevices.values()) {
if (di.mDeviceType == type) {
return new AudioDeviceAttributes(
di.mDeviceType, di.mDeviceAddress, di.mDeviceName);
}
}
}
return null;
}
//---------------------------------------------------------- //----------------------------------------------------------
// For tests only // For tests only
@@ -2027,10 +2292,12 @@ public class AudioDeviceInventory {
*/ */
@VisibleForTesting @VisibleForTesting
public boolean isA2dpDeviceConnected(@NonNull BluetoothDevice device) { public boolean isA2dpDeviceConnected(@NonNull BluetoothDevice device) {
final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, for (DeviceInfo di : getConnectedDevicesOfTypes(
device.getAddress()); Sets.newHashSet(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP))) {
synchronized (mDevicesLock) { if (di.mDeviceAddress.equals(device.getAddress())) {
return (mConnectedDevices.get(key) != null); return true;
} }
} }
return false;
}
} }

View File

@@ -33,6 +33,7 @@ import android.media.AudioManager;
import android.media.AudioSystem; import android.media.AudioSystem;
import android.media.BluetoothProfileConnectionInfo; import android.media.BluetoothProfileConnectionInfo;
import android.os.Binder; import android.os.Binder;
import android.os.Bundle;
import android.os.UserHandle; import android.os.UserHandle;
import android.provider.Settings; import android.provider.Settings;
import android.text.TextUtils; import android.text.TextUtils;
@@ -150,60 +151,12 @@ public class BtHelper {
} }
} }
//----------------------------------------------------------------------
/*package*/ static class BluetoothA2dpDeviceInfo {
private final @NonNull BluetoothDevice mBtDevice;
private final int mVolume;
private final @AudioSystem.AudioFormatNativeEnumForBtCodec int mCodec;
BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice) {
this(btDevice, -1, AudioSystem.AUDIO_FORMAT_DEFAULT);
}
BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice, int volume, int codec) {
mBtDevice = btDevice;
mVolume = volume;
mCodec = codec;
}
public @NonNull BluetoothDevice getBtDevice() {
return mBtDevice;
}
public int getVolume() {
return mVolume;
}
public @AudioSystem.AudioFormatNativeEnumForBtCodec int getCodec() {
return mCodec;
}
// redefine equality op so we can match messages intended for this device
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (this == o) {
return true;
}
if (o instanceof BluetoothA2dpDeviceInfo) {
return mBtDevice.equals(((BluetoothA2dpDeviceInfo) o).getBtDevice());
}
return false;
}
}
// A2DP device events // A2DP device events
/*package*/ static final int EVENT_DEVICE_CONFIG_CHANGE = 0; /*package*/ static final int EVENT_DEVICE_CONFIG_CHANGE = 0;
/*package*/ static final int EVENT_ACTIVE_DEVICE_CHANGE = 1;
/*package*/ static String a2dpDeviceEventToString(int event) { /*package*/ static String deviceEventToString(int event) {
switch (event) { switch (event) {
case EVENT_DEVICE_CONFIG_CHANGE: return "DEVICE_CONFIG_CHANGE"; case EVENT_DEVICE_CONFIG_CHANGE: return "DEVICE_CONFIG_CHANGE";
case EVENT_ACTIVE_DEVICE_CHANGE: return "ACTIVE_DEVICE_CHANGE";
default: default:
return new String("invalid event:" + event); return new String("invalid event:" + event);
} }
@@ -620,11 +573,12 @@ public class BtHelper {
return btHeadsetDeviceToAudioDevice(mBluetoothHeadsetDevice); return btHeadsetDeviceToAudioDevice(mBluetoothHeadsetDevice);
} }
private AudioDeviceAttributes btHeadsetDeviceToAudioDevice(BluetoothDevice btDevice) { private static AudioDeviceAttributes btHeadsetDeviceToAudioDevice(BluetoothDevice btDevice) {
if (btDevice == null) { if (btDevice == null) {
return new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""); return new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, "");
} }
String address = btDevice.getAddress(); String address = btDevice.getAddress();
String name = getName(btDevice);
if (!BluetoothAdapter.checkBluetoothAddress(address)) { if (!BluetoothAdapter.checkBluetoothAddress(address)) {
address = ""; address = "";
} }
@@ -646,7 +600,7 @@ public class BtHelper {
+ " btClass: " + (btClass == null ? "Unknown" : btClass) + " btClass: " + (btClass == null ? "Unknown" : btClass)
+ " nativeType: " + nativeType + " address: " + address); + " nativeType: " + nativeType + " address: " + address);
} }
return new AudioDeviceAttributes(nativeType, address); return new AudioDeviceAttributes(nativeType, address, name);
} }
private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) { private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) {
@@ -655,12 +609,9 @@ public class BtHelper {
} }
int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET; int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET;
AudioDeviceAttributes audioDevice = btHeadsetDeviceToAudioDevice(btDevice); AudioDeviceAttributes audioDevice = btHeadsetDeviceToAudioDevice(btDevice);
String btDeviceName = getName(btDevice);
boolean result = false; boolean result = false;
if (isActive) { if (isActive) {
result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes( result |= mDeviceBroker.handleDeviceConnection(audioDevice, isActive, btDevice);
audioDevice.getInternalType(), audioDevice.getAddress(), btDeviceName),
isActive);
} else { } else {
int[] outDeviceTypes = { int[] outDeviceTypes = {
AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, AudioSystem.DEVICE_OUT_BLUETOOTH_SCO,
@@ -669,14 +620,14 @@ public class BtHelper {
}; };
for (int outDeviceType : outDeviceTypes) { for (int outDeviceType : outDeviceTypes) {
result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes( result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes(
outDeviceType, audioDevice.getAddress(), btDeviceName), outDeviceType, audioDevice.getAddress(), audioDevice.getName()),
isActive); isActive, btDevice);
} }
} }
// handleDeviceConnection() && result to make sure the method get executed // handleDeviceConnection() && result to make sure the method get executed
result = mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes( result = mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes(
inDevice, audioDevice.getAddress(), btDeviceName), inDevice, audioDevice.getAddress(), audioDevice.getName()),
isActive) && result; isActive, btDevice) && result;
return result; return result;
} }
@@ -973,6 +924,30 @@ public class BtHelper {
} }
} }
/*package */ static int getProfileFromType(int deviceType) {
if (AudioSystem.isBluetoothA2dpOutDevice(deviceType)) {
return BluetoothProfile.A2DP;
} else if (AudioSystem.isBluetoothScoDevice(deviceType)) {
return BluetoothProfile.HEADSET;
} else if (AudioSystem.isBluetoothLeDevice(deviceType)) {
return BluetoothProfile.LE_AUDIO;
}
return 0; // 0 is not a valid profile
}
/*package */ static Bundle getPreferredAudioProfiles(String address) {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
return adapter.getPreferredAudioProfiles(adapter.getRemoteDevice(address));
}
/**
* Notifies Bluetooth framework that new preferred audio profiles for Bluetooth devices
* have been applied.
*/
public static void onNotifyPreferredAudioProfileApplied(BluetoothDevice btDevice) {
BluetoothAdapter.getDefaultAdapter().notifyActiveDeviceChangeApplied(btDevice);
}
/** /**
* Returns the string equivalent for the btDeviceClass class. * Returns the string equivalent for the btDeviceClass class.
*/ */