blob: 9289198a44f4fc1d542bdd3a3649b7e99b72a60a [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 com.android.bluetooth.bc;
import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Iterator;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import java.util.UUID;
import java.util.Collection;
import android.os.UserHandle;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import java.nio.charset.StandardCharsets;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Scanner;
import java.util.Map;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Set;
import java.lang.String;
import java.lang.StringBuffer;
import java.lang.Integer;
import java.nio.ByteBuffer;
import java.lang.Byte;
import java.util.stream.IntStream;
import java.util.NoSuchElementException;
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 java.util.UUID;
import android.os.Handler;
import android.os.ParcelUuid;
import android.os.SystemProperties;
import android.os.RemoteException;
import android.bluetooth.BleBroadcastSourceInfo;
import android.bluetooth.BleBroadcastSourceChannel;
//import android.bluetooth.BluetoothBroadcast;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import com.android.bluetooth.btservice.ServiceFactory;
///*_BMS
import com.android.bluetooth.broadcast.BroadcastService;
//_BMS*/
import android.bluetooth.BluetoothCodecConfig;
/*_PACS
import com.android.bluetooth.pacsclient.PacsClientService;
_PACS*/
import android.bluetooth.IBleBroadcastAudioScanAssistCallback;
/**
* Bass Utility functions
*/
final class BassUtils {
private static final String TAG = "BassUtils";
/*LE Scan related members*/
private boolean mBroadcastersAround = false;
private BluetoothAdapter mBluetoothAdapter = null;
private BluetoothLeScanner mLeScanner = null;
private BCService mBCService = null;
///*_BMS
private BroadcastService mBAService = null;
//_BMS*/
public static final String BAAS_UUID = "00001852-0000-1000-8000-00805F9B34FB";
private boolean mIsLocalBMSNotified = false;
private ServiceFactory mFactory = new ServiceFactory();
//Using ArrayList as KEY to hashmap. May be not risk
//in this case as It is used to track the callback to cancel Scanning later
private final Map<ArrayList<IBleBroadcastAudioScanAssistCallback>, ScanCallback> mLeAudioSourceScanCallbacks;
private final Map<BluetoothDevice, ScanCallback> mBassAutoAssist;
private static final int AA_START_SCAN = 1;
private static final int AA_SCAN_SUCCESS = 2;
private static final int AA_SCAN_FAILURE = 3;
private static final int AA_SCAN_TIMEOUT = 4;
//timeout for internal scan
private static final int AA_SCAN_TIMEOUT_MS = 1000;
/**
* Stanadard Codec param types
*/
static final int LOCATION = 3;
//sample rate
static final int SAMPLE_RATE = 1;
//frame duration
static final int FRAME_DURATION = 2;
//Octets per frame
static final int OCTETS_PER_FRAME = 8;
/*_PACS
private PacsClientService mPacsClientService = PacsClientService.getPacsClientService();
_PACS*/
BassUtils (BCService service) {
mBCService = service;
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
mLeAudioSourceScanCallbacks = new HashMap<ArrayList<IBleBroadcastAudioScanAssistCallback>, ScanCallback>();
mBassAutoAssist = new HashMap<BluetoothDevice, ScanCallback>();
///*_BMS
mBAService = BroadcastService.getBroadcastService();
//_BMS*/
}
private ScanCallback mPaSyncScanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
log( "onScanResult:" + result);
}
};
void cleanUp () {
if (mLeAudioSourceScanCallbacks != null) {
mLeAudioSourceScanCallbacks.clear();
}
if (mBassAutoAssist != null) {
mBassAutoAssist.clear();
}
}
boolean leScanControl(boolean on) {
log("leScanControl:" + on);
mLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
if (mLeScanner == null) {
Log.e(TAG, "LeScan handle not available");
return false;
}
if (on) {
mLeScanner.startScan(mPaSyncScanCallback);
} else {
mLeScanner.stopScan(mPaSyncScanCallback);
}
return true;
}
/* private helper to check if the Local BLE Broadcast happening Or not */
public boolean isLocalLEAudioBroadcasting() {
boolean ret = false;
/*String localLeABroadcast = SystemProperties.get("persist.vendor.btstack.isLocalLeAB");
if (!localLeABroadcast.isEmpty() && "true".equals(localLeABroadcast)) {
ret = true;
}
log("property isLocalLEAudioBroadcasting returning " + ret);*/
///*_Broadcast
if (mBAService == null) {
mBAService = BroadcastService.getBroadcastService();
}
if (mBAService != null) {
ret = mBAService.isBroadcastActive();
//ret = mBAService.isBroadcastStreaming();
log("local broadcast streaming:" + ret);
} else {
log("BroadcastService is Null");
}
//_Broadcast*/
log("isLocalLEAudioBroadcasting returning " + ret);
return ret;
}
Handler mAutoAssistScanHandler = new Handler() {
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case AA_START_SCAN:
BluetoothDevice dev = (BluetoothDevice) msg.obj;
Message m = obtainMessage(AA_SCAN_TIMEOUT);
m.obj = dev;
sendMessageDelayed(m, AA_SCAN_TIMEOUT_MS);
searchforLeAudioBroadcasters(dev, null);
break;
case AA_SCAN_SUCCESS:
//Able to find to desired desired Source Device
ScanResult scanRes = (ScanResult) msg.obj;
dev = scanRes.getDevice();
stopSearchforLeAudioBroadcasters(dev,null);
mBCService.selectBroadcastSource(dev, scanRes, false, true);
break;
case AA_SCAN_FAILURE:
//Not able to find the given source
//ignore
break;
case AA_SCAN_TIMEOUT:
dev = (BluetoothDevice)msg.obj;
stopSearchforLeAudioBroadcasters(dev, null);
break;
}
}
};
private void notifyLocalBroadcastSourceFound(ArrayList<IBleBroadcastAudioScanAssistCallback> cbs) {
BluetoothDevice localDev =
BluetoothAdapter.getDefaultAdapter().getRemoteDevice(mBluetoothAdapter.getAddress());
String localName = BluetoothAdapter.getDefaultAdapter().getName();
ScanRecord record = null;
if (localName != null) {
byte name_len = (byte)localName.length();
byte[] bd_name = localName.getBytes(StandardCharsets.US_ASCII);
byte[] name_key = new byte[] {++name_len, 0x09 }; //0x09 TYPE:Name
byte[] scan_r = new byte[name_key.length + bd_name.length];
System.arraycopy(name_key, 0, scan_r, 0, name_key.length);
System.arraycopy(bd_name, 0, scan_r, name_key.length, bd_name.length);
record = ScanRecord.parseFromBytes(scan_r);
log ("Local name populated in fake Scan res:" + record.getDeviceName());
}
ScanResult scanRes = new ScanResult(localDev,
1, 1, 1,2, 0, 0, 0, record, 0);
if (cbs != null) {
for (IBleBroadcastAudioScanAssistCallback cb : cbs) {
try {
cb.onBleBroadcastSourceFound(scanRes);
} catch (RemoteException e) {
Log.e(TAG, "Exception while calling onBleBroadcastSourceFound");
}
}
}
}
public boolean searchforLeAudioBroadcasters (BluetoothDevice srcDevice,
ArrayList<IBleBroadcastAudioScanAssistCallback> cbs
) {
log( "searchforLeAudioBroadcasters: ");
BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();
mIsLocalBMSNotified = false;
if (scanner == null) {
Log.e(TAG, "startLeScan: cannot get BluetoothLeScanner");
return false;
}
synchronized (mLeAudioSourceScanCallbacks) {
if (mLeAudioSourceScanCallbacks.containsKey(cbs)) {
Log.e(TAG, "LE Scan has already started");
return false;
}
ScanCallback scanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
log( "onScanResult:" + result);
if (callbackType != ScanSettings.CALLBACK_TYPE_ALL_MATCHES) {
// Should not happen.
Log.e(TAG, "LE Scan has already started");
return;
}
ScanRecord scanRecord = result.getScanRecord();
//int pInterval = result.getPeriodicAdvertisingInterval();
if (scanRecord != null) {
Map<ParcelUuid, byte[]> listOfUuids = scanRecord.getServiceData();
if (listOfUuids != null) {
//ParcelUuid bmsUuid = new ParcelUuid(BroadcastService.BAAS_UUID);
//boolean isBroadcastSource = listOfUuids.containsKey(bmsUuid);
boolean isBroadcastSource = listOfUuids.containsKey(ParcelUuid.fromString(BAAS_UUID));
log( "isBroadcastSource:" + isBroadcastSource);
if (isBroadcastSource) {
log( "Broadcast Source Found:" + result.getDevice());
if (cbs != null) {
for (IBleBroadcastAudioScanAssistCallback cb : cbs) {
try {
cb.onBleBroadcastSourceFound(result);
} catch (RemoteException e) {
Log.e(TAG, "Exception while calling onBleBroadcastSourceFound");
}
}
} else {
if (srcDevice.equals(result.getDevice())) {
log("matching src Device found");
Message msg = mAutoAssistScanHandler.obtainMessage(AA_SCAN_SUCCESS);
msg.obj = result;
mAutoAssistScanHandler.sendMessage(msg);
}
}
} else {
log( "Broadcast Source UUID not preset, ignore");
}
} else {
Log.e(TAG, "Ignore no UUID");
return;
}
} else {
Log.e(TAG, "Scan record is null, ignoring this Scan res");
return;
}
//Before starting LE Scan, Call local APIs to find out if the local device
//is Broadcaster, then generate callback for Local device
if (!mIsLocalBMSNotified && isLocalLEAudioBroadcasting()) {
//Create a DUMMY scan result for colocated case
notifyLocalBroadcastSourceFound(cbs);
mIsLocalBMSNotified = true;
}
}
public void onScanFailed(int errorCode) {
Log.e(TAG, "Scan Failure:" + errorCode);
}
};
if (mBluetoothAdapter != null) {
if (cbs != null) {
mLeAudioSourceScanCallbacks.put(cbs, scanCallback);
} else {
//internal auto assist trigger remember it
//based on device
mBassAutoAssist.put(srcDevice, scanCallback);
}
ScanSettings settings = new ScanSettings.Builder().setCallbackType(
ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setLegacy(false)
.build();
ScanFilter.Builder filterBuilder = new ScanFilter.Builder();
ScanFilter srcFilter = filterBuilder.setServiceUuid(
ParcelUuid.fromString(BAAS_UUID)).build();
List<ScanFilter> filters = new ArrayList<ScanFilter>();
if (!mIsLocalBMSNotified && isLocalLEAudioBroadcasting()) {
//Create a DUMMY scan result for colocated case
notifyLocalBroadcastSourceFound(cbs);
mIsLocalBMSNotified = true;
}
scanner.startScan(filters, settings, scanCallback);
return true;
} else {
Log.e(TAG, "searchforLeAudioBroadcasters: Adapter is NULL");
return false;
}
}
}
public boolean stopSearchforLeAudioBroadcasters(BluetoothDevice srcDev,
ArrayList<IBleBroadcastAudioScanAssistCallback> cbs) {
log( "stopSearchforLeAudioBroadcasters()");
BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();
if (scanner == null) {
return false;
}
ScanCallback scanCallback = null;
if (cbs != null) {
scanCallback = mLeAudioSourceScanCallbacks.remove(cbs);
} else {
scanCallback = mLeAudioSourceScanCallbacks.remove(srcDev);
}
if (scanCallback == null) {
log( "scan not started yet");
return false;
}
scanner.stopScan(scanCallback);
return true;
}
private int convertConfigurationSRToCapabilitySR(byte sampleRate) {
int ret = BluetoothCodecConfig.SAMPLE_RATE_NONE;
switch (sampleRate) {
case 1:
ret = BluetoothCodecConfig.SAMPLE_RATE_NONE; break;
case 2:
ret = BluetoothCodecConfig.SAMPLE_RATE_NONE; break;
case 3:
ret = BluetoothCodecConfig.SAMPLE_RATE_NONE; break;
case 4:
//ret = BluetoothCodecConfig.SAMPLE_RATE_32000; break;
case 5:
ret = BluetoothCodecConfig.SAMPLE_RATE_44100; break;
case 6:
ret = BluetoothCodecConfig.SAMPLE_RATE_48000; break;
}
log("convertConfigurationSRToCapabilitySR returns:" + ret);
return ret;
}
private boolean isSampleRateSupported(BluetoothDevice device, byte sampleRate) {
boolean ret = false;
/*_PACS
BluetoothCodecConfig[] supportedConfigs = mPacsClientService.getSinkPacs(device);
int actualSampleRate = convertConfigurationSRToCapabilitySR(sampleRate);
if (actualSampleRate == BluetoothCodecConfig.SAMPLE_RATE_NONE) {
return false;
}
for (int i=0; i<supportedConfigs.length; i++) {
if (actualSampleRate == supportedConfigs[i].getSampleRate()) {
ret = true;
}
}
log("isSampleRateSupported returns:" + ret);
_PACS*/
return ret;
}
public List<BleBroadcastSourceChannel> selectBises(BluetoothDevice device,
BleBroadcastSourceInfo srcInfo, BaseData base) {
boolean noPref = SystemProperties.getBoolean("persist.vendor.service.bt.bass_no_pref", false);
if (noPref) {
log("No pref selected");
return null;
} else {
/*_PACS
mPacsClientService = PacsClientService.getPacsClientService();
List<BleBroadcastSourceChannel> bChannels = new ArrayList<BleBroadcastSourceChannel>();
//if (mPacsClientService == null) {
log("selectBises: Pacs Service is null, pick BISes apropriately");
//Pacs not available
if (base != null) {
bChannels = base.pickAllBroadcastChannels();
} else {
bChannels = null;
}
return bChannels;
//}
if (mPacsClientService != null) {
int supportedLocations = 1/*mPacsClientService.getSinkLocations(device);
ArrayList<BaseData.BaseInformation> broadcastedCodecInfo = base.getBISIndexInfos();
if (broadcastedCodecInfo != null) {
for (int i=0; i<broadcastedCodecInfo.size(); i++) {
HashMap<Integer, String> consolidatedUniqueCodecInfo = broadcastedCodecInfo.get(i).consolidatedUniqueCodecInfo;
byte index = broadcastedCodecInfo.get(i).index;
if (consolidatedUniqueCodecInfo != null) {
byte[] bisChannelLocation = consolidatedUniqueCodecInfo.get(LOCATION).getBytes();
byte[] locationValue = new byte[4];
System.arraycopy(bisChannelLocation, 2, locationValue, 0, 4);
log ("locationValue>>> ");
printByteArray(locationValue);
ByteBuffer wrapped = ByteBuffer.wrap(locationValue);
int bisLocation = wrapped.getInt();
log("bisLocation: " + bisLocation);
int reversebisLoc = Integer.reverseBytes(bisLocation);
log("reversebisLoc: " + reversebisLoc);
byte[] bisSampleRate = consolidatedUniqueCodecInfo.get(SAMPLE_RATE).getBytes();
byte bisSR = bisSampleRate[2];
//using bitwise operand as Location can be bitmask
if (isSampleRateSupported(device, bisSR) && (reversebisLoc & supportedLocations) == supportedLocations) {
log("matching location: bisLocation " + reversebisLoc + ":: " + supportedLocations);
BleBroadcastSourceChannel bc = new BleBroadcastSourceChannel(index, String.valueOf(index), true);
bChannels.add(bc);
}
}
}
}
}
if (bChannels != null && bChannels.size() == 0) {
log("selectBises: no channel are selected");
bChannels = null;
}
return bChannels;
_PACS*/
}
return null;
}
public void triggerAutoAssist (BleBroadcastSourceInfo srcInfo) {
//searchforLeAudioBroadcasters (srcInfo.getSourceDevice(), null, AUTO_ASSIST_SCAN_TIMEOUT);
BluetoothDevice dev = srcInfo.getSourceDevice();
Message msg = mAutoAssistScanHandler.obtainMessage(AA_START_SCAN);
msg.obj = srcInfo.getSourceDevice();
mAutoAssistScanHandler.sendMessage(msg);
}
static void log(String msg) {
if (BassClientStateMachine.BASS_DBG) {
Log.d(TAG, msg);
}
}
static void printByteArray(byte[] array) {
log("Entire byte Array as string: " + Arrays.toString(array));
log("printitng byte by bte");
for (int i=0; i<array.length; i++) {
log( "array[" + i + "] :" + Byte.toUnsignedInt(array[i]));
}
}
}