Here are the examples of the java api android.bluetooth.BluetoothGattServerCallback taken from open source projects. By voting up you can indicate which examples are most useful and appropriate.
13 Examples
18
Source : BluetoothServerHelper.java
with MIT License
from covidsafe
with MIT License
from covidsafe
public clreplaced BluetoothServerHelper {
static Context cxt;
public static void createServer(Context cxt) {
BluetoothServerHelper.cxt = cxt;
Constants.writtenUUIDs = new HashSet<>();
Log.e("bleserver", "createserver");
BluetoothManager bluetoothManager = (BluetoothManager) cxt.getSystemService(Context.BLUETOOTH_SERVICE);
if (Constants.gattServer == null) {
Constants.gattServer = bluetoothManager.openGattServer(cxt, gattServerCallback);
Log.e("bleserver", "is server null " + (Constants.gattServer == null));
if (Constants.gattServer != null) {
BluetoothGattService service = new BluetoothGattService(Constants.GATT_SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY);
int permission = BluetoothGattCharacteristic.PERMISSION_WRITE | BluetoothGattCharacteristic.PERMISSION_READ;
int property = BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_READ;
BluetoothGattCharacteristic charac1 = new BluetoothGattCharacteristic(Constants.CHARACTERISTIC_UUID, property, permission);
service.addCharacteristic(charac1);
Constants.gattServer.addService(service);
}
}
}
public static void stopServer() {
if (Constants.gattServer != null) {
Constants.gattServer.close();
Constants.gattServer = null;
}
}
public static BluetoothGattServerCallback gattServerCallback = new BluetoothGattServerCallback() {
@Override
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
Log.e("bleserver", "read request " + characteristic.getUuid().toString());
if (characteristic.getUuid().equals(Constants.CHARACTERISTIC_UUID)) {
Log.e("bleserver", "going to send " + Constants.contactUUID);
byte[] contactUuidBytes = ByteUtils.uuid2bytes(Constants.contactUUID);
Log.e("bleserver", "converted to bytes " + contactUuidBytes.length);
boolean status = Constants.gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, contactUuidBytes);
Log.e("bleserver", "status " + status);
}
Log.e("bleserver", "finished read");
}
@Override
public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
Log.e("bleserver", "write request " + characteristic.getUuid().toString());
Log.e("bleserver", "preparedwrite " + preparedWrite);
Log.e("bleserver", "responseNeeded " + responseNeeded);
if (responseNeeded) {
Constants.gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, null);
}
// 0-15: UUID
// 16: RSSI
// 17: phone model
if (value != null) {
Log.e("bleserver", "data len " + value.length);
if (value.length == 16 || value.length == 17 || value.length == 18) {
//
byte[] uuidByte = Arrays.copyOfRange(value, 0, 16);
String contactUuid = ByteUtils.byte2UUIDstring(uuidByte);
Log.e("bleserver", "contactuuid " + contactUuid);
int rssi = 0;
// byte[-128,127] => int[0,255] => rssi[-255,0]
if (value.length >= 17) {
rssi = -ByteUtils.byteConvert(value[16]);
Log.e("bleserver", "received an rssi value of " + rssi);
} else {
Log.e("bleserver", "rssi value not received");
}
Log.e("bleserver", "rssi " + rssi + "," + device.getAddress());
int deviceID = 0;
if (value.length == 18) {
deviceID = value[17];
}
if (!Constants.writtenUUIDs.contains(contactUuid) && BluetoothUtils.rssiThresholdCheck(rssi, deviceID)) {
Constants.writtenUUIDs.add(contactUuid);
Utils.bleLogToDatabase(cxt, contactUuid, rssi, TimeUtils.getTime(), deviceID);
}
} else {
for (Byte b : value) {
Log.e("bleserver", b + "");
}
}
} else {
Log.e("bleserver", "write data is null");
}
Log.e("bleserver", "finished write");
}
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
super.onConnectionStateChange(device, status, newState);
if (newState == BluetoothProfile.STATE_CONNECTED) {
Log.e("bleserver", "connected " + device.getAddress());
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.e("bleserver", "disconnected");
}
}
@Override
public void onServiceAdded(int status, BluetoothGattService service) {
super.onServiceAdded(status, service);
if (service != null) {
Log.e("bleserver", "service added " + service.getUuid());
List<BluetoothGattCharacteristic> characs = service.getCharacteristics();
if (characs != null) {
for (BluetoothGattCharacteristic charac : characs) {
Log.e("bleserver", "charac " + charac.getUuid());
if (charac != null) {
byte[] bb = charac.getValue();
if (bb != null) {
for (Byte b : bb) {
Log.e("bleserver", b + "");
}
}
}
}
}
}
}
};
}
17
Source : ShadowBluetoothManager.java
with Apache License 2.0
from google
with Apache License 2.0
from google
@Implementation
public BluetoothGattServer openGattServer(Context context, BluetoothGattServerCallback callback) {
return ReflectionHelpers.newInstance(BluetoothGattServer.clreplaced);
}
15
Source : GattServerMainActivity.java
with The Unlicense
from kshtj24
with The Unlicense
from kshtj24
/**
* Created by kreplacedij.saxena on 20-11-2017.
*/
public clreplaced GattServerMainActivity extends AppCompatActivity {
private ArrayAdapter<?> genericListAdapter;
private ArrayList<BluetoothDevice> deviceArrayList;
private ListView deviceListView;
private BluetoothGattServer bluetoothGattServer;
private BluetoothManager bluetoothManager;
private BluetoothGattServerCallback bluetoothGattServerCallback;
private BluetoothDevice miBand;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initialiseViews();
getPermissions();
enableBTAndDiscover();
}
private void initialiseViews() {
if (bluetoothManager == null)
bluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
bluetoothGattServerCallback = new BluetoothGattServerCallback() {
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
switch(newState) {
case BluetoothGattServer.STATE_CONNECTED:
Log.i("Device", "Connected");
addServices();
break;
case BluetoothGattServer.STATE_DISCONNECTED:
Log.e("Device", "Disconnected");
break;
}
}
@Override
public void onServiceAdded(int status, BluetoothGattService service) {
for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
bluetoothGattServer.notifyCharacteristicChanged(miBand, characteristic, false);
}
}
@Override
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
// super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
}
@Override
public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
Log.e("Characteristic", "Write request received");
}
@Override
public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
super.onDescriptorReadRequest(device, requestId, offset, descriptor);
}
@Override
public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);
}
@Override
public void onNotificationSent(BluetoothDevice device, int status) {
super.onNotificationSent(device, status);
}
};
bluetoothGattServer = bluetoothManager.openGattServer(GattServerMainActivity.this, bluetoothGattServerCallback);
deviceListView = (ListView) findViewById(R.id.deviceListView);
deviceListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
miBand = (BluetoothDevice) parent.gereplacedemAtPosition(position);
connectDevice();
}
});
}
private void addServices() {
// bluetoothGattServer.addService(new BluetoothGattService(UUIDs.HEART_RATE_SERVICE, BluetoothGattService.SERVICE_TYPE_PRIMARY));
// bluetoothGattServer.addService(new BluetoothGattService(UUIDs.CUSTOM_BATTERY_SERVICE,BluetoothGattService.SERVICE_TYPE_PRIMARY));
}
private void getPermissions() {
if (getPackageManager().hreplacedystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
if (ContextCompat.checkSelfPermission(getApplicationContext(), android.Manifest.permission.BLUETOOTH_ADMIN) == PackageManager.PERMISSION_DENIED) {
shouldShowRequestPermissionRationale("Please grant this app the following permissions to make it work properly");
requestPermissions(new String[] { android.Manifest.permission.BLUETOOTH_ADMIN, android.Manifest.permission.BLUETOOTH }, 1);
}
if (ContextCompat.checkSelfPermission(getApplicationContext(), android.Manifest.permission_group.LOCATION) == PackageManager.PERMISSION_DENIED) {
shouldShowRequestPermissionRationale("Please grand Location permission for scanning devices nearby");
requestPermissions(new String[] { android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION }, 1);
}
} else {
Toast.makeText(GattServerMainActivity.this, "This device doesn't support Bluetooth LE", Toast.LENGTH_LONG).show();
}
}
private void connectDevice() {
if (miBand.getBondState() == BluetoothDevice.BOND_NONE) {
miBand.createBond();
Log.e("Bond", "Created with Device");
}
bluetoothGattServer.connect(miBand, true);
}
private void enableBTAndDiscover() {
final BluetoothAdapter bluetoothAdapter = ((BluetoothManager) getSystemService(BLUETOOTH_SERVICE)).getAdapter();
final ProgressDialog searchProgress = new ProgressDialog(GattServerMainActivity.this);
searchProgress.setIndeterminate(true);
searchProgress.setreplacedle("BlueTooth LE Device");
searchProgress.setMessage("Searching...");
searchProgress.setCancelable(false);
searchProgress.show();
deviceArrayList = new ArrayList<BluetoothDevice>();
genericListAdapter = new ArrayAdapter<>(GattServerMainActivity.this, android.R.layout.simple_list_item_1, deviceArrayList);
deviceListView.setAdapter(genericListAdapter);
if (bluetoothAdapter == null) {
Toast.makeText(GattServerMainActivity.this, "Bluetooth not supported on this device", Toast.LENGTH_LONG).show();
return;
}
if (!bluetoothAdapter.isEnabled()) {
startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), 1);
}
final ScanCallback leDeviceScanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
Log.e("TAG", "Device found" + " " + result.getDevice().getAddress() + " " + result.getDevice().getName());
if (!deviceArrayList.contains(result.getDevice())) {
deviceArrayList.add(result.getDevice());
genericListAdapter.notifyDataSetChanged();
}
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
}
};
bluetoothAdapter.getBluetoothLeScanner().startScan(leDeviceScanCallback);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
bluetoothAdapter.getBluetoothLeScanner().stopScan(leDeviceScanCallback);
searchProgress.dismiss();
}
}, 10000);
}
}
15
Source : BluetoothGattServerHelper.java
with Apache License 2.0
from google
with Apache License 2.0
from google
/**
* Helper for simplifying operations on {@link BluetoothGattServer}.
*/
@TargetApi(18)
public clreplaced BluetoothGattServerHelper {
private static final String TAG = "BluetoothGattServerHelper";
@VisibleForTesting
static final long OPERATION_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(1);
private static final int MAX_PARALLEL_OPERATIONS = 5;
/**
* BT operation types that can be in flight.
*/
public enum OperationType {
ADD_SERVICE, CLOSE_CONNECTION, START_ADVERTISING
}
/**
* Server handler to notify of server status changes.
*/
public interface ConnectionStatusListener {
/**
* Listener called when the number of connections to the server changes.
*
* @param numConnections the number of current connections.
* @param device The remote device which causes the change
* @param changeState
* BluetoothGattServer.STATE_CONNECTED or BluetoothGattServer.STATE_DISCONNECTED indicating
* whether the callback is triggered by a device connected or disconnected
*/
void onConnectionsChanged(int numConnections, BluetoothDevice device, int changeState);
}
private final Object operationLock = new Object();
private final BluetoothGattServerCallback gattServerCallback = new GattServerCallback();
private BluetoothOperationExecutor bluetoothOperationScheduler = new BluetoothOperationExecutor(MAX_PARALLEL_OPERATIONS);
private final Context context;
private final ConnectionStatusListener statusListener;
final ConcurrentMap<BluetoothDevice, BluetoothGattServerConnection> connections = new ConcurrentHashMap<BluetoothDevice, BluetoothGattServerConnection>();
final ConcurrentMap<UUID, BluetoothSocketService> services = new ConcurrentHashMap<>();
private BluetoothGattServer bluetoothGattServer;
public BluetoothGattServerHelper(Context context, ConnectionStatusListener statusListener) {
this.context = context;
this.statusListener = statusListener;
}
public void open() {
synchronized (operationLock) {
BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
if (bluetoothManager == null) {
Log.e(TAG, "BluetoothManager is null!");
return;
}
bluetoothGattServer = bluetoothManager.openGattServer(context, gattServerCallback);
if (bluetoothGattServer == null) {
Log.e(TAG, "BluetoothGattServer is null!");
}
}
}
/**
* Notifies all clients of a new camera status.
*/
public void notifyStatusChanged(BluetoothSocketService service) {
for (BluetoothGattServerConnection connection : connections.values()) {
connection.notifyStatusChanged(service.getServiceConfig());
}
}
public void addService(BluetoothSocketService service) throws BluetoothException {
services.put(service.getServiceConfig().getServiceUuid(), service);
openService(service);
}
public void removeService(BluetoothSocketService service) {
synchronized (operationLock) {
services.remove(service.getServiceConfig().getServiceUuid());
if (bluetoothGattServer != null) {
bluetoothGattServer.removeService(service.getGattService());
}
}
}
public void close() {
synchronized (operationLock) {
if (bluetoothGattServer != null) {
bluetoothGattServer.close();
bluetoothGattServer = null;
}
services.clear();
connections.clear();
}
}
@VisibleForTesting
void sendNotification(BluetoothDevice device, BluetoothGattCharacteristic characteristic, byte[] data, boolean confirm) throws BluetoothException {
Log.d(TAG, String.format("Sending a %s of %d bytes on characteristics %s on device %s.", confirm ? "indication" : "notification", data.length, characteristic.getUuid(), device));
if (getConnectionByDevice(device) == null) {
Log.e(TAG, String.format("Ignoring request to send notification on unknown device: %s", device));
return;
}
synchronized (operationLock) {
BluetoothGattCharacteristic clonedCharacteristic = BluetoothGattUtils.clone(characteristic);
clonedCharacteristic.setValue(data);
try {
getBluetoothGattServer().notifyCharacteristicChanged(device, clonedCharacteristic, confirm);
} catch (Exception e) {
throw new BluetoothException(e.getMessage(), e);
}
}
}
@VisibleForTesting
void closeConnection(final BluetoothDevice bluetoothDevice) throws BluetoothException {
BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
if (bluetoothManager == null) {
Log.e(TAG, "BluetoothManager is null!");
return;
}
int connectionSate = bluetoothManager.getConnectionState(bluetoothDevice, BluetoothProfile.GATT);
if (connectionSate != BluetoothGatt.STATE_CONNECTED) {
Log.i(TAG, "Connection already closed.");
return;
}
bluetoothOperationScheduler.execute(new Operation<Void>(OperationType.CLOSE_CONNECTION) {
@Override
public void run() throws BluetoothException {
Log.i(TAG, String.format("Cancelling connection to BLE device:%s", bluetoothDevice));
getBluetoothGattServer().cancelConnection(bluetoothDevice);
}
}, OPERATION_TIMEOUT_MILLIS);
}
private void openService(BluetoothSocketService service) throws BluetoothException {
synchronized (operationLock) {
BluetoothGattService gattService = service.getServiceConfig().getBluetoothGattService();
service.setGattService(gattService);
try {
bluetoothOperationScheduler.execute(new Operation<Void>(OperationType.ADD_SERVICE, gattService) {
@Override
public void run() throws BluetoothException {
boolean success = bluetoothGattServer != null && bluetoothGattServer.addService(gattService);
if (!success) {
throw new BluetoothException(String.format("Fails on adding service:%s", gattService));
} else {
Log.i(TAG, String.format("Added service:%s", gattService));
}
}
}, OPERATION_TIMEOUT_MILLIS);
} catch (BluetoothException e) {
close();
throw e;
}
}
}
private BluetoothGattServerConnection getConnectionByDevice(BluetoothDevice device) throws BluetoothGattException {
BluetoothGattServerConnection bluetoothLeConnection = connections.get(device);
if (bluetoothLeConnection == null) {
throw new BluetoothGattException(String.format("Received operation on an unknown device: %s", device), BluetoothGatt.GATT_FAILURE);
}
return bluetoothLeConnection;
}
private BluetoothGattServer getBluetoothGattServer() {
synchronized (operationLock) {
if (bluetoothGattServer == null) {
throw new IllegalStateException("Bluetooth GATT server is not open.");
} else {
return bluetoothGattServer;
}
}
}
private clreplaced GattServerCallback extends BluetoothGattServerCallback {
private static final String TAG = "GattServerCallback";
@Override
public void onServiceAdded(int status, BluetoothGattService service) {
Log.e(TAG, "onServiceAdded");
bluetoothOperationScheduler.notifyCompletion(new Operation<Void>(OperationType.ADD_SERVICE, service), status);
}
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
Log.i(TAG, String.format("onConnectionStateChange: device:%s status:%s state:%d", device, BluetoothGattUtils.getMessageForStatusCode(status), newState));
switch(newState) {
case BluetoothGattServer.STATE_CONNECTED:
if (status != BluetoothGatt.GATT_SUCCESS) {
Log.e(TAG, String.format("Connection to %s failed: %s", device, BluetoothGattUtils.getMessageForStatusCode(status)));
return;
}
Log.i(TAG, String.format("Connected to device %s.", device));
if (connections.containsKey(device)) {
Log.w(TAG, String.format("A connection is already open with device %s. Keeping existing one.", device));
return;
}
connections.put(device, new BluetoothGattServerConnection(BluetoothGattServerHelper.this, device));
if (statusListener != null) {
statusListener.onConnectionsChanged(connections.size(), device, BluetoothGattServer.STATE_CONNECTED);
}
break;
case BluetoothGattServer.STATE_DISCONNECTED:
Log.d(TAG, String.format("Disconnection from %s for: %s", device, BluetoothGattUtils.getMessageForStatusCode(status)));
connections.remove(device);
bluetoothOperationScheduler.notifyCompletion(new Operation<Void>(OperationType.CLOSE_CONNECTION), status);
if (statusListener != null) {
statusListener.onConnectionsChanged(connections.size(), device, BluetoothGattServer.STATE_DISCONNECTED);
}
break;
default:
Log.e(TAG, String.format("Unexpected connection state: %d", newState));
return;
}
}
@Override
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
BluetoothSocketService service = services.get(characteristic.getService().getUuid());
if (service == null) {
Log.e(TAG, "Service not found " + characteristic.getService().getUuid());
return;
}
if (!service.isEnabled()) {
Log.e(TAG, "Service not enabled " + characteristic.getService().getUuid());
return;
}
try {
byte[] value = getConnectionByDevice(device).readCharacteristic(service.getServiceConfig(), offset, characteristic);
getBluetoothGattServer().sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
} catch (BluetoothGattException e) {
Log.e(TAG, String.format("Could not read %s on device %s at offset %d", BluetoothGattUtils.toString(characteristic), device, offset), e);
getBluetoothGattServer().sendResponse(device, requestId, e.getGattErrorCode(), offset, null);
}
}
@Override
public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
BluetoothSocketService service = services.get(characteristic.getService().getUuid());
if (service == null) {
Log.e(TAG, "Service not found " + characteristic.getService().getUuid());
return;
}
if (!service.isEnabled()) {
Log.e(TAG, "Service not enabled " + characteristic.getService().getUuid());
return;
}
try {
getConnectionByDevice(device).writeCharacteristic(service.getServiceConfig(), characteristic, preparedWrite, offset, value);
if (responseNeeded) {
getBluetoothGattServer().sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
}
} catch (BluetoothGattException e) {
Log.e(TAG, String.format("Could not write %s on device %s at offset %d", BluetoothGattUtils.toString(characteristic), device, offset), e);
getBluetoothGattServer().sendResponse(device, requestId, e.getGattErrorCode(), offset, null);
}
}
@Override
public void onNotificationSent(BluetoothDevice device, int status) {
Log.d(TAG, String.format("Received onNotificationSent for device %s with status %s", device, status));
try {
getConnectionByDevice(device).onNotificationSent(status);
} catch (BluetoothGattException e) {
Log.e(TAG, String.format("An error occurred when receiving onNotificationSent"), e);
}
}
@Override
public void onMtuChanged(BluetoothDevice device, int mtu) {
Log.d(TAG, String.format("Received onMtuChanged for device %s with mtu %s", device, mtu));
try {
getConnectionByDevice(device).setMtu(mtu);
} catch (BluetoothGattException e) {
Log.e(TAG, String.format("An error occurred when receiving onMtuChanged."), e);
}
}
@Override
public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
getBluetoothGattServer().sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
}
@Override
public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
Log.d(TAG, String.format("Received onExecuteWrite for device %s with execute: %s", device, execute));
try {
getConnectionByDevice(device).executeWrite(execute);
getBluetoothGattServer().sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null);
} catch (BluetoothGattException e) {
Log.e(TAG, String.format("Could not execute pending writes on device %s", device), e);
getBluetoothGattServer().sendResponse(device, requestId, e.getGattErrorCode(), 0, null);
}
}
}
}
14
Source : BleAdvertiser.java
with MIT License
from ito-org
with MIT License
from ito-org
public clreplaced BleAdvertiser {
private static final String LOG_TAG = "BleAdvertiser";
private final Context context;
private byte[] broadcastData;
private BluetoothLeAdvertiser bluetoothLeAdvertiser;
private AdvertiseCallback bluetoothAdvertiseCallback;
private BluetoothGattServer mBluetoothGattServer;
private BluetoothManager bluetoothManager;
private final BluetoothAdapter bluetoothAdapter;
public BleAdvertiser(BluetoothManager bluetoothManager, Context context) {
bluetoothAdapter = bluetoothManager.getAdapter();
this.bluetoothLeAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser();
this.bluetoothManager = bluetoothManager;
this.context = context;
}
public void setBroadcastData(byte[] broadcastData) {
this.broadcastData = broadcastData;
// Restart advertising so that mac address changes
// Otherwise the device could be recognized that way even though the UUID has changed
if (bluetoothAdvertiseCallback != null) {
restartAdvertising();
}
}
private void restartAdvertising() {
stopAdvertising();
startAdvertising();
}
public void startAdvertising() {
Log.i(LOG_TAG, "Starting Advertising");
AdvertiseSettings settings = new AdvertiseSettings.Builder().setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED).setConnectable(true).setTimeout(0).setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM).build();
AdvertiseData data = new AdvertiseData.Builder().setIncludeTxPowerLevel(true).addServiceUuid(new ParcelUuid(BandemicProfile.BANDEMIC_SERVICE)).setIncludeDeviceName(false).build();
bluetoothAdvertiseCallback = new AdvertiseCallback() {
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
super.onStartSuccess(settingsInEffect);
Log.i(LOG_TAG, "Advertising onStartSuccess");
}
@Override
public void onStartFailure(int errorCode) {
super.onStartFailure(errorCode);
Log.e(LOG_TAG, "Advertising onStartFailure: " + errorCode);
// TODO
}
};
// Set fixed device name to avoid being recognizable except though UUID
// Name is not sent in advertisement anyway but can be requested though GATT Server
// and I don't see any way to disable that
bluetoothAdapter.setName("Phone");
// TODO: check if null when launching with Bluetooth disabled
bluetoothLeAdvertiser.startAdvertising(settings, data, bluetoothAdvertiseCallback);
mBluetoothGattServer = bluetoothManager.openGattServer(context, mGattServerCallback);
if (mBluetoothGattServer == null) {
Log.w(LOG_TAG, "Unable to create GATT server");
return;
}
mBluetoothGattServer.addService(BandemicProfile.createBandemicService());
}
public void stopAdvertising() {
Log.d(LOG_TAG, "Stopping advertising");
if (bluetoothAdvertiseCallback != null) {
bluetoothLeAdvertiser.stopAdvertising(bluetoothAdvertiseCallback);
bluetoothAdvertiseCallback = null;
}
if (mBluetoothGattServer != null) {
mBluetoothGattServer.close();
mBluetoothGattServer = null;
}
}
private BluetoothGattServerCallback mGattServerCallback = new BluetoothGattServerCallback() {
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
Log.i(LOG_TAG, "BluetoothDevice CONNECTED: " + device);
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.i(LOG_TAG, "BluetoothDevice DISCONNECTED: " + device);
}
}
@Override
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
if (BandemicProfile.HASH_OF_UUID.equals(characteristic.getUuid())) {
Log.i(LOG_TAG, "Read Hash of UUID");
Log.i(LOG_TAG, "Offset: " + offset);
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, Arrays.copyOfRange(broadcastData, offset, broadcastData.length));
} else {
// Invalid characteristic
Log.w(LOG_TAG, "Invalid Characteristic Read: " + characteristic.getUuid());
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, 0, null);
}
}
@Override
public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
Log.w(LOG_TAG, "Unknown descriptor read request");
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, 0, null);
}
@Override
public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
Log.w(LOG_TAG, "Unknown descriptor write request");
if (responseNeeded) {
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, 0, null);
}
}
};
}
13
Source : GattServer.java
with Apache License 2.0
from Nilhcem
with Apache License 2.0
from Nilhcem
public clreplaced GattServer {
private static final String TAG = GattServer.clreplaced.getSimpleName();
public interface GattServerListener {
void onInteractorWritten();
byte[] onCounterRead();
}
private Context mContext;
private GattServerListener mListener;
private BluetoothManager mBluetoothManager;
private BluetoothGattServer mBluetoothGattServer;
private BluetoothLeAdvertiser mBluetoothLeAdvertiser;
/* Collection of notification subscribers */
private Set<BluetoothDevice> mRegisteredDevices = new HashSet<>();
/**
* Listens for Bluetooth adapter events to enable/disable
* advertising and server functionality.
*/
private final BroadcastReceiver mBluetoothReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF);
switch(state) {
case BluetoothAdapter.STATE_ON:
startAdvertising();
startServer();
break;
case BluetoothAdapter.STATE_OFF:
stopServer();
stopAdvertising();
break;
default:
// Do nothing
break;
}
}
};
private AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() {
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
Log.i(TAG, "LE Advertise Started.");
}
@Override
public void onStartFailure(int errorCode) {
Log.w(TAG, "LE Advertise Failed: " + errorCode);
}
};
private BluetoothGattServerCallback mGattServerCallback = new BluetoothGattServerCallback() {
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
Log.i(TAG, "BluetoothDevice CONNECTED: " + device);
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.i(TAG, "BluetoothDevice DISCONNECTED: " + device);
// Remove device from any active subscriptions
mRegisteredDevices.remove(device);
}
}
@Override
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
if (CHARACTERISTIC_COUNTER_UUID.equals(characteristic.getUuid())) {
Log.i(TAG, "Read counter");
byte[] value = mListener.onCounterRead();
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, value);
} else {
// Invalid characteristic
Log.w(TAG, "Invalid Characteristic Read: " + characteristic.getUuid());
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, 0, null);
}
}
@Override
public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
if (CHARACTERISTIC_INTERACTOR_UUID.equals(characteristic.getUuid())) {
Log.i(TAG, "Write interactor");
if (mListener != null) {
mListener.onInteractorWritten();
}
notifyRegisteredDevices();
} else {
// Invalid characteristic
Log.w(TAG, "Invalid Characteristic Write: " + characteristic.getUuid());
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, 0, null);
}
}
@Override
public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
if (DESCRIPTOR_CONFIG.equals(descriptor.getUuid())) {
Log.d(TAG, "Config descriptor read request");
byte[] returnValue;
if (mRegisteredDevices.contains(device)) {
returnValue = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
} else {
returnValue = BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;
}
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, returnValue);
} else if (DESCRIPTOR_USER_DESC.equals(descriptor.getUuid())) {
Log.d(TAG, "User description descriptor read request");
byte[] returnValue = AwesomenessProfile.getUserDescription(descriptor.getCharacteristic().getUuid());
returnValue = Arrays.copyOfRange(returnValue, offset, returnValue.length);
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, returnValue);
} else {
Log.w(TAG, "Unknown descriptor read request");
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, 0, null);
}
}
@Override
public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
if (DESCRIPTOR_CONFIG.equals(descriptor.getUuid())) {
if (Arrays.equals(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE, value)) {
Log.d(TAG, "Subscribe device to notifications: " + device);
mRegisteredDevices.add(device);
} else if (Arrays.equals(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE, value)) {
Log.d(TAG, "Unsubscribe device from notifications: " + device);
mRegisteredDevices.remove(device);
}
if (responseNeeded) {
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null);
}
} else {
Log.w(TAG, "Unknown descriptor write request");
if (responseNeeded) {
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, 0, null);
}
}
}
};
public void onCreate(Context context, GattServerListener listener) throws RuntimeException {
mContext = context;
mListener = listener;
mBluetoothManager = (BluetoothManager) context.getSystemService(BLUETOOTH_SERVICE);
BluetoothAdapter bluetoothAdapter = mBluetoothManager.getAdapter();
if (!checkBluetoothSupport(bluetoothAdapter)) {
throw new RuntimeException("GATT server requires Bluetooth support");
}
// Register for system Bluetooth events
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
mContext.registerReceiver(mBluetoothReceiver, filter);
if (!bluetoothAdapter.isEnabled()) {
Log.d(TAG, "Bluetooth is currently disabled... enabling");
bluetoothAdapter.enable();
} else {
Log.d(TAG, "Bluetooth enabled... starting services");
startAdvertising();
startServer();
}
}
public void onDestroy() {
BluetoothAdapter bluetoothAdapter = mBluetoothManager.getAdapter();
if (bluetoothAdapter.isEnabled()) {
stopServer();
stopAdvertising();
}
mContext.unregisterReceiver(mBluetoothReceiver);
mListener = null;
}
private boolean checkBluetoothSupport(BluetoothAdapter bluetoothAdapter) {
if (bluetoothAdapter == null) {
Log.w(TAG, "Bluetooth is not supported");
return false;
}
if (!mContext.getPackageManager().hreplacedystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Log.w(TAG, "Bluetooth LE is not supported");
return false;
}
return true;
}
private void startAdvertising() {
BluetoothAdapter bluetoothAdapter = mBluetoothManager.getAdapter();
mBluetoothLeAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser();
if (mBluetoothLeAdvertiser == null) {
Log.w(TAG, "Failed to create advertiser");
return;
}
AdvertiseSettings settings = new AdvertiseSettings.Builder().setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED).setConnectable(true).setTimeout(0).setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM).build();
AdvertiseData data = new AdvertiseData.Builder().setIncludeDeviceName(true).setIncludeTxPowerLevel(false).addServiceUuid(new ParcelUuid(SERVICE_UUID)).build();
mBluetoothLeAdvertiser.startAdvertising(settings, data, mAdvertiseCallback);
}
private void stopAdvertising() {
if (mBluetoothLeAdvertiser == null) {
return;
}
mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback);
}
private void startServer() {
mBluetoothGattServer = mBluetoothManager.openGattServer(mContext, mGattServerCallback);
if (mBluetoothGattServer == null) {
Log.w(TAG, "Unable to create GATT server");
return;
}
mBluetoothGattServer.addService(createAwesomenessService());
}
private void stopServer() {
if (mBluetoothGattServer == null) {
return;
}
mBluetoothGattServer.close();
}
private BluetoothGattService createAwesomenessService() {
BluetoothGattService service = new BluetoothGattService(SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY);
// Counter characteristic (read-only, supports notifications)
BluetoothGattCharacteristic counter = new BluetoothGattCharacteristic(CHARACTERISTIC_COUNTER_UUID, BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_NOTIFY, BluetoothGattCharacteristic.PERMISSION_READ);
BluetoothGattDescriptor counterConfig = new BluetoothGattDescriptor(DESCRIPTOR_CONFIG, BluetoothGattDescriptor.PERMISSION_READ | BluetoothGattDescriptor.PERMISSION_WRITE);
counter.addDescriptor(counterConfig);
BluetoothGattDescriptor counterDescription = new BluetoothGattDescriptor(DESCRIPTOR_USER_DESC, BluetoothGattDescriptor.PERMISSION_READ);
counter.addDescriptor(counterDescription);
// Interactor characteristic
BluetoothGattCharacteristic interactor = new BluetoothGattCharacteristic(CHARACTERISTIC_INTERACTOR_UUID, BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE, BluetoothGattCharacteristic.PERMISSION_WRITE);
BluetoothGattDescriptor interactorDescription = new BluetoothGattDescriptor(DESCRIPTOR_USER_DESC, BluetoothGattDescriptor.PERMISSION_READ);
interactor.addDescriptor(interactorDescription);
service.addCharacteristic(counter);
service.addCharacteristic(interactor);
return service;
}
private void notifyRegisteredDevices() {
if (mRegisteredDevices.isEmpty()) {
Log.i(TAG, "No subscribers registered");
return;
}
Log.i(TAG, "Sending update to " + mRegisteredDevices.size() + " subscribers");
for (BluetoothDevice device : mRegisteredDevices) {
BluetoothGattCharacteristic counterCharacteristic = mBluetoothGattServer.getService(SERVICE_UUID).getCharacteristic(CHARACTERISTIC_COUNTER_UUID);
byte[] value = mListener.onCounterRead();
counterCharacteristic.setValue(value);
mBluetoothGattServer.notifyCharacteristicChanged(device, counterCharacteristic, false);
}
}
}
13
Source : GattServerActivity.java
with Apache License 2.0
from androidthings
with Apache License 2.0
from androidthings
public clreplaced GattServerActivity extends Activity {
private static final String TAG = GattServerActivity.clreplaced.getSimpleName();
/* Local UI */
private TextView mLocalTimeView;
/* Bluetooth API */
private BluetoothManager mBluetoothManager;
private BluetoothGattServer mBluetoothGattServer;
private BluetoothLeAdvertiser mBluetoothLeAdvertiser;
/* Collection of notification subscribers */
private Set<BluetoothDevice> mRegisteredDevices = new HashSet<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_server);
mLocalTimeView = (TextView) findViewById(R.id.text_time);
// Devices with a display should not go to sleep
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
mBluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
BluetoothAdapter bluetoothAdapter = mBluetoothManager.getAdapter();
// We can't continue without proper Bluetooth support
if (!checkBluetoothSupport(bluetoothAdapter)) {
finish();
}
// Register for system Bluetooth events
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(mBluetoothReceiver, filter);
if (!bluetoothAdapter.isEnabled()) {
Log.d(TAG, "Bluetooth is currently disabled...enabling");
bluetoothAdapter.enable();
} else {
Log.d(TAG, "Bluetooth enabled...starting services");
startAdvertising();
startServer();
}
}
@Override
protected void onStart() {
super.onStart();
// Register for system clock events
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_TIME_TICK);
filter.addAction(Intent.ACTION_TIME_CHANGED);
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
registerReceiver(mTimeReceiver, filter);
}
@Override
protected void onStop() {
super.onStop();
unregisterReceiver(mTimeReceiver);
}
@Override
protected void onDestroy() {
super.onDestroy();
BluetoothAdapter bluetoothAdapter = mBluetoothManager.getAdapter();
if (bluetoothAdapter.isEnabled()) {
stopServer();
stopAdvertising();
}
unregisterReceiver(mBluetoothReceiver);
}
/**
* Verify the level of Bluetooth support provided by the hardware.
* @param bluetoothAdapter System {@link BluetoothAdapter}.
* @return true if Bluetooth is properly supported, false otherwise.
*/
private boolean checkBluetoothSupport(BluetoothAdapter bluetoothAdapter) {
if (bluetoothAdapter == null) {
Log.w(TAG, "Bluetooth is not supported");
return false;
}
if (!getPackageManager().hreplacedystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Log.w(TAG, "Bluetooth LE is not supported");
return false;
}
return true;
}
/**
* Listens for system time changes and triggers a notification to
* Bluetooth subscribers.
*/
private BroadcastReceiver mTimeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
byte adjustReason;
switch(intent.getAction()) {
case Intent.ACTION_TIME_CHANGED:
adjustReason = TimeProfile.ADJUST_MANUAL;
break;
case Intent.ACTION_TIMEZONE_CHANGED:
adjustReason = TimeProfile.ADJUST_TIMEZONE;
break;
default:
case Intent.ACTION_TIME_TICK:
adjustReason = TimeProfile.ADJUST_NONE;
break;
}
long now = System.currentTimeMillis();
notifyRegisteredDevices(now, adjustReason);
updateLocalUi(now);
}
};
/**
* Listens for Bluetooth adapter events to enable/disable
* advertising and server functionality.
*/
private BroadcastReceiver mBluetoothReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF);
switch(state) {
case BluetoothAdapter.STATE_ON:
startAdvertising();
startServer();
break;
case BluetoothAdapter.STATE_OFF:
stopServer();
stopAdvertising();
break;
default:
}
}
};
/**
* Begin advertising over Bluetooth that this device is connectable
* and supports the Current Time Service.
*/
private void startAdvertising() {
BluetoothAdapter bluetoothAdapter = mBluetoothManager.getAdapter();
mBluetoothLeAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser();
if (mBluetoothLeAdvertiser == null) {
Log.w(TAG, "Failed to create advertiser");
return;
}
AdvertiseSettings settings = new AdvertiseSettings.Builder().setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED).setConnectable(true).setTimeout(0).setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM).build();
AdvertiseData data = new AdvertiseData.Builder().setIncludeDeviceName(true).setIncludeTxPowerLevel(false).addServiceUuid(new ParcelUuid(TimeProfile.TIME_SERVICE)).build();
mBluetoothLeAdvertiser.startAdvertising(settings, data, mAdvertiseCallback);
}
/**
* Stop Bluetooth advertisements.
*/
private void stopAdvertising() {
if (mBluetoothLeAdvertiser == null)
return;
mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback);
}
/**
* Initialize the GATT server instance with the services/characteristics
* from the Time Profile.
*/
private void startServer() {
mBluetoothGattServer = mBluetoothManager.openGattServer(this, mGattServerCallback);
if (mBluetoothGattServer == null) {
Log.w(TAG, "Unable to create GATT server");
return;
}
mBluetoothGattServer.addService(TimeProfile.createTimeService());
// Initialize the local UI
updateLocalUi(System.currentTimeMillis());
}
/**
* Shut down the GATT server.
*/
private void stopServer() {
if (mBluetoothGattServer == null)
return;
mBluetoothGattServer.close();
}
/**
* Callback to receive information about the advertisement process.
*/
private AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() {
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
Log.i(TAG, "LE Advertise Started.");
}
@Override
public void onStartFailure(int errorCode) {
Log.w(TAG, "LE Advertise Failed: " + errorCode);
}
};
/**
* Send a time service notification to any devices that are subscribed
* to the characteristic.
*/
private void notifyRegisteredDevices(long timestamp, byte adjustReason) {
if (mRegisteredDevices.isEmpty()) {
Log.i(TAG, "No subscribers registered");
return;
}
byte[] exactTime = TimeProfile.getExactTime(timestamp, adjustReason);
Log.i(TAG, "Sending update to " + mRegisteredDevices.size() + " subscribers");
for (BluetoothDevice device : mRegisteredDevices) {
BluetoothGattCharacteristic timeCharacteristic = mBluetoothGattServer.getService(TimeProfile.TIME_SERVICE).getCharacteristic(TimeProfile.CURRENT_TIME);
timeCharacteristic.setValue(exactTime);
mBluetoothGattServer.notifyCharacteristicChanged(device, timeCharacteristic, false);
}
}
/**
* Update graphical UI on devices that support it with the current time.
*/
private void updateLocalUi(long timestamp) {
Date date = new Date(timestamp);
String displayDate = DateFormat.getMediumDateFormat(this).format(date) + "\n" + DateFormat.getTimeFormat(this).format(date);
mLocalTimeView.setText(displayDate);
}
/**
* Callback to handle incoming requests to the GATT server.
* All read/write requests for characteristics and descriptors are handled here.
*/
private BluetoothGattServerCallback mGattServerCallback = new BluetoothGattServerCallback() {
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
Log.i(TAG, "BluetoothDevice CONNECTED: " + device);
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.i(TAG, "BluetoothDevice DISCONNECTED: " + device);
// Remove device from any active subscriptions
mRegisteredDevices.remove(device);
}
}
@Override
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
long now = System.currentTimeMillis();
if (TimeProfile.CURRENT_TIME.equals(characteristic.getUuid())) {
Log.i(TAG, "Read CurrentTime");
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, TimeProfile.getExactTime(now, TimeProfile.ADJUST_NONE));
} else if (TimeProfile.LOCAL_TIME_INFO.equals(characteristic.getUuid())) {
Log.i(TAG, "Read LocalTimeInfo");
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, TimeProfile.getLocalTimeInfo(now));
} else {
// Invalid characteristic
Log.w(TAG, "Invalid Characteristic Read: " + characteristic.getUuid());
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, 0, null);
}
}
@Override
public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
if (TimeProfile.CLIENT_CONFIG.equals(descriptor.getUuid())) {
Log.d(TAG, "Config descriptor read");
byte[] returnValue;
if (mRegisteredDevices.contains(device)) {
returnValue = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
} else {
returnValue = BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;
}
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, 0, returnValue);
} else {
Log.w(TAG, "Unknown descriptor read request");
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, 0, null);
}
}
@Override
public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
if (TimeProfile.CLIENT_CONFIG.equals(descriptor.getUuid())) {
if (Arrays.equals(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE, value)) {
Log.d(TAG, "Subscribe device to notifications: " + device);
mRegisteredDevices.add(device);
} else if (Arrays.equals(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE, value)) {
Log.d(TAG, "Unsubscribe device from notifications: " + device);
mRegisteredDevices.remove(device);
}
if (responseNeeded) {
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null);
}
} else {
Log.w(TAG, "Unknown descriptor write request");
if (responseNeeded) {
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, 0, null);
}
}
}
};
}
12
Source : BleServerManager.java
with BSD 3-Clause "New" or "Revised" License
from NordicSemiconductor
with BSD 3-Clause "New" or "Revised" License
from NordicSemiconductor
/**
* The manager for local GATT server. To be used with one or more instances of {@link BleManager}
*
* @since 2.2
*/
@SuppressWarnings({ "WeakerAccess", "unused" })
public abstract clreplaced BleServerManager implements ILogger {
private final static UUID CHARACTERISTIC_EXTENDED_PROPERTIES_DESCRIPTOR_UUID = UUID.fromString("00002900-0000-1000-8000-00805f9b34fb");
private final static UUID CLIENT_USER_DESCRIPTION_DESCRIPTOR_UUID = UUID.fromString("00002901-0000-1000-8000-00805f9b34fb");
private final static UUID CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
/**
* Bluetooth GATT server instance, or null if not opened.
*/
private BluetoothGattServer server;
private final List<BleManager> managers = new ArrayList<>();
private final Context context;
private ServerObserver serverObserver;
/**
* List of server services returned by {@link #initializeServer()}.
* This list is empties when the services are being added one by one to the server.
* To get the server services, use {@link BluetoothGattServer#getServices()} instead.
*/
private Queue<BluetoothGattService> serverServices;
private List<BluetoothGattCharacteristic> sharedCharacteristics;
private List<BluetoothGattDescriptor> sharedDescriptors;
public BleServerManager(@NonNull final Context context) {
this.context = context;
}
/**
* Opens the GATT server and starts initializing services. This method only starts initializing
* services. The {@link ServerObserver#onServerReady()} will be called when all
* services are done.
*
* @return true, if the server has been started successfully. If GATT server could not
* be started, for example the Bluetooth is disabled, false is returned.
* @see #close()
*/
public final boolean open() {
if (server != null)
return true;
serverServices = new LinkedList<>(initializeServer());
final BluetoothManager bm = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
if (bm != null) {
server = bm.openGattServer(context, gattServerCallback);
}
if (server != null) {
log(Log.INFO, "[Server] Server started successfully");
try {
final BluetoothGattService service = serverServices.remove();
server.addService(service);
} catch (final NoSuchElementException e) {
if (serverObserver != null)
serverObserver.onServerReady();
} catch (final Exception e) {
close();
return false;
}
return true;
}
log(Log.WARN, "GATT server initialization failed");
serverServices = null;
return false;
}
/**
* Closes the GATT server.
*/
public final void close() {
if (server != null) {
server.close();
server = null;
}
serverServices = null;
for (BleManager manager : managers) {
// closeServer() must be called before close(). Otherwise close() would remove
// the manager from managers list while iterating this loop.
manager.closeServer();
manager.close();
}
managers.clear();
}
/**
* Sets the server observer.
*
* @param observer the observer.
*/
public final void setServerObserver(@NonNull final ServerObserver observer) {
this.serverObserver = observer;
}
/**
* Returns the {@link BluetoothGattServer} instance.
*/
@Nullable
final BluetoothGattServer getServer() {
return server;
}
/**
* Adds the BLE Manager to be handled.
* @param manager the Ble Manager.
*/
final void addManager(@NonNull final BleManager manager) {
if (!managers.contains(manager)) {
managers.add(manager);
}
}
/**
* Removes the manager. Callbacks will no longer be preplaceded to it.
* @param manager the manager to be removed.
*/
final void removeManager(@NonNull final BleManager manager) {
managers.remove(manager);
}
final boolean isShared(@NonNull final BluetoothGattCharacteristic characteristic) {
return sharedCharacteristics != null && sharedCharacteristics.contains(characteristic);
}
final boolean isShared(@NonNull final BluetoothGattDescriptor descriptor) {
return sharedDescriptors != null && sharedDescriptors.contains(descriptor);
}
@Nullable
private BleManagerHandler getRequestHandler(@NonNull final BluetoothDevice device) {
for (final BleManager manager : managers) {
if (device.equals(manager.getBluetoothDevice())) {
return manager.requestHandler;
}
}
return null;
}
@Override
public void log(final int priority, @NonNull final String message) {
// Override to log events. Simple log can use Logcat:
//
// Log.println(priority, TAG, message);
//
// You may also use Timber:
//
// Timber.log(priority, message);
//
// or nRF Logger:
//
// Logger.log(logSession, LogContract.Log.Level.fromPriority(priority), message);
//
// Starting from nRF Logger 2.1.3, you may use log-timber and plant nRFLoggerTree.
// https://github.com/NordicSemiconductor/nRF-Logger-API
}
@Override
public void log(final int priority, @StringRes final int messageRes, @Nullable final Object... params) {
final String message = context.getString(messageRes, params);
log(priority, message);
}
/**
* This method is called once, just after instantiating the {@link BleServerManager}.
* It should return a list of server GATT services that will be available for the remote device
* to use. You may use {@link #service(UUID, BluetoothGattCharacteristic...)} to easily
* instantiate a service.
* <p>
* Server services will be added to the local GATT configuration on the Android device.
* The library does not know what services are already set up by other apps or
* {@link BleServerManager} instances, so a UUID collision is possible.
* The remote device will discover all services set up by all apps.
* <p>
* In order to enable server callbacks (see {@link android.bluetooth.BluetoothGattServerCallback}),
* but without defining own services, return an empty list.
*
* @since 2.2
* @return The list of server GATT services, or null if no services should be created. An
* empty array to start the GATT server without any services.
*/
@NonNull
protected abstract List<BluetoothGattService> initializeServer();
/**
* A helper method for creating a primary service with given UUID and list of characteristics.
* This method can be called from {@link #initializeServer()}.
*
* @param uuid The service UUID.
* @param characteristics The optional list of characteristics.
* @return The new service.
*/
@NonNull
protected final BluetoothGattService service(@NonNull final UUID uuid, final BluetoothGattCharacteristic... characteristics) {
final BluetoothGattService service = new BluetoothGattService(uuid, BluetoothGattService.SERVICE_TYPE_PRIMARY);
for (BluetoothGattCharacteristic characteristic : characteristics) {
service.addCharacteristic(characteristic);
}
return service;
}
/**
* A helper method that creates a characteristic with given UUID, properties and permissions.
* Optionally, an initial value and a list of descriptors may be set.
* <p>
* The Client Characteristic Configuration Descriptor (CCCD) will be added automatically if
* {@link BluetoothGattCharacteristic#PROPERTY_NOTIFY} or {@link BluetoothGattCharacteristic#PROPERTY_INDICATE}
* was set, if not added explicitly in the descriptors list.
* <p>
* If {@link #reliableWrite()} was added as one of the descriptors or the Characteristic User
* Description descriptor was created with any of write permissions
* (see {@link #description(String, boolean)}) the
* {@link BluetoothGattCharacteristic#PROPERTY_EXTENDED_PROPS} property will be added automatically.
* <p>
* The value of the characteristic will NOT be shared between clients. Each client will write
* and read its own copy. To create a shared characteristic, use
* {@link #sharedCharacteristic(UUID, int, int, byte[], BluetoothGattDescriptor...)} instead.
*
* @param uuid The characteristic UUID.
* @param properties The bit mask of characteristic properties. See {@link BluetoothGattCharacteristic}
* for details.
* @param permissions The bit mask or characteristic permissions. See {@link BluetoothGattCharacteristic}
* for details.
* @param initialValue The optional initial value of the characteristic.
* @param descriptors The optional list of descriptors.
* @return The characteristic.
*/
@NonNull
protected final BluetoothGattCharacteristic characteristic(@NonNull final UUID uuid, @CharacteristicProperties int properties, @CharacteristicPermissions final int permissions, @Nullable final byte[] initialValue, final BluetoothGattDescriptor... descriptors) {
// Look for Client Characteristic Configuration descriptor,
// Characteristic User Description descriptor and Characteristic Extended Properties descriptor.
boolean writableAuxiliaries = false;
boolean cccdFound = false;
boolean cepdFound = false;
BluetoothGattDescriptor cepd = null;
for (final BluetoothGattDescriptor descriptor : descriptors) {
if (CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID.equals(descriptor.getUuid())) {
cccdFound = true;
} else if (CLIENT_USER_DESCRIPTION_DESCRIPTOR_UUID.equals(descriptor.getUuid()) && 0 != (descriptor.getPermissions() & (BluetoothGattDescriptor.PERMISSION_WRITE | BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED | BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED_MITM))) {
writableAuxiliaries = true;
} else if (CHARACTERISTIC_EXTENDED_PROPERTIES_DESCRIPTOR_UUID.equals(descriptor.getUuid())) {
cepd = descriptor;
cepdFound = true;
}
}
if (writableAuxiliaries) {
if (cepd == null) {
cepd = new BluetoothGattDescriptor(CHARACTERISTIC_EXTENDED_PROPERTIES_DESCRIPTOR_UUID, BluetoothGattDescriptor.PERMISSION_READ);
cepd.setValue(new byte[] { 0x02, 0x00 });
} else {
if (cepd.getValue() != null && cepd.getValue().length == 2) {
cepd.getValue()[0] |= 0x02;
} else {
cepd.setValue(new byte[] { 0x02, 0x00 });
}
}
}
final boolean cccdRequired = (properties & (BluetoothGattCharacteristic.PROPERTY_NOTIFY | BluetoothGattCharacteristic.PROPERTY_INDICATE)) != 0;
final boolean reliableWrite = cepd != null && cepd.getValue() != null && cepd.getValue().length == 2 && (cepd.getValue()[0] & 0x01) != 0;
if (writableAuxiliaries || reliableWrite) {
properties |= BluetoothGattCharacteristic.PROPERTY_EXTENDED_PROPS;
}
if ((properties & BluetoothGattCharacteristic.PROPERTY_EXTENDED_PROPS) != 0 && cepd == null) {
cepd = new BluetoothGattDescriptor(CHARACTERISTIC_EXTENDED_PROPERTIES_DESCRIPTOR_UUID, BluetoothGattDescriptor.PERMISSION_READ);
cepd.setValue(new byte[] { 0, 0 });
}
final BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(uuid, properties, permissions);
if (cccdRequired && !cccdFound) {
characteristic.addDescriptor(cccd());
}
for (BluetoothGattDescriptor descriptor : descriptors) {
characteristic.addDescriptor(descriptor);
}
if (cepd != null && !cepdFound) {
characteristic.addDescriptor(cepd);
}
characteristic.setValue(initialValue);
return characteristic;
}
/**
* A helper method that creates a characteristic with given UUID, properties and permissions.
* Optionally, an initial value and a list of descriptors may be set.
* <p>
* The Client Characteristic Configuration Descriptor (CCCD) will be added automatically if
* {@link BluetoothGattCharacteristic#PROPERTY_NOTIFY} or {@link BluetoothGattCharacteristic#PROPERTY_INDICATE}
* was set, if not added explicitly in the descriptors list.
* <p>
* If {@link #reliableWrite()} was added as one of the descriptors or the Characteristic User
* Description descriptor was created with any of write permissions
* (see {@link #description(String, boolean)}) the
* {@link BluetoothGattCharacteristic#PROPERTY_EXTENDED_PROPS} property will be added automatically.
* <p>
* The value of the characteristic will NOT be shared between clients. Each client will write
* and read its own copy. To create a shared characteristic, use
* {@link #sharedCharacteristic(UUID, int, int, byte[], BluetoothGattDescriptor...)} instead.
*
* @param uuid The characteristic UUID.
* @param properties The bit mask of characteristic properties. See {@link BluetoothGattCharacteristic}
* for details.
* @param permissions The bit mask or characteristic permissions. See {@link BluetoothGattCharacteristic}
* for details.
* @param initialValue The optional initial value of the characteristic.
* @param descriptors The optional list of descriptors.
* @return The characteristic.
*/
@NonNull
protected final BluetoothGattCharacteristic characteristic(@NonNull final UUID uuid, @CharacteristicProperties final int properties, @CharacteristicPermissions final int permissions, @Nullable final Data initialValue, final BluetoothGattDescriptor... descriptors) {
return characteristic(uuid, properties, permissions, initialValue != null ? initialValue.getValue() : null, descriptors);
}
/**
* A helper method that creates a characteristic with given UUID, properties and permissions.
* Optionally, a list of descriptors may be set.
* <p>
* The Client Characteristic Configuration Descriptor (CCCD) will be added automatically if
* {@link BluetoothGattCharacteristic#PROPERTY_NOTIFY} or {@link BluetoothGattCharacteristic#PROPERTY_INDICATE}
* was set, if not added explicitly in the descriptors list.
* <p>
* If {@link #reliableWrite()} was added as one of the descriptors or the Characteristic User
* Description descriptor was created with any of write permissions
* (see {@link #description(String, boolean)}) the
* {@link BluetoothGattCharacteristic#PROPERTY_EXTENDED_PROPS} property will be added automatically.
* <p>
* The value of the characteristic will NOT be shared between clients. Each client will write
* and read its own copy. To create a shared characteristic, use
* {@link #sharedCharacteristic(UUID, int, int, byte[], BluetoothGattDescriptor...)} instead.
*
* @param uuid The characteristic UUID.
* @param properties The bit mask of characteristic properties. See {@link BluetoothGattCharacteristic}
* for details.
* @param permissions The bit mask or characteristic permissions. See {@link BluetoothGattCharacteristic}
* for details.
* @param descriptors The optional list of descriptors.
* @return The characteristic.
*/
@NonNull
protected final BluetoothGattCharacteristic characteristic(@NonNull final UUID uuid, @CharacteristicProperties final int properties, @CharacteristicPermissions final int permissions, final BluetoothGattDescriptor... descriptors) {
return characteristic(uuid, properties, permissions, (byte[]) null, descriptors);
}
/**
* A helper method that creates a characteristic with given UUID, properties and permissions.
* Optionally, an initial value and a list of descriptors may be set.
* <p>
* The Client Characteristic Configuration Descriptor (CCCD) will be added automatically if
* {@link BluetoothGattCharacteristic#PROPERTY_NOTIFY} or {@link BluetoothGattCharacteristic#PROPERTY_INDICATE}
* was set, if not added explicitly in the descriptors list.
* <p>
* If {@link #reliableWrite()} was added as one of the descriptors or the Characteristic User
* Description descriptor was created with any of write permissions
* (see {@link #description(String, boolean)}) the
* {@link BluetoothGattCharacteristic#PROPERTY_EXTENDED_PROPS} property will be added automatically.
* <p>
* The value of the characteristic is shared between clients. A value written by one of the
* connected clients will be available for all other clients. To create a sandboxed characteristic,
* use {@link #characteristic(UUID, int, int, byte[], BluetoothGattDescriptor...)} instead.
*
* @param uuid The characteristic UUID.
* @param properties The bit mask of characteristic properties. See {@link BluetoothGattCharacteristic}
* for details.
* @param permissions The bit mask or characteristic permissions. See {@link BluetoothGattCharacteristic}
* for details.
* @param initialValue The optional initial value of the characteristic.
* @param descriptors The optional list of descriptors.
* @return The characteristic.
*/
@NonNull
protected final BluetoothGattCharacteristic sharedCharacteristic(@NonNull final UUID uuid, @CharacteristicProperties final int properties, @CharacteristicPermissions final int permissions, @Nullable final byte[] initialValue, final BluetoothGattDescriptor... descriptors) {
final BluetoothGattCharacteristic characteristic = characteristic(uuid, properties, permissions, initialValue, descriptors);
if (sharedCharacteristics == null)
sharedCharacteristics = new ArrayList<>();
sharedCharacteristics.add(characteristic);
return characteristic;
}
/**
* A helper method that creates a characteristic with given UUID, properties and permissions.
* Optionally, an initial value and a list of descriptors may be set.
* <p>
* The Client Characteristic Configuration Descriptor (CCCD) will be added automatically if
* {@link BluetoothGattCharacteristic#PROPERTY_NOTIFY} or {@link BluetoothGattCharacteristic#PROPERTY_INDICATE}
* was set, if not added explicitly in the descriptors list.
* <p>
* If {@link #reliableWrite()} was added as one of the descriptors or the Characteristic User
* Description descriptor was created with any of write permissions
* (see {@link #description(String, boolean)}) the
* {@link BluetoothGattCharacteristic#PROPERTY_EXTENDED_PROPS} property will be added automatically.
* <p>
* The value of the characteristic is shared between clients. A value written by one of the
* connected clients will be available for all other clients. To create a sandboxed characteristic,
* use {@link #characteristic(UUID, int, int, byte[], BluetoothGattDescriptor...)} instead.
*
* @param uuid The characteristic UUID.
* @param properties The bit mask of characteristic properties. See {@link BluetoothGattCharacteristic}
* for details.
* @param permissions The bit mask or characteristic permissions. See {@link BluetoothGattCharacteristic}
* for details.
* @param initialValue The optional initial value of the characteristic.
* @param descriptors The optional list of descriptors.
* @return The characteristic.
*/
@NonNull
protected final BluetoothGattCharacteristic sharedCharacteristic(@NonNull final UUID uuid, @CharacteristicProperties final int properties, @CharacteristicPermissions final int permissions, @Nullable final Data initialValue, final BluetoothGattDescriptor... descriptors) {
return sharedCharacteristic(uuid, properties, permissions, initialValue != null ? initialValue.getValue() : null, descriptors);
}
/**
* A helper method that creates a characteristic with given UUID, properties and permissions.
* Optionally, a list of descriptors may be set.
* <p>
* The Client Characteristic Configuration Descriptor (CCCD) will be added automatically if
* {@link BluetoothGattCharacteristic#PROPERTY_NOTIFY} or {@link BluetoothGattCharacteristic#PROPERTY_INDICATE}
* was set, if not added explicitly in the descriptors list.
* <p>
* If {@link #reliableWrite()} was added as one of the descriptors or the Characteristic User
* Description descriptor was created with any of write permissions
* (see {@link #description(String, boolean)}) the
* {@link BluetoothGattCharacteristic#PROPERTY_EXTENDED_PROPS} property will be added automatically.
* <p>
* The value of the characteristic is shared between clients. A value written by one of the
* connected clients will be available for all other clients. To create a sandboxed characteristic,
* use {@link #characteristic(UUID, int, int, byte[], BluetoothGattDescriptor...)} instead.
*
* @param uuid The characteristic UUID.
* @param properties The bit mask of characteristic properties. See {@link BluetoothGattCharacteristic}
* for details.
* @param permissions The bit mask or characteristic permissions. See {@link BluetoothGattCharacteristic}
* for details.
* @param descriptors The optional list of descriptors.
* @return The characteristic.
*/
@NonNull
protected final BluetoothGattCharacteristic sharedCharacteristic(@NonNull final UUID uuid, @CharacteristicProperties final int properties, @CharacteristicPermissions final int permissions, final BluetoothGattDescriptor... descriptors) {
return sharedCharacteristic(uuid, properties, permissions, (byte[]) null, descriptors);
}
/**
* A helper method that creates a descriptor with given UUID and permissions.
* Optionally, an initial value may be set.
* <p>
* The value of the descriptor will NOT be shared between clients. Each client will write
* and read its own copy. To create a shared descriptor, use
* {@link #sharedDescriptor(UUID, int, byte[])} instead.
*
* @param uuid The characteristic UUID.
* @param permissions The bit mask or descriptor permissions. See {@link BluetoothGattCharacteristic}
* for details.
* @param initialValue The optional initial value of the descriptor.
* @return The characteristic.
*/
@NonNull
protected final BluetoothGattDescriptor descriptor(@NonNull final UUID uuid, @DescriptorPermissions final int permissions, @Nullable final byte[] initialValue) {
final BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(uuid, permissions);
descriptor.setValue(initialValue);
return descriptor;
}
/**
* A helper method that creates a descriptor with given UUID and permissions.
* Optionally, an initial value may be set.
* <p>
* The value of the descriptor will NOT be shared between clients. Each client will write
* and read its own copy. To create a shared descriptor, use
* {@link #sharedDescriptor(UUID, int, byte[])} instead.
*
* @param uuid The characteristic UUID.
* @param permissions The bit mask or descriptor permissions. See {@link BluetoothGattCharacteristic}
* for details.
* @param initialValue The optional initial value of the descriptor.
* @return The characteristic.
*/
@NonNull
protected final BluetoothGattDescriptor descriptor(@NonNull final UUID uuid, @DescriptorPermissions final int permissions, @Nullable final Data initialValue) {
return descriptor(uuid, permissions, initialValue != null ? initialValue.getValue() : null);
}
/**
* A helper method that creates a descriptor with given UUID and permissions.
* Optionally, an initial value may be set.
* <p>
* The value of the characteristic is shared between clients. A value written by one of the
* connected clients will be available for all other clients. To create a sandboxed characteristic,
* use {@link #characteristic(UUID, int, int, byte[], BluetoothGattDescriptor...)} instead.
*
* @param uuid The characteristic UUID.
* @param permissions The bit mask or characteristic permissions. See {@link BluetoothGattCharacteristic}
* for details.
* @param initialValue The optional initial value of the characteristic.
* @return The characteristic.
*/
@NonNull
protected final BluetoothGattDescriptor sharedDescriptor(@NonNull final UUID uuid, @DescriptorPermissions final int permissions, @Nullable final byte[] initialValue) {
final BluetoothGattDescriptor descriptor = descriptor(uuid, permissions, initialValue);
if (sharedDescriptors == null)
sharedDescriptors = new ArrayList<>();
sharedDescriptors.add(descriptor);
return descriptor;
}
/**
* A helper method that creates a descriptor with given UUID and permissions.
* Optionally, an initial value may be set.
* <p>
* The value of the characteristic is shared between clients. A value written by one of the
* connected clients will be available for all other clients. To create a sandboxed characteristic,
* use {@link #characteristic(UUID, int, int, byte[], BluetoothGattDescriptor...)} instead.
*
* @param uuid The characteristic UUID.
* @param permissions The bit mask or characteristic permissions. See {@link BluetoothGattCharacteristic}
* for details.
* @param initialValue The optional initial value of the characteristic.
* @return The characteristic.
*/
@NonNull
protected final BluetoothGattDescriptor sharedDescriptor(@NonNull final UUID uuid, @DescriptorPermissions final int permissions, @Nullable final Data initialValue) {
return sharedDescriptor(uuid, permissions, initialValue != null ? initialValue.getValue() : null);
}
/**
* This helper method returns a new instance of Client Characteristic Configuration Descriptor
* (CCCD) that can be added to a server characteristic in {@link #initializeServer()}.
*
* @return The CCC descriptor used to enable and disable notifications or indications.
*/
@NonNull
protected final BluetoothGattDescriptor cccd() {
return descriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID, BluetoothGattDescriptor.PERMISSION_READ | BluetoothGattDescriptor.PERMISSION_WRITE, new byte[] { 0, 0 });
}
/**
* This helper method returns a new instance of Client Characteristic Configuration Descriptor
* (CCCD) that can be added to a server characteristic in {@link #initializeServer()}.
*
* @return The CCC descriptor used to enable and disable notifications or indications.
*/
@NonNull
protected final BluetoothGattDescriptor sharedCccd() {
return sharedDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID, BluetoothGattDescriptor.PERMISSION_READ | BluetoothGattDescriptor.PERMISSION_WRITE, new byte[] { 0, 0 });
}
/**
* This helper method returns a new instance of Characteristic Extended Properties descriptor
* that can be added to a server characteristic.
* This descriptor should be added it {@link BluetoothGattCharacteristic#PROPERTY_EXTENDED_PROPS}
* property is set.
* @return The CEP descriptor with Reliable Write bit set.
*/
@NonNull
protected final BluetoothGattDescriptor reliableWrite() {
return sharedDescriptor(CHARACTERISTIC_EXTENDED_PROPERTIES_DESCRIPTOR_UUID, BluetoothGattDescriptor.PERMISSION_READ, new byte[] { 1, 0 });
}
/**
* This helper method returns a new instance of Client User Description Descriptor
* that can be added to a server characteristic in {@link #initializeServer()}.
*
* @param description the UTF-8 string that is a user textual description of the characteristic.
* @param writableAuxiliaries if true, the descriptor will be writable and the Writable Auxiliaries
* bit in Characteristic Extended Properties descriptor will be set.
* See Vol. 3, Part F, Section 3.3.3.2 in Bluetooth Core specification 5.1.
* @return The User Description descriptor.
*/
@NonNull
protected final BluetoothGattDescriptor description(@Nullable final String description, final boolean writableAuxiliaries) {
final BluetoothGattDescriptor cud = descriptor(CLIENT_USER_DESCRIPTION_DESCRIPTOR_UUID, BluetoothGattDescriptor.PERMISSION_READ | (writableAuxiliaries ? BluetoothGattDescriptor.PERMISSION_WRITE : 0), description != null ? description.getBytes() : null);
if (!writableAuxiliaries) {
if (sharedDescriptors == null)
sharedDescriptors = new ArrayList<>();
sharedDescriptors.add(cud);
}
return cud;
}
private final BluetoothGattServerCallback gattServerCallback = new BluetoothGattServerCallback() {
@Override
public void onServiceAdded(final int status, @NonNull final BluetoothGattService service) {
if (status == BluetoothGatt.GATT_SUCCESS) {
try {
final BluetoothGattService nextService = serverServices.remove();
server.addService(nextService);
} catch (final Exception e) {
log(Log.INFO, "[Server] All services added successfully");
if (serverObserver != null)
serverObserver.onServerReady();
serverServices = null;
}
} else {
log(Log.ERROR, "[Server] Adding service failed with error " + status);
}
}
@Override
public void onConnectionStateChange(@NonNull final BluetoothDevice device, final int status, final int newState) {
if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothGatt.STATE_CONNECTED) {
log(Log.INFO, "[Server] " + device.getAddress() + " is now connected");
if (serverObserver != null)
serverObserver.onDeviceConnectedToServer(device);
} else {
if (status == BluetoothGatt.GATT_SUCCESS) {
log(Log.INFO, "[Server] " + device.getAddress() + " is disconnected");
} else {
log(Log.WARN, "[Server] " + device.getAddress() + " has disconnected connected with status: " + status);
}
if (serverObserver != null)
serverObserver.onDeviceDisconnectedFromServer(device);
}
}
@Override
public void onCharacteristicReadRequest(@NonNull final BluetoothDevice device, final int requestId, final int offset, @NonNull final BluetoothGattCharacteristic characteristic) {
final BleManagerHandler handler = getRequestHandler(device);
if (handler != null) {
handler.onCharacteristicReadRequest(server, device, requestId, offset, characteristic);
}
}
@Override
public void onCharacteristicWriteRequest(@NonNull final BluetoothDevice device, final int requestId, @NonNull final BluetoothGattCharacteristic characteristic, final boolean preparedWrite, final boolean responseNeeded, final int offset, @NonNull final byte[] value) {
final BleManagerHandler handler = getRequestHandler(device);
if (handler != null) {
handler.onCharacteristicWriteRequest(server, device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
}
}
@Override
public void onDescriptorReadRequest(@NonNull final BluetoothDevice device, final int requestId, final int offset, @NonNull final BluetoothGattDescriptor descriptor) {
final BleManagerHandler handler = getRequestHandler(device);
if (handler != null) {
handler.onDescriptorReadRequest(server, device, requestId, offset, descriptor);
}
}
@Override
public void onDescriptorWriteRequest(@NonNull final BluetoothDevice device, final int requestId, @NonNull final BluetoothGattDescriptor descriptor, final boolean preparedWrite, final boolean responseNeeded, final int offset, @NonNull final byte[] value) {
final BleManagerHandler handler = getRequestHandler(device);
if (handler != null) {
handler.onDescriptorWriteRequest(server, device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);
}
}
@Override
public void onExecuteWrite(@NonNull final BluetoothDevice device, final int requestId, final boolean execute) {
final BleManagerHandler handler = getRequestHandler(device);
if (handler != null) {
handler.onExecuteWrite(server, device, requestId, execute);
}
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public void onNotificationSent(@NonNull final BluetoothDevice device, final int status) {
final BleManagerHandler handler = getRequestHandler(device);
if (handler != null) {
handler.onNotificationSent(server, device, status);
}
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
@Override
public void onMtuChanged(@NonNull final BluetoothDevice device, final int mtu) {
final BleManagerHandler handler = getRequestHandler(device);
if (handler != null) {
handler.onMtuChanged(server, device, mtu);
}
}
};
}
11
Source : MainActivity.java
with MIT License
from thejeshgn
with MIT License
from thejeshgn
/**
* It has everything that is needed by GattServer to respond
* MIT Licnese
* 2016 - Thejesh GN - https://hejeshgn.com
*/
public clreplaced MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private ILogSession mLogSession;
private DataManager dataManager = DataManager.getInstance();
private BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
private BluetoothLeAdvertiser mBluetoothLeAdvertiser;
private BluetoothGattServer mGattServer;
private ArrayList<BluetoothDevice> mConnectedDevices;
private ArrayAdapter<BluetoothDevice> mConnectedDevicesAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mLogSession = Logger.newSession(this, "Hello", "BleUARTPeripheral");
setContentView(R.layout.activity_main);
ListView list = new ListView(this);
setContentView(list);
mConnectedDevices = new ArrayList<BluetoothDevice>();
mConnectedDevicesAdapter = new ArrayAdapter<BluetoothDevice>(this, android.R.layout.simple_list_item_1, mConnectedDevices);
list.setAdapter(mConnectedDevicesAdapter);
mBluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
mBluetoothAdapter = mBluetoothManager.getAdapter();
Logger.log(mLogSession, LogContract.Log.Level.DEBUG, "In on create");
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main_tool_bar, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Toast.makeText(this, "Inside menu item.", Toast.LENGTH_SHORT).show();
Logger.log(mLogSession, LogContract.Log.Level.INFO, "Inside onOptionsItemSelected");
switch(item.gereplacedemId()) {
case R.id.action_about:
Intent myIntent = new Intent(MainActivity.this, AboutActivity.clreplaced);
startActivity(myIntent);
return true;
case R.id.action_log:
return true;
case R.id.action_start_stop:
// User chose the "Favorite" action, mark the current item
// as a favorite...
return true;
default:
// If we got here, the user's action was not recognized.
// Invoke the superclreplaced to handle it.
return super.onOptionsItemSelected(item);
}
}
protected void onResume() {
super.onResume();
/*
* Make sure bluettoth is enabled
*/
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
// Bluetooth is disabled
Logger.log(mLogSession, LogContract.Log.Level.DEBUG, "Bluetooth is disabled. Request enable");
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivity(enableBtIntent);
finish();
return;
}
/*
* Check for Bluetooth LE Support
*/
if (!getPackageManager().hreplacedystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this, "No LE Support.", Toast.LENGTH_SHORT).show();
finish();
return;
}
/*
* Check for advertising support.
*/
if (!mBluetoothAdapter.isMultipleAdvertisementSupported()) {
Toast.makeText(this, "No Advertising Support.", Toast.LENGTH_SHORT).show();
finish();
return;
}
Logger.log(mLogSession, LogContract.Log.Level.DEBUG, "Get Advertiser");
mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
Logger.log(mLogSession, LogContract.Log.Level.DEBUG, "Open GattServer");
mGattServer = mBluetoothManager.openGattServer(this, mGattServerCallback);
// If everything is okay then start
initServer();
startAdvertising();
}
/*
* Callback handles events from the framework describing
* if we were successful in starting the advertisement requests.
*/
private AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() {
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
Log.i(TAG, "Peripheral Advertise Started.");
postStatusMessage("GATT Server Ready");
}
@Override
public void onStartFailure(int errorCode) {
Log.w(TAG, "Peripheral Advertise Failed: " + errorCode);
postStatusMessage("GATT Server Error " + errorCode);
}
};
private Handler mHandler = new Handler();
private void postStatusMessage(final String message) {
mHandler.post(new Runnable() {
@Override
public void run() {
setreplacedle(message);
}
});
}
/*
* Create the GATT server instance, attaching all services and
* characteristics that should be exposed
*/
private void initServer() {
BluetoothGattService UART_SERVICE = new BluetoothGattService(UARTProfile.UART_SERVICE, BluetoothGattService.SERVICE_TYPE_PRIMARY);
BluetoothGattCharacteristic TX_READ_CHAR = new BluetoothGattCharacteristic(UARTProfile.TX_READ_CHAR, // Read-only characteristic, supports notifications
BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_NOTIFY, BluetoothGattCharacteristic.PERMISSION_READ);
// Descriptor for read notifications
BluetoothGattDescriptor TX_READ_CHAR_DESC = new BluetoothGattDescriptor(UARTProfile.TX_READ_CHAR_DESC, UARTProfile.DESCRIPTOR_PERMISSION);
TX_READ_CHAR.addDescriptor(TX_READ_CHAR_DESC);
BluetoothGattCharacteristic RX_WRITE_CHAR = new BluetoothGattCharacteristic(UARTProfile.RX_WRITE_CHAR, // write permissions
BluetoothGattCharacteristic.PROPERTY_WRITE, BluetoothGattCharacteristic.PERMISSION_WRITE);
UART_SERVICE.addCharacteristic(TX_READ_CHAR);
UART_SERVICE.addCharacteristic(RX_WRITE_CHAR);
mGattServer.addService(UART_SERVICE);
}
/*
* Initialize the advertiser
*/
private void startAdvertising() {
if (mBluetoothLeAdvertiser == null)
return;
AdvertiseSettings settings = new AdvertiseSettings.Builder().setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED).setConnectable(true).setTimeout(0).setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM).build();
AdvertiseData data = new AdvertiseData.Builder().setIncludeDeviceName(true).addServiceUuid(new ParcelUuid(UARTProfile.UART_SERVICE)).build();
mBluetoothLeAdvertiser.startAdvertising(settings, data, mAdvertiseCallback);
}
private void postDeviceChange(final BluetoothDevice device, final boolean toAdd) {
mHandler.post(new Runnable() {
@Override
public void run() {
// This will add the item to our list and update the adapter at the same time.
if (toAdd) {
if (mConnectedDevicesAdapter.getPosition(device) < 0) {
mConnectedDevicesAdapter.add(device);
}
} else {
mConnectedDevicesAdapter.remove(device);
}
}
});
}
/*
* Terminate the server and any running callbacks
*/
private void shutdownServer() {
// mHandler.removeCallbacks(mNotifyRunnable);
if (mGattServer == null)
return;
mGattServer.close();
}
// private Runnable mNotifyRunnable = new Runnable() {
// @Override
// public void run() {
// mHandler.postDelayed(this, 2000);
// }
// };
/* Callback handles all incoming requests from GATT clients.
* From connections to read/write requests.
*/
private BluetoothGattServerCallback mGattServerCallback = new BluetoothGattServerCallback() {
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
super.onConnectionStateChange(device, status, newState);
Log.i(TAG, "onConnectionStateChange " + UARTProfile.getStatusDescription(status) + " " + UARTProfile.getStateDescription(newState));
if (newState == BluetoothProfile.STATE_CONNECTED) {
postDeviceChange(device, true);
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
postDeviceChange(device, false);
}
}
@Override
public void onServiceAdded(int status, BluetoothGattService service) {
Log.d("Start", "Our gatt server service was added.");
super.onServiceAdded(status, service);
}
@Override
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
Log.d(TAG, "READ called onCharacteristicReadRequest " + characteristic.getUuid().toString());
if (UARTProfile.TX_READ_CHAR.equals(characteristic.getUuid())) {
mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, storage);
}
}
@Override
public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
Log.i(TAG, "onCharacteristicWriteRequest " + characteristic.getUuid().toString());
if (UARTProfile.RX_WRITE_CHAR.equals(characteristic.getUuid())) {
// IMP: Copy the received value to storage
if (responseNeeded) {
mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, value);
Log.d(TAG, "Received data on " + characteristic.getUuid().toString());
Log.d(TAG, "Received data" + bytesToHex(value));
}
dataManager.addPacket(value);
if (dataManager.isMessageComplete()) {
storage = dataManager.getTheCompleteMesssage();
dataManager.clear();
// IMP: Respond
sendOurResponse();
}
mHandler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, "We received data", Toast.LENGTH_SHORT).show();
}
});
}
}
@Override
public void onNotificationSent(BluetoothDevice device, int status) {
Log.d("GattServer", "onNotificationSent");
super.onNotificationSent(device, status);
}
@Override
public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
Log.d("HELLO", "Our gatt server descriptor was read.");
super.onDescriptorReadRequest(device, requestId, offset, descriptor);
Log.d("DONE", "Our gatt server descriptor was read.");
}
@Override
public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
Log.d("HELLO", "Our gatt server descriptor was written.");
super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);
Log.d("DONE", "Our gatt server descriptor was written.");
// NOTE: Its important to send response. It expects response else it will disconnect
if (responseNeeded) {
mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, value);
}
}
};
// Send notification to all the devices once you write
private void sendOurResponse() {
for (BluetoothDevice device : mConnectedDevices) {
BluetoothGattCharacteristic readCharacteristic = mGattServer.getService(UARTProfile.UART_SERVICE).getCharacteristic(UARTProfile.TX_READ_CHAR);
byte[] notify_msg = storage;
String hexStorage = bytesToHex(storage);
Log.d(TAG, "received string = " + bytesToHex(storage));
if (hexStorage.equals("77686F616D69")) {
notify_msg = "I am echo an machine".getBytes();
} else if (bytesToHex(storage).equals("64617465")) {
DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
Date date = new Date();
notify_msg = dateFormat.format(date).getBytes();
} else {
// TODO: Do nothing send what you received. Basically echo
}
List messages = Util.createPacketsToSend(notify_msg);
for (int i = 0; i < messages.size(); i++) {
byte[] message = (byte[]) messages.get(i);
readCharacteristic.setValue(message);
Log.d(TAG, "Sending Notifications" + message);
boolean is_notified = mGattServer.notifyCharacteristicChanged(device, readCharacteristic, false);
Log.d(TAG, "Notifications =" + is_notified);
}
}
}
private byte[] storage = hexStringToByteArray("1111");
final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
// Helper function converts byte array to hex string
// for priting
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
// Helper function converts hex string into
// byte array
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
}
return data;
}
}
11
Source : GattServer.java
with MIT License
from adafruit
with MIT License
from adafruit
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public clreplaced GattServer implements PeripheralService.Listener {
// Log
private final static String TAG = GattServer.clreplaced.getSimpleName();
// Constants
private final static String kPreferences = "GattServer";
private final static String kPreferences_includeDeviceName = "includeDeviceName";
// Config
// private final static boolean kAddDelayAfterBeforeClosing = true; // To Fix issue: https://stackoverflow.com/questions/29758890/bluetooth-gatt-callback-not-working-with-new-api-for-lollipop, https://issuetracker.google.com/issues/37057260
// Listener
public interface Listener {
// Connection
void onCentralConnectionStatusChanged(int status);
// Advertising
void onWillStartAdvertising();
void onDidStartAdvertising();
void onDidStopAdvertising();
void onAdvertisingError(int errorCode);
}
// Data
private BluetoothManager mBluetoothManager;
private BluetoothGattServer mGattServer;
private BluetoothLeAdvertiser mAdvertiser;
private int mMtuSize;
private boolean mShouldStartAdvertising = false;
private boolean mIsAdvertising = false;
private Listener mListener;
private List<PeripheralService> mPeripheralServices = new ArrayList<>();
private Semapreplaced mAddServicesSemapreplaced;
// Data - preparedWrite
clreplaced ServiceCharacteristicKey {
// Based on https://stackoverflow.com/questions/14677993/how-to-create-a-hashmap-with-two-keys-key-pair-value
private final UUID serviceUuid;
private final UUID characteristicUuid;
ServiceCharacteristicKey(UUID serviceUuid, UUID characteristicUuid) {
this.serviceUuid = serviceUuid;
this.characteristicUuid = characteristicUuid;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof ServiceCharacteristicKey))
return false;
ServiceCharacteristicKey key = (ServiceCharacteristicKey) o;
return serviceUuid.equals(key.serviceUuid) && characteristicUuid.equals(key.characteristicUuid);
}
@Override
public int hashCode() {
int result = serviceUuid.hashCode();
result = 31 * result + characteristicUuid.hashCode();
return result;
}
}
private Map<ServiceCharacteristicKey, byte[]> mPreparedWrites = new HashMap<>();
// Static methods
static public boolean isPeripheralModeSupported() {
return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP);
}
//
public GattServer(Context context, Listener listener) {
mListener = listener;
mBluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
// Update internal status
onBluetoothStateChanged(context);
// Set Ble status receiver
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
LocalBroadcastManager.getInstance(context).registerReceiver(mBleAdapterStateReceiver, filter);
}
public void removeListener(Context context) {
// Unregister Ble Status
LocalBroadcastManager.getInstance(context).unregisterReceiver(mBleAdapterStateReceiver);
mListener = null;
}
private final BroadcastReceiver mBleAdapterStateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (action != null && action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
onBluetoothStateChanged(context);
}
}
};
private void onBluetoothStateChanged(Context context) {
// Setup advertiser
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter != null) {
if (!adapter.isEnabled()) {
Log.w(TAG, "BluetoothLE is disabled");
}
if (!adapter.isMultipleAdvertisementSupported()) {
Log.w(TAG, "Multiple advertisement not supported");
}
mAdvertiser = adapter.getBluetoothLeAdvertiser();
if (mAdvertiser == null) {
Log.w(TAG, "Device does not support Peripheral Mode");
}
} else {
Log.w(TAG, "Device does not support Bluetooth");
}
if (mAdvertiser == null) {
mIsAdvertising = false;
}
if (mShouldStartAdvertising && mAdvertiser != null) {
startAdvertising(context);
}
}
public boolean isPeripheralModeAvailable() {
return mAdvertiser != null;
}
// region Local Name
public String getLocalBluetoothName() {
String name = null;
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter != null) {
name = bluetoothAdapter.getName();
}
return name;
}
public void setLocalBluetoothName(String name) {
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter != null) {
bluetoothAdapter.setName(name);
} else {
Log.w(TAG, "Trying to set bluetooth name with null adapter");
}
}
// endregion
// region Service Management
@NonNull
public List<PeripheralService> getServices() {
return mPeripheralServices;
}
public void addService(@NonNull PeripheralService service) {
service.setListener(this);
mPeripheralServices.add(service);
}
public void removeService(@NonNull PeripheralService service) {
UUID serviceUuid = service.getService().getUuid();
final int index = indexOfPeripheralServicesWithUuid(serviceUuid);
if (index >= 0) {
mPeripheralServices.remove(index);
service.setListener(null);
}
}
public void removeAllServices() {
for (PeripheralService service : mPeripheralServices) {
service.setListener(null);
}
mPeripheralServices.clear();
}
private int indexOfPeripheralServicesWithUuid(@NonNull UUID uuid) {
boolean found = false;
int i = 0;
while (i < mPeripheralServices.size() && !found) {
final UUID peripheralServiceUuid = mPeripheralServices.get(i).getService().getUuid();
if (peripheralServiceUuid.equals(uuid)) {
found = true;
} else {
i++;
}
}
return found ? i : -1;
}
// endregion
// region Advertising
public synchronized boolean startAdvertising(@NonNull Context context) {
mShouldStartAdvertising = true;
if (mBluetoothManager == null || mAdvertiser == null) {
Log.e(TAG, "startAdvertising with nil objects");
return false;
}
// Clean / Setup
stopAdvertising(/*context, */
false);
// Start Gatt Server
Log.d(TAG, "startAdvertising");
mAddServicesSemapreplaced = new Semapreplaced(1, true);
mGattServer = mBluetoothManager.openGattServer(context.getApplicationContext(), mGattServerCallback);
if (mGattServer != null) {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (!adapter.isMultipleAdvertisementSupported() && mPeripheralServices.size() > 1) {
Log.w(TAG, "Trying to advertise multiple services but multipleAdvertisement is no supported on this device");
return false;
}
for (PeripheralService peripheralService : mPeripheralServices) {
if (peripheralService.isEnabled()) {
BluetoothGattService service = peripheralService.getService();
try {
// Semapreplaced to wait for onServiceAdded callback before adding a new service (check addService docs)
mAddServicesSemapreplaced.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
// Note: Wait for onServiceAdded callback before adding another service
final boolean isAdded = mGattServer.addService(service);
if (!isAdded) {
Log.e(TAG, "startGattServer service not added");
}
}
}
// mAddServicesSemapreplaced.release(); // Force release any remaining permits (there are no more services that are going to be added)
// Start advertising
AdvertiseSettings advertiseSettings = new AdvertiseSettings.Builder().setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY).setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH).setConnectable(true).build();
AdvertiseData.Builder advertiseDataBuilder = new AdvertiseData.Builder().setIncludeTxPowerLevel(true);
// Needed to show the local name
advertiseDataBuilder.setIncludeDeviceName(isIncludingDeviceName(context));
final boolean isUuartServiceEnabled = indexOfPeripheralServicesWithUuid(UartPeripheralService.kUartServiceUUID) >= 0;
if (isUuartServiceEnabled) {
// If UART is enabled, add the UUID to the advertisement packet
ParcelUuid serviceParcelUuid = new ParcelUuid(UartPeripheralService.kUartServiceUUID);
advertiseDataBuilder.addServiceUuid(serviceParcelUuid);
}
AdvertiseData advertiseData = advertiseDataBuilder.build();
if (mListener != null) {
mListener.onWillStartAdvertising();
}
mAdvertiser.startAdvertising(advertiseSettings, advertiseData, mAdvertisingCallback);
return true;
} else {
Log.e(TAG, "gatt server is null");
return false;
}
}
public boolean isIncludingDeviceName(@NonNull Context context) {
SharedPreferences preferences = context.getSharedPreferences(kPreferences, MODE_PRIVATE);
return preferences.getBoolean(kPreferences_includeDeviceName, false);
}
public void setIncludeDeviceName(@NonNull Context context, boolean enabled) {
SharedPreferences.Editor preferencesEditor = context.getSharedPreferences(kPreferences, MODE_PRIVATE).edit();
preferencesEditor.putBoolean(kPreferences_includeDeviceName, enabled);
preferencesEditor.apply();
}
private AdvertiseCallback mAdvertisingCallback = new AdvertiseCallback() {
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
super.onStartSuccess(settingsInEffect);
Log.d(TAG, "Advertising onStartSuccess");
mIsAdvertising = true;
if (mListener != null) {
mListener.onDidStartAdvertising();
}
}
@Override
public void onStartFailure(int errorCode) {
super.onStartFailure(errorCode);
/*
if (errorCode == 3) { // Already advertising
stopAdvertising();
}*/
if (errorCode == ADVERTISE_FAILED_ALREADY_STARTED) {
Log.d(TAG, "Advertising onStartFailure because it was already advertising. Failure recovered");
mIsAdvertising = true;
if (mListener != null) {
mListener.onDidStartAdvertising();
}
} else {
Log.e(TAG, "Advertising onStartFailure: " + errorCode);
mIsAdvertising = false;
if (mListener != null) {
mListener.onAdvertisingError(errorCode);
}
}
}
};
private synchronized void stopAdvertising(/*@NonNull Context context, */
boolean notifyListener) {
mShouldStartAdvertising = false;
if (mAdvertiser != null && mIsAdvertising) {
try {
mAdvertiser.stopAdvertising(mAdvertisingCallback);
} catch (IllegalStateException e) {
// Discard IllegalStateException reported in Android vitals crashes
Log.w(TAG, "stopAdvertising illegalstate: " + e);
}
mIsAdvertising = false;
Log.d(TAG, "Advertising stopAdvertising");
}
if (mGattServer != null) {
List<BluetoothDevice> devices = mBluetoothManager.getConnectedDevices(BluetoothGattServer.GATT_SERVER);
for (int i = 0; i < devices.size(); i++) {
mGattServer.cancelConnection(devices.get(i));
}
mGattServer.clearServices();
/*
if (kAddDelayAfterBeforeClosing) { // Hack to avoid Android internal bugs. Exists on Android v6 and v7... maybe more...
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}*/
mGattServer.close();
mGattServer = null;
}
if (notifyListener && mListener != null) {
mListener.onDidStopAdvertising();
}
}
public void stopAdvertising() /*@NonNull Context context*/
{
stopAdvertising(/*context, */
true);
}
public boolean isAdvertising() {
return mIsAdvertising;
}
// end region
// region Gatt Server
private BluetoothGattServerCallback mGattServerCallback = new BluetoothGattServerCallback() {
@Override
public void onConnectionStateChange(final BluetoothDevice device, int status, int newState) {
super.onConnectionStateChange(device, status, newState);
Log.d(TAG, "GattServer: onConnectionStateChange status: " + status + "state:" + newState);
if (status != BluetoothGatt.GATT_SUCCESS) {
Log.w(TAG, "\tonConnectionStateChange error reported");
}
if (mListener != null) {
mListener.onCentralConnectionStatusChanged(newState);
}
}
@Override
public void onServiceAdded(int status, BluetoothGattService service) {
super.onServiceAdded(status, service);
Log.d(TAG, "GattServer: onServiceAdded");
mAddServicesSemapreplaced.release();
}
@Override
public void onMtuChanged(BluetoothDevice device, int mtu) {
super.onMtuChanged(device, mtu);
Log.d(TAG, "Mtu changed: " + mtu);
mMtuSize = mtu;
}
@Override
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
Log.d(TAG, "GattServer: onCharacteristicReadRequest characteristic: " + characteristic.getUuid().toString() + " requestId: " + requestId + " offset: " + offset);
boolean isCharacteristicValid = false;
final UUID serviceUuid = characteristic.getService().getUuid();
final int indexOfPeripheral = indexOfPeripheralServicesWithUuid(serviceUuid);
if (indexOfPeripheral >= 0) {
PeripheralService peripheralService = mPeripheralServices.get(indexOfPeripheral);
final UUID characteristicUuid = characteristic.getUuid();
BluetoothGattCharacteristic serviceCharacteristic = peripheralService.getCharacteristic(characteristicUuid);
if (serviceCharacteristic != null) {
processReadRequest(device, requestId, offset, serviceCharacteristic.getValue());
isCharacteristicValid = true;
}
}
if (!isCharacteristicValid) {
mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_READ_NOT_PERMITTED, offset, null);
}
}
private void processReadRequest(BluetoothDevice device, int requestId, int offset, byte[] bytes) {
byte[] value = bytes != null ? bytes : new byte[] { 0 };
if (offset > value.length) {
mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_INVALID_OFFSET, 0, new byte[] { 0 });
} else {
final int responseSize = Math.min(mMtuSize, value.length - offset);
byte[] responseChunck = new byte[responseSize];
System.arraycopy(value, offset, responseChunck, 0, responseSize);
mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, responseChunck);
}
}
@Override
public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
Log.d(TAG, "GattServer: onCharacteristicWriteRequest characteristic: " + characteristic.getUuid().toString() + " requestId: " + requestId + " preparedWrite: " + preparedWrite + " responseNeeded: " + responseNeeded + " offset: " + offset);
final UUID serviceUuid = characteristic.getService().getUuid();
for (PeripheralService peripheralService : mPeripheralServices) {
if (serviceUuid.equals(peripheralService.getService().getUuid())) {
final UUID characteristicUuid = characteristic.getUuid();
BluetoothGattCharacteristic serviceCharacteristic = peripheralService.getCharacteristic(characteristicUuid);
if (serviceCharacteristic != null) {
final ServiceCharacteristicKey key = new ServiceCharacteristicKey(serviceUuid, characteristicUuid);
byte[] currentValue = preparedWrite ? mPreparedWrites.get(key) : value;
currentValue = processWriteRequest(device, requestId, preparedWrite, responseNeeded, offset, value, currentValue);
if (preparedWrite) {
mPreparedWrites.put(key, currentValue);
} else {
// characteristic.setValue(currentValue);
peripheralService.setCharacteristic(characteristic, currentValue);
}
}
}
}
}
@Override
public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
super.onExecuteWrite(device, requestId, execute);
Log.d(TAG, "GattServer: onExecuteWrite requestId: " + requestId + " execute: " + execute);
if (execute) {
for (ServiceCharacteristicKey key : mPreparedWrites.keySet()) {
byte[] value = mPreparedWrites.get(key);
byte[] currentValue = processWriteRequest(device, requestId, false, false, 0, value, null);
BluetoothGattCharacteristic characteristic = mGattServer.getService(key.serviceUuid).getCharacteristic(key.characteristicUuid);
// characteristic.setValue(currentValue);
for (PeripheralService peripheralService : mPeripheralServices) {
if (key.serviceUuid.equals(peripheralService.getService().getUuid())) {
peripheralService.setCharacteristic(characteristic, currentValue);
}
}
}
}
mPreparedWrites.clear();
mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, new byte[] {});
}
@NonNull
private byte[] processWriteRequest(BluetoothDevice device, int requestId, boolean prepared, boolean responseNeeded, int offset, byte[] value, byte[] currentCharacteristicValue) {
// Adjust currentValue to make room for the new data
// Question: should the characteristic value be expanded to accommodate the value? Maybe not...
if (currentCharacteristicValue == null || offset == 0) {
currentCharacteristicValue = new byte[value.length];
} else if (currentCharacteristicValue.length < offset + value.length) {
byte[] newValue = new byte[offset + value.length];
System.arraycopy(currentCharacteristicValue, 0, newValue, 0, currentCharacteristicValue.length);
currentCharacteristicValue = newValue;
}
System.arraycopy(value, 0, currentCharacteristicValue, offset, value.length);
if (responseNeeded) {
mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
}
return currentCharacteristicValue;
}
@Override
public void onNotificationSent(BluetoothDevice device, int status) {
super.onNotificationSent(device, status);
}
@Override
public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
super.onDescriptorReadRequest(device, requestId, offset, descriptor);
}
@Override
public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);
Log.d(TAG, "GattServer: onDescriptorWriteRequest : " + descriptor.getUuid().toString() + " requestId: " + requestId + " preparedWrite: " + preparedWrite + " responseNeeded: " + responseNeeded + " offset: " + offset);
// Check if is enabling notification
if (descriptor.getUuid().equals(BlePeripheral.kClientCharacteristicConfigUUID)) {
int status = BluetoothGatt.GATT_SUCCESS;
if (value.length != 2) {
status = BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH;
} else {
BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
boolean isNotify = Arrays.equals(value, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE) || Arrays.equals(value, BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
boolean isNotifySupported = (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0;
if (isNotify && isNotifySupported) {
descriptor.setValue(value);
UUID serviceUUID = descriptor.getCharacteristic().getService().getUuid();
if (Arrays.equals(value, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)) {
Log.d(TAG, "subscribed to characteristic: " + descriptor.getCharacteristic().getUuid().toString());
for (PeripheralService service : mPeripheralServices) {
if (serviceUUID.equals(service.getService().getUuid())) {
service.subscribe(characteristic.getUuid(), device);
}
}
} else if (Arrays.equals(value, BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)) {
Log.d(TAG, "unsubscribed from characteristic: " + descriptor.getCharacteristic().getUuid().toString());
for (PeripheralService service : mPeripheralServices) {
if (serviceUUID.equals(service.getService().getUuid())) {
service.unsubscribe(characteristic.getUuid(), device);
}
}
}
} else {
status = BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED;
}
// TODO: add support for Indications
}
if (responseNeeded) {
mGattServer.sendResponse(device, requestId, status, offset, value);
}
}
}
};
// end region
// region PeripheralService.Listener
@Override
public void updateValue(@NonNull BluetoothDevice[] devices, @NonNull BluetoothGattCharacteristic characteristic) {
for (BluetoothDevice device : devices) {
mGattServer.notifyCharacteristicChanged(device, characteristic, false);
}
}
// endregion
}
8
Source : HidPeripheral.java
with Apache License 2.0
from kshoji
with Apache License 2.0
from kshoji
/**
* BLE HID over GATT base features
*
* @author K.Shoji
*/
@TargetApi(VERSION_CODES.LOLLIPOP)
public abstract clreplaced HidPeripheral {
private static final String TAG = HidPeripheral.clreplaced.getSimpleName();
/**
* Main items
*/
protected static byte INPUT(final int size) {
return (byte) (0x80 | size);
}
protected static byte OUTPUT(final int size) {
return (byte) (0x90 | size);
}
protected static byte COLLECTION(final int size) {
return (byte) (0xA0 | size);
}
protected static byte FEATURE(final int size) {
return (byte) (0xB0 | size);
}
protected static byte END_COLLECTION(final int size) {
return (byte) (0xC0 | size);
}
/**
* Global items
*/
protected static byte USAGE_PAGE(final int size) {
return (byte) (0x04 | size);
}
protected static byte LOGICAL_MINIMUM(final int size) {
return (byte) (0x14 | size);
}
protected static byte LOGICAL_MAXIMUM(final int size) {
return (byte) (0x24 | size);
}
protected static byte PHYSICAL_MINIMUM(final int size) {
return (byte) (0x34 | size);
}
protected static byte PHYSICAL_MAXIMUM(final int size) {
return (byte) (0x44 | size);
}
protected static byte UNIT_EXPONENT(final int size) {
return (byte) (0x54 | size);
}
protected static byte UNIT(final int size) {
return (byte) (0x64 | size);
}
protected static byte REPORT_SIZE(final int size) {
return (byte) (0x74 | size);
}
protected static byte REPORT_ID(final int size) {
return (byte) (0x84 | size);
}
protected static byte REPORT_COUNT(final int size) {
return (byte) (0x94 | size);
}
/**
* Local items
*/
protected static byte USAGE(final int size) {
return (byte) (0x08 | size);
}
protected static byte USAGE_MINIMUM(final int size) {
return (byte) (0x18 | size);
}
protected static byte USAGE_MAXIMUM(final int size) {
return (byte) (0x28 | size);
}
protected static byte LSB(final int value) {
return (byte) (value & 0xff);
}
protected static byte MSB(final int value) {
return (byte) (value >> 8 & 0xff);
}
/**
* Device Information Service
*/
private static final UUID SERVICE_DEVICE_INFORMATION = BleUuidUtils.fromShortValue(0x180A);
private static final UUID CHARACTERISTIC_MANUFACTURER_NAME = BleUuidUtils.fromShortValue(0x2A29);
private static final UUID CHARACTERISTIC_MODEL_NUMBER = BleUuidUtils.fromShortValue(0x2A24);
private static final UUID CHARACTERISTIC_SERIAL_NUMBER = BleUuidUtils.fromShortValue(0x2A25);
private static final int DEVICE_INFO_MAX_LENGTH = 20;
private String manufacturer = "kshoji.jp";
private String deviceName = "BLE HID";
private String serialNumber = "12345678";
/**
* Battery Service
*/
private static final UUID SERVICE_BATTERY = BleUuidUtils.fromShortValue(0x180F);
private static final UUID CHARACTERISTIC_BATTERY_LEVEL = BleUuidUtils.fromShortValue(0x2A19);
/**
* HID Service
*/
private static final UUID SERVICE_BLE_HID = BleUuidUtils.fromShortValue(0x1812);
private static final UUID CHARACTERISTIC_HID_INFORMATION = BleUuidUtils.fromShortValue(0x2A4A);
private static final UUID CHARACTERISTIC_REPORT_MAP = BleUuidUtils.fromShortValue(0x2A4B);
private static final UUID CHARACTERISTIC_HID_CONTROL_POINT = BleUuidUtils.fromShortValue(0x2A4C);
private static final UUID CHARACTERISTIC_REPORT = BleUuidUtils.fromShortValue(0x2A4D);
private static final UUID CHARACTERISTIC_PROTOCOL_MODE = BleUuidUtils.fromShortValue(0x2A4E);
/**
* Represents Report Map byte array
* @return Report Map data
*/
protected abstract byte[] getReportMap();
/**
* HID Input Report
*/
private final Queue<byte[]> inputReportQueue = new ConcurrentLinkedQueue<>();
protected final void addInputReport(final byte[] inputReport) {
if (inputReport != null && inputReport.length > 0) {
inputReportQueue.offer(inputReport);
}
}
/**
* HID Output Report
*
* @param outputReport the report data
*/
protected abstract void onOutputReport(final byte[] outputReport);
/**
* Gatt Characteristic Descriptor
*/
private static final UUID DESCRIPTOR_REPORT_REFERENCE = BleUuidUtils.fromShortValue(0x2908);
private static final UUID DESCRIPTOR_CLIENT_CHARACTERISTIC_CONFIGURATION = BleUuidUtils.fromShortValue(0x2902);
private static final byte[] EMPTY_BYTES = {};
private static final byte[] RESPONSE_HID_INFORMATION = { 0x11, 0x01, 0x00, 0x03 };
/**
* Instances for the peripheral
*/
private final Context applicationContext;
private final Handler handler;
private final BluetoothLeAdvertiser bluetoothLeAdvertiser;
private BluetoothGattCharacteristic inputReportCharacteristic;
@Nullable
private BluetoothGattServer gattServer;
private final Map<String, BluetoothDevice> bluetoothDevicesMap = new HashMap<>();
/**
* Constructor<br />
* Before constructing the instance, check the Bluetooth availability.
*
* @param context the ApplicationContext
* @param needInputReport true: serves 'Input Report' BLE characteristic
* @param needOutputReport true: serves 'Output Report' BLE characteristic
* @param needFeatureReport true: serves 'Feature Report' BLE characteristic
* @param dataSendingRate sending rate in milliseconds
* @throws UnsupportedOperationException if starting Bluetooth LE Peripheral failed
*/
protected HidPeripheral(final Context context, final boolean needInputReport, final boolean needOutputReport, final boolean needFeatureReport, final int dataSendingRate) throws UnsupportedOperationException {
applicationContext = context.getApplicationContext();
handler = new Handler(applicationContext.getMainLooper());
final BluetoothManager bluetoothManager = (BluetoothManager) applicationContext.getSystemService(Context.BLUETOOTH_SERVICE);
final BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
if (bluetoothAdapter == null) {
throw new UnsupportedOperationException("Bluetooth is not available.");
}
if (!bluetoothAdapter.isEnabled()) {
throw new UnsupportedOperationException("Bluetooth is disabled.");
}
Log.d(TAG, "isMultipleAdvertisementSupported:" + bluetoothAdapter.isMultipleAdvertisementSupported());
if (!bluetoothAdapter.isMultipleAdvertisementSupported()) {
throw new UnsupportedOperationException("Bluetooth LE Advertising not supported on this device.");
}
bluetoothLeAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser();
Log.d(TAG, "bluetoothLeAdvertiser: " + bluetoothLeAdvertiser);
if (bluetoothLeAdvertiser == null) {
throw new UnsupportedOperationException("Bluetooth LE Advertising not supported on this device.");
}
gattServer = bluetoothManager.openGattServer(applicationContext, gattServerCallback);
if (gattServer == null) {
throw new UnsupportedOperationException("gattServer is null, check Bluetooth is ON.");
}
// setup services
addService(setUpHidService(needInputReport, needOutputReport, needFeatureReport));
addService(setUpDeviceInformationService());
addService(setUpBatteryService());
// send report each dataSendingRate, if data available
new Timer().scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
final byte[] polled = inputReportQueue.poll();
if (polled != null && inputReportCharacteristic != null) {
inputReportCharacteristic.setValue(polled);
handler.post(new Runnable() {
@Override
public void run() {
final Set<BluetoothDevice> devices = getDevices();
for (final BluetoothDevice device : devices) {
try {
if (gattServer != null) {
gattServer.notifyCharacteristicChanged(device, inputReportCharacteristic, false);
}
} catch (final Throwable ignored) {
}
}
}
});
}
}
}, 0, dataSendingRate);
}
/**
* Add GATT service to gattServer
*
* @param service the service
*/
private void addService(final BluetoothGattService service) {
replacedert gattServer != null;
boolean serviceAdded = false;
while (!serviceAdded) {
try {
serviceAdded = gattServer.addService(service);
} catch (final Exception e) {
Log.d(TAG, "Adding Service failed", e);
}
}
Log.d(TAG, "Service: " + service.getUuid() + " added.");
}
/**
* Setup Device Information Service
*
* @return the service
*/
private static BluetoothGattService setUpDeviceInformationService() {
final BluetoothGattService service = new BluetoothGattService(SERVICE_DEVICE_INFORMATION, BluetoothGattService.SERVICE_TYPE_PRIMARY);
{
final BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(CHARACTERISTIC_MANUFACTURER_NAME, BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
while (!service.addCharacteristic(characteristic)) ;
}
{
final BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(CHARACTERISTIC_MODEL_NUMBER, BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
while (!service.addCharacteristic(characteristic)) ;
}
{
final BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(CHARACTERISTIC_SERIAL_NUMBER, BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
while (!service.addCharacteristic(characteristic)) ;
}
return service;
}
/**
* Setup Battery Service
*
* @return the service
*/
private static BluetoothGattService setUpBatteryService() {
final BluetoothGattService service = new BluetoothGattService(SERVICE_BATTERY, BluetoothGattService.SERVICE_TYPE_PRIMARY);
// Battery Level
final BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(CHARACTERISTIC_BATTERY_LEVEL, BluetoothGattCharacteristic.PROPERTY_NOTIFY | BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
final BluetoothGattDescriptor clientCharacteristicConfigurationDescriptor = new BluetoothGattDescriptor(DESCRIPTOR_CLIENT_CHARACTERISTIC_CONFIGURATION, BluetoothGattDescriptor.PERMISSION_READ | BluetoothGattDescriptor.PERMISSION_WRITE);
clientCharacteristicConfigurationDescriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
characteristic.addDescriptor(clientCharacteristicConfigurationDescriptor);
while (!service.addCharacteristic(characteristic)) ;
return service;
}
/**
* Setup HID Service
*
* @param isNeedInputReport true: serves 'Input Report' BLE characteristic
* @param isNeedOutputReport true: serves 'Output Report' BLE characteristic
* @param isNeedFeatureReport true: serves 'Feature Report' BLE characteristic
* @return the service
*/
private BluetoothGattService setUpHidService(final boolean isNeedInputReport, final boolean isNeedOutputReport, final boolean isNeedFeatureReport) {
final BluetoothGattService service = new BluetoothGattService(SERVICE_BLE_HID, BluetoothGattService.SERVICE_TYPE_PRIMARY);
// HID Information
{
final BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(CHARACTERISTIC_HID_INFORMATION, BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
while (!service.addCharacteristic(characteristic)) ;
}
// Report Map
{
final BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(CHARACTERISTIC_REPORT_MAP, BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
while (!service.addCharacteristic(characteristic)) ;
}
// Protocol Mode
{
final BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(CHARACTERISTIC_PROTOCOL_MODE, BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE, BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED | BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED);
characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
while (!service.addCharacteristic(characteristic)) ;
}
// HID Control Point
{
final BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(CHARACTERISTIC_HID_CONTROL_POINT, BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE, BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED);
characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
while (!service.addCharacteristic(characteristic)) ;
}
// Input Report
if (isNeedInputReport) {
final BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(CHARACTERISTIC_REPORT, BluetoothGattCharacteristic.PROPERTY_NOTIFY | BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_WRITE, BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED | BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED);
final BluetoothGattDescriptor clientCharacteristicConfigurationDescriptor = new BluetoothGattDescriptor(DESCRIPTOR_CLIENT_CHARACTERISTIC_CONFIGURATION, // | BluetoothGattDescriptor.PERMISSION_WRITE
BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED | BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED);
clientCharacteristicConfigurationDescriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
characteristic.addDescriptor(clientCharacteristicConfigurationDescriptor);
final BluetoothGattDescriptor reportReferenceDescriptor = new BluetoothGattDescriptor(DESCRIPTOR_REPORT_REFERENCE, BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED | BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED);
characteristic.addDescriptor(reportReferenceDescriptor);
while (!service.addCharacteristic(characteristic)) ;
inputReportCharacteristic = characteristic;
}
// Output Report
if (isNeedOutputReport) {
final BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(CHARACTERISTIC_REPORT, BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE, BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED | BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED);
characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
final BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(DESCRIPTOR_REPORT_REFERENCE, BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED | BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED);
characteristic.addDescriptor(descriptor);
while (!service.addCharacteristic(characteristic)) ;
}
// Feature Report
if (isNeedFeatureReport) {
final BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(CHARACTERISTIC_REPORT, BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_WRITE, BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED | BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED);
final BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(DESCRIPTOR_REPORT_REFERENCE, BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED | BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED);
characteristic.addDescriptor(descriptor);
while (!service.addCharacteristic(characteristic)) ;
}
return service;
}
/**
* Starts advertising
*/
public final void startAdvertising() {
handler.post(new Runnable() {
@Override
public void run() {
// set up advertising setting
final AdvertiseSettings advertiseSettings = new AdvertiseSettings.Builder().setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH).setConnectable(true).setTimeout(0).setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY).build();
// set up advertising data
final AdvertiseData advertiseData = new Builder().setIncludeTxPowerLevel(false).setIncludeDeviceName(true).addServiceUuid(ParcelUuid.fromString(SERVICE_DEVICE_INFORMATION.toString())).addServiceUuid(ParcelUuid.fromString(SERVICE_BLE_HID.toString())).addServiceUuid(ParcelUuid.fromString(SERVICE_BATTERY.toString())).build();
// set up scan result
final AdvertiseData scanResult = new Builder().addServiceUuid(ParcelUuid.fromString(SERVICE_DEVICE_INFORMATION.toString())).addServiceUuid(ParcelUuid.fromString(SERVICE_BLE_HID.toString())).addServiceUuid(ParcelUuid.fromString(SERVICE_BATTERY.toString())).build();
Log.d(TAG, "advertiseData: " + advertiseData + ", scanResult: " + scanResult);
bluetoothLeAdvertiser.startAdvertising(advertiseSettings, advertiseData, scanResult, advertiseCallback);
}
});
}
/**
* Stops advertising
*/
public final void stopAdvertising() {
handler.post(new Runnable() {
@Override
public void run() {
try {
bluetoothLeAdvertiser.stopAdvertising(advertiseCallback);
} catch (final IllegalStateException ignored) {
// BT Adapter is not turned ON
}
try {
if (gattServer != null) {
final Set<BluetoothDevice> devices = getDevices();
for (final BluetoothDevice device : devices) {
gattServer.cancelConnection(device);
}
gattServer.close();
gattServer = null;
}
} catch (final IllegalStateException ignored) {
// BT Adapter is not turned ON
}
}
});
}
/**
* Callback for BLE connection<br />
* nothing to do.
*/
private final AdvertiseCallback advertiseCallback = new NullAdvertiseCallback();
private static clreplaced NullAdvertiseCallback extends AdvertiseCallback {
}
/**
* Obtains connected Bluetooth devices
*
* @return the connected Bluetooth devices
*/
private Set<BluetoothDevice> getDevices() {
final Set<BluetoothDevice> deviceSet = new HashSet<>();
synchronized (bluetoothDevicesMap) {
deviceSet.addAll(bluetoothDevicesMap.values());
}
return Collections.unmodifiableSet(deviceSet);
}
/**
* Callback for BLE data transfer
*/
private final BluetoothGattServerCallback gattServerCallback = new BluetoothGattServerCallback() {
@Override
public void onConnectionStateChange(final BluetoothDevice device, final int status, final int newState) {
super.onConnectionStateChange(device, status, newState);
Log.d(TAG, "onConnectionStateChange status: " + status + ", newState: " + newState);
switch(newState) {
case BluetoothProfile.STATE_CONNECTED:
// check bond status
Log.d(TAG, "BluetoothProfile.STATE_CONNECTED bondState: " + device.getBondState());
if (device.getBondState() == BluetoothDevice.BOND_NONE) {
applicationContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
final String action = intent.getAction();
Log.d(TAG, "onReceive action: " + action);
if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
final int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR);
if (state == BluetoothDevice.BOND_BONDED) {
final BluetoothDevice bondedDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// successfully bonded
context.unregisterReceiver(this);
handler.post(new Runnable() {
@Override
public void run() {
if (gattServer != null) {
gattServer.connect(device, true);
}
}
});
Log.d(TAG, "successfully bonded");
}
}
}
}, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
// create bond
try {
device.setPairingConfirmation(true);
} catch (final SecurityException e) {
Log.d(TAG, e.getMessage(), e);
}
device.createBond();
} else if (device.getBondState() == BluetoothDevice.BOND_BONDED) {
handler.post(new Runnable() {
@Override
public void run() {
if (gattServer != null) {
gattServer.connect(device, true);
}
}
});
synchronized (bluetoothDevicesMap) {
bluetoothDevicesMap.put(device.getAddress(), device);
}
}
break;
case BluetoothProfile.STATE_DISCONNECTED:
final String deviceAddress = device.getAddress();
// try reconnect immediately
handler.post(new Runnable() {
@Override
public void run() {
if (gattServer != null) {
// gattServer.cancelConnection(device);
gattServer.connect(device, true);
}
}
});
synchronized (bluetoothDevicesMap) {
bluetoothDevicesMap.remove(deviceAddress);
}
break;
default:
// do nothing
break;
}
}
@Override
public void onCharacteristicReadRequest(final BluetoothDevice device, final int requestId, final int offset, final BluetoothGattCharacteristic characteristic) {
super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
if (gattServer == null) {
return;
}
Log.d(TAG, "onCharacteristicReadRequest characteristic: " + characteristic.getUuid() + ", offset: " + offset);
handler.post(new Runnable() {
@Override
public void run() {
final UUID characteristicUuid = characteristic.getUuid();
if (BleUuidUtils.matches(CHARACTERISTIC_HID_INFORMATION, characteristicUuid)) {
gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, RESPONSE_HID_INFORMATION);
} else if (BleUuidUtils.matches(CHARACTERISTIC_REPORT_MAP, characteristicUuid)) {
if (offset == 0) {
gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, getReportMap());
} else {
final int remainLength = getReportMap().length - offset;
if (remainLength > 0) {
final byte[] data = new byte[remainLength];
System.arraycopy(getReportMap(), offset, data, 0, remainLength);
gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, data);
} else {
gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, null);
}
}
} else if (BleUuidUtils.matches(CHARACTERISTIC_HID_CONTROL_POINT, characteristicUuid)) {
gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, new byte[] { 0 });
} else if (BleUuidUtils.matches(CHARACTERISTIC_REPORT, characteristicUuid)) {
gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, EMPTY_BYTES);
} else if (BleUuidUtils.matches(CHARACTERISTIC_MANUFACTURER_NAME, characteristicUuid)) {
gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, manufacturer.getBytes(StandardCharsets.UTF_8));
} else if (BleUuidUtils.matches(CHARACTERISTIC_SERIAL_NUMBER, characteristicUuid)) {
gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, serialNumber.getBytes(StandardCharsets.UTF_8));
} else if (BleUuidUtils.matches(CHARACTERISTIC_MODEL_NUMBER, characteristicUuid)) {
gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, deviceName.getBytes(StandardCharsets.UTF_8));
} else if (BleUuidUtils.matches(CHARACTERISTIC_BATTERY_LEVEL, characteristicUuid)) {
// always 100%
gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, new byte[] { 0x64 });
} else {
gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, characteristic.getValue());
}
}
});
}
@Override
public void onDescriptorReadRequest(final BluetoothDevice device, final int requestId, final int offset, final BluetoothGattDescriptor descriptor) {
super.onDescriptorReadRequest(device, requestId, offset, descriptor);
Log.d(TAG, "onDescriptorReadRequest requestId: " + requestId + ", offset: " + offset + ", descriptor: " + descriptor.getUuid());
if (gattServer == null) {
return;
}
handler.post(new Runnable() {
@Override
public void run() {
if (BleUuidUtils.matches(DESCRIPTOR_REPORT_REFERENCE, descriptor.getUuid())) {
final int characteristicProperties = descriptor.getCharacteristic().getProperties();
if (characteristicProperties == (BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_NOTIFY)) {
// Input Report
gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, new byte[] { 0, 1 });
} else if (characteristicProperties == (BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE)) {
// Output Report
gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, new byte[] { 0, 2 });
} else if (characteristicProperties == (BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_WRITE)) {
// Feature Report
gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, new byte[] { 0, 3 });
} else {
gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, 0, EMPTY_BYTES);
}
}
}
});
}
@Override
public void onCharacteristicWriteRequest(final BluetoothDevice device, final int requestId, final BluetoothGattCharacteristic characteristic, final boolean preparedWrite, final boolean responseNeeded, final int offset, final byte[] value) {
super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
Log.d(TAG, "onCharacteristicWriteRequest characteristic: " + characteristic.getUuid() + ", value: " + Arrays.toString(value));
if (gattServer == null) {
return;
}
if (responseNeeded) {
if (BleUuidUtils.matches(CHARACTERISTIC_REPORT, characteristic.getUuid())) {
if (characteristic.getProperties() == (BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE)) {
// Output Report
onOutputReport(value);
// send empty
gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, EMPTY_BYTES);
} else {
// send empty
gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, EMPTY_BYTES);
}
}
}
}
@Override
public void onDescriptorWriteRequest(final BluetoothDevice device, final int requestId, final BluetoothGattDescriptor descriptor, final boolean preparedWrite, final boolean responseNeeded, final int offset, final byte[] value) {
super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);
Log.d(TAG, "onDescriptorWriteRequest descriptor: " + descriptor.getUuid() + ", value: " + Arrays.toString(value) + ", responseNeeded: " + responseNeeded + ", preparedWrite: " + preparedWrite);
descriptor.setValue(value);
if (responseNeeded) {
if (BleUuidUtils.matches(DESCRIPTOR_CLIENT_CHARACTERISTIC_CONFIGURATION, descriptor.getUuid())) {
// send empty
if (gattServer != null) {
gattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, EMPTY_BYTES);
}
}
}
}
@Override
public void onServiceAdded(final int status, final BluetoothGattService service) {
super.onServiceAdded(status, service);
Log.d(TAG, "onServiceAdded status: " + status + ", service: " + service.getUuid());
if (status != 0) {
Log.d(TAG, "onServiceAdded Adding Service failed..");
}
}
};
/**
* Set the manufacturer name
*
* @param newManufacturer the name
*/
public final void setManufacturer(@NonNull final String newManufacturer) {
// length check
final byte[] manufacturerBytes = newManufacturer.getBytes(StandardCharsets.UTF_8);
if (manufacturerBytes.length > DEVICE_INFO_MAX_LENGTH) {
// shorten
final byte[] bytes = new byte[DEVICE_INFO_MAX_LENGTH];
System.arraycopy(manufacturerBytes, 0, bytes, 0, DEVICE_INFO_MAX_LENGTH);
manufacturer = new String(bytes, StandardCharsets.UTF_8);
} else {
manufacturer = newManufacturer;
}
}
/**
* Set the device name
*
* @param newDeviceName the name
*/
public final void setDeviceName(@NonNull final String newDeviceName) {
// length check
final byte[] deviceNameBytes = newDeviceName.getBytes(StandardCharsets.UTF_8);
if (deviceNameBytes.length > DEVICE_INFO_MAX_LENGTH) {
// shorten
final byte[] bytes = new byte[DEVICE_INFO_MAX_LENGTH];
System.arraycopy(deviceNameBytes, 0, bytes, 0, DEVICE_INFO_MAX_LENGTH);
deviceName = new String(bytes, StandardCharsets.UTF_8);
} else {
deviceName = newDeviceName;
}
}
/**
* Set the serial number
*
* @param newSerialNumber the number
*/
public final void setSerialNumber(@NonNull final String newSerialNumber) {
// length check
final byte[] deviceNameBytes = newSerialNumber.getBytes(StandardCharsets.UTF_8);
if (deviceNameBytes.length > DEVICE_INFO_MAX_LENGTH) {
// shorten
final byte[] bytes = new byte[DEVICE_INFO_MAX_LENGTH];
System.arraycopy(deviceNameBytes, 0, bytes, 0, DEVICE_INFO_MAX_LENGTH);
serialNumber = new String(bytes, StandardCharsets.UTF_8);
} else {
serialNumber = newSerialNumber;
}
}
}
6
Source : CSCService.java
with MIT License
from starryalley
with MIT License
from starryalley
public clreplaced CSCService extends Service {
private static final String TAG = CSCService.clreplaced.getSimpleName();
private static final int ONGOING_NOTIFICATION_ID = 9999;
private static final String CHANNEL_DEFAULT_IMPORTANCE = "csc_ble_channel";
private static final String MAIN_CHANNEL_NAME = "CscService";
// Ant+ sensors
private AntPlusBikeSpeedDistancePcc bsdPcc = null;
private PccReleaseHandle<AntPlusBikeSpeedDistancePcc> bsdReleaseHandle = null;
private AntPlusBikeCadencePcc bcPcc = null;
private PccReleaseHandle<AntPlusBikeCadencePcc> bcReleaseHandle = null;
private AntPlusHeartRatePcc hrPcc = null;
private PccReleaseHandle<AntPlusHeartRatePcc> hrReleaseHandle = null;
private AntPlusStrideSdmPcc ssPcc = null;
private PccReleaseHandle<AntPlusStrideSdmPcc> ssReleaseHandle = null;
// Checks that the callback that is done after a BluetoothGattServer.addService() has been complete.
// More services cannot be added until the callback has completed successfully
private boolean btServiceInitialized = false;
// 700x23c cirreplacedference in meter
private static final BigDecimal cirreplacedference = new BigDecimal("2.095");
// m/s to km/h ratio
private static final BigDecimal msToKmSRatio = new BigDecimal("3.6");
// bluetooth API
private BluetoothManager mBluetoothManager;
private BluetoothGattServer mBluetoothGattServer;
private BluetoothLeAdvertiser mBluetoothLeAdvertiser;
// notification subscribers
private Set<BluetoothDevice> mRegisteredDevices = new HashSet<>();
// last wheel and crank (speed/cadence) information to send to CSCProfile
private long replacedulativeWheelRevolution = 0;
private long replacedulativeCrankRevolution = 0;
private int lastWheelEventTime = 0;
private int lastCrankEventTime = 0;
// for UI updates
private long lastSpeedTimestamp = 0;
private long lastCadenceTimestamp = 0;
private long lastHRTimestamp = 0;
private long lastSSDistanceTimestamp = 0;
private long lastSSSpeedTimestamp = 0;
private long lastSSStrideCountTimestamp = 0;
private float lastSpeed = 0;
private int lastCadence = 0;
private int lastHR = 0;
private long lastSSDistance = 0;
private float lastSSSpeed = 0;
private long lastStridePerMinute = 0;
// for onCreate() failure case
private boolean initialised = false;
// Used to flag if we have a combined speed and cadence sensor and have already re-connected as combined
private boolean combinedSensorConnected = false;
// Binder for activities wishing to communicate with this service
private final IBinder binder = new LocalBinder();
private AntPluginPcc.IPluginAccessResultReceiver<AntPlusBikeSpeedDistancePcc> mBSDResultReceiver = new AntPluginPcc.IPluginAccessResultReceiver<AntPlusBikeSpeedDistancePcc>() {
@Override
public void onResultReceived(AntPlusBikeSpeedDistancePcc result, RequestAccessResult resultCode, DeviceState initialDeviceState) {
if (resultCode == RequestAccessResult.SUCCESS) {
bsdPcc = result;
Log.d(TAG, result.getDeviceName() + ": " + initialDeviceState);
subscribeToEvents();
} else if (resultCode == RequestAccessResult.USER_CANCELLED) {
Log.d(TAG, "BSD Closed:" + resultCode);
} else {
Log.w(TAG, "BSD state changed:" + initialDeviceState + ", resultCode:" + resultCode);
}
// send broadcast
Intent i = new Intent("idv.markkuo.cscblebridge.ANTDATA");
i.putExtra("bsd_service_status", initialDeviceState.toString() + "\n(" + resultCode + ")");
sendBroadcast(i);
}
private void subscribeToEvents() {
bsdPcc.subscribeCalculatedSpeedEvent(new AntPlusBikeSpeedDistancePcc.CalculatedSpeedReceiver(cirreplacedference) {
@Override
public void onNewCalculatedSpeed(final long estTimestamp, final EnumSet<EventFlag> eventFlags, final BigDecimal calculatedSpeed) {
// convert m/s to km/h
lastSpeed = calculatedSpeed.multiply(msToKmSRatio).floatValue();
// Log.v(TAG, "Speed:" + lastSpeed);
}
});
bsdPcc.subscribeRawSpeedAndDistanceDataEvent(new AntPlusBikeSpeedDistancePcc.IRawSpeedAndDistanceDataReceiver() {
@Override
public void onNewRawSpeedAndDistanceData(long estTimestamp, EnumSet<EventFlag> eventFlags, BigDecimal timestampOfLastEvent, long replacedulativeRevolutions) {
// estTimestamp - The estimated timestamp of when this event was triggered. Useful for correlating multiple events and determining when data was sent for more accurate data records.
// eventFlags - Informational flags about the event.
// timestampOfLastEvent - Sensor reported time counter value of last distance or speed computation (up to 1/200s accuracy). Units: s. Rollover: Every ~46 quadrillion s (~1.5 billion years).
// replacedulativeRevolutions - Total number of revolutions since the sensor was first connected. Note: If the subscriber is not the first PCC connected to the device the acreplacedulation will probably already be at a value greater than 0 and the subscriber should save the first received value as a relative zero for itself. Units: revolutions. Rollover: Every ~9 quintillion revolutions.
Log.v(TAG, "=> BSD: replacedulative revolution:" + replacedulativeRevolutions + ", lastEventTime:" + timestampOfLastEvent);
replacedulativeWheelRevolution = replacedulativeRevolutions;
lastWheelEventTime = (int) (timestampOfLastEvent.doubleValue() * 1024.0);
lastSpeedTimestamp = estTimestamp;
}
});
if (bsdPcc.isSpeedAndCadenceCombinedSensor() && !combinedSensorConnected) {
// reconnect cadence sensor as combined sensor
if (bcReleaseHandle != null) {
bcReleaseHandle.close();
}
combinedSensorConnected = true;
bcReleaseHandle = AntPlusBikeCadencePcc.requestAccess(getApplicationContext(), bsdPcc.getAntDeviceNumber(), 0, true, mBCResultReceiver, mBCDeviceStateChangeReceiver);
}
}
};
private AntPluginPcc.IPluginAccessResultReceiver<AntPlusBikeCadencePcc> mBCResultReceiver = new AntPluginPcc.IPluginAccessResultReceiver<AntPlusBikeCadencePcc>() {
// Handle the result, connecting to events on success or reporting
// failure to user.
@Override
public void onResultReceived(AntPlusBikeCadencePcc result, RequestAccessResult resultCode, DeviceState initialDeviceState) {
if (resultCode == RequestAccessResult.SUCCESS) {
bcPcc = result;
Log.d(TAG, result.getDeviceName() + ": " + initialDeviceState);
subscribeToEvents();
} else if (resultCode == RequestAccessResult.USER_CANCELLED) {
Log.d(TAG, "BC Closed:" + resultCode);
} else {
Log.w(TAG, "BC state changed:" + initialDeviceState + ", resultCode:" + resultCode);
}
// send broadcast
Intent i = new Intent("idv.markkuo.cscblebridge.ANTDATA");
i.putExtra("bc_service_status", initialDeviceState.toString() + "\n(" + resultCode + ")");
sendBroadcast(i);
}
private void subscribeToEvents() {
bcPcc.subscribeCalculatedCadenceEvent(new AntPlusBikeCadencePcc.ICalculatedCadenceReceiver() {
@Override
public void onNewCalculatedCadence(final long estTimestamp, final EnumSet<EventFlag> eventFlags, final BigDecimal calculatedCadence) {
// Log.v(TAG, "Cadence:" + calculatedCadence.intValue());
lastCadence = calculatedCadence.intValue();
}
});
bcPcc.subscribeRawCadenceDataEvent(new AntPlusBikeCadencePcc.IRawCadenceDataReceiver() {
@Override
public void onNewRawCadenceData(final long estTimestamp, final EnumSet<EventFlag> eventFlags, final BigDecimal timestampOfLastEvent, final long replacedulativeRevolutions) {
Log.v(TAG, "=> BC: replacedulative revolution:" + replacedulativeRevolutions + ", lastEventTime:" + timestampOfLastEvent);
replacedulativeCrankRevolution = replacedulativeRevolutions;
lastCrankEventTime = (int) (timestampOfLastEvent.doubleValue() * 1024.0);
lastCadenceTimestamp = estTimestamp;
}
});
if (bcPcc.isSpeedAndCadenceCombinedSensor() && !combinedSensorConnected) {
// reconnect speed sensor as a combined sensor
if (bsdReleaseHandle != null) {
bsdReleaseHandle.close();
}
combinedSensorConnected = true;
bsdReleaseHandle = AntPlusBikeSpeedDistancePcc.requestAccess(getApplicationContext(), bcPcc.getAntDeviceNumber(), 0, true, mBSDResultReceiver, mBSDDeviceStateChangeReceiver);
}
}
};
private AntPluginPcc.IPluginAccessResultReceiver<AntPlusHeartRatePcc> mHRResultReceiver = new AntPluginPcc.IPluginAccessResultReceiver<AntPlusHeartRatePcc>() {
@Override
public void onResultReceived(AntPlusHeartRatePcc result, RequestAccessResult resultCode, DeviceState initialDeviceState) {
if (resultCode == RequestAccessResult.SUCCESS) {
hrPcc = result;
Log.d(TAG, result.getDeviceName() + ": " + initialDeviceState);
subscribeToEvents();
} else if (resultCode == RequestAccessResult.USER_CANCELLED) {
Log.d(TAG, "HR Closed:" + resultCode);
} else {
Log.w(TAG, "HR state changed:" + initialDeviceState + ", resultCode:" + resultCode);
}
// send broadcast
Intent i = new Intent("idv.markkuo.cscblebridge.ANTDATA");
i.putExtra("hr_service_status", initialDeviceState.toString() + "\n(" + resultCode + ")");
sendBroadcast(i);
}
private void subscribeToEvents() {
hrPcc.subscribeHeartRateDataEvent(new AntPlusHeartRatePcc.IHeartRateDataReceiver() {
@Override
public void onNewHeartRateData(final long estTimestamp, EnumSet<EventFlag> eventFlags, final int computedHeartRate, final long heartBeatCount, final BigDecimal heartBeatEventTime, final AntPlusHeartRatePcc.DataState dataState) {
lastHR = computedHeartRate;
lastHRTimestamp = estTimestamp;
}
});
}
};
private AntPluginPcc.IPluginAccessResultReceiver<AntPlusStrideSdmPcc> mSSResultReceiver = new AntPluginPcc.IPluginAccessResultReceiver<AntPlusStrideSdmPcc>() {
@Override
public void onResultReceived(AntPlusStrideSdmPcc result, RequestAccessResult resultCode, DeviceState initialDeviceState) {
if (resultCode == RequestAccessResult.SUCCESS) {
ssPcc = result;
Log.d(TAG, result.getDeviceName() + ": " + initialDeviceState);
subscribeToEvents();
} else if (resultCode == RequestAccessResult.USER_CANCELLED) {
Log.d(TAG, "SS Closed:" + resultCode);
} else {
Log.w(TAG, "SS state changed: " + initialDeviceState + ", resultCode:" + resultCode);
}
// send broadcast
Intent i = new Intent("idv.markkuo.cscblebridge.ANTDATA");
i.putExtra("ss_service_status", initialDeviceState.toString() + "\n(" + resultCode + ")");
sendBroadcast(i);
}
private void subscribeToEvents() {
// https://www.thisisant.com/developer/ant-plus/device-profiles#528_tab
ssPcc.subscribeStrideCountEvent(new AntPlusStrideSdmPcc.IStrideCountReceiver() {
private static final int TEN_SECONDS_IN_MS = 10000;
private static final int FALLBACK_MAX_LIST_SIZE = 500;
private static final float ONE_MINUTE_IN_MS = 60000f;
private final LinkedList<Pair<Long, Long>> strideList = new LinkedList<>();
private final Semapreplaced lock = new Semapreplaced(1);
@Override
public void onNewStrideCount(long estTimestamp, EnumSet<EventFlag> eventFlags, final long replacedulativeStrides) {
new Thread(() -> {
try {
lock.acquire();
// Calculate number of strides per minute, updates happen around every 500 ms, this number
// may be off by that amount but it isn't too significant
strideList.addFirst(new Pair<>(estTimestamp, replacedulativeStrides));
long strideCount = 0;
boolean valueFound = false;
int i = 0;
for (Pair<Long, Long> p : strideList) {
// Cadence over the last 10 seconds
if (estTimestamp - p.first >= TEN_SECONDS_IN_MS) {
valueFound = true;
strideCount = calculateStepsPerMin(estTimestamp, replacedulativeStrides, p);
break;
} else if (i + 1 == strideList.size()) {
// No value was found yet, it has not been 10 seconds. Give an early rough estimate
strideCount = calculateStepsPerMin(estTimestamp, replacedulativeStrides, p);
}
i++;
}
while ((valueFound && strideList.size() >= i + 1) || strideList.size() > FALLBACK_MAX_LIST_SIZE) {
strideList.removeLast();
}
lastSSStrideCountTimestamp = estTimestamp;
lastStridePerMinute = strideCount;
lock.release();
} catch (InterruptedException e) {
Log.e(TAG, "Unable to acquire lock to update running cadence", e);
}
}).start();
}
private long calculateStepsPerMin(long estTimestamp, long replacedulativeStrides, Pair<Long, Long> p) {
float elapsedTimeMs = estTimestamp - p.first;
if (elapsedTimeMs == 0) {
return 0;
}
return (long) ((replacedulativeStrides - p.second) * (ONE_MINUTE_IN_MS / elapsedTimeMs));
}
});
ssPcc.subscribeDistanceEvent(new AntPlusStrideSdmPcc.IDistanceReceiver() {
@Override
public void onNewDistance(long estTimestamp, EnumSet<EventFlag> eventFlags, BigDecimal distance) {
lastSSDistanceTimestamp = estTimestamp;
lastSSDistance = distance.longValue();
}
});
ssPcc.subscribeInstantaneousSpeedEvent(new AntPlusStrideSdmPcc.IInstantaneousSpeedReceiver() {
@Override
public void onNewInstantaneousSpeed(long estTimestamp, EnumSet<EventFlag> eventFlags, BigDecimal instantaneousSpeed) {
lastSSDistanceTimestamp = estTimestamp;
lastSSSpeed = instantaneousSpeed.floatValue();
}
});
}
};
private enum AntSensorType {
CyclingSpeed, CyclingCadence, HR, StrideBasedSpeedAndDistance
}
private clreplaced AntDeviceChangeReceiver implements AntPluginPcc.IDeviceStateChangeReceiver {
private AntSensorType type;
AntDeviceChangeReceiver(AntSensorType type) {
this.type = type;
}
@Override
public void onDeviceStateChange(final DeviceState newDeviceState) {
String extraName = "unknown";
if (type == AntSensorType.CyclingSpeed) {
extraName = "bsd_service_status";
Log.d(TAG, "Speed sensor onDeviceStateChange:" + newDeviceState);
} else if (type == AntSensorType.CyclingCadence) {
extraName = "bc_service_status";
Log.d(TAG, "Cadence sensor onDeviceStateChange:" + newDeviceState);
} else if (type == AntSensorType.HR) {
extraName = "hr_service_status";
Log.d(TAG, "HR sensor onDeviceStateChange:" + newDeviceState);
} else if (type == AntSensorType.StrideBasedSpeedAndDistance) {
extraName = "ss_service_status";
Log.d(TAG, "Stride based speed and distance onDeviceStateChange:" + newDeviceState);
}
// send broadcast about device status
Intent i = new Intent("idv.markkuo.cscblebridge.ANTDATA");
i.putExtra(extraName, newDeviceState.name());
sendBroadcast(i);
// if the device is dead (closed)
if (newDeviceState == DeviceState.DEAD) {
bsdPcc = null;
}
}
}
private AntPluginPcc.IDeviceStateChangeReceiver mBSDDeviceStateChangeReceiver = new AntDeviceChangeReceiver(AntSensorType.CyclingSpeed);
private AntPluginPcc.IDeviceStateChangeReceiver mBCDeviceStateChangeReceiver = new AntDeviceChangeReceiver(AntSensorType.CyclingCadence);
private AntPluginPcc.IDeviceStateChangeReceiver mHRDeviceStateChangeReceiver = new AntDeviceChangeReceiver(AntSensorType.HR);
private AntPluginPcc.IDeviceStateChangeReceiver mSSDeviceStateChangeReceiver = new AntDeviceChangeReceiver(AntSensorType.StrideBasedSpeedAndDistance);
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "Service onStartCommand");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel(CHANNEL_DEFAULT_IMPORTANCE, MAIN_CHANNEL_NAME);
// Create the PendingIntent
PendingIntent notifyPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this.getApplicationContext(), MainActivity.clreplaced), PendingIntent.FLAG_UPDATE_CURRENT);
// build a notification
Notification notification = new Notification.Builder(this, CHANNEL_DEFAULT_IMPORTANCE).setContentreplacedle(getText(R.string.app_name)).setContentText("Active").setSmallIcon(R.drawable.ic_notification_icon).setAutoCancel(true).setContentIntent(notifyPendingIntent).setTicker(getText(R.string.app_name)).build();
startForeground(ONGOING_NOTIFICATION_ID, notification);
} else {
Notification notification = new NotificationCompat.Builder(this, CHANNEL_DEFAULT_IMPORTANCE).setContentreplacedle(getString(R.string.app_name)).setContentText("Active").setSmallIcon(R.drawable.ic_notification_icon).setPriority(NotificationCompat.PRIORITY_DEFAULT).setAutoCancel(true).build();
startForeground(ONGOING_NOTIFICATION_ID, notification);
}
return Service.START_NOT_STICKY;
}
@RequiresApi(Build.VERSION_CODES.O)
private void createNotificationChannel(String channelId, String channelName) {
NotificationChannel channel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_LOW);
channel.setLightColor(Color.BLUE);
channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
manager.createNotificationChannel(channel);
}
private boolean checkBluetoothSupport(BluetoothAdapter bluetoothAdapter) {
if (bluetoothAdapter == null) {
Log.w(TAG, "Bluetooth is not supported");
return false;
}
if (!getPackageManager().hreplacedystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Log.w(TAG, "Bluetooth LE is not supported");
return false;
}
return true;
}
private BroadcastReceiver mBluetoothReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF);
switch(state) {
case BluetoothAdapter.STATE_ON:
startAdvertising();
startServer();
break;
case BluetoothAdapter.STATE_OFF:
stopServer();
stopAdvertising();
break;
}
}
};
@Override
public void onCreate() {
Log.d(TAG, "Service started");
super.onCreate();
// ANT+
initAntPlus();
// Bluetooth LE
mBluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
replacedert mBluetoothManager != null;
BluetoothAdapter bluetoothAdapter = mBluetoothManager.getAdapter();
// continue without proper Bluetooth support
if (!checkBluetoothSupport(bluetoothAdapter)) {
Log.e(TAG, "Bluetooth LE isn't supported. This won't run");
stopSelf();
return;
}
// Register for system Bluetooth events
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(mBluetoothReceiver, filter);
if (!bluetoothAdapter.isEnabled()) {
Log.d(TAG, "Bluetooth is currently disabled...enabling");
bluetoothAdapter.enable();
} else {
Log.d(TAG, "Bluetooth enabled...starting services");
startAdvertising();
startServer();
}
initialised = true;
}
@Override
public void onTaskRemoved(Intent rootIntent) {
Log.d(TAG, "onTaskRemoved called");
super.onTaskRemoved(rootIntent);
stopForeground(true);
stopSelf();
}
@Override
public void onDestroy() {
Log.d(TAG, "Service destroyed");
super.onDestroy();
if (initialised) {
// stop BLE
BluetoothAdapter bluetoothAdapter = mBluetoothManager.getAdapter();
if (bluetoothAdapter != null && bluetoothAdapter.isEnabled()) {
stopServer();
stopAdvertising();
}
unregisterReceiver(mBluetoothReceiver);
// stop ANT+
if (bsdReleaseHandle != null)
bsdReleaseHandle.close();
if (bcReleaseHandle != null)
bcReleaseHandle.close();
if (hrReleaseHandle != null)
hrReleaseHandle.close();
if (ssReleaseHandle != null)
ssReleaseHandle.close();
combinedSensorConnected = false;
}
}
/**
* Begin advertising over Bluetooth that this device is connectable
*/
private void startAdvertising() {
BluetoothAdapter bluetoothAdapter = mBluetoothManager.getAdapter();
if (bluetoothAdapter == null) {
Log.e(TAG, "Failed to create bluetooth adapter");
return;
}
mBluetoothLeAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser();
if (mBluetoothLeAdvertiser == null) {
Log.w(TAG, "Failed to create advertiser");
return;
}
AdvertiseSettings settings = new AdvertiseSettings.Builder().setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY).setConnectable(true).setTimeout(0).setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH).build();
AdvertiseData advData = new AdvertiseData.Builder().setIncludeTxPowerLevel(true).addServiceUuid(new ParcelUuid(CSCProfile.CSC_SERVICE)).addServiceUuid(new ParcelUuid(CSCProfile.HR_SERVICE)).addServiceUuid(new ParcelUuid(CSCProfile.RSC_SERVICE)).build();
AdvertiseData advScanResponse = new AdvertiseData.Builder().setIncludeDeviceName(true).build();
mBluetoothLeAdvertiser.startAdvertising(settings, advData, advScanResponse, mAdvertiseCallback);
}
/**
* Stop Bluetooth advertisements
*/
private void stopAdvertising() {
if (mBluetoothLeAdvertiser == null)
return;
mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback);
}
/**
* Initialize the GATT server
*/
private void startServer() {
mBluetoothGattServer = mBluetoothManager.openGattServer(this, mGattServerCallback);
if (mBluetoothGattServer == null) {
Log.w(TAG, "Unable to create GATT server");
return;
}
btServiceInitialized = false;
// TODO: enable either 1 of them or both of them according to user selection
if (mBluetoothGattServer.addService(CSCProfile.createCSCService((byte) (CSCProfile.CSC_FEATURE_WHEEL_REV | CSCProfile.CSC_FEATURE_CRANK_REV)))) {
Log.d(TAG, "CSCP enabled!");
} else {
Log.d(TAG, "Failed to add csc service to bluetooth layer!");
}
// We cannot add another service until the callback for the previous service has completed
while (!btServiceInitialized) ;
btServiceInitialized = false;
if (mBluetoothGattServer.addService(CSCProfile.createHRService())) {
Log.d(TAG, "HR enabled!");
} else {
Log.d(TAG, "Failed to add hr service to bluetooth layer!");
}
// We cannot add another service until the callback for the previous service has completed
while (!btServiceInitialized) ;
if (mBluetoothGattServer.addService(CSCProfile.createRscService())) {
Log.d(TAG, "RSC enabled!");
} else {
Log.d(TAG, "Failed to add rsc service to bluetooth layer");
}
Log.d(TAG, "Enumerating (" + mBluetoothGattServer.getServices().size() + ") BT services");
for (BluetoothGattService b : mBluetoothGattServer.getServices()) {
Log.d(TAG, "Services registered: " + b.getUuid().toString());
}
// start periodicUpdate, sending notification to subscribed device and UI
handler.post(periodicUpdate);
}
/**
* Shut down the GATT server
*/
private void stopServer() {
if (mBluetoothGattServer == null)
return;
// stop periodicUpdate
handler.removeCallbacksAndMessages(null);
mBluetoothGattServer.close();
}
/**
* Callback to receive information about the advertisement process
*/
private AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() {
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
Log.i(TAG, "LE Advertise Started:" + settingsInEffect);
}
@Override
public void onStartFailure(int errorCode) {
Log.w(TAG, "LE Advertise Failed: " + errorCode);
}
};
Handler handler = new Handler();
private Runnable periodicUpdate = new Runnable() {
@Override
public void run() {
// scheduled next run in 1 sec
handler.postDelayed(periodicUpdate, 1000);
// send to registered BLE devices. It's a no-op if there is no GATT client
notifyRegisteredDevices();
// update UI by sending broadcast to our main activity
Intent i = new Intent("idv.markkuo.cscblebridge.ANTDATA");
i.putExtra("speed", lastSpeed);
i.putExtra("cadence", lastCadence);
i.putExtra("hr", lastHR);
i.putExtra("ss_distance", lastSSDistance);
i.putExtra("ss_speed", lastSSSpeed);
i.putExtra("ss_stride_count", lastStridePerMinute);
i.putExtra("speed_timestamp", lastSpeedTimestamp);
i.putExtra("cadence_timestamp", lastCadenceTimestamp);
i.putExtra("hr_timestamp", lastHRTimestamp);
i.putExtra("ss_distance_timestamp", lastSSDistanceTimestamp);
i.putExtra("ss_speed_timestamp", lastSSSpeedTimestamp);
i.putExtra("ss_stride_count_timestamp", lastSSStrideCountTimestamp);
Log.v(TAG, "Updating UI: speed:" + lastSpeed + ", cadence:" + lastCadence + ", hr " + lastHR + ", speed_ts:" + lastSpeedTimestamp + ", cadence_ts:" + lastCadenceTimestamp + ", " + lastHRTimestamp + ", ss_distance: " + lastSSDistance + ", ss_distance_timestamp: " + lastSSDistanceTimestamp + ", ss_speed: " + lastSSSpeed + ", ss_speed_timestamp: " + lastSSSpeedTimestamp + ", ss_stride_count: " + lastStridePerMinute + ", ss_stride_count_timestamp: " + lastSSStrideCountTimestamp);
sendBroadcast(i);
}
};
/**
* Send a CSC service notification to any devices that are subscribed
* to the characteristic
*/
private void notifyRegisteredDevices() {
if (mRegisteredDevices.isEmpty()) {
Log.v(TAG, "No subscribers registered");
return;
}
byte[] data = CSCProfile.getMeasurement(replacedulativeWheelRevolution, lastWheelEventTime, replacedulativeCrankRevolution, lastCrankEventTime);
Log.v(TAG, "Sending update to " + mRegisteredDevices.size() + " subscribers");
for (BluetoothDevice device : mRegisteredDevices) {
BluetoothGattService service = mBluetoothGattServer.getService(CSCProfile.CSC_SERVICE);
if (service != null) {
BluetoothGattCharacteristic measurementCharacteristic = mBluetoothGattServer.getService(CSCProfile.CSC_SERVICE).getCharacteristic(CSCProfile.CSC_MEASUREMENT);
if (!measurementCharacteristic.setValue(data)) {
Log.w(TAG, "CSC Measurement data isn't set properly!");
}
// false is used to send a notification
mBluetoothGattServer.notifyCharacteristicChanged(device, measurementCharacteristic, false);
} else {
Log.v(TAG, "Service " + CSCProfile.CSC_SERVICE + " was not found as an installed service");
}
service = mBluetoothGattServer.getService(CSCProfile.HR_SERVICE);
if (service != null) {
Log.v(TAG, "Processing Heart Rate");
BluetoothGattCharacteristic measurementCharacteristic = mBluetoothGattServer.getService(CSCProfile.HR_SERVICE).getCharacteristic(CSCProfile.HR_MEASUREMENT);
byte[] hrData = CSCProfile.getHR(lastHR, lastHRTimestamp);
if (!measurementCharacteristic.setValue(hrData)) {
Log.w(TAG, "HR Measurement data isn't set properly!");
}
mBluetoothGattServer.notifyCharacteristicChanged(device, measurementCharacteristic, false);
} else {
Log.v(TAG, "Service " + CSCProfile.HR_SERVICE + " was not found as an installed service");
}
service = mBluetoothGattServer.getService(CSCProfile.RSC_SERVICE);
if (service != null) {
Log.v(TAG, "Processing Running Speed and Cadence sensor");
BluetoothGattCharacteristic measurementCharacteristic = mBluetoothGattServer.getService(CSCProfile.RSC_SERVICE).getCharacteristic(CSCProfile.RSC_MEASUREMENT);
byte[] rscData = CSCProfile.getRsc(lastSSDistance, lastSSSpeed, lastStridePerMinute);
if (!measurementCharacteristic.setValue(rscData)) {
Log.w(TAG, "RSC Measurement data isn't set properly!");
}
mBluetoothGattServer.notifyCharacteristicChanged(device, measurementCharacteristic, false);
} else {
Log.v(TAG, "Service " + CSCProfile.RSC_SERVICE + " was not found as an installed service");
}
}
}
private final BluetoothGattServerCallback mGattServerCallback = new BluetoothGattServerCallback() {
@Override
public void onServiceAdded(int status, BluetoothGattService service) {
Log.i(TAG, "onServiceAdded(): status:" + status + ", service:" + service);
// Sets up for next service to be added
btServiceInitialized = true;
}
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
if (mRegisteredDevices.contains(device)) {
if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.i(TAG, "BluetoothDevice DISCONNECTED: " + device.getName() + " [" + device.getAddress() + "]");
// Remove device from any active subscriptions
mRegisteredDevices.remove(device);
} else {
Log.i(TAG, "onConnectionStateChange() status:" + status + "->" + newState + ", device" + device);
}
}
}
@Override
public void onNotificationSent(BluetoothDevice device, int status) {
Log.v(TAG, "onNotificationSent() result:" + status);
}
@Override
public void onMtuChanged(BluetoothDevice device, int mtu) {
Log.d(TAG, "onMtuChanged:" + device + " =>" + mtu);
}
@Override
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
if (CSCProfile.CSC_MEASUREMENT.equals(characteristic.getUuid())) {
Log.i(TAG, "Read CSC Measurement");
// TODO: this should never happen since this characteristic doesn't support read
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null);
} else if (CSCProfile.CSC_FEATURE.equals(characteristic.getUuid())) {
Log.i(TAG, "Read CSC Feature");
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, CSCProfile.getFeature());
} else if (CSCProfile.RSC_MEASUREMENT.equals(characteristic.getUuid())) {
Log.i(TAG, "READ RSC Measurement");
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null);
} else if (CSCProfile.RSC_FEATURE.equals(characteristic.getUuid())) {
Log.i(TAG, "READ RSC Feature");
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, CSCProfile.getRscFeature());
} else {
// Invalid characteristic
Log.w(TAG, "Invalid Characteristic Read: " + characteristic.getUuid());
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, 0, null);
}
}
@Override
public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
if (CSCProfile.CLIENT_CONFIG.equals(descriptor.getUuid())) {
Log.d(TAG, "Config descriptor read");
byte[] returnValue;
if (mRegisteredDevices.contains(device)) {
returnValue = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
} else {
returnValue = BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;
}
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, returnValue);
} else {
Log.w(TAG, "Unknown descriptor read request");
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, offset, null);
}
}
@Override
public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
if (CSCProfile.CLIENT_CONFIG.equals(descriptor.getUuid())) {
if (Arrays.equals(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE, value)) {
Log.d(TAG, "Subscribe device to notifications: " + device);
mRegisteredDevices.add(device);
} else if (Arrays.equals(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE, value)) {
Log.d(TAG, "Unsubscribe device from notifications: " + device);
mRegisteredDevices.remove(device);
}
if (responseNeeded) {
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null);
}
} else {
Log.w(TAG, "Unknown descriptor write request");
if (responseNeeded) {
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, 0, null);
}
}
}
};
/**
* Initialize searching for all supported sensors
*/
private void initAntPlus() {
Log.d(TAG, "requesting ANT+ access");
startSpeedSensorSearch();
startCadenceSensorSearch();
startHRSensorSearch();
startStrideSdmSensorSearch();
}
/**
* Initializes the speed sensor search
*/
protected void startSpeedSensorSearch() {
// Release the old access if it exists
if (bsdReleaseHandle != null)
bsdReleaseHandle.close();
combinedSensorConnected = false;
// starts speed sensor search
bsdReleaseHandle = AntPlusBikeSpeedDistancePcc.requestAccess(this, 0, 0, false, mBSDResultReceiver, mBSDDeviceStateChangeReceiver);
// send initial state for UI
Intent i = new Intent("idv.markkuo.cscblebridge.ANTDATA");
i.putExtra("bsd_service_status", "SEARCHING");
sendBroadcast(i);
}
/**
* Initializes the cadence sensor search
*/
protected void startCadenceSensorSearch() {
// Release the old access if it exists
if (bcReleaseHandle != null)
bcReleaseHandle.close();
// starts cadence sensor search
bcReleaseHandle = AntPlusBikeCadencePcc.requestAccess(this, 0, 0, false, mBCResultReceiver, mBCDeviceStateChangeReceiver);
// send initial state for UI
Intent i = new Intent("idv.markkuo.cscblebridge.ANTDATA");
i.putExtra("bc_service_status", "SEARCHING");
sendBroadcast(i);
}
/**
* Initializes the HR sensor search
*/
protected void startHRSensorSearch() {
// Release the old access if it exists
if (hrReleaseHandle != null)
hrReleaseHandle.close();
// starts hr sensor search
hrReleaseHandle = AntPlusHeartRatePcc.requestAccess(this, 0, 0, mHRResultReceiver, mHRDeviceStateChangeReceiver);
// send initial state for UI
Intent i = new Intent("idv.markkuo.cscblebridge.ANTDATA");
i.putExtra("hr_service_status", "SEARCHING");
sendBroadcast(i);
}
/**
* Initialized the Stride SDM (Stride based Speed and Distance Monitor) sensor search
*
* ex. Garmin Foot Pod
*/
protected void startStrideSdmSensorSearch() {
if (ssReleaseHandle != null)
ssReleaseHandle.close();
ssReleaseHandle = AntPlusStrideSdmPcc.requestAccess(this, 0, 0, mSSResultReceiver, mSSDeviceStateChangeReceiver);
Intent i = new Intent("idv.markkuo.cscblebridge.ANTDATA");
i.putExtra("ss_service_status", "SEARCHING");
sendBroadcast(i);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder;
}
/**
* Get the services for communicating with it
*/
public clreplaced LocalBinder extends Binder {
CSCService getService() {
return CSCService.this;
}
}
}
4
Source : BluetoothPeripheralManager.java
with MIT License
from weliem
with MIT License
from weliem
/**
* This clreplaced represent a peripheral running on the local phone
*/
@SuppressWarnings("UnusedReturnValue")
public clreplaced BluetoothPeripheralManager {
protected static final UUID CCC_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
private static final String CONTEXT_IS_NULL = "context is null";
private static final String BLUETOOTH_MANAGER_IS_NULL = "BluetoothManager is null";
private static final String SERVICE_IS_NULL = "service is null";
private static final String CHARACTERISTIC_IS_NULL = "characteristic is null";
private static final String DEVICE_IS_NULL = "device is null";
private static final String CHARACTERISTIC_VALUE_IS_NULL = "characteristic value is null";
private static final String CENTRAL_IS_NULL = "central is null";
private static final String ADDRESS_IS_NULL = "address is null";
@NotNull
private final Context context;
@NotNull
private final Handler mainHandler = new Handler(Looper.getMainLooper());
@NotNull
private final BluetoothManager bluetoothManager;
@NotNull
private final BluetoothAdapter bluetoothAdapter;
@NotNull
private final BluetoothLeAdvertiser bluetoothLeAdvertiser;
@NotNull
private final BluetoothGattServer bluetoothGattServer;
@NotNull
private final BluetoothPeripheralManagerCallback callback;
@NotNull
protected final Queue<Runnable> commandQueue = new ConcurrentLinkedQueue<>();
@NotNull
private final HashMap<BluetoothGattCharacteristic, byte[]> writeLongCharacteristicTemporaryBytes = new HashMap<>();
@NotNull
private final HashMap<BluetoothGattDescriptor, byte[]> writeLongDescriptorTemporaryBytes = new HashMap<>();
@NotNull
private final Map<String, BluetoothCentral> connectedCentralsMap = new ConcurrentHashMap<>();
@Nullable
private BluetoothGattCharacteristic currentNotifyCharacteristic = null;
@NotNull
private byte[] currentNotifyValue = new byte[0];
private volatile boolean commandQueueBusy = false;
protected final BluetoothGattServerCallback bluetoothGattServerCallback = new BluetoothGattServerCallback() {
@Override
public void onConnectionStateChange(final BluetoothDevice device, final int status, final int newState) {
if (status == BluetoothGatt.GATT_SUCCESS) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
// Call connect() even though we are already connected
// It basically tells Android we will really use this connection
// If we don't do this, then cancelConnection won't work
// See https://issuetracker.google.com/issues/37127644
if (connectedCentralsMap.containsKey(device.getAddress())) {
return;
} else {
// This will lead to onConnectionStateChange be called again
bluetoothGattServer.connect(device, false);
}
handleDeviceConnected(device);
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
// Deal is double disconnect messages
if (!connectedCentralsMap.containsKey(device.getAddress()))
return;
handleDeviceDisconnected(device);
}
} else {
Timber.i("Device '%s' disconnected with status %d", device.getName(), status);
handleDeviceDisconnected(device);
}
}
private void handleDeviceConnected(@NotNull final BluetoothDevice device) {
Timber.i("Central '%s' (%s) connected", device.getName(), device.getAddress());
final BluetoothCentral bluetoothCentral = new BluetoothCentral(device.getAddress(), device.getName());
connectedCentralsMap.put(bluetoothCentral.getAddress(), bluetoothCentral);
mainHandler.post(new Runnable() {
@Override
public void run() {
callback.onCentralConnected(bluetoothCentral);
}
});
}
private void handleDeviceDisconnected(@NotNull final BluetoothDevice device) {
final BluetoothCentral bluetoothCentral = getCentral(device);
Timber.i("Central '%s' (%s) disconnected", bluetoothCentral.getName(), bluetoothCentral.getAddress());
mainHandler.post(new Runnable() {
@Override
public void run() {
callback.onCentralDisconnected(bluetoothCentral);
}
});
removeCentral(device);
}
@Override
public void onServiceAdded(final int status, @NotNull final BluetoothGattService service) {
mainHandler.post(new Runnable() {
@Override
public void run() {
callback.onServiceAdded(GattStatus.fromValue(status), service);
}
});
completedCommand();
}
@Override
public void onCharacteristicReadRequest(@NotNull final BluetoothDevice device, final int requestId, final int offset, @NotNull final BluetoothGattCharacteristic characteristic) {
Timber.i("read request for characteristic <%s> with offset %d", characteristic.getUuid(), offset);
final BluetoothCentral bluetoothCentral = getCentral(device);
mainHandler.post(new Runnable() {
@Override
public void run() {
// Call onCharacteristic before any responses are sent, even if it is a long read
if (offset == 0) {
callback.onCharacteristicRead(bluetoothCentral, characteristic);
}
// If data is longer than MTU - 1, cut the array. Only ATT_MTU - 1 bytes can be sent in Long Read.
final byte[] value = copyOf(nonnullOf(characteristic.getValue()), offset, bluetoothCentral.getCurrentMtu() - 1);
bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
}
});
}
@Override
public void onCharacteristicWriteRequest(@NotNull final BluetoothDevice device, final int requestId, @NotNull final BluetoothGattCharacteristic characteristic, final boolean preparedWrite, final boolean responseNeeded, final int offset, @Nullable final byte[] value) {
Timber.i("write characteristic %s request <%s> offset %d for <%s>", responseNeeded ? "WITH_RESPONSE" : "WITHOUT_RESPONSE", bytes2String(value), offset, characteristic.getUuid());
final byte[] safeValue = nonnullOf(value);
final BluetoothCentral bluetoothCentral = getCentral(device);
mainHandler.post(new Runnable() {
@Override
public void run() {
GattStatus status = GattStatus.SUCCESS;
if (!preparedWrite) {
status = callback.onCharacteristicWrite(bluetoothCentral, characteristic, safeValue);
if (status == GattStatus.SUCCESS) {
characteristic.setValue(safeValue);
}
} else {
if (offset == 0) {
writeLongCharacteristicTemporaryBytes.put(characteristic, safeValue);
} else {
final byte[] temporaryBytes = writeLongCharacteristicTemporaryBytes.get(characteristic);
if (temporaryBytes != null && offset == temporaryBytes.length) {
writeLongCharacteristicTemporaryBytes.put(characteristic, mergeArrays(temporaryBytes, safeValue));
} else {
status = GattStatus.INVALID_OFFSET;
}
}
}
if (responseNeeded) {
bluetoothGattServer.sendResponse(device, requestId, status.value, offset, safeValue);
}
}
});
}
@Override
public void onDescriptorReadRequest(@NotNull final BluetoothDevice device, final int requestId, final int offset, @NotNull final BluetoothGattDescriptor descriptor) {
Timber.i("read request for descriptor <%s> with offset %d", descriptor.getUuid(), offset);
final BluetoothCentral bluetoothCentral = getCentral(device);
mainHandler.post(new Runnable() {
@Override
public void run() {
// Call onDescriptorRead before any responses are sent, even if it is a long read
if (offset == 0) {
callback.onDescriptorRead(bluetoothCentral, descriptor);
}
// If data is longer than MTU - 1, cut the array. Only ATT_MTU - 1 bytes can be sent in Long Read.
final byte[] value = copyOf(nonnullOf(descriptor.getValue()), offset, bluetoothCentral.getCurrentMtu() - 1);
bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
}
});
}
@Override
public void onDescriptorWriteRequest(@NotNull final BluetoothDevice device, final int requestId, @NotNull final BluetoothGattDescriptor descriptor, final boolean preparedWrite, final boolean responseNeeded, final int offset, @Nullable final byte[] value) {
final byte[] safeValue = nonnullOf(value);
final BluetoothGattCharacteristic characteristic = Objects.requireNonNull(descriptor.getCharacteristic(), "Descriptor does not have characteristic");
Timber.i("write descriptor %s request <%s> offset %d for <%s>", responseNeeded ? "WITH_RESPONSE" : "WITHOUT_RESPONSE", bytes2String(value), offset, descriptor.getUuid());
final BluetoothCentral bluetoothCentral = getCentral(device);
mainHandler.post(new Runnable() {
@Override
public void run() {
GattStatus status = GattStatus.SUCCESS;
if (descriptor.getUuid().equals(CCC_DESCRIPTOR_UUID)) {
status = checkCccDescriptorValue(safeValue, characteristic);
} else {
if (!preparedWrite) {
// Ask callback if value is ok or not
status = callback.onDescriptorWrite(bluetoothCentral, descriptor, safeValue);
} else {
if (offset == 0) {
writeLongDescriptorTemporaryBytes.put(descriptor, safeValue);
} else {
final byte[] temporaryBytes = writeLongDescriptorTemporaryBytes.get(descriptor);
if (temporaryBytes != null && offset == temporaryBytes.length) {
writeLongDescriptorTemporaryBytes.put(descriptor, mergeArrays(temporaryBytes, safeValue));
} else {
status = GattStatus.INVALID_OFFSET;
}
}
}
}
if (status == GattStatus.SUCCESS && !preparedWrite) {
descriptor.setValue(safeValue);
}
if (responseNeeded) {
bluetoothGattServer.sendResponse(device, requestId, status.value, offset, safeValue);
}
if (status == GattStatus.SUCCESS && descriptor.getUuid().equals(CCC_DESCRIPTOR_UUID)) {
if (Arrays.equals(safeValue, BluetoothGattDescriptor.ENABLE_INDICATION_VALUE) || Arrays.equals(safeValue, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)) {
Timber.i("notifying enabled for <%s>", characteristic.getUuid());
callback.onNotifyingEnabled(bluetoothCentral, characteristic);
} else {
Timber.i("notifying disabled for <%s>", characteristic.getUuid());
callback.onNotifyingDisabled(bluetoothCentral, characteristic);
}
}
}
});
}
// Check value to see if it is valid and if matches the characteristic properties
private GattStatus checkCccDescriptorValue(@NotNull final byte[] safeValue, @NotNull final BluetoothGattCharacteristic characteristic) {
GattStatus status = GattStatus.SUCCESS;
if (safeValue.length != 2) {
status = GattStatus.INVALID_ATTRIBUTE_VALUE_LENGTH;
} else if (!(Arrays.equals(safeValue, BluetoothGattDescriptor.ENABLE_INDICATION_VALUE) || Arrays.equals(safeValue, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE) || Arrays.equals(safeValue, BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE))) {
status = GattStatus.VALUE_NOT_ALLOWED;
} else if (!supportsIndicate(characteristic) && Arrays.equals(safeValue, BluetoothGattDescriptor.ENABLE_INDICATION_VALUE)) {
status = GattStatus.REQUEST_NOT_SUPPORTED;
} else if (!supportsNotify(characteristic) && Arrays.equals(safeValue, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)) {
status = GattStatus.REQUEST_NOT_SUPPORTED;
}
return status;
}
@Override
public void onExecuteWrite(@NotNull final BluetoothDevice device, final int requestId, final boolean execute) {
final BluetoothCentral bluetoothCentral = getCentral(device);
if (execute) {
mainHandler.post(new Runnable() {
@Override
public void run() {
GattStatus status = GattStatus.SUCCESS;
if (!writeLongCharacteristicTemporaryBytes.isEmpty()) {
final BluetoothGattCharacteristic characteristic = writeLongCharacteristicTemporaryBytes.keySet().iterator().next();
if (characteristic != null) {
// Ask callback if value is ok or not
status = callback.onCharacteristicWrite(bluetoothCentral, characteristic, writeLongCharacteristicTemporaryBytes.get(characteristic));
if (status == GattStatus.SUCCESS) {
characteristic.setValue(writeLongCharacteristicTemporaryBytes.get(characteristic));
writeLongCharacteristicTemporaryBytes.clear();
}
}
} else if (!writeLongDescriptorTemporaryBytes.isEmpty()) {
final BluetoothGattDescriptor descriptor = writeLongDescriptorTemporaryBytes.keySet().iterator().next();
if (descriptor != null) {
// Ask callback if value is ok or not
status = callback.onDescriptorWrite(bluetoothCentral, descriptor, writeLongDescriptorTemporaryBytes.get(descriptor));
if (status == GattStatus.SUCCESS) {
descriptor.setValue(writeLongDescriptorTemporaryBytes.get(descriptor));
writeLongDescriptorTemporaryBytes.clear();
}
}
}
bluetoothGattServer.sendResponse(device, requestId, status.value, 0, null);
}
});
} else {
// Long write was cancelled, clean up already received bytes
writeLongCharacteristicTemporaryBytes.clear();
writeLongDescriptorTemporaryBytes.clear();
bluetoothGattServer.sendResponse(device, requestId, GattStatus.SUCCESS.value, 0, null);
}
}
@Override
public void onNotificationSent(@NotNull final BluetoothDevice device, final int status) {
final BluetoothCentral bluetoothCentral = getCentral(device);
@NotNull
final BluetoothGattCharacteristic characteristic = Objects.requireNonNull(currentNotifyCharacteristic);
@NotNull
final byte[] value = Objects.requireNonNull(currentNotifyValue);
currentNotifyValue = new byte[0];
mainHandler.post(new Runnable() {
@Override
public void run() {
callback.onNotificationSent(bluetoothCentral, value, characteristic, GattStatus.fromValue(status));
}
});
completedCommand();
}
@Override
public void onMtuChanged(@NotNull final BluetoothDevice device, final int mtu) {
Timber.i("new MTU: %d", mtu);
BluetoothCentral bluetoothCentral = getCentral(device);
bluetoothCentral.setCurrentMtu(mtu);
}
@Override
public void onPhyUpdate(@NotNull final BluetoothDevice device, final int txPhy, final int rxPhy, final int status) {
super.onPhyUpdate(device, txPhy, rxPhy, status);
}
@Override
public void onPhyRead(@NotNull final BluetoothDevice device, final int txPhy, final int rxPhy, final int status) {
super.onPhyRead(device, txPhy, rxPhy, status);
}
};
protected final AdvertiseCallback advertiseCallback = new AdvertiseCallback() {
@Override
public void onStartSuccess(@NotNull final AdvertiseSettings settingsInEffect) {
Timber.i("advertising started");
mainHandler.post(new Runnable() {
@Override
public void run() {
callback.onAdvertisingStarted(settingsInEffect);
}
});
}
@Override
public void onStartFailure(final int errorCode) {
final AdvertiseError advertiseError = AdvertiseError.fromValue(errorCode);
Timber.e("advertising failed with error '%s'", advertiseError);
mainHandler.post(new Runnable() {
@Override
public void run() {
callback.onAdvertiseFailure(advertiseError);
}
});
}
};
protected void onAdvertisingStopped() {
Timber.i("advertising stopped");
mainHandler.post(new Runnable() {
@Override
public void run() {
callback.onAdvertisingStopped();
}
});
}
/**
* Create a BluetoothPeripheralManager
*
* @param context the application context
* @param bluetoothManager a valid BluetoothManager
* @param callback an instance of BluetoothPeripheralManagerCallback where the callbacks will be handled
*/
public BluetoothPeripheralManager(@NotNull final Context context, @NotNull final BluetoothManager bluetoothManager, @NotNull final BluetoothPeripheralManagerCallback callback) {
this.context = Objects.requireNonNull(context, CONTEXT_IS_NULL);
this.callback = Objects.requireNonNull(callback, "Callback is null");
this.bluetoothManager = Objects.requireNonNull(bluetoothManager, BLUETOOTH_MANAGER_IS_NULL);
this.bluetoothAdapter = bluetoothManager.getAdapter();
this.bluetoothLeAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser();
this.bluetoothGattServer = bluetoothManager.openGattServer(context, bluetoothGattServerCallback);
// Register for broadcasts on BluetoothAdapter state change
final IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
context.registerReceiver(adapterStateReceiver, filter);
}
/**
* Close the BluetoothPeripheralManager
*
* Application should call this method as early as possible after it is done with
* this BluetoothPeripheralManager.
*/
public void close() {
stopAdvertising();
context.unregisterReceiver(adapterStateReceiver);
bluetoothGattServer.close();
}
/**
* Start Bluetooth LE Advertising. The {@code advertiseData} will be broadcasted if the
* operation succeeds. The {@code scanResponse} is returned when a scanning device sends an
* active scan request. This method returns immediately, the operation status is delivered
* through {@link BluetoothPeripheralManagerCallback#onAdvertisingStarted(AdvertiseSettings)} or {@link BluetoothPeripheralManagerCallback#onAdvertiseFailure(AdvertiseError)}.
*
* @param settings the AdvertiseSettings
* @param advertiseData the AdvertiseData
* @param scanResponse the ScanResponse
*/
public void startAdvertising(@NotNull final AdvertiseSettings settings, @NotNull final AdvertiseData advertiseData, @NotNull final AdvertiseData scanResponse) {
if (!bluetoothAdapter.isMultipleAdvertisementSupported()) {
Timber.e("device does not support advertising");
} else {
bluetoothLeAdvertiser.startAdvertising(settings, advertiseData, scanResponse, advertiseCallback);
}
}
/**
* Stop advertising
*/
public void stopAdvertising() {
bluetoothLeAdvertiser.stopAdvertising(advertiseCallback);
onAdvertisingStopped();
}
/**
* Add a service to the peripheral
*
* <p>Once a service has been added to the list, the service and its
* included characteristics will be provided by the local peripheral.
*
* <p>If the local peripheral has already exposed services when this function
* is called, a service update notification will be sent to all clients.
*
* A callback on {@link BluetoothPeripheralManagerCallback#onServiceAdded)} will be received when this operation has completed
*
* @param service the service to add
* @return true if the operation was enqueued, false otherwise
*/
public boolean add(@NotNull final BluetoothGattService service) {
Objects.requireNonNull(service, SERVICE_IS_NULL);
final boolean result = commandQueue.add(new Runnable() {
@Override
public void run() {
if (!bluetoothGattServer.addService(service)) {
Timber.e("adding service %s failed", service.getUuid());
completedCommand();
}
}
});
if (result) {
nextCommand();
} else {
Timber.e("could not enqueue add service command");
}
return result;
}
/**
* Remove a service
*
* @param service the service to remove
* @return true if the service was removed, otherwise false
*/
public boolean remove(@NotNull final BluetoothGattService service) {
Objects.requireNonNull(service, SERVICE_IS_NULL);
return bluetoothGattServer.removeService(service);
}
/**
* Remove all services
*/
public void removeAllServices() {
bluetoothGattServer.clearServices();
}
/**
* Get a list of the all advertised services of this peripheral
*
* @return a list of zero or more services
*/
@NotNull
public List<BluetoothGattService> getServices() {
return bluetoothGattServer.getServices();
}
/**
* Send a notification or indication that a local characteristic has been
* updated
*
* <p>A notification or indication is sent to all remote centrals to signal
* that the characteristic has been updated.
*
* @param characteristic the characteristic for which to send a notification
* @return true if the operation was enqueued, otherwise false
*/
public boolean notifyCharacteristicChanged(@NotNull final byte[] value, @NotNull final BluetoothGattCharacteristic characteristic) {
Objects.requireNonNull(value, CHARACTERISTIC_VALUE_IS_NULL);
Objects.requireNonNull(characteristic, CHARACTERISTIC_IS_NULL);
if (doesNotSupportNotifying(characteristic))
return false;
boolean result = true;
for (BluetoothDevice device : getConnectedDevices()) {
if (!notifyCharacteristicChanged(copyOf(value), device, characteristic)) {
result = false;
}
}
return result;
}
private boolean notifyCharacteristicChanged(@NotNull final byte[] value, @NotNull final BluetoothDevice bluetoothDevice, @NotNull final BluetoothGattCharacteristic characteristic) {
Objects.requireNonNull(value, CHARACTERISTIC_VALUE_IS_NULL);
Objects.requireNonNull(bluetoothDevice, DEVICE_IS_NULL);
Objects.requireNonNull(characteristic, CHARACTERISTIC_IS_NULL);
Objects.requireNonNull(characteristic.getValue(), CHARACTERISTIC_VALUE_IS_NULL);
if (doesNotSupportNotifying(characteristic))
return false;
final boolean confirm = supportsIndicate(characteristic);
final boolean result = commandQueue.add(new Runnable() {
@Override
public void run() {
currentNotifyValue = value;
currentNotifyCharacteristic = characteristic;
characteristic.setValue(value);
if (!bluetoothGattServer.notifyCharacteristicChanged(bluetoothDevice, characteristic, confirm)) {
Timber.e("notifying characteristic changed failed for <%s>", characteristic.getUuid());
BluetoothPeripheralManager.this.completedCommand();
}
}
});
if (result) {
nextCommand();
} else {
Timber.e("could not enqueue notify command");
}
return result;
}
/**
* Cancel a connection to a Central
*
* @param bluetoothCentral the Central
*/
public void cancelConnection(@NotNull final BluetoothCentral bluetoothCentral) {
Objects.requireNonNull(bluetoothCentral, CENTRAL_IS_NULL);
cancelConnection(bluetoothAdapter.getRemoteDevice(bluetoothCentral.getAddress()));
}
private void cancelConnection(@NotNull final BluetoothDevice bluetoothDevice) {
Objects.requireNonNull(bluetoothDevice, DEVICE_IS_NULL);
Timber.i("cancelConnection with '%s' (%s)", bluetoothDevice.getName(), bluetoothDevice.getAddress());
bluetoothGattServer.cancelConnection(bluetoothDevice);
}
@NotNull
private List<BluetoothDevice> getConnectedDevices() {
return bluetoothManager.getConnectedDevices(BluetoothGattServer.GATT);
}
/**
* Get the set of connected Centrals
*
* @return a set with zero or more connected Centrals
*/
@NotNull
public Set<BluetoothCentral> getConnectedCentrals() {
Set<BluetoothCentral> bluetoothCentrals = new HashSet<>(connectedCentralsMap.values());
return Collections.unmodifiableSet(bluetoothCentrals);
}
/**
* The current command has been completed, move to the next command in the queue (if any)
*/
private void completedCommand() {
commandQueue.poll();
commandQueueBusy = false;
nextCommand();
}
/**
* Execute the next command in the subscribe queue.
* A queue is used because some calls have to be executed sequentially.
*/
private void nextCommand() {
synchronized (this) {
// If there is still a command being executed, then bail out
if (commandQueueBusy)
return;
// Check if there is something to do at all
final Runnable bluetoothCommand = commandQueue.peek();
if (bluetoothCommand == null)
return;
// Execute the next command in the queue
commandQueueBusy = true;
mainHandler.post(new Runnable() {
@Override
public void run() {
try {
bluetoothCommand.run();
} catch (Exception ex) {
Timber.e(ex, "command exception");
BluetoothPeripheralManager.this.completedCommand();
}
}
});
}
}
@Nullable
public BluetoothCentral getCentral(@NotNull final String address) {
Objects.requireNonNull(address, ADDRESS_IS_NULL);
return connectedCentralsMap.get(address);
}
@NotNull
private BluetoothCentral getCentral(@NotNull final BluetoothDevice device) {
Objects.requireNonNull(device, DEVICE_IS_NULL);
BluetoothCentral result = connectedCentralsMap.get(device.getAddress());
if (result == null) {
result = new BluetoothCentral(device.getAddress(), device.getName());
}
return result;
}
private void removeCentral(@NotNull final BluetoothDevice device) {
Objects.requireNonNull(device, DEVICE_IS_NULL);
connectedCentralsMap.remove(device.getAddress());
}
private final BroadcastReceiver adapterStateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (action == null)
return;
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
handleAdapterState(state);
}
}
};
private void handleAdapterState(final int state) {
switch(state) {
case BluetoothAdapter.STATE_OFF:
Timber.d("bluetooth turned off");
cancelAllConnectionsWhenBluetoothOff();
break;
case BluetoothAdapter.STATE_TURNING_OFF:
Timber.d("bluetooth turning off");
break;
case BluetoothAdapter.STATE_ON:
Timber.d("bluetooth turned on");
break;
case BluetoothAdapter.STATE_TURNING_ON:
Timber.d("bluetooth turning on");
break;
}
}
private void cancelAllConnectionsWhenBluetoothOff() {
final Set<BluetoothCentral> bluetoothCentrals = getConnectedCentrals();
for (BluetoothCentral bluetoothCentral : bluetoothCentrals) {
bluetoothGattServerCallback.onConnectionStateChange(bluetoothAdapter.getRemoteDevice(bluetoothCentral.getAddress()), 0, BluetoothProfile.STATE_DISCONNECTED);
}
onAdvertisingStopped();
}
@NotNull
private byte[] copyOf(@NotNull final byte[] source, final int offset, final int maxSize) {
if (source.length > maxSize) {
final int chunkSize = Math.min(source.length - offset, maxSize);
return Arrays.copyOfRange(source, offset, offset + chunkSize);
}
return Arrays.copyOf(source, source.length);
}
/**
* Make a safe copy of a nullable byte array
*
* @param source byte array to copy
* @return non-null copy of the source byte array or an empty array if source was null
*/
@NotNull
byte[] copyOf(@Nullable final byte[] source) {
return (source == null) ? new byte[0] : Arrays.copyOf(source, source.length);
}
/**
* Make a byte array nonnull by either returning the original byte array if non-null or an empty bytearray
*
* @param source byte array to make nonnull
* @return the source byte array or an empty array if source was null
*/
@NotNull
private byte[] nonnullOf(@Nullable final byte[] source) {
return (source == null) ? new byte[0] : source;
}
private boolean supportsNotify(@NotNull final BluetoothGattCharacteristic characteristic) {
return (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0;
}
private boolean supportsIndicate(@NotNull final BluetoothGattCharacteristic characteristic) {
return (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0;
}
private boolean doesNotSupportNotifying(@NotNull final BluetoothGattCharacteristic characteristic) {
return !(supportsIndicate(characteristic) || supportsNotify(characteristic));
}
}