Skip to content

Instantly share code, notes, and snippets.

@lekaha
Created September 18, 2018 08:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lekaha/ce12372cca68975f011190d2f6a6bef6 to your computer and use it in GitHub Desktop.
Save lekaha/ce12372cca68975f011190d2f6a6bef6 to your computer and use it in GitHub Desktop.
SimpleRxBluetoothAdapter is a BT utility with Rx
package mobile.lekeha.android.bluetoothadapter;
import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import io.reactivex.Completable;
import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.SingleEmitter;
import io.reactivex.SingleOnSubscribe;
import io.reactivex.SingleSource;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Action;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
import io.reactivex.subjects.PublishSubject;
import java.util.ArrayList;
import java.util.UUID;
import java.util.concurrent.Callable;
public class SimpleRxBluetoothAdapter {
private static String[] REQUIRED_PERMISSIONS = new String[] {Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN};
private static UUID BT_UUID = UUID.randomUUID();
@Nullable private BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
@NonNull
private DiscoveryBroadcastReceiver broadcastReceiver = new DiscoveryBroadcastReceiver();
@Nullable PublishSubject<BluetoothDevice> discoveredDevices;
@Nullable PublishSubject<Integer> boundDeviceSingle;
@NonNull private CompositeDisposable compositeDisposable = new CompositeDisposable();
@NonNull private Context context;
private boolean checkedPermissions = false;
public SimpleRxBluetoothAdapter(@NonNull Context context) {
this.context = context;
}
public void start() {
discoveredDevices = PublishSubject.create();
registerBroadcastReceiver();
}
public void stop() {
if (discoveredDevices != null) {
discoveredDevices.onComplete();
}
unregisterBroadcastReceiver();
compositeDisposable.dispose();
}
public boolean isEnabled() {
return (bluetoothAdapter != null) && (bluetoothAdapter.isEnabled());
}
public Observable findPairedDevices() {
return assertion().andThen(Observable.fromIterable(bluetoothAdapter.getBondedDevices()));
}
public Single<Boolean> isPaired(@NonNull BluetoothDevice device) {
return assertion().andThen(Single.just(bluetoothAdapter.getBondedDevices()
.contains(device)));
}
public Single<Boolean> pair(@NonNull final BluetoothDevice device) {
return assertion().andThen(isPaired(device)).flatMap(isPaired -> {
return Single.create(new SingleOnSubscribe<Boolean>() {
@Override
public void subscribe(SingleEmitter<Boolean> emitter) throws Exception {
if (!isPaired) {
boundDeviceSingle = PublishSubject.create();
compositeDisposable.add(boundDeviceSingle.subscribe(new Consumer<Integer>() {
@Override
public void accept(Integer integer) throws Exception {
if (BluetoothDevice.BOND_BONDING == integer) {
emitter.onSuccess(true);
} else {
emitter.onSuccess(false);
}
}
}));
device.createBond();
}
}
});
});
}
public Single connect(@NonNull final BluetoothDevice device) {
return assertion().andThen(pair(device)).flatMap(new Function<Boolean, SingleSource<?>>() {
@Override
public SingleSource<BluetoothDevice> apply(Boolean aBoolean) throws Exception {
if (aBoolean) {
return Single.fromCallable(new Callable<BluetoothDevice>() {
@Override
public BluetoothDevice call() throws Exception {
BluetoothSocket socket = device.createRfcommSocketToServiceRecord(BT_UUID);
socket.connect();
return device;
}
});
}
return null;
}
});
}
public Observable<BluetoothDevice> discoverDevices() {
return assertion().andThen(scanDevices());
}
private Observable<BluetoothDevice> scanDevices() {
if (bluetoothAdapter.isDiscovering()) {
return discoveredDevices;
}
bluetoothAdapter.startDiscovery();
return discoveredDevices;
}
private Completable assertion() {
return Completable.fromAction(new Action() {
@Override
public void run() {
if (bluetoothAdapter == null) {
throw new IllegalArgumentException("Bluetooth is not supported");
}
if (!isEnabled()) {
throw new IllegalArgumentException("Bluetooth is not enabled");
}
ArrayList<String> missingPermissions = new ArrayList<>();
if (!checkedPermissions) {
for (String permission : REQUIRED_PERMISSIONS) {
if (ContextCompat.checkSelfPermission(context,
permission) == PackageManager.PERMISSION_DENIED) {
missingPermissions.add(permission);
}
}
}
if (!missingPermissions.isEmpty()) {
throw new IllegalArgumentException("Missing required permission: " + missingPermissions);
}
checkedPermissions = true;
if (discoveredDevices == null) {
throw new IllegalArgumentException("Have not yet started");
}
}
});
}
private void registerBroadcastReceiver() {
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
context.registerReceiver(broadcastReceiver, filter);
}
private void unregisterBroadcastReceiver() {
context.unregisterReceiver(broadcastReceiver);
}
private class DiscoveryBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
discoveredDevices.onNext(device);
}
if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) {
}
if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
discoveredDevices.onComplete();
}
if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
BluetoothDevice.BOND_BONDING);
boundDeviceSingle.onNext(state);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment