package org.envirocar.app.services; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.content.IntentFilter; import android.os.Parcelable; import org.envirocar.app.exception.NoOBDSocketConnectedException; import org.envirocar.app.exception.UUIDSanityCheckFailedException; import org.envirocar.core.logging.Logger; import org.envirocar.core.utils.BroadcastUtils; import org.envirocar.obd.bluetooth.BluetoothSocketWrapper; import org.envirocar.obd.bluetooth.FallbackBluetoothSocket; import org.envirocar.obd.bluetooth.NativeBluetoothSocket; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.UUID; import rx.Observable; import rx.Subscriber; import rx.exceptions.OnErrorThrowable; /** * TODO JavaDoc * * @author dewall */ public class OBDConnectionHandler { private static final Logger LOG = Logger.getLogger(OBDConnectionHandler.class); private static final UUID EMBEDDED_BOARD_SPP = UUID .fromString("00001101-0000-1000-8000-00805F9B34FB"); private final Context context; /** * Constructor * * @param context The context of the current scope. */ public OBDConnectionHandler(Context context) { this.context = context; } /** * @param device the device to start a connection to. */ public Observable<BluetoothSocketWrapper> getOBDConnectionObservable( final BluetoothDevice device) { return Observable.just(device) .map(bluetoothDevice -> { if (bluetoothDevice.fetchUuidsWithSdp()) return bluetoothDevice; else throw OnErrorThrowable.from(new UUIDSanityCheckFailedException()); }) .concatMap(bluetoothDevice -> getUUIDList(bluetoothDevice)) .concatMap(uuids -> createOBDBluetoothObservable(device, uuids)); } public void shutdownSocket(BluetoothSocketWrapper socket) { LOG.info("Shutting down bluetooth socket."); try { if (socket.getInputStream() != null) socket.getInputStream().close(); if (socket.getOutputStream() != null) socket.getOutputStream().close(); socket.close(); } catch (Exception e) { LOG.severe(e.getMessage(), e); } } /** * @param device * @return */ private Observable<List<UUID>> getUUIDList(final BluetoothDevice device) { LOG.info(String.format("getUUIDList(%s)", device.getName())); return BroadcastUtils.createBroadcastObservable(context, new IntentFilter(BluetoothDevice.ACTION_UUID)) .first() .map(intent -> { LOG.info("getUUIDList(): map call"); // Get the device and the UUID provided by the incoming intent. BluetoothDevice deviceExtra = intent .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); Parcelable[] uuidExtra = intent .getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID); // If the received broadcast does not belong to this receiver, // skip it. if (!deviceExtra.getAddress().equals(device.getAddress())) return null; // Result list to return List<UUID> res = new ArrayList<UUID>(); LOG.info(String.format("Adding default UUID: %s", EMBEDDED_BOARD_SPP)); res.add(EMBEDDED_BOARD_SPP); // Create a uuid for every string and return it for (Parcelable uuid : uuidExtra) { UUID next = UUID.fromString(uuid.toString()); if (!res.contains(next)) { res.add(next); } } // return the result list return res; }); } private Observable<BluetoothSocketWrapper> createOBDBluetoothObservable( BluetoothDevice device, List<UUID> uuids) { return Observable.create(new Observable.OnSubscribe<BluetoothSocketWrapper>() { private BluetoothSocketWrapper socketWrapper; @Override public void call(Subscriber<? super BluetoothSocketWrapper> subscriber) { for (UUID uuid : uuids) { // Stop if the subscriber is unsubscribed. if (subscriber.isUnsubscribed()) return; try { LOG.info("Trying to create native bleutooth socket"); socketWrapper = new NativeBluetoothSocket(device .createRfcommSocketToServiceRecord(uuid)); } catch (IOException e) { LOG.warn(e.getMessage(), e); continue; } try { connectSocket(); } catch (FallbackBluetoothSocket.FallbackException | InterruptedException | IOException e) { LOG.warn(e.getMessage(), e); shutdownSocket(socketWrapper); socketWrapper = null; } if (socketWrapper != null) { LOG.info("successful connected"); subscriber.onNext(socketWrapper); socketWrapper = null; subscriber.onCompleted(); return; } } if (socketWrapper == null) { subscriber.onError(new NoOBDSocketConnectedException()); } } private void connectSocket() throws FallbackBluetoothSocket.FallbackException, InterruptedException, IOException { try { // This is a blocking call and will only return on a // successful connection or an exception socketWrapper.connect(); } catch (IOException e) { LOG.warn("Exception on bluetooth connection. Trying the fallback... : " + e.getMessage(), e); //try the fallback socketWrapper = new FallbackBluetoothSocket( socketWrapper.getUnderlyingSocket()); Thread.sleep(500); socketWrapper.connect(); } } }); } }