blob: c16bcb6fe0d5a66154edaee7b2c0cb927f3bd239 [file] [log] [blame]
/******************************************************************************
*
* Copyright 2021 The Android Open Source Project
*
* 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 android.bluetooth;
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.annotation.IntDef;
import android.annotation.SdkConstant.SdkConstantType;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Retention;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.IBluetoothGatt;
import android.bluetooth.IBluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothAdapter.LeScanCallback;
import android.os.Binder;
import android.os.IBinder;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import java.io.InvalidClassException;
import android.os.DeadObjectException;
import android.util.Log;
import android.content.Context;
import java.util.UUID;
import android.os.ParcelUuid;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Objects;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanRecord;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.BleBroadcastAudioScanAssistManager;
import android.os.SystemProperties;
/**
* This class provides methods to perform Broadcast Assistance related
* operations.
* <p>
* Use {@link BleBroadcastAudioScanAssistManager()} to get an
* instance of {@link BleBroadcastAudioScanAssistManager}.
* <p>
* <b>Note:</b> Most of the methods here require
* {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
*
* @hide
*/
public final class BleBroadcastAudioScanAssistManager {
private static final String TAG = "BleBroadcastAudioScanAssistManager";
private static final boolean DBG = true;
private static final boolean VDBG = true;
/** @hide */
@IntDef(prefix = "SYNC_", value = {
SYNC_METADATA,
SYNC_AUDIO,
SYNC_METADATA_AUDIO
})
@Retention(RetentionPolicy.SOURCE)
public @interface BroadcastAssistSyncState {}
/**
* Input to {@link BleBroadcastAudioScanAssistManager#addBroadcastSource} method
* where Application wants to synchronize only to Metadata (i.e. Only Periodic advs) and not to
* Broadcsat audio stream (i.e. BIS )from broadcast source
*/
public static final int SYNC_METADATA = 0;
/**
* Input to {@link BleBroadcastAudioScanAssistManager#addBroadcastSource} method
* where Application wants to synchronize only to Broadcast Audio stream (i.e. BIS) and not to
Metadata (i.e. Periodic advs )from broadcast source
*/
public static final int SYNC_AUDIO = 1;
/**
* Input to {@link BleBroadcastAudioScanAssistManager#addBroadcastSource} method
* where Application wants to synchronize to both Broadcast Audio stream (i.e. BIS) and also to
* Metadata (i.e. Periodic advs )from broadcast source
*/
public static final int SYNC_METADATA_AUDIO = 2;
private BluetoothAdapter mBluetoothAdapter;
BleBroadcastAudioScanAssistCallback mAppCallback;
BluetoothDevice mBluetoothDevice;
int mSyncState = SYNC_METADATA;
BluetoothSyncHelper mBluetoothSyncHelper = null;
BleBroadcastSourceInfo mBroadcastAudioSourceInfo = null;
private byte INVALID_SOURCE_ID = -1;
/**
* Intent used to broadcast the "Broadcast receiver State" information of a Scan delegator device.
* Whenever there is a change in Broadcast source Information stored at Scan delegator device
* this Itent will be delivered to Application layer
*
* {@link #BluetoothSyncHelper} profile need to be connected to the Scan delegator device
* to get these notifications
*
* <p>This intent will have two extra:
* <ul>
* <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device for which broadcast reciver
* state information is broadcasted. It can
* be null if no device is active. </li>
* </ul>
* <ul>
* <li> {@link BleBroadcastSourceInfo#EXTRA_SOURCE_INFO} - The BleBroadcastSourceInfo Object
* having information Broadcast receiver state </li>
* </ul>
* <ul>
* <li> {@link BleBroadcastSourceInfo#EXTRA_SOURCE_INFO_INDEX} - Index of the BleBroadcastSourceInfo
* object broadcasted </li>
* </ul>
* <ul>
* <li> {@link BleBroadcastSourceInfo#EXTRA_MAX_NUM_SOURCE_INFOS} - Maximum number of source Informations
* that this Broadcast receiver can hold </li>
* </ul>
*
* <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
* receive.
*
* @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_BROADCAST_SOURCE_INFO =
"android.bluetooth.BroadcastAudioSAManager.action.BROADCAST_SOURCE_INFO";
// These callbacks run on the main thread.
private final class BassclientServiceListener
implements BluetoothProfile.ServiceListener {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
log(TAG, "BassService connected");
onBluetoothSyncHelperStateChanged(true, proxy);
}
public void onServiceDisconnected(int profile) {
log(TAG, "BassService disconnected");
onBluetoothSyncHelperStateChanged(false, null);
}
}
private void onBluetoothSyncHelperStateChanged(boolean on, BluetoothProfile proxy) {
if (on) {
mBluetoothSyncHelper = (BluetoothSyncHelper) proxy;
mBluetoothSyncHelper.registerAppCallback(mBluetoothDevice, mAppCallback);
this.notifyAll();
} else {
mBluetoothSyncHelper = null;
}
}
/*package*/BleBroadcastAudioScanAssistManager(BluetoothSyncHelper scanOffloader, BluetoothDevice device,
BleBroadcastAudioScanAssistCallback callback
) {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mAppCallback = callback;
mBluetoothDevice = device;
mBluetoothSyncHelper = scanOffloader;
}
/*finalize method to cleanup*/
protected void finalize() {
log(TAG, "finalize()");
if (mBluetoothSyncHelper != null) {
mBluetoothSyncHelper.unregisterAppCallback(mBluetoothDevice, mAppCallback);
}
}
/**
* Search for Le Audio Broadcasters on behalf of the Scan delegator with which this
* {@ BleBroadcastAudioScanAssistManager} is instantiated
*
* search results will be delivered to application using
* {@link BleBroadcastAudioScanAssistCallback#onBleBroadcastSourceFound}
*
* @return returns true if It is successfully initiated the Search for Audio broadcasters,
* false otherwise
* @hide
*/
public boolean searchforLeAudioBroadcasters () {
log(TAG, "searchforLeAudioBroadcasters: ");
if (mBluetoothSyncHelper != null) {
return mBluetoothSyncHelper.searchforLeAudioBroadcasters(mBluetoothDevice);
} else {
Log.e(TAG, "searchforLeAudioBroadcasters: mBluetoothSyncHelper is null");
}
return false;
}
/**
* Stops an ongoing Bluetooth LE Search for Audio Broadcasters.
*
* @return returns true if It is successfully initiated the Stopped the Search for Audio broadcasters
* false otherwise
*
*@hide
*/
@RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
public boolean stopSearchforLeAudioBroadcasters() {
log(TAG, "stopSearchforLeAudioBroadcasters()");
if (mBluetoothSyncHelper != null) {
return mBluetoothSyncHelper.stopSearchforLeAudioBroadcasters(mBluetoothDevice);
} else {
Log.e(TAG, "stopSearchforLeAudioBroadcasters: mBluetoothSyncHelper is null");
}
return false;
}
/* Internal helper function to convert user input sync state to required internal
* format
*/
private int convertMetadataSyncState(int syncState) {
if (syncState == SYNC_METADATA_AUDIO || syncState == SYNC_METADATA) {
return BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC;
}
return BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IDLE;
}
/* Internal helper function to convert user input sync state to required internal
* format
*/
private int convertAudioDataSyncState(int syncState) {
if (syncState == SYNC_METADATA_AUDIO || syncState == SYNC_AUDIO) {
return BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED;
} else {
Log.e(TAG, "searchforLeAudioBroadcasters: mBluetoothSyncHelper is null");
}
return BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED;
}
/**
* Selects broadcast source for the Scan delegator. This internally performs Periodic
* synchronization to the given Broadcast source device, upon acquision of Synchronization information,
* It will be notified with avaiable Broadcast source channels that can be synchronized in the remote
* device.
* Application should select set of Broadcast channels that need to be synchronized and follow up
* with a call to {@link #addBroadcastSource} operation
*
* Result of selction of Broadcast source will be delivered through
* {@link BleBroadcastAudioScanAssistCallback#OnBroadcastAudioSourceSelected}
*
* If this operation need to be performed over all the members of coordinated set members, isGroupOp
* will be set to true. Select broadcast source operation will be performed on behalf of
* all the Coordinated set devices
*
*
* @param ScanResult {@link #ScanResult} of the Broadcasting source,
* this is the result obtained from the {@link BleBroadcastAudioScanAssistCallback#onBleBroadcastSourceFound}
* @param isGroupOp set to true If Application wants to perform this operation for the whole
* coordinated set members
*
* @return returns true if It is successfully initiated select Broadcast source operation
* false otherwise
* @hide
*/
public boolean selectBroadcastSource(ScanResult scanRes, boolean isGroupOp) {
if (scanRes == null) {
Log.e(TAG, "selectBroadcastSource: Invalid scan res");
return false;
}
log(TAG, "selectBroadcastSource: " + scanRes);
if (mBluetoothSyncHelper != null) {
return mBluetoothSyncHelper.selectBroadcastSource(mBluetoothDevice, scanRes, isGroupOp);
} else {
Log.e(TAG, "selectBroadcastSource: mBluetoothSyncHelper is null");
}
return false;
}
private boolean isValidBroadcastSourceInfo(BleBroadcastSourceInfo srcInfo) {
boolean ret = true;
List<BleBroadcastSourceInfo> currentSourceInfos =
mBluetoothSyncHelper.getAllBroadcastSourceInformation(mBluetoothDevice);
if (currentSourceInfos == null) {
Log.e(TAG, "no source info details for remote");
ret = false;
} else {
for (int i=0; i<currentSourceInfos.size(); i++) {
if (srcInfo.matches(currentSourceInfos.get(i))) {
ret = false;
break;
}
}
}
log(TAG, "isValidBroadcastSourceInfo returns: " + ret);
return ret;
}
private boolean isValidSourceId (byte sourceId) {
boolean retVal = false;
List<BleBroadcastSourceInfo> currentSourceInfos =
mBluetoothSyncHelper.getAllBroadcastSourceInformation(mBluetoothDevice);
if (currentSourceInfos == null) {
retVal = false;
} else {
for (int i=0; i<currentSourceInfos.size(); i++) {
if (currentSourceInfos.get(i).getSourceId() == sourceId) {
retVal = true;
break;
}
}
}
log(TAG, "isValidSourceId returns: " + retVal);
return retVal;
}
private void printSelectedIndicies(List<BleBroadcastSourceChannel> selectedBISIndicies) {
if (selectedBISIndicies == null) {
log(TAG, "printSelectedIndicies : no selected indicies");
return;
}
for (int i=0; i<selectedBISIndicies.size(); i++) {
log(TAG, selectedBISIndicies.get(i).getDescription() + ": " + selectedBISIndicies.get(i).getStatus());
}
}
/**
* Adds a broadcast source information to the Scan delegator. This internally performs Periodic
* synchronization to the given Broadcast source device, upon acquision of Synchronization information,
* It will be written on to the "Scan delegators" Characteristics
*
* Result of addition of Broadcast source to the scan delegator will be delivered through
* {@link BleBroadcastAudioScanAssistCallback#onBleBroadcastAudioSourceAdded}
*
* Successful addition Broadcast source will be indicated through Broadcast reciver state information
* update intent through {@link #ACTION_BROADCAST_RECEIVER_STATE} intent
*
*
* If this operation need to be performed over all the members of coordinated set members, isGroupOp
* will be set to true. add broadcast source operation will be performed on behalf of
* all the Coordinated set devices
*
* Same Broadcast source Information will be written on to all the members of Coordinated set and
* PAST will be performed based on the request.
*
* In case of Group Operation, If there is any matching entry already present in any of coordinated set members,
* Add Broadcast source opeation will be failed and result will notified through
* {@link BleBroadcastAudioScanAssistCallback#onBleBroadcastAudioSourceAdded}
*
* @param audioSource {@link #BluetoothDevice} object selected as Source which need to be synchronized with
* @param ScanResult {@link #ScanResult} result obtained from the {@link BleBroadcastAudioScanAssistCallback#onBleBroadcastSourceFound}
* @param syncState can be one of {@link #SYNC_METADATA},
* {@link #SYNC_METADATA_AUDIO}
* @param selectedBroadcastChannels is a List of Broadcast channels that need to be synchronized with the given broadcast audio source
* from Avaialble Broadcast indicies.
* Avaiable broadcast indicies are notified application using {@link BleBroadcastAudioScanAssistCallback#onBleBroadcastSourceSelected}
* BroadcastSourceChannel.mStatus set to be TRUE or FALSE based on the need of synchronization.
*
*
* null value of selectedBroadcastChannels resulting in syncing to all avaialble Broadcast channels.
* check {@link BleBroadcastSourceChannel} for more information
* @param isGroupOp set to true If Application wants to perform this operation for the whole
* coordinated set members, False otherwise
*
* @return returns true if It is successfully initiated add Broadcast source operation
* false otherwise
* @hide
*/
public boolean addBroadcastSource (BluetoothDevice audioSource,
@BroadcastAssistSyncState int syncState,
List<BleBroadcastSourceChannel> selectedBroadcastChannels,
boolean isGroupOp) {
if (mBluetoothSyncHelper == null) {
log(TAG, "addBroadcastSource: no BluetoothSyncHelper handle");
return false;
}
if (syncState != SYNC_METADATA &&
syncState != SYNC_METADATA_AUDIO) {
log(TAG, "addBroadcastSource: Invalid syncState" + syncState);
return false;
}
printSelectedIndicies(selectedBroadcastChannels);
int metadataSyncState = -1;
int audioSyncState = -1;
mSyncState = syncState;
metadataSyncState = convertMetadataSyncState (mSyncState);
audioSyncState = convertAudioDataSyncState(mSyncState);
if (mBroadcastAudioSourceInfo == null) {
//all of these will be overriden at service layer later
mBroadcastAudioSourceInfo = new BleBroadcastSourceInfo(
audioSource,
(byte)0xBB, /*advSid*/
BleBroadcastSourceInfo.BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC,
metadataSyncState,
audioSyncState,
selectedBroadcastChannels);
if (mBroadcastAudioSourceInfo == null) {
Log.e(TAG, "addBroadcastSource: mBroadcastAudioSourceInfo instantiated failure");
return false;
}
}
if(isValidBroadcastSourceInfo(mBroadcastAudioSourceInfo)) {
mBluetoothSyncHelper.addBroadcastSource(mBluetoothDevice,
mBroadcastAudioSourceInfo,
isGroupOp
);
} else {
log(TAG, "Similar source information already exists");
return false;
}
return true;
}
/**
* Updates a broadcast source information in the Scan delegator.
* It will be written on to the Scan delegator's Characteristics
*
* Result of updating of Broadcast source to the scan delegator will be delivered through
* {@link BleBroadcastAudioScanAssistCallback#onBleBroadcastAudioSourceUpdated}
*
* However Successful updating of Broadcast source information will be indicated through Broadcast reciver state information
* update intent through {@link #ACTION_BROADCAST_RECEIVER_STATE} intent
*
* If this operation need to be performed over all the members of coordinated set members, isGroupOp
* will be set to true. Update broadcast source operation will be performed on behalf of
* all the Coordinated set devices
*
* Same Broadcast source Information change will be written on to all the members of Coordinated set and
* PAST will be performed based on the request from remote.
*
* In case of Group Operation, If there are no matching source Information present in any of coordinated set members,
* Update Broadcast source opeation will be failed and result will notified through
* {@link BleBroadcastAudioScanAssistCallback#onBleBroadcastAudioSourceUpdated}
*
* @param sourceId sourceId of the Broadcast Source information which need to be updated
* @param syncState can be one of {@link #SYNC_METADATA},
* {@link #SYNC_AUDIO}, {@link #SYNC_METADATA_AUDIO}
*
* @param selectedBroadcastChannels is a List of Broadcast channels that need to be synchronized with the given broadcast audio source
* from Avaialble Broadcast indicies.
* Avaiable broadcast indicies are notified application using {@link BleBroadcastAudioScanAssistCallback#onBleBroadcastSourceSelected}
* BroadcastSourceChannel.mStatus set to be TRUE or FALSE based on the need of synchronization.
*
* null value of selectedBroadcastChannels resulting in syncing to all avaialble Broadcast channels.
* check {@link BleurceChannel} for more information
* @param isGroupOp set to true If Application wants to perform this operation for the whole
* coordinated set members, False otherwise
*
* @return returns true if It is successfully initiated update Broadcast source information
* operation
* false otherwise
* @hide
*/
public boolean updateBroadcastSource (byte sourceId, int syncState,
List<BleBroadcastSourceChannel> selectedBroadcastChannels,
boolean isGroupOp) {
if (mBluetoothSyncHelper == null) {
log(TAG, "updateBroadcastSource: no BluetoothSyncHelper handle");
return false;
}
if (isValidSourceId(sourceId) == false) {
log(TAG, "updateBroadcastSource: Invalid source Id");
return false;
}
int audioSyncState = -1;
int metadataSyncState = -1;
log(TAG, "updateBroadcastSource: sourceId" + sourceId + ", syncState:" + syncState);
mSyncState = syncState;
metadataSyncState = convertMetadataSyncState (mSyncState);
audioSyncState = convertAudioDataSyncState(mSyncState);
printSelectedIndicies(selectedBroadcastChannels);
log(TAG, "updateBroadcastSource: audioSyncState:" + audioSyncState);
log(TAG, "updateBroadcastSource: metadataSyncState:" + metadataSyncState);
BleBroadcastSourceInfo sourceInfo = new BleBroadcastSourceInfo(sourceId);
if (sourceInfo != null) {
sourceInfo.setMetadataSyncState(metadataSyncState);
sourceInfo.setAudioSyncState(audioSyncState);
sourceInfo.setSourceId(sourceId);
sourceInfo.setBroadcastChannelsSyncStatus(selectedBroadcastChannels);
} else {
Log.e(TAG, "updateBroadcastSource: sourceInfo not created");
return false;
}
return mBluetoothSyncHelper.updateBroadcastSource(mBluetoothDevice,
sourceInfo,
isGroupOp);
}
/**
* Sets the Broadcast pin code to the Scan delegator so that It can decrypt
* the synchronized audio at the reciver side
*
* It will be written on to the Scan delegator's Characteristics.
* Result of Setting of Broadcast PIN code to the scan delegator will be delivered through
* {@link BleBroadcastAudioScanAssistCallback#onBroadcastPinUpdated}
*
* If this operation need to be performed over all the members of coordinated set members, isGroupOp
* will be set to true. set Broadcast PIN operation will be performed on all the Coordinated set devices
*
* Same Broadcast PIN code will be written on to all the members of Coordinated set and
* on the request from remote.
*
* In case of Group Operation, If there are no matching source Information(BD address, adv instance)
* present in any of coordinated set members,
* Set Broadcast PIN opeation will be failed and result will notified through
* {@link BleBroadcastAudioScanAssistCallback#onBroadcastPinUpdated}
*
*
* However, Successful updating of Broadcast PIN code will be indicated through Broadcast reciver state information
* update intent through {@link #ACTION_BROADCAST_RECEIVER_STATE} intent.
*
* @param sourceId sourceId of the Broadcast Source information which need to be updated
* @param broadcastCode is the String of maximum 16 characters in length
* @param isGroupOp set to true If Application wants to perform this operation for the whole
* coordinated set members, False otherwise
*
* @return returns true if It is successfully initiated set Broadcast code operation
* false otherwise
* @hide
*/
public boolean setBroadcastCode (byte sourceId, String broadcastCode, boolean isGroupOp) {
if (mBluetoothSyncHelper == null) {
log(TAG, "setBroadcastCode: no BluetoothSyncHelper handle");
return false;
}
if (isValidSourceId(sourceId) == false) {
log(TAG, "setBroadcastCode: Invalid source Id");
return false;
}
log(TAG, "setBroadcastCode: " + "sourceId:"
+ sourceId + "BroadcastCode:" + broadcastCode);
BleBroadcastSourceInfo sourceInfo = new BleBroadcastSourceInfo(sourceId);
if (sourceInfo != null) {
sourceInfo.setSourceId(sourceId);
sourceInfo.setBroadcastCode(broadcastCode);
} else {
Log.e(TAG, "setBroadcastCode: sourceInfo not created");
return false;
}
return mBluetoothSyncHelper.setBroadcastCode(mBluetoothDevice,
sourceInfo,
isGroupOp);
}
/**
* Removes the Broadcast Source Information from the Scan delegator
* It will be written on to the "Scan delegators" Characteristics
*
* Result of removal of Broadcast source to the scan delegator will be delivered through
* {@link BleBroadcastAudioScanAssistCallback#OnBroadcastAudioSourRemoved}
*
* If this operation need to be performed over all the members of coordinated set members, isGroupOp
* will be set to true. remove broadcast operation will be performed on all the Coordinated set devices
*
* Remove Broadcast will be performed on to all the members of Coordinated set
*
* In case of Group Operation, If there are no matching source Information(BD address, adv instance)
* present in any of coordinated set members.
*
* Set Broadcast PIN opeation will be failed and result will notified through
* {@link BleBroadcastAudioScanAssistCallback#onBroadcastPinUpdated}
* Successful removal of Brocast source information will be indicated through
* Broadcast receiver state Information through
* {@link #ACTION_BROADCAST_RECEIVER_STATE} intent
*
* @param sourceId sourceId of the Broadcast Source information which need to be updated
* @param isGroupOp set to true If Application wants to perform this operation for the whole
* coordinated set members, False otherwise
*
* @return returns true if It is successfully initiated remove broadcast source operation
* false otherwise
* @hide
*/
public boolean removeBroadcastSource (byte sourceId, boolean isGroupOp) {
if (mBluetoothSyncHelper == null) {
log(TAG, "removeBroadcastSource: no BluetoothSyncHelper handle");
return false;
}
if (isValidSourceId(sourceId) == false) {
log(TAG, "removeBroadcastSource: Invalid source Id");
return false;
}
log(TAG, "removeBroadcastSource: sourceId" + sourceId);
return mBluetoothSyncHelper.removeBroadcastSource(mBluetoothDevice,
sourceId,
isGroupOp);
}
/**
* Get all the Broadcast Source Information stored in remote Scan delegators
*
* @return returns the List of Broadcast Source Information {@link #BleBroadcastSourceInfo} stored in
* remote and its corresponding state or null in case if there are nothing
*
* @hide
*/
public List<BleBroadcastSourceInfo> getAllBroadcastSourceInformation () {
if (mBluetoothSyncHelper == null) {
log(TAG, "GetNumberOfAcceptableBroadcastSources: no BluetoothSyncHelper handle");
return null;
}
return mBluetoothSyncHelper.getAllBroadcastSourceInformation(mBluetoothDevice);
}
private static void log(String TAG, String msg) {
BleBroadcastSourceInfo.BASS_Debug(TAG, msg);
}
}