Loading core/java/android/bluetooth/BluetoothLeAudio.java +136 −6 Original line number Diff line number Diff line Loading @@ -65,9 +65,6 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. * * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to * receive. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED = Loading @@ -82,15 +79,83 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { * be null if no device is active. </li> * </ul> * * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to * receive. * * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED = "android.bluetooth.action.LE_AUDIO_ACTIVE_DEVICE_CHANGED"; /** * Intent used to broadcast group node status information. * * <p>This intent will have 3 extra: * <ul> * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can * be null if no device is active. </li> * <li> {@link #EXTRA_LE_AUDIO_GROUP_ID} - Group id. </li> * <li> {@link #EXTRA_LE_AUDIO_GROUP_NODE_STATUS} - Group node status. </li> * </ul> * * @hide */ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_LE_AUDIO_GROUP_NODE_STATUS_CHANGED = "android.bluetooth.action.LE_AUDIO_GROUP_NODE_STATUS_CHANGED"; /** * Intent used to broadcast group status information. * * <p>This intent will have 4 extra: * <ul> * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can * be null if no device is active. </li> * <li> {@link #EXTRA_LE_AUDIO_GROUP_ID} - Group id. </li> * <li> {@link #EXTRA_LE_AUDIO_GROUP_STATUS} - Group status. </li> * </ul> * * @hide */ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_LE_AUDIO_GROUP_STATUS_CHANGED = "android.bluetooth.action.LE_AUDIO_GROUP_STATUS_CHANGED"; /** * Intent used to broadcast group audio configuration changed information. * * <p>This intent will have 5 extra: * <ul> * <li> {@link #EXTRA_LE_AUDIO_GROUP_ID} - Group id. </li> * <li> {@link #EXTRA_LE_AUDIO_DIRECTION} - Direction as bit mask. </li> * <li> {@link #EXTRA_LE_AUDIO_SINK_LOCATION} - Sink location as per Bluetooth Assigned * Numbers </li> * <li> {@link #EXTRA_LE_AUDIO_SOURCE_LOCATION} - Source location as per Bluetooth Assigned * Numbers </li> * <li> {@link #EXTRA_LE_AUDIO_AVAILABLE_CONTEXTS} - Available contexts for group as per * Bluetooth Assigned Numbers </li> * </ul> * * @hide */ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_LE_AUDIO_CONF_CHANGED = "android.bluetooth.action.LE_AUDIO_CONF_CHANGED"; /** * Indicates conversation between humans as, for example, in telephony or video calls. * @hide */ public static final int CONTEXT_TYPE_COMMUNICATION = 0x0002; /** * Indicates media as, for example, in music, public radio, podcast or video soundtrack. * @hide */ public static final int CONTEXT_TYPE_MEDIA = 0x0004; /** * This represents an invalid group ID. * Loading @@ -98,6 +163,71 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { */ public static final int GROUP_ID_INVALID = IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID; /** * Contains group id. * @hide */ public static final String EXTRA_LE_AUDIO_GROUP_ID = "android.bluetooth.extra.LE_AUDIO_GROUP_ID"; /** * Contains group node status, can be any of * <p> * <ul> * <li> {@link #GROUP_NODE_ADDED} </li> * <li> {@link #GROUP_NODE_REMOVED} </li> * </ul> * <p> * @hide */ public static final String EXTRA_LE_AUDIO_GROUP_NODE_STATUS = "android.bluetooth.extra.LE_AUDIO_GROUP_NODE_STATUS"; /** * Contains group status, can be any of * * <p> * <ul> * <li> {@link #GROUP_STATUS_IDLE} </li> * <li> {@link #GROUP_STATUS_STREAMING} </li> * <li> {@link #GROUP_STATUS_SUSPENDED} </li> * <li> {@link #GROUP_STATUS_RECONFIGURED} </li> * <li> {@link #GROUP_STATUS_DESTROYED} </li> * </ul> * <p> * @hide */ public static final String EXTRA_LE_AUDIO_GROUP_STATUS = "android.bluetooth.extra.LE_AUDIO_GROUP_STATUS"; /** * Contains bit mask for direction, bit 0 set when Sink, bit 1 set when Source. * @hide */ public static final String EXTRA_LE_AUDIO_DIRECTION = "android.bluetooth.extra.LE_AUDIO_DIRECTION"; /** * Contains source location as per Bluetooth Assigned Numbers * @hide */ public static final String EXTRA_LE_AUDIO_SOURCE_LOCATION = "android.bluetooth.extra.LE_AUDIO_SOURCE_LOCATION"; /** * Contains sink location as per Bluetooth Assigned Numbers * @hide */ public static final String EXTRA_LE_AUDIO_SINK_LOCATION = "android.bluetooth.extra.LE_AUDIO_SINK_LOCATION"; /** * Contains available context types for group as per Bluetooth Assigned Numbers * @hide */ public static final String EXTRA_LE_AUDIO_AVAILABLE_CONTEXTS = "android.bluetooth.extra.LE_AUDIO_AVAILABLE_CONTEXTS"; private BluetoothAdapter mAdapter; private final BluetoothProfileConnector<IBluetoothLeAudio> mProfileConnector = new BluetoothProfileConnector(this, BluetoothProfile.LE_AUDIO, "BluetoothLeAudio", Loading media/java/android/media/AudioManager.java +34 −0 Original line number Diff line number Diff line Loading @@ -5273,6 +5273,40 @@ public class AudioManager { } } /** * Indicate Le Audio output device connection state change and eventually suppress * the {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} intent. * @param device Bluetooth device connected/disconnected * @param state new connection state (BluetoothProfile.STATE_xxx) * @param suppressNoisyIntent if true the * {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} intent will not be sent. * {@hide} */ public void setBluetoothLeAudioOutDeviceConnectionState(BluetoothDevice device, int state, boolean suppressNoisyIntent) { final IAudioService service = getService(); try { service.setBluetoothLeAudioOutDeviceConnectionState(device, state, suppressNoisyIntent); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Indicate Le Audio input connection state change. * @param device Bluetooth device connected/disconnected * @param state new connection state (BluetoothProfile.STATE_xxx) * {@hide} */ public void setBluetoothLeAudioInDeviceConnectionState(BluetoothDevice device, int state) { final IAudioService service = getService(); try { service.setBluetoothLeAudioInDeviceConnectionState(device, state); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Indicate A2DP source or sink connection state change and eventually suppress * the {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} intent. Loading media/java/android/media/IAudioService.aidl +5 −0 Original line number Diff line number Diff line Loading @@ -256,6 +256,11 @@ interface IAudioService { void setBluetoothHearingAidDeviceConnectionState(in BluetoothDevice device, int state, boolean suppressNoisyIntent, int musicDevice); void setBluetoothLeAudioOutDeviceConnectionState(in BluetoothDevice device, int state, boolean suppressNoisyIntent); void setBluetoothLeAudioInDeviceConnectionState(in BluetoothDevice device, int state); void setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(in BluetoothDevice device, int state, int profile, boolean suppressNoisyIntent, int a2dpVolume); Loading services/core/java/com/android/server/audio/AudioDeviceBroker.java +94 −1 Original line number Diff line number Diff line Loading @@ -563,6 +563,37 @@ import java.util.concurrent.atomic.AtomicBoolean; sendLMsgNoDelay(MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT, SENDMSG_QUEUE, info); } private static final class LeAudioDeviceConnectionInfo { final @NonNull BluetoothDevice mDevice; final @AudioService.BtProfileConnectionState int mState; final boolean mSupprNoisy; final @NonNull String mEventSource; LeAudioDeviceConnectionInfo(@NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, boolean suppressNoisyIntent, @NonNull String eventSource) { mDevice = device; mState = state; mSupprNoisy = suppressNoisyIntent; mEventSource = eventSource; } } /*package*/ void postBluetoothLeAudioOutDeviceConnectionState( @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, boolean suppressNoisyIntent, @NonNull String eventSource) { final LeAudioDeviceConnectionInfo info = new LeAudioDeviceConnectionInfo( device, state, suppressNoisyIntent, eventSource); sendLMsgNoDelay(MSG_L_LE_AUDIO_DEVICE_OUT_CONNECTION_CHANGE_EXT, SENDMSG_QUEUE, info); } /*package*/ void postBluetoothLeAudioInDeviceConnectionState( @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, @NonNull String eventSource) { final LeAudioDeviceConnectionInfo info = new LeAudioDeviceConnectionInfo( device, state, false, eventSource); sendLMsgNoDelay(MSG_L_LE_AUDIO_DEVICE_IN_CONNECTION_CHANGE_EXT, SENDMSG_QUEUE, info); } /** * Current Bluetooth SCO audio active state indicated by BtHelper via setBluetoothScoOn(). Loading Loading @@ -849,6 +880,23 @@ import java.util.concurrent.atomic.AtomicBoolean; delay); } /*package*/ void postSetLeAudioOutConnectionState( @AudioService.BtProfileConnectionState int state, @NonNull BluetoothDevice device, int delay) { sendILMsg(MSG_IL_SET_LE_AUDIO_OUT_CONNECTION_STATE, SENDMSG_QUEUE, state, device, delay); } /*package*/ void postSetLeAudioInConnectionState( @AudioService.BtProfileConnectionState int state, @NonNull BluetoothDevice device) { sendILMsgNoDelay(MSG_IL_SET_LE_AUDIO_IN_CONNECTION_STATE, SENDMSG_QUEUE, state, device); } /*package*/ void postDisconnectA2dp() { sendMsgNoDelay(MSG_DISCONNECT_A2DP, SENDMSG_QUEUE); } Loading Loading @@ -1154,7 +1202,20 @@ import java.util.concurrent.atomic.AtomicBoolean; synchronized (mDeviceStateLock) { mDeviceInventory.onSetHearingAidConnectionState( (BluetoothDevice) msg.obj, msg.arg1, mAudioService.getHearingAidStreamType()); mAudioService.getBluetoothContextualVolumeStream()); } break; case MSG_IL_SET_LE_AUDIO_OUT_CONNECTION_STATE: synchronized (mDeviceStateLock) { mDeviceInventory.onSetLeAudioOutConnectionState( (BluetoothDevice) msg.obj, msg.arg1, mAudioService.getBluetoothContextualVolumeStream()); } break; case MSG_IL_SET_LE_AUDIO_IN_CONNECTION_STATE: synchronized (mDeviceStateLock) { mDeviceInventory.onSetLeAudioInConnectionState( (BluetoothDevice) msg.obj, msg.arg1); } break; case MSG_BT_HEADSET_CNCT_FAILED: Loading Loading @@ -1343,6 +1404,31 @@ import java.util.concurrent.atomic.AtomicBoolean; final int capturePreset = msg.arg1; mDeviceInventory.onSaveClearPreferredDevicesForCapturePreset(capturePreset); } break; case MSG_L_LE_AUDIO_DEVICE_OUT_CONNECTION_CHANGE_EXT: { final LeAudioDeviceConnectionInfo info = (LeAudioDeviceConnectionInfo) msg.obj; AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( "setLeAudioDeviceOutConnectionState state=" + info.mState + " addr=" + info.mDevice.getAddress() + " supprNoisy=" + info.mSupprNoisy + " src=" + info.mEventSource)).printLog(TAG)); synchronized (mDeviceStateLock) { mDeviceInventory.setBluetoothLeAudioOutDeviceConnectionState( info.mDevice, info.mState, info.mSupprNoisy); } } break; case MSG_L_LE_AUDIO_DEVICE_IN_CONNECTION_CHANGE_EXT: { final LeAudioDeviceConnectionInfo info = (LeAudioDeviceConnectionInfo) msg.obj; AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( "setLeAudioDeviceInConnectionState state=" + info.mState + " addr=" + info.mDevice.getAddress() + " src=" + info.mEventSource)).printLog(TAG)); synchronized (mDeviceStateLock) { mDeviceInventory.setBluetoothLeAudioInDeviceConnectionState(info.mDevice, info.mState); } } break; default: Log.wtf(TAG, "Invalid message " + msg.what); } Loading Loading @@ -1424,6 +1510,11 @@ import java.util.concurrent.atomic.AtomicBoolean; private static final int MSG_IL_SET_PREF_DEVICES_FOR_STRATEGY = 40; private static final int MSG_I_REMOVE_PREF_DEVICES_FOR_STRATEGY = 41; private static final int MSG_IL_SET_LE_AUDIO_OUT_CONNECTION_STATE = 42; private static final int MSG_IL_SET_LE_AUDIO_IN_CONNECTION_STATE = 43; private static final int MSG_L_LE_AUDIO_DEVICE_OUT_CONNECTION_CHANGE_EXT = 44; private static final int MSG_L_LE_AUDIO_DEVICE_IN_CONNECTION_CHANGE_EXT = 45; private static boolean isMessageHandledUnderWakelock(int msgId) { switch(msgId) { case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: Loading @@ -1439,6 +1530,8 @@ import java.util.concurrent.atomic.AtomicBoolean; case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION: case MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT: case MSG_CHECK_MUTE_MUSIC: case MSG_L_LE_AUDIO_DEVICE_OUT_CONNECTION_CHANGE_EXT: case MSG_L_LE_AUDIO_DEVICE_IN_CONNECTION_CHANGE_EXT: return true; default: return false; Loading services/core/java/com/android/server/audio/AudioDeviceInventory.java +93 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHearingAid; import android.bluetooth.BluetoothLeAudio; import android.bluetooth.BluetoothProfile; import android.content.Intent; import android.media.AudioDeviceAttributes; Loading Loading @@ -423,6 +424,45 @@ public class AudioDeviceInventory { } } /*package*/ void onSetLeAudioConnectionState(BluetoothDevice btDevice, @AudioService.BtProfileConnectionState int state, int streamType, int device) { String address = btDevice.getAddress(); if (!BluetoothAdapter.checkBluetoothAddress(address)) { address = ""; } AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( "onSetLeAudioConnectionState addr=" + address)); synchronized (mDevicesLock) { DeviceInfo di = null; boolean isConnected = false; String key = DeviceInfo.makeDeviceListKey(device, btDevice.getAddress()); di = mConnectedDevices.get(key); isConnected = di != null; if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { makeLeAudioDeviceUnavailable(address, device); } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { makeLeAudioDeviceAvailable(address, BtHelper.getName(btDevice), streamType, device, "onSetLeAudioConnectionState"); } } } /*package*/ void onSetLeAudioOutConnectionState(BluetoothDevice btDevice, @AudioService.BtProfileConnectionState int state, int streamType) { // TODO: b/198610537 clarify DEVICE_OUT_BLE_HEADSET vs DEVICE_OUT_BLE_SPEAKER criteria onSetLeAudioConnectionState(btDevice, state, streamType, AudioSystem.DEVICE_OUT_BLE_HEADSET); } /*package*/ void onSetLeAudioInConnectionState(BluetoothDevice btDevice, @AudioService.BtProfileConnectionState int state) { onSetLeAudioConnectionState(btDevice, state, AudioSystem.STREAM_DEFAULT, AudioSystem.DEVICE_IN_BLE_HEADSET); } @GuardedBy("AudioDeviceBroker.mDeviceStateLock") /*package*/ void onBluetoothA2dpActiveDeviceChange( @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int event) { Loading Loading @@ -943,6 +983,28 @@ public class AudioDeviceInventory { } } /*package*/ int setBluetoothLeAudioOutDeviceConnectionState( @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, boolean suppressNoisyIntent) { synchronized (mDevicesLock) { /* Active device become null and it's previous device is not connected anymore */ int delay = 0; if (!suppressNoisyIntent) { int intState = (state == BluetoothLeAudio.STATE_CONNECTED) ? 1 : 0; delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_BLE_HEADSET, intState, AudioSystem.DEVICE_NONE); } mDeviceBroker.postSetLeAudioOutConnectionState(state, device, delay); return delay; } } /*package*/ void setBluetoothLeAudioInDeviceConnectionState( @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state) { synchronized (mDevicesLock) { mDeviceBroker.postSetLeAudioInConnectionState(state, device); } } //------------------------------------------------------------------- // Internal utilities Loading Loading @@ -1114,6 +1176,36 @@ public class AudioDeviceInventory { .record(); } @GuardedBy("mDevicesLock") private void makeLeAudioDeviceAvailable(String address, String name, int streamType, int device, String eventSource) { if (device != AudioSystem.DEVICE_NONE) { AudioSystem.setDeviceConnectionState(device, AudioSystem.DEVICE_STATE_AVAILABLE, address, name, AudioSystem.AUDIO_FORMAT_DEFAULT); mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address), new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT)); mDeviceBroker.postAccessoryPlugMediaUnmute(device); } if (streamType == AudioSystem.STREAM_DEFAULT) { // No need to update volume for input devices return; } mDeviceBroker.postApplyVolumeOnDevice(streamType, device, "makeLeAudioDeviceAvailable"); } @GuardedBy("mDevicesLock") private void makeLeAudioDeviceUnavailable(String address, int device) { if (device != AudioSystem.DEVICE_NONE) { AudioSystem.setDeviceConnectionState(device, AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", AudioSystem.AUDIO_FORMAT_DEFAULT); mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address)); } setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/); } @GuardedBy("mDevicesLock") private void setCurrentAudioRouteNameIfPossible(String name, boolean fromA2dp) { synchronized (mCurAudioRoutes) { Loading Loading @@ -1150,6 +1242,7 @@ public class AudioDeviceInventory { BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_HEARING_AID); BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_A2DP_SET); BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_USB_SET); BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_BLE_SET); } // must be called before removing the device from mConnectedDevices Loading Loading
core/java/android/bluetooth/BluetoothLeAudio.java +136 −6 Original line number Diff line number Diff line Loading @@ -65,9 +65,6 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. * * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to * receive. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED = Loading @@ -82,15 +79,83 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { * be null if no device is active. </li> * </ul> * * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to * receive. * * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED = "android.bluetooth.action.LE_AUDIO_ACTIVE_DEVICE_CHANGED"; /** * Intent used to broadcast group node status information. * * <p>This intent will have 3 extra: * <ul> * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can * be null if no device is active. </li> * <li> {@link #EXTRA_LE_AUDIO_GROUP_ID} - Group id. </li> * <li> {@link #EXTRA_LE_AUDIO_GROUP_NODE_STATUS} - Group node status. </li> * </ul> * * @hide */ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_LE_AUDIO_GROUP_NODE_STATUS_CHANGED = "android.bluetooth.action.LE_AUDIO_GROUP_NODE_STATUS_CHANGED"; /** * Intent used to broadcast group status information. * * <p>This intent will have 4 extra: * <ul> * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can * be null if no device is active. </li> * <li> {@link #EXTRA_LE_AUDIO_GROUP_ID} - Group id. </li> * <li> {@link #EXTRA_LE_AUDIO_GROUP_STATUS} - Group status. </li> * </ul> * * @hide */ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_LE_AUDIO_GROUP_STATUS_CHANGED = "android.bluetooth.action.LE_AUDIO_GROUP_STATUS_CHANGED"; /** * Intent used to broadcast group audio configuration changed information. * * <p>This intent will have 5 extra: * <ul> * <li> {@link #EXTRA_LE_AUDIO_GROUP_ID} - Group id. </li> * <li> {@link #EXTRA_LE_AUDIO_DIRECTION} - Direction as bit mask. </li> * <li> {@link #EXTRA_LE_AUDIO_SINK_LOCATION} - Sink location as per Bluetooth Assigned * Numbers </li> * <li> {@link #EXTRA_LE_AUDIO_SOURCE_LOCATION} - Source location as per Bluetooth Assigned * Numbers </li> * <li> {@link #EXTRA_LE_AUDIO_AVAILABLE_CONTEXTS} - Available contexts for group as per * Bluetooth Assigned Numbers </li> * </ul> * * @hide */ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_LE_AUDIO_CONF_CHANGED = "android.bluetooth.action.LE_AUDIO_CONF_CHANGED"; /** * Indicates conversation between humans as, for example, in telephony or video calls. * @hide */ public static final int CONTEXT_TYPE_COMMUNICATION = 0x0002; /** * Indicates media as, for example, in music, public radio, podcast or video soundtrack. * @hide */ public static final int CONTEXT_TYPE_MEDIA = 0x0004; /** * This represents an invalid group ID. * Loading @@ -98,6 +163,71 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { */ public static final int GROUP_ID_INVALID = IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID; /** * Contains group id. * @hide */ public static final String EXTRA_LE_AUDIO_GROUP_ID = "android.bluetooth.extra.LE_AUDIO_GROUP_ID"; /** * Contains group node status, can be any of * <p> * <ul> * <li> {@link #GROUP_NODE_ADDED} </li> * <li> {@link #GROUP_NODE_REMOVED} </li> * </ul> * <p> * @hide */ public static final String EXTRA_LE_AUDIO_GROUP_NODE_STATUS = "android.bluetooth.extra.LE_AUDIO_GROUP_NODE_STATUS"; /** * Contains group status, can be any of * * <p> * <ul> * <li> {@link #GROUP_STATUS_IDLE} </li> * <li> {@link #GROUP_STATUS_STREAMING} </li> * <li> {@link #GROUP_STATUS_SUSPENDED} </li> * <li> {@link #GROUP_STATUS_RECONFIGURED} </li> * <li> {@link #GROUP_STATUS_DESTROYED} </li> * </ul> * <p> * @hide */ public static final String EXTRA_LE_AUDIO_GROUP_STATUS = "android.bluetooth.extra.LE_AUDIO_GROUP_STATUS"; /** * Contains bit mask for direction, bit 0 set when Sink, bit 1 set when Source. * @hide */ public static final String EXTRA_LE_AUDIO_DIRECTION = "android.bluetooth.extra.LE_AUDIO_DIRECTION"; /** * Contains source location as per Bluetooth Assigned Numbers * @hide */ public static final String EXTRA_LE_AUDIO_SOURCE_LOCATION = "android.bluetooth.extra.LE_AUDIO_SOURCE_LOCATION"; /** * Contains sink location as per Bluetooth Assigned Numbers * @hide */ public static final String EXTRA_LE_AUDIO_SINK_LOCATION = "android.bluetooth.extra.LE_AUDIO_SINK_LOCATION"; /** * Contains available context types for group as per Bluetooth Assigned Numbers * @hide */ public static final String EXTRA_LE_AUDIO_AVAILABLE_CONTEXTS = "android.bluetooth.extra.LE_AUDIO_AVAILABLE_CONTEXTS"; private BluetoothAdapter mAdapter; private final BluetoothProfileConnector<IBluetoothLeAudio> mProfileConnector = new BluetoothProfileConnector(this, BluetoothProfile.LE_AUDIO, "BluetoothLeAudio", Loading
media/java/android/media/AudioManager.java +34 −0 Original line number Diff line number Diff line Loading @@ -5273,6 +5273,40 @@ public class AudioManager { } } /** * Indicate Le Audio output device connection state change and eventually suppress * the {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} intent. * @param device Bluetooth device connected/disconnected * @param state new connection state (BluetoothProfile.STATE_xxx) * @param suppressNoisyIntent if true the * {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} intent will not be sent. * {@hide} */ public void setBluetoothLeAudioOutDeviceConnectionState(BluetoothDevice device, int state, boolean suppressNoisyIntent) { final IAudioService service = getService(); try { service.setBluetoothLeAudioOutDeviceConnectionState(device, state, suppressNoisyIntent); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Indicate Le Audio input connection state change. * @param device Bluetooth device connected/disconnected * @param state new connection state (BluetoothProfile.STATE_xxx) * {@hide} */ public void setBluetoothLeAudioInDeviceConnectionState(BluetoothDevice device, int state) { final IAudioService service = getService(); try { service.setBluetoothLeAudioInDeviceConnectionState(device, state); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Indicate A2DP source or sink connection state change and eventually suppress * the {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} intent. Loading
media/java/android/media/IAudioService.aidl +5 −0 Original line number Diff line number Diff line Loading @@ -256,6 +256,11 @@ interface IAudioService { void setBluetoothHearingAidDeviceConnectionState(in BluetoothDevice device, int state, boolean suppressNoisyIntent, int musicDevice); void setBluetoothLeAudioOutDeviceConnectionState(in BluetoothDevice device, int state, boolean suppressNoisyIntent); void setBluetoothLeAudioInDeviceConnectionState(in BluetoothDevice device, int state); void setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(in BluetoothDevice device, int state, int profile, boolean suppressNoisyIntent, int a2dpVolume); Loading
services/core/java/com/android/server/audio/AudioDeviceBroker.java +94 −1 Original line number Diff line number Diff line Loading @@ -563,6 +563,37 @@ import java.util.concurrent.atomic.AtomicBoolean; sendLMsgNoDelay(MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT, SENDMSG_QUEUE, info); } private static final class LeAudioDeviceConnectionInfo { final @NonNull BluetoothDevice mDevice; final @AudioService.BtProfileConnectionState int mState; final boolean mSupprNoisy; final @NonNull String mEventSource; LeAudioDeviceConnectionInfo(@NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, boolean suppressNoisyIntent, @NonNull String eventSource) { mDevice = device; mState = state; mSupprNoisy = suppressNoisyIntent; mEventSource = eventSource; } } /*package*/ void postBluetoothLeAudioOutDeviceConnectionState( @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, boolean suppressNoisyIntent, @NonNull String eventSource) { final LeAudioDeviceConnectionInfo info = new LeAudioDeviceConnectionInfo( device, state, suppressNoisyIntent, eventSource); sendLMsgNoDelay(MSG_L_LE_AUDIO_DEVICE_OUT_CONNECTION_CHANGE_EXT, SENDMSG_QUEUE, info); } /*package*/ void postBluetoothLeAudioInDeviceConnectionState( @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, @NonNull String eventSource) { final LeAudioDeviceConnectionInfo info = new LeAudioDeviceConnectionInfo( device, state, false, eventSource); sendLMsgNoDelay(MSG_L_LE_AUDIO_DEVICE_IN_CONNECTION_CHANGE_EXT, SENDMSG_QUEUE, info); } /** * Current Bluetooth SCO audio active state indicated by BtHelper via setBluetoothScoOn(). Loading Loading @@ -849,6 +880,23 @@ import java.util.concurrent.atomic.AtomicBoolean; delay); } /*package*/ void postSetLeAudioOutConnectionState( @AudioService.BtProfileConnectionState int state, @NonNull BluetoothDevice device, int delay) { sendILMsg(MSG_IL_SET_LE_AUDIO_OUT_CONNECTION_STATE, SENDMSG_QUEUE, state, device, delay); } /*package*/ void postSetLeAudioInConnectionState( @AudioService.BtProfileConnectionState int state, @NonNull BluetoothDevice device) { sendILMsgNoDelay(MSG_IL_SET_LE_AUDIO_IN_CONNECTION_STATE, SENDMSG_QUEUE, state, device); } /*package*/ void postDisconnectA2dp() { sendMsgNoDelay(MSG_DISCONNECT_A2DP, SENDMSG_QUEUE); } Loading Loading @@ -1154,7 +1202,20 @@ import java.util.concurrent.atomic.AtomicBoolean; synchronized (mDeviceStateLock) { mDeviceInventory.onSetHearingAidConnectionState( (BluetoothDevice) msg.obj, msg.arg1, mAudioService.getHearingAidStreamType()); mAudioService.getBluetoothContextualVolumeStream()); } break; case MSG_IL_SET_LE_AUDIO_OUT_CONNECTION_STATE: synchronized (mDeviceStateLock) { mDeviceInventory.onSetLeAudioOutConnectionState( (BluetoothDevice) msg.obj, msg.arg1, mAudioService.getBluetoothContextualVolumeStream()); } break; case MSG_IL_SET_LE_AUDIO_IN_CONNECTION_STATE: synchronized (mDeviceStateLock) { mDeviceInventory.onSetLeAudioInConnectionState( (BluetoothDevice) msg.obj, msg.arg1); } break; case MSG_BT_HEADSET_CNCT_FAILED: Loading Loading @@ -1343,6 +1404,31 @@ import java.util.concurrent.atomic.AtomicBoolean; final int capturePreset = msg.arg1; mDeviceInventory.onSaveClearPreferredDevicesForCapturePreset(capturePreset); } break; case MSG_L_LE_AUDIO_DEVICE_OUT_CONNECTION_CHANGE_EXT: { final LeAudioDeviceConnectionInfo info = (LeAudioDeviceConnectionInfo) msg.obj; AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( "setLeAudioDeviceOutConnectionState state=" + info.mState + " addr=" + info.mDevice.getAddress() + " supprNoisy=" + info.mSupprNoisy + " src=" + info.mEventSource)).printLog(TAG)); synchronized (mDeviceStateLock) { mDeviceInventory.setBluetoothLeAudioOutDeviceConnectionState( info.mDevice, info.mState, info.mSupprNoisy); } } break; case MSG_L_LE_AUDIO_DEVICE_IN_CONNECTION_CHANGE_EXT: { final LeAudioDeviceConnectionInfo info = (LeAudioDeviceConnectionInfo) msg.obj; AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( "setLeAudioDeviceInConnectionState state=" + info.mState + " addr=" + info.mDevice.getAddress() + " src=" + info.mEventSource)).printLog(TAG)); synchronized (mDeviceStateLock) { mDeviceInventory.setBluetoothLeAudioInDeviceConnectionState(info.mDevice, info.mState); } } break; default: Log.wtf(TAG, "Invalid message " + msg.what); } Loading Loading @@ -1424,6 +1510,11 @@ import java.util.concurrent.atomic.AtomicBoolean; private static final int MSG_IL_SET_PREF_DEVICES_FOR_STRATEGY = 40; private static final int MSG_I_REMOVE_PREF_DEVICES_FOR_STRATEGY = 41; private static final int MSG_IL_SET_LE_AUDIO_OUT_CONNECTION_STATE = 42; private static final int MSG_IL_SET_LE_AUDIO_IN_CONNECTION_STATE = 43; private static final int MSG_L_LE_AUDIO_DEVICE_OUT_CONNECTION_CHANGE_EXT = 44; private static final int MSG_L_LE_AUDIO_DEVICE_IN_CONNECTION_CHANGE_EXT = 45; private static boolean isMessageHandledUnderWakelock(int msgId) { switch(msgId) { case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: Loading @@ -1439,6 +1530,8 @@ import java.util.concurrent.atomic.AtomicBoolean; case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION: case MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT: case MSG_CHECK_MUTE_MUSIC: case MSG_L_LE_AUDIO_DEVICE_OUT_CONNECTION_CHANGE_EXT: case MSG_L_LE_AUDIO_DEVICE_IN_CONNECTION_CHANGE_EXT: return true; default: return false; Loading
services/core/java/com/android/server/audio/AudioDeviceInventory.java +93 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHearingAid; import android.bluetooth.BluetoothLeAudio; import android.bluetooth.BluetoothProfile; import android.content.Intent; import android.media.AudioDeviceAttributes; Loading Loading @@ -423,6 +424,45 @@ public class AudioDeviceInventory { } } /*package*/ void onSetLeAudioConnectionState(BluetoothDevice btDevice, @AudioService.BtProfileConnectionState int state, int streamType, int device) { String address = btDevice.getAddress(); if (!BluetoothAdapter.checkBluetoothAddress(address)) { address = ""; } AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( "onSetLeAudioConnectionState addr=" + address)); synchronized (mDevicesLock) { DeviceInfo di = null; boolean isConnected = false; String key = DeviceInfo.makeDeviceListKey(device, btDevice.getAddress()); di = mConnectedDevices.get(key); isConnected = di != null; if (isConnected && state != BluetoothProfile.STATE_CONNECTED) { makeLeAudioDeviceUnavailable(address, device); } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { makeLeAudioDeviceAvailable(address, BtHelper.getName(btDevice), streamType, device, "onSetLeAudioConnectionState"); } } } /*package*/ void onSetLeAudioOutConnectionState(BluetoothDevice btDevice, @AudioService.BtProfileConnectionState int state, int streamType) { // TODO: b/198610537 clarify DEVICE_OUT_BLE_HEADSET vs DEVICE_OUT_BLE_SPEAKER criteria onSetLeAudioConnectionState(btDevice, state, streamType, AudioSystem.DEVICE_OUT_BLE_HEADSET); } /*package*/ void onSetLeAudioInConnectionState(BluetoothDevice btDevice, @AudioService.BtProfileConnectionState int state) { onSetLeAudioConnectionState(btDevice, state, AudioSystem.STREAM_DEFAULT, AudioSystem.DEVICE_IN_BLE_HEADSET); } @GuardedBy("AudioDeviceBroker.mDeviceStateLock") /*package*/ void onBluetoothA2dpActiveDeviceChange( @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int event) { Loading Loading @@ -943,6 +983,28 @@ public class AudioDeviceInventory { } } /*package*/ int setBluetoothLeAudioOutDeviceConnectionState( @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state, boolean suppressNoisyIntent) { synchronized (mDevicesLock) { /* Active device become null and it's previous device is not connected anymore */ int delay = 0; if (!suppressNoisyIntent) { int intState = (state == BluetoothLeAudio.STATE_CONNECTED) ? 1 : 0; delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_BLE_HEADSET, intState, AudioSystem.DEVICE_NONE); } mDeviceBroker.postSetLeAudioOutConnectionState(state, device, delay); return delay; } } /*package*/ void setBluetoothLeAudioInDeviceConnectionState( @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state) { synchronized (mDevicesLock) { mDeviceBroker.postSetLeAudioInConnectionState(state, device); } } //------------------------------------------------------------------- // Internal utilities Loading Loading @@ -1114,6 +1176,36 @@ public class AudioDeviceInventory { .record(); } @GuardedBy("mDevicesLock") private void makeLeAudioDeviceAvailable(String address, String name, int streamType, int device, String eventSource) { if (device != AudioSystem.DEVICE_NONE) { AudioSystem.setDeviceConnectionState(device, AudioSystem.DEVICE_STATE_AVAILABLE, address, name, AudioSystem.AUDIO_FORMAT_DEFAULT); mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address), new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT)); mDeviceBroker.postAccessoryPlugMediaUnmute(device); } if (streamType == AudioSystem.STREAM_DEFAULT) { // No need to update volume for input devices return; } mDeviceBroker.postApplyVolumeOnDevice(streamType, device, "makeLeAudioDeviceAvailable"); } @GuardedBy("mDevicesLock") private void makeLeAudioDeviceUnavailable(String address, int device) { if (device != AudioSystem.DEVICE_NONE) { AudioSystem.setDeviceConnectionState(device, AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", AudioSystem.AUDIO_FORMAT_DEFAULT); mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address)); } setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/); } @GuardedBy("mDevicesLock") private void setCurrentAudioRouteNameIfPossible(String name, boolean fromA2dp) { synchronized (mCurAudioRoutes) { Loading Loading @@ -1150,6 +1242,7 @@ public class AudioDeviceInventory { BECOMING_NOISY_INTENT_DEVICES_SET.add(AudioSystem.DEVICE_OUT_HEARING_AID); BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_A2DP_SET); BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_USB_SET); BECOMING_NOISY_INTENT_DEVICES_SET.addAll(AudioSystem.DEVICE_OUT_ALL_BLE_SET); } // must be called before removing the device from mConnectedDevices Loading