BTHandler.java 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  1. package nl.digitalthings.mebarista;
  2. import android.bluetooth.BluetoothDevice;
  3. import android.bluetooth.BluetoothSocket;
  4. import android.bluetooth.BluetoothAdapter;
  5. import android.content.BroadcastReceiver;
  6. import android.content.Context;
  7. import android.content.Intent;
  8. import android.content.IntentFilter;
  9. import android.content.SharedPreferences;
  10. import android.os.Build;
  11. import android.preference.PreferenceManager;
  12. import android.util.Log;
  13. import java.io.IOException;
  14. import java.io.InputStream;
  15. import java.io.OutputStream;
  16. import java.lang.reflect.InvocationTargetException;
  17. import java.lang.reflect.Method;
  18. import java.util.Timer;
  19. import java.util.TimerTask;
  20. import java.util.UUID;
  21. public class BTHandler {
  22. private static final String TAG = "BT2";
  23. // bluetooth
  24. static String SPP_UUID = "00001101-0000-1000-8000-00805F9B34FB";
  25. BluetoothAdapter mBluetoothAdapter;
  26. BluetoothSocket socket = null;
  27. private InputStream inStream;
  28. private OutputStream outStream;
  29. // service
  30. BaristaService service;
  31. // Discovery related state
  32. boolean no_connected = false;
  33. boolean discovery_allow = true;
  34. boolean discovery_running = false;
  35. boolean discovery_timer_running = false;
  36. Timer discovery_timer = new Timer();
  37. // Util
  38. SharedPreferences sharedPref;
  39. public BTHandler(BaristaService serv) {
  40. Log.i( TAG, "Constructor" );
  41. // mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
  42. mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
  43. service = serv;
  44. inStream = null;
  45. outStream = null;
  46. socket = null;
  47. // not allowed here:
  48. // sharedPref = PreferenceManager.getDefaultSharedPreferences(service);
  49. IntentFilter f1, f2, f3, f4, f5, f6; // Not sure if necessary
  50. // TODO: move over to connect or something
  51. service.ma.registerReceiver( mReceiver, f1 = new IntentFilter( BluetoothDevice.ACTION_FOUND ) ); // Don't forget to unregister during onDestroy
  52. service.ma.registerReceiver( mReceiver, f2 = new IntentFilter( BluetoothDevice.ACTION_PAIRING_REQUEST ) ); // Don't forget to unregister during onDestroy
  53. service.ma.registerReceiver( mReceiver, f3 = new IntentFilter( BluetoothDevice.ACTION_BOND_STATE_CHANGED ) ); // Don't forget to unregister during onDestroy
  54. service.ma.registerReceiver( mReceiver, f4 = new IntentFilter( BluetoothDevice.ACTION_ACL_CONNECTED ) ); // Don't forget to unregister during onDestroy
  55. service.ma.registerReceiver( mReceiver, f5 = new IntentFilter( BluetoothDevice.ACTION_ACL_DISCONNECTED)); // Don't forget to unregister during onDestroy
  56. service.ma.registerReceiver( mReceiver, f6 = new IntentFilter( BluetoothAdapter.ACTION_DISCOVERY_FINISHED)); // Don't forget to unregister during onDestroy
  57. service.ma.registerReceiver( mReceiver, f6 = new IntentFilter( BluetoothAdapter.ACTION_DISCOVERY_STARTED)); // Don't forget to unregister during onDestroy
  58. }
  59. // Called from BaristaService upon onDestroy
  60. public void close_object() {
  61. // TODO: or should it be done six times?
  62. // crasht soms : Receiver not registered service.ma.unregisterReceiver(mReceiver);
  63. discovery_timer.cancel();
  64. }
  65. // Connect wrappers
  66. private BluetoothSocket connect_secure(BluetoothDevice device) {
  67. BluetoothSocket socket = null;
  68. try {
  69. socket = device.createRfcommSocketToServiceRecord( UUID.fromString(SPP_UUID) );
  70. } catch (IOException e) {
  71. try { socket.close(); } catch (IOException e1) { }
  72. // Creating the socket failed: continue to iterate over bonded devices
  73. Log.e( TAG, "Secure SOCKET CREATION failed");
  74. }
  75. return socket;
  76. }
  77. private BluetoothSocket connect_insecure(BluetoothDevice device) {
  78. BluetoothSocket socket = null;
  79. try {
  80. socket = device.createInsecureRfcommSocketToServiceRecord(UUID.fromString(SPP_UUID));
  81. } catch (IOException e) {
  82. try { socket.close(); } catch (IOException e1) { }
  83. // Creating the socket failed: continue to iterate over bonded devices
  84. Log.e( TAG, "Insecure SOCKET CREATION failed");
  85. }
  86. return socket;
  87. }
  88. private BluetoothSocket connect_introspection(BluetoothDevice device) {
  89. BluetoothSocket socket = null;
  90. try {
  91. Class<?> clazz = device.getClass();
  92. Class<?>[] paramTypes = new Class<?>[] {Integer.TYPE};
  93. Method m = clazz.getMethod("createRfcommSocket", paramTypes);
  94. Object[] params = new Object[] {Integer.valueOf(1)};
  95. socket = (BluetoothSocket) m.invoke(device, params);
  96. } catch (InvocationTargetException e) {
  97. try { socket.close(); } catch (IOException e1) { }
  98. // Creating the socket failed: continue to iterate over bonded devices
  99. Log.e( TAG, "Introspection SOCKET CREATION failed");
  100. }
  101. catch (NoSuchMethodException e) {
  102. try { socket.close(); } catch (IOException e1) { }
  103. // Creating the socket failed: continue to iterate over bonded devices
  104. Log.e( TAG, "Introspection SOCKET CREATION failed");
  105. }
  106. catch (IllegalAccessException e) {
  107. try { socket.close(); } catch (IOException e1) { }
  108. // Creating the socket failed: continue to iterate over bonded devices
  109. Log.e( TAG, "Introspection SOCKET CREATION failed");
  110. }
  111. return socket;
  112. }
  113. public boolean scan_connect() {
  114. if( mBluetoothAdapter == null )
  115. return false;
  116. // SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(service);
  117. // SharedPreferences preference = service.getSharedPreferences( "preference", 0);
  118. // Iterate over bonded devices to find clients
  119. // while(!stop) {
  120. for(BluetoothDevice device : mBluetoothAdapter.getBondedDevices() ) {
  121. String deviceName = device.getName();
  122. if( deviceName == null ) {
  123. // getting friendly bluetooth name may fail
  124. continue;
  125. }
  126. if( !service.is_meCoffee( deviceName ) )
  127. continue;
  128. Log.i( TAG, "CONNECTING to " + deviceName);
  129. socket = null;
  130. // As per the Android documentation:
  131. // http://developer.android.com/reference/android/bluetooth/BluetoothSocket.html#connect()
  132. mBluetoothAdapter.cancelDiscovery();
  133. // Use the introspection variant ( for older devices ? )
  134. socket = connect_introspection( device );
  135. // Failed? Continue the loop
  136. if( socket == null )
  137. continue;
  138. // Connect to socket
  139. if( !socket.isConnected() ) {
  140. try {
  141. socket.connect();
  142. } catch (IOException e) {
  143. System.out.println( e.getMessage() );
  144. try { socket.close(); } catch(Exception ei) { }
  145. // Connecting the socket failed: continue to iterate over bonded devices
  146. Log.e( TAG, "SOCKET CONNECT failed" );
  147. continue;
  148. }
  149. }
  150. // Create input and outputstreams
  151. try {
  152. inStream = socket.getInputStream();
  153. outStream = socket.getOutputStream();
  154. } catch (IOException e1) {
  155. // Getting the streams failed: continue to iterate over bonded devices
  156. try { socket.close(); } catch(Exception ei) {}
  157. Log.e( TAG, "STREAMs failed" );
  158. continue;
  159. }
  160. // Check for nulls
  161. if( inStream == null || outStream == null) {
  162. try { socket.close(); } catch(Exception ei) {}
  163. continue;
  164. }
  165. // succeeded: break out of this function by returning...
  166. SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(service);
  167. SharedPreferences.Editor editor = settings.edit();
  168. editor.putString("pref_bt_devicename", device.getName());
  169. editor.commit();
  170. if( !no_connected )
  171. service.connected( inStream, outStream );
  172. return true;
  173. } // bonded_devices
  174. // Nothing connected
  175. return false;
  176. // try { Thread.sleep(5000); } catch (InterruptedException e) { }
  177. // } // while true
  178. } // scan_connect
  179. // --- AUTO pairing ---
  180. // http://stackoverflow.com/questions/9608140/how-to-unpair-or-delete-paired-bluetooth-device-programmatically-on-android
  181. private void unpairDevice(BluetoothDevice device) {
  182. try {
  183. Method method = device.getClass().getMethod("removeBond", (Class[]) null);
  184. method.invoke(device, (Object[]) null);
  185. } catch (Exception e) {
  186. e.printStackTrace();
  187. }
  188. }
  189. // http://stackoverflow.com/questions/17168263/how-to-pair-bluetooth-device-programmatically-android
  190. // http://developer.android.com/guide/topics/connectivity/bluetooth.html
  191. private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
  192. public void onReceive(Context context, Intent intent) {
  193. String action = intent.getAction();
  194. // TODO: get his out of here, should only happen once
  195. sharedPref = PreferenceManager.getDefaultSharedPreferences(service);
  196. switch( action ) {
  197. case BluetoothAdapter.ACTION_DISCOVERY_STARTED:
  198. // TODO:
  199. Log.i( TAG, "Discovery started" );
  200. discovery_running = true; // is this even useful ?
  201. service.spinner( true );
  202. break;
  203. case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:
  204. Log.i( TAG, "Discovery finished" );
  205. discovery_running = false;
  206. service.spinner( false );
  207. if( inStream != null || !discovery_allow || discovery_running )
  208. return;
  209. if( !sharedPref.getBoolean( "pref_bt_keepdiscovering", false ) )
  210. return;
  211. if( !mBluetoothAdapter.isDiscovering() && !discovery_timer_running )
  212. Log.i(TAG, "Restarting timer for new discovery in " + 60 + " seconds");
  213. discovery_timer_running = true;
  214. discovery_timer.schedule(new TimerTask() {
  215. @Override
  216. public void run() {
  217. if( ! ( mBluetoothAdapter.isDiscovering() || discovery_running ) ) {
  218. if( inStream == null ) {
  219. Log.i("BT2", "Restarting discovery from timer" );
  220. mBluetoothAdapter.startDiscovery();
  221. }
  222. }
  223. else
  224. Log.i("BT2", "Discovery from timer: discovery was already running?" );
  225. discovery_timer_running = false;
  226. }
  227. }, 60*1000 );
  228. break;
  229. case BluetoothDevice.ACTION_ACL_CONNECTED:
  230. Log.i( TAG, "ACL Connected" );
  231. // TODO: set flag
  232. break;
  233. case BluetoothDevice.ACTION_ACL_DISCONNECTED:
  234. Log.i(TAG, "ACL Disconnected");
  235. // TODO: if we were not connected: do nothing ( because spurious messages on app startup )
  236. if( inStream != null )
  237. try {
  238. inStream.close();
  239. } catch (IOException e) {
  240. e.printStackTrace();
  241. }
  242. if( outStream != null )
  243. try {
  244. outStream.close();
  245. } catch (IOException e) {
  246. e.printStackTrace();
  247. }
  248. if( socket != null )
  249. try {
  250. socket.close();
  251. } catch (IOException e) {
  252. e.printStackTrace();
  253. }
  254. inStream = null;
  255. outStream = null;
  256. service.connected( inStream, outStream );
  257. if( !sharedPref.getBoolean( "pref_bt_keepdiscovering", false ) || !discovery_allow || discovery_running )
  258. return;
  259. if( !mBluetoothAdapter.isDiscovering() ) {
  260. Log.i("BT2", "Restarting discovery from disconnect" );
  261. mBluetoothAdapter.startDiscovery();
  262. }
  263. else
  264. Log.i("BT2", "Discovery from disconnect: was already running?" );
  265. break;
  266. case BluetoothDevice.ACTION_FOUND:
  267. {
  268. discovery_running = true; // apparently we are discovering
  269. BluetoothDevice device_discovered = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
  270. Log.i( TAG, "discovered " + device_discovered.getName());
  271. // Do not take action if we are currently connected
  272. if( inStream != null )
  273. return;
  274. // Only service BT2
  275. if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && device_discovered.getType() != BluetoothDevice.DEVICE_TYPE_CLASSIC )
  276. return;
  277. // Only service devices which name is right
  278. if( !service.is_meCoffee( device_discovered.getName() ) )
  279. return;
  280. Log.i( TAG, "Checking device" );
  281. // If we are currently connected to another device, do nothing
  282. // TODO
  283. // If already bonded to this device, do nothing
  284. if( device_discovered.getBondState() == BluetoothDevice.BOND_BONDED ) {
  285. Log.i( TAG, "Already bonded" );
  286. scan_connect();
  287. return;
  288. }
  289. // We are trying to pair to a new device, stop discovering
  290. // http://stackoverflow.com/questions/16326750/cant-cancel-bluetooth-discovery-process
  291. // mBluetoothAdapter.cancelDiscovery(); according to SO link, unreliable
  292. // Break existing bonds
  293. for(BluetoothDevice device_bonded : mBluetoothAdapter.getBondedDevices() ) {
  294. if( service.is_meCoffee( device_bonded.getName() ) ) {
  295. Log.i( TAG, "Unbonded " + device_bonded.getName() );
  296. unpairDevice( device_bonded );
  297. }
  298. }
  299. mBluetoothAdapter.cancelDiscovery();
  300. Log.i( TAG, "Pairing" );
  301. // Programmatically pair with the device
  302. if( !device_discovered.setPin( "4321".getBytes() ) )
  303. Log.i( TAG, "setPin failed" );
  304. try {
  305. if (!device_discovered.setPairingConfirmation(false))
  306. Log.i(TAG, "setPairingConfirmation failed");
  307. }
  308. catch( Exception e ) {
  309. Log.i(TAG, "setPairingConfirmation exception");
  310. }
  311. if ( !device_discovered.createBond() ) {
  312. Log.i( TAG, "createBond failed: restarting discovery" );
  313. mBluetoothAdapter.startDiscovery();
  314. }
  315. }
  316. break;
  317. case BluetoothDevice.ACTION_PAIRING_REQUEST:
  318. Log.i( TAG, "ACTION_PAIRING_REQUEST" );
  319. BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
  320. // device.setPairingConfirmation(false);
  321. device.setPin( "4321".getBytes() );
  322. break;
  323. case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
  324. if( intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1) == BluetoothDevice.BOND_BONDED )
  325. scan_connect();
  326. break;
  327. default:
  328. Log.e( TAG, "unknown action: " + action );
  329. break;
  330. }
  331. }
  332. };
  333. public void discover() {
  334. discovery_allow = true;
  335. if( mBluetoothAdapter == null )
  336. return;
  337. if( scan_connect() ) {
  338. Log.i( TAG, "Connected already paired device" );
  339. return ;
  340. }
  341. Log.i(TAG, "No connected device was able to connect or found, discovering");
  342. mBluetoothAdapter.startDiscovery();
  343. }
  344. public void discover_stop() {
  345. /*
  346. try {
  347. service.ma.unregisterReceiver(mReceiver);
  348. }
  349. catch( Exception e ) {
  350. // TODO: do better on mReceiver
  351. }
  352. */
  353. discovery_allow = false;
  354. mBluetoothAdapter.cancelDiscovery();
  355. discovery_timer.cancel();
  356. discovery_timer = new Timer();
  357. }
  358. void close() {
  359. Log.i( TAG, "Closing" );
  360. if( inStream != null )
  361. try {
  362. inStream.close();
  363. inStream = null;
  364. } catch (Exception e) {
  365. e.printStackTrace();
  366. }
  367. if( outStream != null )
  368. try {
  369. outStream.close();
  370. outStream = null;
  371. } catch (Exception e) {
  372. e.printStackTrace();
  373. }
  374. if( socket != null )
  375. try { socket.close(); socket = null; } catch (IOException e1) { }
  376. // TODO: waarom service niet informeren?
  377. }
  378. public void write(byte[] bytes) {
  379. if(outStream != null) {
  380. try {
  381. outStream.write(bytes);
  382. outStream.flush();
  383. } catch (IOException e) {
  384. e.printStackTrace();
  385. }
  386. }
  387. else
  388. Log.i( TAG, "outStream null, not writing" );
  389. }
  390. }