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.BtSequence.SequenceResult.detour; import static io.trygvis.android.bt.BtSequence.SequenceResult.fail; import static io.trygvis.android.bt.BtSequence.SequenceResult.waitForNextEvent; public class BtDevice implements Comparable { private final static String TAG = BtDevice.class.getSimpleName(); private final DefaultBtService btService; private final BluetoothDevice bluetoothDevice; private BluetoothGatt gatt; private Integer rssi; private A tag; private final String address; private final long id; private final boolean seenBefore; private final Date firstSeen; private Date lastSeen; /** * If seen in last scan. */ private boolean recentlySeen; 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, boolean seenBefore, Date firstSeen, Date lastSeen, boolean recentlySeen) { this.btService = btService; this.bluetoothDevice = bluetoothDevice; this.id = id; this.rssi = rssi; this.seenBefore = seenBefore; this.firstSeen = firstSeen; this.lastSeen = lastSeen; this.recentlySeen = recentlySeen; this.tag = btDbIntegration.createTag(db, this); this.address = bluetoothDevice.getAddress(); } public long getId() { return id; } public A getTag() { return tag; } public String getAddress() { return address; } public String getName() { return bluetoothDevice.getName(); } public Integer getRssi() { return rssi; } public boolean isSeenBefore() { return seenBefore; } public Date getFirstSeen() { return firstSeen; } public Date getLastSeen() { return lastSeen; } public void setLastSeen(Date lastSeen) { this.lastSeen = lastSeen; } public boolean isRecentlySeen() { return recentlySeen; } public void setRecentlySeen(boolean recentlySeen) { this.recentlySeen = recentlySeen; } /** * The first handler must handle a onDirect(). *

* Services will be discovered. */ public synchronized void withConnection(BtSequence sequence) { if (callback != null) { throw new RuntimeException("The current callback is not done."); } Log.i(TAG, "withConnection(), address=" + address + ", connected: " + (gatt != null)); BtSequence newSequence; if (gatt == null) { newSequence = new BtSequence(). 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 BtSequence().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(sequence); }); } else { newSequence = sequence; } BtSequencer sequencer = new BtSequencer(bluetoothDevice.getAddress(), newSequence. onFinally(success -> { Log.i(TAG, "Promise done, device is available again: address=" + address + ", success=" + success); callback = null; })); callback = new BtBluetoothGattCallback(sequencer); if (gatt == null) { gatt = bluetoothDevice.connectGatt(btService, false, wrappingCallback); } else { sequencer.onDirect(); } } public boolean connected() { return connected; } @Override public String toString() { return "BtDevice{address=" + address + '}'; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } BtDevice other = (BtDevice) o; return address.equals(other.getAddress()); } @Override public int hashCode() { return address.hashCode(); } @Override public int compareTo(BtDevice that) { return address.compareTo(that.address); } 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; if (oldConnected && BtDevice.this.connected) { Log.i(TAG, "Wrapping: Extra 'onConnectionStateChange' event, ignoring. gatt=" + gatt); return; } try { if (callback != null) { callback.onConnectionStateChange(gatt, status, newState); } } finally { if (!BtDevice.this.connected) { if (oldConnected) { Log.i(TAG, "Wrapping: Lost connection, removing gatt. gatt=" + gatt); } else { Log.i(TAG, "Wrapping: Lost connection, was not connected. gatt=" + gatt); } BtDevice.this.gatt = null; } else { Log.i(TAG, "Wrapping: 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); } } } }