blob: 1dc128021beb679db3d122a37671452c079f526d [file] [log] [blame]
/*
* Copyright 2021 HIMSA II K/S - www.himsa.com.
* Represented by EHIMA - www.ehima.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.bluetooth.leaudio;
import android.app.Application;
import android.bluetooth.*;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.ParcelUuid;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.Pair;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
public class BluetoothProxy {
private static BluetoothProxy INSTANCE;
private final Application application;
private final BluetoothAdapter bluetoothAdapter;
private BluetoothLeAudio bluetoothLeAudio = null;
private BluetoothLeBroadcast mBluetoothLeBroadcast = null;
private BluetoothLeBroadcastAssistant mBluetoothLeBroadcastAssistant = null;
private Set<BluetoothDevice> mBroadcastScanDelegatorDevices = new HashSet<>();
private BluetoothCsipSetCoordinator bluetoothCsis = null;
private BluetoothVolumeControl bluetoothVolumeControl = null;
private BluetoothHapClient bluetoothHapClient = null;
private BluetoothProfile.ServiceListener profileListener = null;
private BluetoothHapClient.Callback hapCallback = null;
private OnBassEventListener mBassEventListener;
private OnLocalBroadcastEventListener mLocalBroadcastEventListener;
private final IntentFilter adapterIntentFilter;
private final IntentFilter bassIntentFilter;
private IntentFilter intentFilter;
private final ExecutorService mExecutor;
private final Map<Integer, UUID> mGroupLocks = new HashMap<>();
private int GROUP_NODE_ADDED = 1;
private int GROUP_NODE_REMOVED = 2;
private boolean mLeAudioCallbackRegistered = false;
private BluetoothLeAudio.Callback mLeAudioCallbacks =
new BluetoothLeAudio.Callback() {
@Override
public void onCodecConfigChanged(int groupId, BluetoothLeAudioCodecStatus status) {}
@Override
public void onGroupStatusChanged(int groupId, int groupStatus) {
List<LeAudioDeviceStateWrapper> valid_devices = null;
valid_devices = allLeAudioDevicesMutable.getValue().stream().filter(
state -> state.leAudioData.nodeStatusMutable.getValue() != null
&& state.leAudioData.nodeStatusMutable.getValue().first
.equals(groupId))
.collect(Collectors.toList());
for (LeAudioDeviceStateWrapper dev : valid_devices) {
dev.leAudioData.groupStatusMutable.setValue(
new Pair<>(groupId, new Pair<>(groupStatus, 0)));
}
}
@Override
public void onGroupNodeAdded(BluetoothDevice device, int groupId) {
Log.d("LeCB:", device.getAddress() + " group added " + groupId);
if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) {
Log.d("LeCB:", "invalid parameter");
return;
}
Optional<LeAudioDeviceStateWrapper> valid_device_opt = allLeAudioDevicesMutable
.getValue().stream()
.filter(state -> state.device.getAddress().equals(device.getAddress()))
.findAny();
if (!valid_device_opt.isPresent()) {
Log.d("LeCB:", "Device not present");
return;
}
LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
LeAudioDeviceStateWrapper.LeAudioData svc_data = valid_device.leAudioData;
svc_data.nodeStatusMutable.setValue(new Pair<>(groupId, GROUP_NODE_ADDED));
svc_data.groupStatusMutable.setValue(new Pair<>(groupId, new Pair<>(-1, -1)));
}
@Override
public void onGroupNodeRemoved(BluetoothDevice device, int groupId) {
if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) {
Log.d("LeCB:", "invalid parameter");
return;
}
Log.d("LeCB:", device.getAddress() + " group added " + groupId);
if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) {
Log.d("LeCB:", "invalid parameter");
return;
}
Optional<LeAudioDeviceStateWrapper> valid_device_opt = allLeAudioDevicesMutable
.getValue().stream()
.filter(state -> state.device.getAddress().equals(device.getAddress()))
.findAny();
if (!valid_device_opt.isPresent()) {
Log.d("LeCB:", "Device not present");
return;
}
LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
LeAudioDeviceStateWrapper.LeAudioData svc_data = valid_device.leAudioData;
svc_data.nodeStatusMutable.setValue(new Pair<>(groupId, GROUP_NODE_REMOVED));
svc_data.groupStatusMutable.setValue(new Pair<>(groupId, new Pair<>(-1, -1)));
}
};
private final MutableLiveData<Boolean> enabledBluetoothMutable;
private final BroadcastReceiver adapterIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
int toState =
intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
if (toState == BluetoothAdapter.STATE_ON) {
enabledBluetoothMutable.setValue(true);
} else if (toState == BluetoothAdapter.STATE_OFF) {
enabledBluetoothMutable.setValue(false);
}
}
}
};
private final MutableLiveData<List<LeAudioDeviceStateWrapper>> allLeAudioDevicesMutable;
private final BroadcastReceiver leAudioIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (allLeAudioDevicesMutable.getValue() != null) {
if (device != null) {
Optional<LeAudioDeviceStateWrapper> valid_device_opt = allLeAudioDevicesMutable
.getValue().stream()
.filter(state -> state.device.getAddress().equals(device.getAddress()))
.findAny();
if (valid_device_opt.isPresent()) {
LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
LeAudioDeviceStateWrapper.LeAudioData svc_data = valid_device.leAudioData;
int group_id;
// Handle Le Audio actions
switch (action) {
case BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED: {
final int toState =
intent.getIntExtra(BluetoothLeAudio.EXTRA_STATE, -1);
if (toState == BluetoothLeAudio.STATE_CONNECTED
|| toState == BluetoothLeAudio.STATE_DISCONNECTED)
svc_data.isConnectedMutable
.postValue(toState == BluetoothLeAudio.STATE_CONNECTED);
group_id = bluetoothLeAudio.getGroupId(device);
svc_data.nodeStatusMutable.setValue(
new Pair<>(group_id, GROUP_NODE_ADDED));
svc_data.groupStatusMutable
.setValue(new Pair<>(group_id, new Pair<>(-1, -1)));
break;
}
}
}
}
}
}
};
private final BroadcastReceiver hapClientIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (allLeAudioDevicesMutable.getValue() != null) {
if (device != null) {
Optional<LeAudioDeviceStateWrapper> valid_device_opt = allLeAudioDevicesMutable
.getValue().stream()
.filter(state -> state.device.getAddress().equals(device.getAddress()))
.findAny();
if (valid_device_opt.isPresent()) {
LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
LeAudioDeviceStateWrapper.HapData svc_data = valid_device.hapData;
switch (action) {
case BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED: {
final int toState =
intent.getIntExtra(BluetoothHapClient.EXTRA_STATE, -1);
svc_data.hapStateMutable.postValue(toState);
break;
}
// Hidden API
case "android.bluetooth.action.HAP_DEVICE_AVAILABLE": {
final int features = intent
.getIntExtra("android.bluetooth.extra.HAP_FEATURES", -1);
svc_data.hapFeaturesMutable.postValue(features);
break;
}
default:
// Do nothing
break;
}
}
}
}
}
};
private final BroadcastReceiver volumeControlIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (allLeAudioDevicesMutable.getValue() != null) {
if (device != null) {
Optional<LeAudioDeviceStateWrapper> valid_device_opt = allLeAudioDevicesMutable
.getValue().stream()
.filter(state -> state.device.getAddress().equals(device.getAddress()))
.findAny();
if (valid_device_opt.isPresent()) {
LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
LeAudioDeviceStateWrapper.VolumeControlData svc_data =
valid_device.volumeControlData;
switch (action) {
case BluetoothVolumeControl.ACTION_CONNECTION_STATE_CHANGED:
final int toState =
intent.getIntExtra(BluetoothVolumeControl.EXTRA_STATE, -1);
if (toState == BluetoothVolumeControl.STATE_CONNECTED
|| toState == BluetoothVolumeControl.STATE_DISCONNECTED)
svc_data.isConnectedMutable.postValue(
toState == BluetoothVolumeControl.STATE_CONNECTED);
break;
}
}
}
}
}
};
private final MutableLiveData<BluetoothLeBroadcastMetadata> mBroadcastUpdateMutableLive;
private final MutableLiveData<Pair<Integer /* reason */, Integer /* broadcastId */>> mBroadcastPlaybackStartedMutableLive;
private final MutableLiveData<Pair<Integer /* reason */, Integer /* broadcastId */>> mBroadcastPlaybackStoppedMutableLive;
private final MutableLiveData<Integer /* broadcastId */> mBroadcastAddedMutableLive;
private final MutableLiveData<Pair<Integer /* reason */, Integer /* broadcastId */>> mBroadcastRemovedMutableLive;
private final MutableLiveData<String> mBroadcastStatusMutableLive;
private final BluetoothLeBroadcast.Callback mBroadcasterCallback =
new BluetoothLeBroadcast.Callback() {
@Override
public void onBroadcastStarted(int reason, int broadcastId) {
if ((reason != BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST)
&& (reason != BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST)) {
mBroadcastStatusMutableLive.postValue("Unable to create broadcast: "
+ broadcastId + ", reason: " + reason);
}
mBroadcastAddedMutableLive.postValue(broadcastId);
if (mLocalBroadcastEventListener != null) {
mLocalBroadcastEventListener.onBroadcastStarted(broadcastId);
}
}
@Override
public void onBroadcastStartFailed(int reason) {
mBroadcastStatusMutableLive
.postValue("Unable to START broadcast due to reason: " + reason);
}
@Override
public void onBroadcastStopped(int reason, int broadcastId) {
mBroadcastRemovedMutableLive.postValue(new Pair<>(reason, broadcastId));
if (mLocalBroadcastEventListener != null) {
mLocalBroadcastEventListener.onBroadcastStopped(broadcastId);
}
}
@Override
public void onBroadcastStopFailed(int reason) {
mBroadcastStatusMutableLive
.postValue("Unable to STOP broadcast due to reason: " + reason);
}
@Override
public void onPlaybackStarted(int reason, int broadcastId) {
mBroadcastPlaybackStartedMutableLive.postValue(new Pair<>(reason, broadcastId));
}
@Override
public void onPlaybackStopped(int reason, int broadcastId) {
mBroadcastPlaybackStoppedMutableLive.postValue(new Pair<>(reason, broadcastId));
}
@Override
public void onBroadcastUpdated(int reason, int broadcastId) {
mBroadcastStatusMutableLive.postValue("Broadcast " + broadcastId
+ "has been updated due to reason: " + reason);
if (mLocalBroadcastEventListener != null) {
mLocalBroadcastEventListener.onBroadcastUpdated(broadcastId);
}
}
@Override
public void onBroadcastUpdateFailed(int reason, int broadcastId) {
mBroadcastStatusMutableLive.postValue("Unable to UPDATE broadcast "
+ broadcastId + " due to reason: " + reason);
}
@Override
public void onBroadcastMetadataChanged(int broadcastId,
BluetoothLeBroadcastMetadata metadata) {
mBroadcastUpdateMutableLive.postValue(metadata);
if (mLocalBroadcastEventListener != null) {
mLocalBroadcastEventListener.onBroadcastMetadataChanged(
broadcastId, metadata);
}
}
};
// TODO: Add behaviors in empty methods if necessary.
private final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
new BluetoothLeBroadcastAssistant.Callback() {
@Override
public void onSearchStarted(int reason) {}
@Override
public void onSearchStartFailed(int reason) {}
@Override
public void onSearchStopped(int reason) {}
@Override
public void onSearchStopFailed(int reason) {}
@Override
public void onSourceFound(BluetoothLeBroadcastMetadata source) {
Log.d("BluetoothProxy", "onSourceFound");
if (mBassEventListener != null) {
mBassEventListener.onSourceFound(source);
}
}
@Override
public void onSourceAdded(BluetoothDevice sink, int sourceId, int reason) {}
@Override
public void onSourceAddFailed(BluetoothDevice sink,
BluetoothLeBroadcastMetadata source, int reason) {}
@Override
public void onSourceModified(BluetoothDevice sink, int sourceId, int reason) {}
@Override
public void onSourceModifyFailed(BluetoothDevice sink, int sourceId, int reason) {}
@Override
public void onSourceRemoved(BluetoothDevice sink, int sourceId, int reason) {}
@Override
public void onSourceRemoveFailed(BluetoothDevice sink, int sourceId, int reason) {}
@Override
public void onReceiveStateChanged(BluetoothDevice sink, int sourceId,
BluetoothLeBroadcastReceiveState state) {
Log.d("BluetoothProxy", "onReceiveStateChanged");
if (allLeAudioDevicesMutable.getValue() != null) {
Optional<LeAudioDeviceStateWrapper> valid_device_opt = allLeAudioDevicesMutable
.getValue().stream()
.filter(stateWrapper -> stateWrapper.device.getAddress().equals(
sink.getAddress()))
.findAny();
if (!valid_device_opt.isPresent())
return;
LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
LeAudioDeviceStateWrapper.BassData svc_data = valid_device.bassData;
// TODO: Is the receiver_id same with BluetoothLeBroadcastReceiveState.getSourceId()?
// If not, find getSourceId() usages and fix the issues.
// rstate.receiver_id = intent.getIntExtra(
// BluetoothBroadcastAudioScan.EXTRA_BASS_RECEIVER_ID, -1);
/**
* From "Introducing-Bluetooth-LE-Audio-book" 8.6.3.1:
*
* The Source_ID is an Acceptor generated number which is used to identify a
* specific set of
* broadcast device and BIG information. It is local to an Acceptor and used as a
* reference for
* a Broadcast Assistant. In the case of a Coordinated Set of Acceptors, such as
* a left and right
* earbud, the Source_IDs are not related and may be different, even if both are
* receiving the
* same BIS, as each Acceptor independently creates their own Source ID values
*/
/**
* From BluetoothBroadcastAudioScan.EXTRA_BASS_RECEIVER_ID:
*
* Broadcast receiver's endpoint identifier.
*/
synchronized(this) {
HashMap<Integer, BluetoothLeBroadcastReceiveState> states =
svc_data.receiverStatesMutable.getValue();
if (states == null)
states = new HashMap<>();
states.put(state.getSourceId(), state);
// Use SetValue instead of PostValue() since we want to make it
// synchronous due to getValue() we do here as well
// Otherwise we could miss the update and store only the last
// receiver ID
// svc_data.receiverStatesMutable.setValue(states);
svc_data.receiverStatesMutable.postValue(states);
}
}
}
};
private final BroadcastReceiver bassIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(BluetoothLeBroadcastAssistant.ACTION_CONNECTION_STATE_CHANGED)) {
final BluetoothDevice device = intent.getParcelableExtra(
BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class);
if (allLeAudioDevicesMutable.getValue() != null) {
if (device != null) {
Optional<LeAudioDeviceStateWrapper> valid_device_opt =
allLeAudioDevicesMutable
.getValue().stream()
.filter(state -> state.device.getAddress().equals(
device.getAddress()))
.findAny();
if (valid_device_opt.isPresent()) {
LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
LeAudioDeviceStateWrapper.BassData svc_data = valid_device.bassData;
final int toState = intent
.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
if (toState == BluetoothProfile.STATE_CONNECTED
|| toState == BluetoothProfile.STATE_DISCONNECTED)
svc_data.isConnectedMutable.postValue(
toState == BluetoothProfile.STATE_CONNECTED);
}
}
}
}
// TODO: Remove this if unnecessary.
// case BluetoothBroadcastAudioScan.ACTION_BASS_BROADCAST_ANNONCEMENT_AVAILABLE:
// // FIXME: Never happen since there is no valid device with this intent
// break;
}
};
private BluetoothProxy(Application application) {
this.application = application;
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
enabledBluetoothMutable = new MutableLiveData<>();
allLeAudioDevicesMutable = new MutableLiveData<>();
mBroadcastUpdateMutableLive = new MutableLiveData<>();
mBroadcastStatusMutableLive = new MutableLiveData<>();
mBroadcastPlaybackStartedMutableLive = new MutableLiveData<>();
mBroadcastPlaybackStoppedMutableLive = new MutableLiveData<>();
mBroadcastAddedMutableLive = new MutableLiveData();
mBroadcastRemovedMutableLive = new MutableLiveData<>();
MutableLiveData<String> mBroadcastStatusMutableLive;
mExecutor = Executors.newSingleThreadExecutor();
adapterIntentFilter = new IntentFilter();
adapterIntentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
application.registerReceiver(adapterIntentReceiver, adapterIntentFilter);
bassIntentFilter = new IntentFilter();
bassIntentFilter.addAction(BluetoothLeBroadcastAssistant.ACTION_CONNECTION_STATE_CHANGED);
application.registerReceiver(bassIntentReceiver, bassIntentFilter);
}
// Lazy constructing Singleton acquire method
public static BluetoothProxy getBluetoothProxy(Application application) {
if (INSTANCE == null) {
INSTANCE = new BluetoothProxy(application);
}
return (INSTANCE);
}
public void initProfiles() {
if (profileListener != null) return;
hapCallback = new BluetoothHapClient.Callback() {
@Override
public void onPresetSelected(BluetoothDevice device, int presetIndex, int statusCode) {
Optional<LeAudioDeviceStateWrapper> valid_device_opt = allLeAudioDevicesMutable
.getValue().stream()
.filter(state -> state.device.getAddress().equals(device.getAddress()))
.findAny();
if (!valid_device_opt.isPresent())
return;
LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
LeAudioDeviceStateWrapper.HapData svc_data = valid_device.hapData;
svc_data.hapActivePresetIndexMutable.postValue(presetIndex);
svc_data.hapStatusMutable
.postValue("Preset changed to " + presetIndex + ", reason: " + statusCode);
}
@Override
public void onPresetSelectionFailed(BluetoothDevice device, int statusCode) {
Optional<LeAudioDeviceStateWrapper> valid_device_opt = allLeAudioDevicesMutable
.getValue().stream()
.filter(state -> state.device.getAddress().equals(device.getAddress()))
.findAny();
if (!valid_device_opt.isPresent())
return;
LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
LeAudioDeviceStateWrapper.HapData svc_data = valid_device.hapData;
svc_data.hapStatusMutable
.postValue("Select preset failed with status " + statusCode);
}
@Override
public void onPresetSelectionForGroupFailed(int hapGroupId, int statusCode) {
List<LeAudioDeviceStateWrapper> valid_devices = null;
if (hapGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID)
valid_devices = allLeAudioDevicesMutable.getValue().stream()
.filter(state -> state.leAudioData.nodeStatusMutable.getValue() != null
&& state.leAudioData.nodeStatusMutable.getValue().first
.equals(hapGroupId))
.collect(Collectors.toList());
if (valid_devices != null) {
for (LeAudioDeviceStateWrapper device : valid_devices) {
device.hapData.hapStatusMutable.postValue("Select preset for group "
+ hapGroupId + " failed with status " + statusCode);
}
}
}
@Override
public void onPresetInfoChanged(BluetoothDevice device,
List<BluetoothHapPresetInfo> presetInfoList, int statusCode) {
Optional<LeAudioDeviceStateWrapper> valid_device_opt = allLeAudioDevicesMutable
.getValue().stream()
.filter(state -> state.device.getAddress().equals(device.getAddress()))
.findAny();
if (!valid_device_opt.isPresent())
return;
LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
LeAudioDeviceStateWrapper.HapData svc_data = valid_device.hapData;
svc_data.hapStatusMutable
.postValue("Preset list changed due to status " + statusCode);
svc_data.hapPresetsMutable.postValue(presetInfoList);
}
@Override
public void onSetPresetNameFailed(BluetoothDevice device, int status) {
Optional<LeAudioDeviceStateWrapper> valid_device_opt = allLeAudioDevicesMutable
.getValue().stream()
.filter(state -> state.device.getAddress().equals(device.getAddress()))
.findAny();
if (!valid_device_opt.isPresent())
return;
LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
LeAudioDeviceStateWrapper.HapData svc_data = valid_device.hapData;
svc_data.hapStatusMutable.postValue("Name set error: " + status);
}
@Override
public void onSetPresetNameForGroupFailed(int hapGroupId, int status) {
List<LeAudioDeviceStateWrapper> valid_devices = null;
if (hapGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID)
valid_devices = allLeAudioDevicesMutable.getValue().stream()
.filter(state -> state.leAudioData.nodeStatusMutable.getValue() != null
&& state.leAudioData.nodeStatusMutable.getValue().first
.equals(hapGroupId))
.collect(Collectors.toList());
if (valid_devices != null) {
for (LeAudioDeviceStateWrapper device : valid_devices) {
device.hapData.hapStatusMutable
.postValue("Group Name set error: " + status);
}
}
}
};
profileListener = new BluetoothProfile.ServiceListener() {
@Override
public void onServiceConnected(int i, BluetoothProfile bluetoothProfile) {
Log.d("BluetoothProxy", "onServiceConnected(): i = " + i + " bluetoothProfile = " +
bluetoothProfile);
switch (i) {
case BluetoothProfile.CSIP_SET_COORDINATOR:
bluetoothCsis = (BluetoothCsipSetCoordinator) bluetoothProfile;
break;
case BluetoothProfile.LE_AUDIO:
bluetoothLeAudio = (BluetoothLeAudio) bluetoothProfile;
if (!mLeAudioCallbackRegistered) {
try {
bluetoothLeAudio.registerCallback(mExecutor, mLeAudioCallbacks);
mLeAudioCallbackRegistered = true;
} catch (Exception e){
Log.e("Unicast:" ,
" Probably not supported: Exception on registering callbacks: " + e);
}
}
break;
case BluetoothProfile.VOLUME_CONTROL:
bluetoothVolumeControl = (BluetoothVolumeControl) bluetoothProfile;
break;
case BluetoothProfile.HAP_CLIENT:
bluetoothHapClient = (BluetoothHapClient) bluetoothProfile;
bluetoothHapClient.registerCallback(mExecutor, hapCallback);
break;
case BluetoothProfile.LE_AUDIO_BROADCAST:
mBluetoothLeBroadcast = (BluetoothLeBroadcast) bluetoothProfile;
mBluetoothLeBroadcast.registerCallback(mExecutor, mBroadcasterCallback);
break;
case BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT:
Log.d("BluetoothProxy", "LE_AUDIO_BROADCAST_ASSISTANT Service connected");
mBluetoothLeBroadcastAssistant = (BluetoothLeBroadcastAssistant)
bluetoothProfile;
mBluetoothLeBroadcastAssistant.registerCallback(mExecutor,
mBroadcastAssistantCallback);
break;
}
queryLeAudioDevices();
}
@Override
public void onServiceDisconnected(int i) {}
};
initCsisProxy();
initLeAudioProxy();
initVolumeControlProxy();
initHapProxy();
initLeAudioBroadcastProxy();
initBassProxy();
}
public void cleanupProfiles() {
if (profileListener == null) return;
cleanupCsisProxy();
cleanupLeAudioProxy();
cleanupVolumeControlProxy();
cleanupHapProxy();
cleanupLeAudioBroadcastProxy();
cleanupBassProxy();
profileListener = null;
}
private void initCsisProxy() {
if (!isCoordinatedSetProfileSupported()) return;
if (bluetoothCsis == null) {
bluetoothAdapter.getProfileProxy(this.application, profileListener,
BluetoothProfile.CSIP_SET_COORDINATOR);
}
}
private void cleanupCsisProxy() {
if (!isCoordinatedSetProfileSupported()) return;
if (bluetoothCsis != null) {
bluetoothAdapter.closeProfileProxy(BluetoothProfile.LE_AUDIO, bluetoothCsis);
}
}
private void initLeAudioProxy() {
if (!isLeAudioUnicastSupported()) return;
if (bluetoothLeAudio == null) {
bluetoothAdapter.getProfileProxy(this.application, profileListener,
BluetoothProfile.LE_AUDIO);
}
intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
application.registerReceiver(leAudioIntentReceiver, intentFilter);
}
private void cleanupLeAudioProxy() {
if (!isLeAudioUnicastSupported()) return;
if (bluetoothLeAudio != null) {
bluetoothAdapter.closeProfileProxy(BluetoothProfile.LE_AUDIO, bluetoothLeAudio);
application.unregisterReceiver(leAudioIntentReceiver);
}
}
private void initVolumeControlProxy() {
if (!isVolumeControlClientSupported()) return;
bluetoothAdapter.getProfileProxy(this.application, profileListener,
BluetoothProfile.VOLUME_CONTROL);
intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothVolumeControl.ACTION_CONNECTION_STATE_CHANGED);
application.registerReceiver(volumeControlIntentReceiver, intentFilter);
}
private void cleanupVolumeControlProxy() {
if (!isVolumeControlClientSupported()) return;
if (bluetoothVolumeControl != null) {
bluetoothAdapter.closeProfileProxy(BluetoothProfile.VOLUME_CONTROL,
bluetoothVolumeControl);
application.unregisterReceiver(volumeControlIntentReceiver);
}
}
private void initHapProxy() {
if (!isLeAudioHearingAccessClientSupported()) return;
bluetoothAdapter.getProfileProxy(this.application, profileListener,
BluetoothProfile.HAP_CLIENT);
intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED);
intentFilter.addAction("android.bluetooth.action.HAP_DEVICE_AVAILABLE");
application.registerReceiver(hapClientIntentReceiver, intentFilter);
}
private void cleanupHapProxy() {
if (!isLeAudioHearingAccessClientSupported()) return;
if (bluetoothHapClient != null) {
bluetoothHapClient.unregisterCallback(hapCallback);
bluetoothAdapter.closeProfileProxy(BluetoothProfile.HAP_CLIENT, bluetoothHapClient);
application.unregisterReceiver(hapClientIntentReceiver);
}
}
private void initBassProxy() {
if (!isLeAudioBroadcastScanAssistanSupported()) return;
bluetoothAdapter.getProfileProxy(this.application, profileListener,
BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
}
private void cleanupBassProxy() {
if (!isLeAudioBroadcastScanAssistanSupported()) return;
if (mBluetoothLeBroadcastAssistant != null) {
mBluetoothLeBroadcastAssistant.unregisterCallback(mBroadcastAssistantCallback);
bluetoothAdapter.closeProfileProxy(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT,
mBluetoothLeBroadcastAssistant);
}
}
private Boolean checkForEnabledBluetooth() {
Boolean current_state = bluetoothAdapter.isEnabled();
// Force the update since event may not come if bt was already enabled
if (enabledBluetoothMutable.getValue() != current_state)
enabledBluetoothMutable.setValue(current_state);
return current_state;
}
public void queryLeAudioDevices() {
if (checkForEnabledBluetooth()) {
// Consider those with the ASC service as valid devices
List<LeAudioDeviceStateWrapper> validDevices = new ArrayList<>();
for (BluetoothDevice dev : bluetoothAdapter.getBondedDevices()) {
LeAudioDeviceStateWrapper state_wrapper = new LeAudioDeviceStateWrapper(dev);
Boolean valid_device = false;
if (Arrays.asList(dev.getUuids() != null ? dev.getUuids() : new ParcelUuid[0])
.contains(ParcelUuid
.fromString(application.getString(R.string.svc_uuid_le_audio)))) {
if (state_wrapper.leAudioData == null)
state_wrapper.leAudioData = new LeAudioDeviceStateWrapper.LeAudioData();
valid_device = true;
if (bluetoothLeAudio != null) {
state_wrapper.leAudioData.isConnectedMutable.postValue(bluetoothLeAudio
.getConnectionState(dev) == BluetoothLeAudio.STATE_CONNECTED);
int group_id = bluetoothLeAudio.getGroupId(dev);
state_wrapper.leAudioData.nodeStatusMutable
.setValue(new Pair<>(group_id, GROUP_NODE_ADDED));
state_wrapper.leAudioData.groupStatusMutable
.setValue(new Pair<>(group_id, new Pair<>(-1, -1)));
}
}
if (Arrays.asList(dev.getUuids() != null ? dev.getUuids() : new ParcelUuid[0])
.contains(ParcelUuid.fromString(
application.getString(R.string.svc_uuid_volume_control)))) {
if (state_wrapper.volumeControlData == null)
state_wrapper.volumeControlData =
new LeAudioDeviceStateWrapper.VolumeControlData();
valid_device = true;
if (bluetoothVolumeControl != null) {
state_wrapper.volumeControlData.isConnectedMutable
.postValue(bluetoothVolumeControl.getConnectionState(
dev) == BluetoothVolumeControl.STATE_CONNECTED);
// FIXME: We don't have the api to get the volume and mute states? :(
}
}
if (Arrays.asList(dev.getUuids() != null ? dev.getUuids() : new ParcelUuid[0])
.contains(ParcelUuid
.fromString(application.getString(R.string.svc_uuid_has)))) {
if (state_wrapper.hapData == null)
state_wrapper.hapData = new LeAudioDeviceStateWrapper.HapData();
valid_device = true;
if (bluetoothHapClient != null) {
state_wrapper.hapData.hapStateMutable
.postValue(bluetoothHapClient.getConnectionState(dev));
boolean is_connected = bluetoothHapClient
.getConnectionState(dev) == BluetoothHapClient.STATE_CONNECTED;
if (is_connected) {
// Use hidden API
try {
Method getFeaturesMethod = BluetoothHapClient.class
.getDeclaredMethod("getFeatures", BluetoothDevice.class);
getFeaturesMethod.setAccessible(true);
state_wrapper.hapData.hapFeaturesMutable
.postValue((Integer) getFeaturesMethod
.invoke(bluetoothHapClient, dev));
} catch (NoSuchMethodException | IllegalAccessException
| InvocationTargetException e) {
state_wrapper.hapData.hapStatusMutable
.postValue("Hidden API for getFeatures not accessible.");
}
state_wrapper.hapData.hapPresetsMutable
.postValue(bluetoothHapClient.getAllPresetInfo(dev));
try {
Method getActivePresetIndexMethod =
BluetoothHapClient.class.getDeclaredMethod(
"getActivePresetIndex", BluetoothDevice.class);
getActivePresetIndexMethod.setAccessible(true);
state_wrapper.hapData.hapActivePresetIndexMutable
.postValue((Integer) getActivePresetIndexMethod
.invoke(bluetoothHapClient, dev));
} catch (NoSuchMethodException | IllegalAccessException
| InvocationTargetException e) {
state_wrapper.hapData.hapStatusMutable
.postValue("Hidden API for getFeatures not accessible.");
}
}
}
}
if (Arrays.asList(dev.getUuids() != null ? dev.getUuids() : new ParcelUuid[0])
.contains(ParcelUuid.fromString(
application.getString(R.string.svc_uuid_broadcast_audio)))) {
if (state_wrapper.bassData == null)
state_wrapper.bassData = new LeAudioDeviceStateWrapper.BassData();
valid_device = true;
if (mBluetoothLeBroadcastAssistant != null) {
boolean is_connected = mBluetoothLeBroadcastAssistant
.getConnectionState(dev) == BluetoothProfile.STATE_CONNECTED;
state_wrapper.bassData.isConnectedMutable.setValue(is_connected);
}
}
if (valid_device) validDevices.add(state_wrapper);
}
// Async update
allLeAudioDevicesMutable.postValue(validDevices);
}
}
public void connectLeAudio(BluetoothDevice device, boolean connect) {
if (bluetoothLeAudio != null) {
if (connect) {
try {
Method connectMethod = BluetoothLeAudio.class.getDeclaredMethod("connect",
BluetoothDevice.class);
connectMethod.setAccessible(true);
connectMethod.invoke(bluetoothLeAudio, device);
} catch (NoSuchMethodException | IllegalAccessException
| InvocationTargetException e) {
// Do nothing
}
} else {
try {
Method disconnectMethod = BluetoothLeAudio.class.getDeclaredMethod("disconnect",
BluetoothDevice.class);
disconnectMethod.setAccessible(true);
disconnectMethod.invoke(bluetoothLeAudio, device);
} catch (NoSuchMethodException | IllegalAccessException
| InvocationTargetException e) {
// Do nothing
}
}
}
}
public void streamAction(Integer group_id, int action, Integer content_type) {
if (bluetoothLeAudio != null) {
switch (action) {
case 0:
// No longer available, not needed
// bluetoothLeAudio.groupStream(group_id, content_type);
break;
case 1:
// No longer available, not needed
// bluetoothLeAudio.groupSuspend(group_id);
break;
case 2:
// No longer available, not needed
// bluetoothLeAudio.groupStop(group_id);
break;
default:
break;
}
}
}
public void groupSet(BluetoothDevice device, Integer group_id) {
if (bluetoothLeAudio == null) return;
try {
Method groupAddNodeMethod = BluetoothLeAudio.class.getDeclaredMethod("groupAddNode",
Integer.class, BluetoothDevice.class);
groupAddNodeMethod.setAccessible(true);
groupAddNodeMethod.invoke(bluetoothLeAudio, group_id, device);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
// Do nothing
}
}
public void groupUnset(BluetoothDevice device, Integer group_id) {
if (bluetoothLeAudio == null) return;
try {
Method groupRemoveNodeMethod = BluetoothLeAudio.class
.getDeclaredMethod("groupRemoveNode", Integer.class, BluetoothDevice.class);
groupRemoveNodeMethod.setAccessible(true);
groupRemoveNodeMethod.invoke(bluetoothLeAudio, group_id, device);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
// Do nothing
}
}
public void groupSetLock(Integer group_id, boolean lock) {
if (bluetoothCsis == null) return;
Log.d("Lock", "lock: " + lock);
if (lock) {
if (mGroupLocks.containsKey(group_id)) {
Log.e("Lock", "group" + group_id + " is already in locking process or locked: " + lock);
return;
}
UUID uuid = bluetoothCsis.lockGroup(group_id, mExecutor,
(int group, int op_status, boolean is_locked) -> {
Log.d("LockCb", "lock: " + is_locked + " status: " + op_status);
if (((op_status == BluetoothStatusCodes.SUCCESS)
|| (op_status == BluetoothStatusCodes.ERROR_CSIP_LOCKED_GROUP_MEMBER_LOST))
&& (group != BluetoothLeAudio.GROUP_ID_INVALID)) {
allLeAudioDevicesMutable.getValue().forEach((dev_wrapper) -> {
if (dev_wrapper.leAudioData.nodeStatusMutable.getValue() != null
&& dev_wrapper.leAudioData.nodeStatusMutable
.getValue().first.equals(group_id)) {
dev_wrapper.leAudioData.groupLockStateMutable.postValue(
new Pair<Integer, Boolean>(group, is_locked));
}
});
} else {
// TODO: Set error status so it could be notified/toasted to the
// user
}
if (!is_locked)
mGroupLocks.remove(group_id);
});
// Store the lock key
mGroupLocks.put(group_id, uuid);
} else {
if (!mGroupLocks.containsKey(group_id)) return;
// Use the stored lock key
bluetoothCsis.unlockGroup(mGroupLocks.get(group_id));
mGroupLocks.remove(group_id);
}
}
public void connectBass(BluetoothDevice device, boolean connect) {
if (mBluetoothLeBroadcastAssistant != null) {
if (connect) {
mBluetoothLeBroadcastAssistant.setConnectionPolicy(device,
BluetoothProfile.CONNECTION_POLICY_ALLOWED);
} else {
mBluetoothLeBroadcastAssistant.setConnectionPolicy(device,
BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
}
}
}
public boolean scanForBroadcasts(@Nullable BluetoothDevice scanDelegator, boolean scan) {
if (mBluetoothLeBroadcastAssistant != null) {
// Note: startSearchingForSources() does not support scanning on behalf of
// a specific device - it only searches for all BASS connected devices.
// Therefore, we manage the list of the devices and start/stop the scanning.
if (scan) {
if (scanDelegator != null) {
mBroadcastScanDelegatorDevices.add(scanDelegator);
}
mBluetoothLeBroadcastAssistant.startSearchingForSources(new ArrayList<>());
if (mBassEventListener != null) {
mBassEventListener.onScanningStateChanged(true);
}
} else {
if (scanDelegator != null) {
mBroadcastScanDelegatorDevices.remove(scanDelegator);
}
if (mBroadcastScanDelegatorDevices.isEmpty()) {
mBluetoothLeBroadcastAssistant.stopSearchingForSources();
if (mBassEventListener != null) {
mBassEventListener.onScanningStateChanged(false);
}
}
}
return true;
}
return false;
}
public boolean stopBroadcastObserving() {
if (mBluetoothLeBroadcastAssistant != null) {
mBroadcastScanDelegatorDevices.clear();
mBluetoothLeBroadcastAssistant.stopSearchingForSources();
if (mBassEventListener != null) {
mBassEventListener.onScanningStateChanged(false);
}
return true;
}
return false;
}
// TODO: Uncomment this method if necessary
// public boolean getBroadcastReceiverState(BluetoothDevice device, int receiver_id) {
// if (mBluetoothLeBroadcastAssistant != null) {
// return mBluetoothLeBroadcastAssistant.getBroadcastReceiverState(device, receiver_id);
// }
// return false;
// }
public boolean addBroadcastSource(BluetoothDevice sink, BluetoothLeBroadcastMetadata sourceMetadata) {
if (mBluetoothLeBroadcastAssistant != null) {
mBluetoothLeBroadcastAssistant.addSource(sink, sourceMetadata, true /* isGroupOp */);
return true;
}
return false;
}
public boolean modifyBroadcastSource(BluetoothDevice sink, int sourceId,
BluetoothLeBroadcastMetadata metadata) {
if (mBluetoothLeBroadcastAssistant != null) {
mBluetoothLeBroadcastAssistant.modifySource(sink, sourceId, metadata);
return true;
}
return false;
}
public boolean removeBroadcastSource(BluetoothDevice sink, int sourceId) {
if (mBluetoothLeBroadcastAssistant != null) {
mBluetoothLeBroadcastAssistant.removeSource(sink, sourceId);
return true;
}
return false;
}
public void setVolume(BluetoothDevice device, int volume) {
if (bluetoothLeAudio != null) {
bluetoothLeAudio.setVolume(volume);
}
}
public LiveData<Boolean> getBluetoothEnabled() {
return enabledBluetoothMutable;
}
public LiveData<List<LeAudioDeviceStateWrapper>> getAllLeAudioDevices() {
return allLeAudioDevicesMutable;
}
public void connectHap(BluetoothDevice device, boolean connect) {
if (bluetoothHapClient != null) {
if (connect) {
bluetoothHapClient.setConnectionPolicy(device,
BluetoothProfile.CONNECTION_POLICY_ALLOWED);
} else {
bluetoothHapClient.setConnectionPolicy(device,
BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
}
}
}
public boolean hapReadPresetInfo(BluetoothDevice device, int preset_index) {
if (bluetoothHapClient == null)
return false;
BluetoothHapPresetInfo new_preset = null;
// Use hidden API
try {
Method getPresetInfoMethod = BluetoothHapClient.class.getDeclaredMethod("getPresetInfo",
BluetoothDevice.class, Integer.class);
getPresetInfoMethod.setAccessible(true);
new_preset = (BluetoothHapPresetInfo) getPresetInfoMethod.invoke(bluetoothHapClient,
device, preset_index);
if (new_preset == null)
return false;
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
// Do nothing'
return false;
}
Optional<LeAudioDeviceStateWrapper> valid_device_opt = allLeAudioDevicesMutable.getValue()
.stream().filter(state -> state.device.getAddress().equals(device.getAddress()))
.findAny();
if (!valid_device_opt.isPresent())
return false;
LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
LeAudioDeviceStateWrapper.HapData svc_data = valid_device.hapData;
List current_presets = svc_data.hapPresetsMutable.getValue();
if (current_presets == null)
current_presets = new ArrayList<BluetoothHapPresetInfo>();
// Remove old one and add back the new one
ListIterator<BluetoothHapPresetInfo> iter = current_presets.listIterator();
while (iter.hasNext()) {
if (iter.next().getIndex() == new_preset.getIndex()) {
iter.remove();
}
}
current_presets.add(new_preset);
svc_data.hapPresetsMutable.postValue(current_presets);
return true;
}
public boolean hapSetActivePreset(BluetoothDevice device, int preset_index) {
if (bluetoothHapClient == null)
return false;
bluetoothHapClient.selectPreset(device, preset_index);
return true;
}
public boolean hapChangePresetName(BluetoothDevice device, int preset_index, String name) {
if (bluetoothHapClient == null)
return false;
bluetoothHapClient.setPresetName(device, preset_index, name);
return true;
}
public boolean hapPreviousDevicePreset(BluetoothDevice device) {
if (bluetoothHapClient == null)
return false;
// Use hidden API
try {
Method switchToPreviousPresetMethod = BluetoothHapClient.class
.getDeclaredMethod("switchToPreviousPreset", BluetoothDevice.class);
switchToPreviousPresetMethod.setAccessible(true);
switchToPreviousPresetMethod.invoke(bluetoothHapClient, device);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
// Do nothing
}
return true;
}
public boolean hapNextDevicePreset(BluetoothDevice device) {
if (bluetoothHapClient == null)
return false;
// Use hidden API
try {
Method switchToNextPresetMethod = BluetoothHapClient.class
.getDeclaredMethod("switchToNextPreset", BluetoothDevice.class);
switchToNextPresetMethod.setAccessible(true);
switchToNextPresetMethod.invoke(bluetoothHapClient, device);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
// Do nothing
}
return true;
}
public boolean hapPreviousGroupPreset(int group_id) {
if (bluetoothHapClient == null)
return false;
// Use hidden API
try {
Method switchToPreviousPresetForGroupMethod = BluetoothHapClient.class
.getDeclaredMethod("switchToPreviousPresetForGroup", Integer.class);
switchToPreviousPresetForGroupMethod.setAccessible(true);
switchToPreviousPresetForGroupMethod.invoke(bluetoothHapClient, group_id);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
// Do nothing
}
return true;
}
public boolean hapNextGroupPreset(int group_id) {
if (bluetoothHapClient == null)
return false;
// Use hidden API
try {
Method switchToNextPresetForGroupMethod = BluetoothHapClient.class
.getDeclaredMethod("switchToNextPresetForGroup", Integer.class);
switchToNextPresetForGroupMethod.setAccessible(true);
switchToNextPresetForGroupMethod.invoke(bluetoothHapClient, group_id);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
// Do nothing
}
return true;
}
public int hapGetHapGroup(BluetoothDevice device) {
if (bluetoothHapClient == null)
return BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
// Use hidden API
try {
Method getHapGroupMethod = BluetoothHapClient.class.getDeclaredMethod("getHapGroup",
BluetoothDevice.class);
getHapGroupMethod.setAccessible(true);
return (Integer) getHapGroupMethod.invoke(bluetoothHapClient, device);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
// Do nothing
}
return BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
}
private void initLeAudioBroadcastProxy() {
if (!isLeAudioBroadcastSourceSupported()) return;
if (mBluetoothLeBroadcast == null) {
bluetoothAdapter.getProfileProxy(this.application, profileListener,
BluetoothProfile.LE_AUDIO_BROADCAST);
}
}
private void cleanupLeAudioBroadcastProxy() {
if (!isLeAudioBroadcastSourceSupported()) return;
if (mBluetoothLeBroadcast != null) {
bluetoothAdapter.closeProfileProxy(BluetoothProfile.LE_AUDIO_BROADCAST,
mBluetoothLeBroadcast);
}
}
public LiveData<BluetoothLeBroadcastMetadata> getBroadcastUpdateMetadataLive() {
return mBroadcastUpdateMutableLive;
}
public LiveData<Pair<Integer /* reason */, Integer /* broadcastId */>> getBroadcastPlaybackStartedMutableLive() {
return mBroadcastPlaybackStartedMutableLive;
}
public LiveData<Pair<Integer /* reason */, Integer /* broadcastId */>> getBroadcastPlaybackStoppedMutableLive() {
return mBroadcastPlaybackStoppedMutableLive;
}
public LiveData<Integer /* broadcastId */> getBroadcastAddedMutableLive() {
return mBroadcastAddedMutableLive;
}
public LiveData<Pair<Integer /* reason */, Integer /* broadcastId */>> getBroadcastRemovedMutableLive() {
return mBroadcastRemovedMutableLive;
}
public LiveData<String> getBroadcastStatusMutableLive() {
return mBroadcastStatusMutableLive;
}
public boolean startBroadcast(String programInfo, byte[] code) {
if (mBluetoothLeBroadcast == null)
return false;
BluetoothLeAudioContentMetadata.Builder contentBuilder =
new BluetoothLeAudioContentMetadata.Builder();
if (!programInfo.isEmpty()) {
contentBuilder.setProgramInfo(programInfo);
}
mBluetoothLeBroadcast.startBroadcast(contentBuilder.build(), code);
return true;
}
public boolean stopBroadcast(int broadcastId) {
if (mBluetoothLeBroadcast == null) return false;
mBluetoothLeBroadcast.stopBroadcast(broadcastId);
return true;
}
public List<BluetoothLeBroadcastMetadata> getAllLocalBroadcasts() {
if (mBluetoothLeBroadcast == null) return Collections.emptyList();
return mBluetoothLeBroadcast.getAllBroadcastMetadata();
}
public boolean updateBroadcast(int broadcastId, String programInfo) {
if (mBluetoothLeBroadcast == null) return false;
BluetoothLeAudioContentMetadata.Builder contentBuilder =
new BluetoothLeAudioContentMetadata.Builder();
contentBuilder.setProgramInfo(programInfo);
mBluetoothLeBroadcast.updateBroadcast(broadcastId, contentBuilder.build());
return true;
}
public int getMaximumNumberOfBroadcast() {
if (mBluetoothLeBroadcast == null) {
Log.d("BluetoothProxy", "mBluetoothLeBroadcast is null");
return 0;
}
return mBluetoothLeBroadcast.getMaximumNumberOfBroadcasts();
}
public boolean isPlaying(int broadcastId) {
if (mBluetoothLeBroadcast == null) return false;
return mBluetoothLeBroadcast.isPlaying(broadcastId);
}
boolean isLeAudioUnicastSupported() {
return (bluetoothAdapter
.isLeAudioSupported() == BluetoothStatusCodes.FEATURE_SUPPORTED);
}
boolean isCoordinatedSetProfileSupported() {
return isLeAudioUnicastSupported();
}
boolean isVolumeControlClientSupported() {
return isLeAudioUnicastSupported();
}
boolean isLeAudioHearingAccessClientSupported() {
return isLeAudioUnicastSupported();
}
public boolean isLeAudioBroadcastSourceSupported() {
return (bluetoothAdapter
.isLeAudioBroadcastSourceSupported() == BluetoothStatusCodes.FEATURE_SUPPORTED);
}
public boolean isLeAudioBroadcastScanAssistanSupported() {
return (bluetoothAdapter
.isLeAudioBroadcastAssistantSupported() == BluetoothStatusCodes.FEATURE_SUPPORTED);
}
public void setOnBassEventListener(OnBassEventListener listener) {
mBassEventListener = listener;
}
// Used by BroadcastScanViewModel
public interface OnBassEventListener {
void onSourceFound(BluetoothLeBroadcastMetadata source);
void onScanningStateChanged(boolean isScanning);
}
public void setOnLocalBroadcastEventListener(OnLocalBroadcastEventListener listener) {
mLocalBroadcastEventListener = listener;
}
// Used by BroadcastScanViewModel
public interface OnLocalBroadcastEventListener {
// TODO: Add arguments in methods
void onBroadcastStarted(int broadcastId);
void onBroadcastStopped(int broadcastId);
void onBroadcastUpdated(int broadcastId);
void onBroadcastMetadataChanged(int broadcastId, BluetoothLeBroadcastMetadata metadata);
}
}