Created
February 8, 2020 17:31
-
-
Save liamkernighan/aa43a076c1283f03977e32722026a565 to your computer and use it in GitHub Desktop.
Java ble service
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.nasladdin.partner.services; | |
import android.app.Service; | |
import android.bluetooth.BluetoothAdapter; | |
import android.bluetooth.BluetoothDevice; | |
import android.bluetooth.BluetoothGatt; | |
import android.bluetooth.BluetoothGattCallback; | |
import android.bluetooth.BluetoothGattCharacteristic; | |
import android.bluetooth.BluetoothGattDescriptor; | |
import android.bluetooth.BluetoothGattService; | |
import android.bluetooth.BluetoothManager; | |
import android.bluetooth.BluetoothProfile; | |
import android.content.Context; | |
import android.content.Intent; | |
import android.os.Binder; | |
import android.os.IBinder; | |
import com.nasladdin.partner.application.GlobalApplication; | |
import com.nasladdin.partner.dependencyinjection.DependencyContainer; | |
import com.nasladdin.partner.helpers.sharedpreferences.BluetoothDevicePreferencesManager; | |
import java.util.List; | |
import java.util.UUID; | |
public class BluetoothLeService extends Service { | |
public class LocalBinder extends Binder { | |
public BluetoothLeService getService() { | |
return BluetoothLeService.this; | |
} | |
} | |
//region constants and enums | |
/** Данная характеристика поступает при нажатии кнопки сканера и поднесении RFID-метки. | |
* Подписываемся на эту неё при подключении сканера RFID-меток | |
*/ | |
private final static UUID DATA_ARRIVED_SCANNER_CHARACTERISTIC_UUID = UUID.fromString("0000ffe1-0000-1000-8000-00805f9b34fb"); | |
/** | |
* Набор байт, который ожидает устройство для того, чтобы начать сканирование. "Программное нажатие кнопки". | |
*/ | |
private final static byte[] SEND_START_SCANNING_WITHOUT_BUTTON_COMMAND = new byte[] { 6, 0, 1, 4, 0, -84, 54, 0, 0, 0 }; // Взято из кода китайцев. | |
// todo удалить | |
public final static String RFID_ARRIVED_INTENT_EXTRA = "RFID_ARRIVED_INTENT_EXTRA"; | |
public final static String DEVICE_DISCONNECTED_ADAPTER_STATE = "DEVICE_DISCONNECTED_ADAPTER_STATE"; | |
public final static String CONNECTED_DEVICE_NAME_INTENT_KEY = "CONNECTED_DEVICE_NAME_INTENT_KEY"; | |
public final static String CONNECTED_DEVICE_VIEW_ENTITY_INTENT_KEY = "CONNECTED_DEVICE_VIEW_ENTITY_INTENT_KEY"; | |
public enum ConnectionState { | |
CONNECTED, | |
CONNECTING, | |
DISCONNECTED, | |
} | |
public enum IntentAction { | |
CONNECTED ("CONNECTED"), | |
DISCONNECTED ("DISCONNECTED"), | |
SINGLE_BUTTON_RFID_ARRIVED("SINGLE_BUTTON_RFID_ARRIVED"), | |
BATCH_WITHOUT_BUTTON_RFID_ARRIVED("BATCH_WITHOUT_BUTTON_RFID_ARRIVED"); | |
private String _title; | |
IntentAction(String title) { | |
_title = title; | |
} | |
public String getTitle() { | |
return this._title; | |
} | |
} | |
private enum ScanMode { | |
SINGLE_WITH_BUTTON, BATCH_WITHOUT_BUTTON, | |
} | |
//endregion | |
//region props | |
private BluetoothManager _bluetoothManager; | |
private BluetoothAdapter _bluetoothAdapter; | |
private String _bluetoothDeviceAddress; | |
private String _bluetoothDeviceName; | |
private BluetoothGatt _bluetoothGatt; | |
private ConnectionState _connectionState = ConnectionState.DISCONNECTED; | |
public ConnectionState getConnectionState() { | |
return _connectionState; | |
} | |
private final IBinder _binder = new LocalBinder(); | |
private BluetoothDevicePreferencesManager _bluetoothDevicePreferencesManager; | |
private BluetoothGattCharacteristic _notificationDataCharacteristic; // Храним в классе для отправки данных на устройство. | |
private StringBuilder _batchScanStringBuilder = new StringBuilder(); | |
private ScanMode _scanMode = ScanMode.SINGLE_WITH_BUTTON; | |
@Override | |
public void onCreate() { | |
super.onCreate(); | |
DependencyContainer container = GlobalApplication.Companion.getDependencyContainer(); | |
_bluetoothDevicePreferencesManager = container.getBluetoothDevicePreferencesManager(); | |
} | |
private final BluetoothGattCallback _gattCallback = new BluetoothGattCallback() { | |
@Override | |
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { | |
super.onDescriptorRead(gatt, descriptor, status); | |
} | |
@Override | |
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { | |
if (_connectionState == ConnectionState.CONNECTED && newState == BluetoothProfile.STATE_CONNECTED) { | |
return; | |
} | |
if (newState == BluetoothProfile.STATE_CONNECTED) { | |
setStateConnected(); | |
_bluetoothGatt.discoverServices(); | |
broadcastConnectedEvent(); | |
} | |
else if (newState == BluetoothProfile.STATE_DISCONNECTED) { | |
broadcastDisconnectedEvent(); | |
} | |
} | |
private void setStateConnected() { | |
_connectionState = ConnectionState.CONNECTED; | |
_bluetoothDevicePreferencesManager.setDevice(_bluetoothDeviceAddress, _bluetoothDeviceName); | |
} | |
@Override | |
public void onServicesDiscovered(BluetoothGatt gatt, int status) { | |
if (status == BluetoothGatt.GATT_SUCCESS) { | |
_bluetoothGatt = gatt; | |
subscribeToRfidCharacteristicNotification(gatt.getServices()); | |
} | |
} | |
private void subscribeToRfidCharacteristicNotification(List<BluetoothGattService> services) { | |
for (BluetoothGattService service : services) { | |
BluetoothGattCharacteristic characteristic = service.getCharacteristic(DATA_ARRIVED_SCANNER_CHARACTERISTIC_UUID); | |
if (characteristic != null) { | |
_bluetoothGatt.setCharacteristicNotification(characteristic, true); | |
_notificationDataCharacteristic = characteristic; | |
break; | |
} | |
} | |
} | |
@Override | |
public void onCharacteristicRead(BluetoothGatt gatt, | |
BluetoothGattCharacteristic characteristic, | |
int status) { | |
if (status == BluetoothGatt.GATT_SUCCESS) { | |
} | |
} | |
@Override | |
public void onCharacteristicChanged(BluetoothGatt gatt, | |
BluetoothGattCharacteristic characteristic) { | |
if (!characteristic.getUuid().equals(DATA_ARRIVED_SCANNER_CHARACTERISTIC_UUID)) { | |
return; | |
} | |
broadcastScannedRfid(characteristic); | |
} | |
}; | |
//endregion | |
public String getCurrentlyConnectedDeviceName() { | |
if (_connectionState == ConnectionState.CONNECTED) { | |
return _bluetoothDeviceName; | |
} | |
return null; | |
} | |
@Override | |
public IBinder onBind(Intent intent) { | |
return _binder; | |
} | |
@Override | |
public boolean onUnbind(Intent intent) { | |
close(); | |
return super.onUnbind(intent); | |
} | |
private void close() { | |
if (_bluetoothGatt == null) { | |
return; | |
} | |
_bluetoothGatt.close(); | |
_bluetoothGatt = null; | |
} | |
public boolean initBluetoothAdapter() { | |
if (_bluetoothManager == null) { | |
_bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); | |
if (_bluetoothManager == null) { | |
return false; | |
} | |
} | |
_bluetoothAdapter = _bluetoothManager.getAdapter(); | |
return _bluetoothAdapter != null; | |
} | |
public void beginConnectingDevice(String address) { | |
// Обязательно высвобождаем ресурсы и отписываемся ото всех событий текущего подключения. | |
disconnectCurrentDevice(); | |
if (_bluetoothAdapter == null || address == null) { | |
return; | |
} | |
// Previously connected device. Try to reconnect. | |
if (address.equals(_bluetoothDeviceAddress) && _connectionState != ConnectionState.CONNECTED) { | |
if (_bluetoothGatt != null && _bluetoothGatt.connect()) { | |
_connectionState = ConnectionState.CONNECTING; | |
} | |
return; | |
} | |
if (address.equals(_bluetoothDeviceAddress)) { | |
return; | |
} | |
final BluetoothDevice device = _bluetoothAdapter.getRemoteDevice(address); | |
if (device == null) { | |
return; | |
} | |
_bluetoothGatt = device.connectGatt(this, true, _gattCallback); | |
_bluetoothDeviceAddress = address; | |
_bluetoothDeviceName = device.getName(); | |
_connectionState = ConnectionState.CONNECTING; | |
} | |
public void disconnectCurrentDevice() { | |
_bluetoothDeviceAddress = null; | |
_bluetoothDeviceName = null; | |
_connectionState = ConnectionState.DISCONNECTED; | |
if (_bluetoothGatt != null) { | |
_bluetoothGatt.disconnect(); | |
_bluetoothGatt.close(); // close обязателен. В противном случае будем ловить события с предыдущих подключений и неизвестные ошибки. Второй вариант - _adapter.cancelDiscovery(), но этот тоже хорошо работает. | |
_bluetoothGatt = null; | |
} | |
_bluetoothDevicePreferencesManager.deleteDevice(); | |
} | |
private void broadcastScannedRfid(BluetoothGattCharacteristic characteristic) { | |
final byte[] data = characteristic.getValue(); | |
if (data == null || data.length == 0) return; | |
String rfid = bytesToHexString(data, 0 , data.length); | |
String intentAction = _scanMode == ScanMode.SINGLE_WITH_BUTTON | |
? IntentAction.SINGLE_BUTTON_RFID_ARRIVED.getTitle() | |
: IntentAction.BATCH_WITHOUT_BUTTON_RFID_ARRIVED.getTitle(); | |
if (_scanMode == ScanMode.BATCH_WITHOUT_BUTTON) { | |
_batchScanStringBuilder.append(rfid); | |
} | |
Intent intent = new Intent(intentAction); | |
intent.putExtra(RFID_ARRIVED_INTENT_EXTRA, rfid); | |
sendBroadcast(intent); | |
} | |
public void resetAccumulatedBatchRfids() { | |
_batchScanStringBuilder.setLength(0); | |
} | |
public String getAccumulatedBatchScanRfids() { | |
return _batchScanStringBuilder.toString(); | |
} | |
private void broadcastDisconnectedEvent() { | |
final Intent intent = new Intent(IntentAction.DISCONNECTED.getTitle()); | |
intent.putExtra(DEVICE_DISCONNECTED_ADAPTER_STATE, _bluetoothAdapter.isEnabled()); | |
sendBroadcast(intent); | |
} | |
private void broadcastConnectedEvent() { | |
final Intent intent = new Intent(IntentAction.CONNECTED.getTitle()); | |
intent.putExtra(CONNECTED_DEVICE_NAME_INTENT_KEY, getCurrentlyConnectedDeviceName()); | |
sendBroadcast(intent); | |
} | |
public void startBatchScanningRfidsWithoutButton() { | |
// todo если нажать на кнопку при пакетном сканировании, то мы валимся с ошибкой. | |
if (_bluetoothAdapter == null || _bluetoothGatt == null) { | |
throw new IllegalStateException("Bluetooth is not initialized."); | |
} | |
_notificationDataCharacteristic.setValue(SEND_START_SCANNING_WITHOUT_BUTTON_COMMAND); | |
_scanMode = ScanMode.BATCH_WITHOUT_BUTTON; | |
_bluetoothGatt.writeCharacteristic(_notificationDataCharacteristic); | |
} | |
public void resetModeToSingleButton() { | |
_scanMode = ScanMode.SINGLE_WITH_BUTTON; | |
} | |
public static String bytesToHexString(byte[] src, int offset, int length) { | |
StringBuilder sb = new StringBuilder(); | |
for (int i = 0; i < length; i++) { | |
String asHex = Integer.toHexString(src[i + offset] & 0xFF); | |
sb.append((asHex.length() == 1) ? "0" + asHex : asHex); | |
} | |
return sb.toString().toUpperCase(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment