diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9511d9b..29eb38e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,6 +4,8 @@ + + diff --git a/app/src/main/java/eu/piotro/sondechaser/data/BlueAdapter.java b/app/src/main/java/eu/piotro/sondechaser/data/BlueAdapter.java new file mode 100644 index 0000000..6300af1 --- /dev/null +++ b/app/src/main/java/eu/piotro/sondechaser/data/BlueAdapter.java @@ -0,0 +1,204 @@ +package eu.piotro.sondechaser.data; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothManager; +import android.bluetooth.BluetoothSocket; +import android.content.Context; +import android.content.pm.PackageManager; +import android.widget.PopupMenu; +import android.widget.Toast; + +import androidx.core.app.ActivityCompat; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.util.Set; +import java.util.UUID; + +public class BlueAdapter { + private final Activity rootActivity; + private final BluetoothAdapter bluetoothAdapter; + + private BluetoothSocket bluetoothSocket; + + private BufferedReader reader; + private PrintWriter writer; + + private String freqString = "403.000"; + + private BluetoothDevice device = null; + private String deviceAddress = null; + private int type = 2; + + private static final UUID WELL_KNOWN_SERIAL_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); + + public BlueAdapter(Activity rootActivity) { + this.rootActivity = rootActivity; + + BluetoothManager bluMgr = (BluetoothManager) rootActivity.getBaseContext().getSystemService(Context.BLUETOOTH_SERVICE); + this.bluetoothAdapter = bluMgr.getAdapter(); + } + + private boolean permissionCheck() { + if (ActivityCompat.checkSelfPermission(rootActivity, Manifest.permission.BLUETOOTH) != PackageManager.PERMISSION_GRANTED) { + if(ActivityCompat.shouldShowRequestPermissionRationale(rootActivity, Manifest.permission.BLUETOOTH)) { + rootActivity.runOnUiThread(()-> Toast.makeText(rootActivity, "No bluetooth permission. Enable in system app settings.", Toast.LENGTH_LONG).show() ); + } else { + ActivityCompat.requestPermissions(rootActivity, new String[]{Manifest.permission.BLUETOOTH}, 2); + } + return false; + } + return true; + } + + public void setDeviceAddress(String menuEntry) { + if (menuEntry.contains("(")) + deviceAddress = menuEntry.substring(menuEntry.lastIndexOf('(')+1, menuEntry.length()-1); + } + public void setFrequency(String freq) { + freqString = freq.replace(',', '.'); + updateFreq(); + } + public void setType(String typestr) { + this.type = Integer.parseInt(typestr.substring(0, typestr.indexOf(':'))); + updateFreq(); + } + + @SuppressLint("MissingPermission") + private void connectDevice() { + if (!permissionCheck()) + return; + + if (device == null) { + if (deviceAddress != null) { + device = this.bluetoothAdapter.getRemoteDevice(deviceAddress); + } + if(device == null) + return; + } + + try { + if (bluetoothSocket != null) + bluetoothSocket.close(); + + try { + bluetoothSocket = device.createInsecureRfcommSocketToServiceRecord(WELL_KNOWN_SERIAL_UUID); + } catch (IOException e) { + // TODO: Replace with status text, or ignore for now in settings is probably enough + //rootActivity.runOnUiThread(()-> Toast.makeText(rootActivity, "Bluetooth is disabled", Toast.LENGTH_LONG).show() ); + try { Thread.sleep(10000); } catch (InterruptedException ignored) {} + } + } catch (IOException e) { + bluetoothSocket = null; + e.printStackTrace(); + } + } + + @SuppressLint("MissingPermission") + public void connectSocket() { + if (!permissionCheck()) + return; + try { + if (bluetoothSocket != null) { + bluetoothSocket.connect(); // this fails when device is offline - just silently ignore it + reader = new BufferedReader(new InputStreamReader(bluetoothSocket.getInputStream())); + writer = new PrintWriter(new OutputStreamWriter(bluetoothSocket.getOutputStream())); + updateFreq(); + } else { + connectDevice(); + } + } catch (Exception ignored) {} + } + + public String readLine() { + if (bluetoothSocket == null || reader == null || !bluetoothSocket.isConnected()) + return null; + + try { + return reader.readLine(); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + private void updateFreq() { + if (bluetoothSocket == null || reader == null) + return; + + String cmd = "o{f="+freqString+"/tipo="+type+"}"; + writer.println(cmd); + writer.flush(); // <<< + } + + @SuppressLint("MissingPermission") + public void fillMenu(Activity activity, PopupMenu menu) { + if (!permissionCheck()) + return; + + if (!bluetoothAdapter.isEnabled()) { + menu.getMenu().add("Bluetooth is disabled."); + menu.getMenu().add("Enable in system settings."); + return; + } + + Set paired = this.bluetoothAdapter.getBondedDevices(); + activity.runOnUiThread(()-> { + for (BluetoothDevice dev : paired) { + menu.getMenu().add(dev.getName() + " (" + dev.getAddress() + ")"); + } + menu.getMenu().add("IF NOT LISTED HERE, PAIR DEVICE"); + menu.getMenu().add("FROM SYSTEM SETTINGS FIRST"); + }); + } + + class BlockedReaderThread implements Runnable { + private String lastLine; + @Override + public void run() { + for (;;) { + System.out.println("BTHREAD: loop "); + String line = readLine(); // this fails in all cases (device offline, closed transmission error) + if (line == null) { + System.out.println("BTTHREAD: Reconnect"); + connectSocket(); + try { Thread.sleep(2000); } catch (InterruptedException ignored) {} + } else { + System.out.println("BTHREAD: received " + line); + } + lastLine = line; + + try { Thread.sleep(200); } catch (InterruptedException ignored) {} + } + } + + public String getLine() { + return lastLine; + } + + } + + private BlockedReaderThread thread = null; + + public BlockedReaderThread getRunnable() { + if (thread == null) + thread = new BlockedReaderThread(); + return thread; + } + + public boolean isConnected() { + if (bluetoothSocket == null) + return false; + return bluetoothSocket.isConnected(); + } + + // NOTE: whaaaaaaaaa? Disabling BT on phone does not dissconect BtSocket, and it is *even* able to reconnect and continue working + // after BT is DISABLED. I don't understand that. Streams will close but if socket will remain able to reconnect +}