package io.trygvis.android.bt; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.database.sqlite.SQLiteDatabase; import android.util.Log; import java.util.Date; import static io.trygvis.android.bt.BtPromise.BtBluetoothGattCallback; import static io.trygvis.android.bt.BtPromise.PromiseResult.detour; import static io.trygvis.android.bt.BtPromise.PromiseResult.fail; import static io.trygvis.android.bt.BtPromise.PromiseResult.waitForNextEvent; public class BtDevice { private final static String TAG = BtDevice.class.getSimpleName(); private final DefaultBtService btService; private final BluetoothDevice bluetoothDevice; private BluetoothGatt gatt; private Integer rssi; private BtScanResult scanResult; private A tag; private final String address; private final long id; private final boolean seenBefore; private final Date firstSeen; private Date lastSeen; private boolean connected; private final WrappingBluetoothGattCallback wrappingCallback = new WrappingBluetoothGattCallback(); private BtBluetoothGattCallback callback; BtDevice(DefaultBtService btService, BluetoothDevice bluetoothDevice, SQLiteDatabase db, BtService.BtDbIntegration btDbIntegration, long id, Integer rssi, BtScanResult scanResult, boolean seenBefore, Date firstSeen, Date lastSeen) { this.btService = btService; this.bluetoothDevice = bluetoothDevice; this.id = id; this.rssi = rssi; this.scanResult = scanResult; this.seenBefore = seenBefore; this.firstSeen = firstSeen; this.lastSeen = lastSeen; this.tag = btDbIntegration.createTag(db, this); this.address = bluetoothDevice.getAddress(); } public long getId() { return id; } public A getTag() { return tag; } public String getAddress() { return bluetoothDevice.getAddress(); } public String getName() { return bluetoothDevice.getName(); } public Integer getRssi() { return rssi; } public BtScanResult getScanResult() { return scanResult; } public boolean isSeenBefore() { return seenBefore; } public Date getFirstSeen() { return firstSeen; } public Date getLastSeen() { return lastSeen; } public void setLastSeen(Date lastSeen) { this.lastSeen = lastSeen; } /** * The first handler must handle a onDirect(). *

* Services will be discovered. */ public synchronized void withConnection(BtPromise promise) { if (callback != null) { throw new RuntimeException("The current callback is not done."); } Log.i(TAG, "withConnection(), address=" + bluetoothDevice.getAddress() + ", connected: " + (gatt != null)); BtPromise newPromise; if (gatt == null) { newPromise = new BtPromise(). ignoreFailureForNext(). onConnectionStateChange((gatt, status, newState) -> { Log.i(TAG, "defaultConnectCallback: status=" + status + ", newState=" + newState); btService.sendBroadcast(btService.createDeviceConnection(address)); if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothGatt.STATE_CONNECTED) { Log.i(TAG, "Connected to " + address + ", discovering services"); return gatt.discoverServices() ? waitForNextEvent() : fail(); } else { Log.i(TAG, "Could not connect to " + address + ", trying again"); return detour(new BtPromise().onConnectionStateChange((gatt2, status2, newState2) -> { if (status2 == BluetoothGatt.GATT_SUCCESS && newState2 == BluetoothGatt.STATE_CONNECTED) { Log.i(TAG, "Connected to " + address + ", discovering services"); return gatt.discoverServices() ? waitForNextEvent() : fail(); } Log.i(TAG, "Could still not connect to " + address + ", failing."); return fail(); })); } }). onServicesDiscovered(gatt -> { Log.i(TAG, "Services discovered, has " + gatt.getServices().size() + " services"); return detour(promise); }); } else { newPromise = promise; } callback = newPromise. onFinally(success -> { Log.i(TAG, "Promise done, device is available again: address=" + address + ", success=" + success); callback = null; }). asCallback(bluetoothDevice.getAddress()); if (gatt == null) { gatt = bluetoothDevice.connectGatt(btService, false, wrappingCallback); } else { callback.onEvent(BtPromise.EventType.onDirect, "", gatt, null, null, BluetoothGatt.GATT_SUCCESS, BluetoothGatt.STATE_CONNECTED); } } public boolean connected() { return connected; } @Override public String toString() { return "BtDevice{address=" + bluetoothDevice.getAddress() + '}'; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } BtDevice other = (BtDevice) o; return getAddress().equals(other.getAddress()); } @Override public int hashCode() { return getAddress().hashCode(); } private class WrappingBluetoothGattCallback extends BluetoothGattCallback { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { Log.i(TAG, "Wrapping: onConnectionStateChange: " + "address=" + gatt.getDevice().getAddress() + ", " + "status=" + status + ", " + "newState=" + newState); boolean oldConnected = connected; BtDevice.this.connected = status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothGatt.STATE_CONNECTED; try { if (callback != null) { callback.onConnectionStateChange(gatt, status, newState); } } finally { if (!BtDevice.this.connected) { if (oldConnected) { Log.i(TAG, "Wrapper: Lost connection, removing gatt. gatt=" + gatt); } else { Log.i(TAG, "Wrapper: Lost connection, was not connected. gatt=" + gatt); } BtDevice.this.gatt = null; } else { Log.i(TAG, "Wrapper: connected"); } } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (callback != null) { callback.onServicesDiscovered(gatt, status); } } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { if (callback != null) { callback.onCharacteristicRead(gatt, characteristic, status); } } @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { if (callback != null) { callback.onCharacteristicWrite(gatt, characteristic, status); } } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { if (callback != null) { callback.onCharacteristicChanged(gatt, characteristic); } } @Override public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { if (callback != null) { callback.onDescriptorRead(gatt, descriptor, status); } } @Override public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { if (callback != null) { callback.onDescriptorWrite(gatt, descriptor, status); } } @Override public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { if (callback != null) { callback.onReliableWriteCompleted(gatt, status); } } @Override public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { if (callback != null) { callback.onReadRemoteRssi(gatt, rssi, status); } } } }