diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..681f41a --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,116 @@ + + + + + + + +
+ + + + xmlns:android + + ^$ + + + +
+
+ + + + xmlns:.* + + ^$ + + + BY_NAME + +
+
+ + + + .*:id + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + .*:name + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + name + + ^$ + + + +
+
+ + + + style + + ^$ + + + +
+
+ + + + .* + + ^$ + + + BY_NAME + +
+
+ + + + .* + + http://schemas.android.com/apk/res/android + + + ANDROID_ATTRIBUTE_ORDER + +
+
+ + + + .* + + .* + + + BY_NAME + +
+
+
+
+
+
\ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 5cd0919..6506080 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -1,17 +1,21 @@ + diff --git a/.idea/misc.xml b/.idea/misc.xml index d4d3260..7ac1c05 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,8 +1,5 @@ - - - - - - - - - - - - - - + diff --git a/.idea/modules.xml b/.idea/modules.xml index 8efa824..1a0686b 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,8 +2,10 @@ + - + + \ No newline at end of file diff --git a/ardutooth/build.gradle b/ardutooth/build.gradle index 1507d62..4a3b5a3 100644 --- a/ardutooth/build.gradle +++ b/ardutooth/build.gradle @@ -1,11 +1,11 @@ apply plugin: 'com.android.library' android { - compileSdkVersion 25 - buildToolsVersion "25.0.3" + compileSdkVersion 29 + buildToolsVersion '29.0.2' defaultConfig { minSdkVersion 14 - targetSdkVersion 25 + targetSdkVersion 29 versionCode 2 versionName "2.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" @@ -19,10 +19,11 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + api fileTree(dir: 'libs', include: ['*.jar']) + androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) - compile 'com.android.support:appcompat-v7:25.3.1' - testCompile 'junit:junit:4.12' + //noinspection GradleCompatible + api 'com.android.support:appcompat-v7:25.3.1' + testImplementation 'junit:junit:4.13' } diff --git a/ardutooth/src/main/java/io/github/giuseppebrb/ardutooth/Ardutooth.java b/ardutooth/src/main/java/io/github/giuseppebrb/ardutooth/Ardutooth.java index d4232e7..d2d856a 100644 --- a/ardutooth/src/main/java/io/github/giuseppebrb/ardutooth/Ardutooth.java +++ b/ardutooth/src/main/java/io/github/giuseppebrb/ardutooth/Ardutooth.java @@ -3,14 +3,19 @@ import android.app.Activity; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothSocket; +import android.os.Build; +import android.support.annotation.RequiresApi; import android.util.Log; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.util.UUID; /** * This singleton class represents the main component of the library. - * It can be used to easily set a stable connection with an Arduino and to send data to it using the Serial Monitor. + * It can be used to easily set a stable connection with an Arduino and to send/receive data to/from it using the Serial Monitor. * *

The first thing you need is to create or get an instance of {@link Ardutooth} using something like * {@code Ardutooth mArdutooth = Ardutooth.getInstance(this)} where parameter represents the instance of @@ -65,7 +70,7 @@ public boolean isConnected() { } catch (Exception e) { Log.d(Ardutooth.TAG, "An error occurred while retrieving connection", e); } - return mBtHandler.connected; + return BluetoothHandler.connected; } /** @@ -206,4 +211,38 @@ public void sendBoolean(boolean value) { e.printStackTrace(); } } + + /** + * Reads a single character, as defined by {@link BufferedReader}'s readLine method, + * from the Arduino, casts it into a {@link char}, and returns it. + * + * @return char value read + */ + public char receiveChar(){ + char c = 0; + if (mBtHandler.getSocket() != null) + try { + c = (char) mBtHandler.getInputReader().read(); + } catch (IOException e) { + e.printStackTrace(); + } + return c; + } + + /** + * Reads a line, as defined by {@link BufferedReader}'s readLine method, from the Arduino. + * + * @return {@link String} line read + */ + public String receiveLine(){ + String result = ""; + if (mBtHandler.getSocket() != null) + try { + result = mBtHandler.getInputReader().readLine(); + } catch (IOException ex) { + ex.printStackTrace(); + } + return result; + } + } \ No newline at end of file diff --git a/ardutooth/src/main/java/io/github/giuseppebrb/ardutooth/BluetoothHandler.java b/ardutooth/src/main/java/io/github/giuseppebrb/ardutooth/BluetoothHandler.java index 68f9c26..45f90f5 100644 --- a/ardutooth/src/main/java/io/github/giuseppebrb/ardutooth/BluetoothHandler.java +++ b/ardutooth/src/main/java/io/github/giuseppebrb/ardutooth/BluetoothHandler.java @@ -1,5 +1,6 @@ package io.github.giuseppebrb.ardutooth; +import android.annotation.SuppressLint; import android.app.Activity; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; @@ -15,9 +16,14 @@ import android.util.Log; import android.widget.Toast; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.Set; /** * This singleton class handles the bluetooth connection with Arduino. @@ -37,6 +43,7 @@ class BluetoothHandler { private Activity mActivity; private OutputStream mOutStream; + private BufferedReader mReader; /** * Constructor @@ -59,7 +66,7 @@ private BluetoothHandler(Activity activity) { } /** - * Allows to create or get the unique instance of the compononent. + * Allows to create or get the unique instance of the component. * * @param activity Define the {@link android.content.Context} where the object will work. * @return @@ -80,21 +87,28 @@ protected void createConnection() { /** * Open the input and output communication with Arduino. + * + * @return true if communication has been set up successfully, else false */ - private void connect() { + @SuppressLint("NewApi") + private boolean connect() { OutputStream tmpOut = null; + InputStream tmpIn = null; try { mSocket.connect(); try { tmpOut = mSocket.getOutputStream(); + tmpIn = mSocket.getInputStream(); } catch (IOException e) { Log.e(Ardutooth.TAG, "Error occurred when creating output stream", e); } mOutStream = tmpOut; + mReader = new BufferedReader(new InputStreamReader(tmpIn, StandardCharsets.US_ASCII)); } catch (IOException e) { Log.e(Ardutooth.TAG, "Error opening connection", e); closeConnection(); } + return mSocket.isConnected(); } /** @@ -105,6 +119,7 @@ protected void closeConnection() { try { mSocket.close(); mOutStream.close(); + mReader.close(); } catch (IOException e) { Log.e(Ardutooth.TAG, "Error while closing socket", e); Toast.makeText(mActivity.getApplication(), mActivity.getString(R.string.error_occurred_disconnecting), Toast.LENGTH_LONG).show(); @@ -118,12 +133,16 @@ protected void closeConnection() { * Retrieve a bluetooth connection if there's one already established. */ protected void retrieveConnection() { + + Log.d(Ardutooth.TAG, "Attempting to retrieve existing connection"); if (mAdapter.getProfileConnectionState(BluetoothHeadset.HEADSET) == BluetoothHeadset.STATE_CONNECTED) { List devices = mBluetoothHeadset.getConnectedDevices(); mBtDevice = devices.get(0); connected = true; Log.d(Ardutooth.TAG, "Already connected with a device"); - } + } else + Log.d(Ardutooth.TAG, "Not connected with a device"); + } /** @@ -137,7 +156,9 @@ private boolean isBluetoothSupported() { /** * Check if bluetooth is on, if no ask the user to turn it on. - * If bluetooth is on but there's no connection yet ask the user to open bluetooth settings and to connect with the Arduino bluetooth module. + * If bluetooth is on but there's no connection yet, create a list of paired devices and ask + * the user to pick one to connect with. If there are no paired devices, open bluetooth settings + * and pair and connect with the Arduino bluetooth module. */ private void checkBluetoothState() { if (isBluetoothSupported()) { @@ -164,24 +185,19 @@ public void onClick(DialogInterface dialog, int which) { } else { Log.d(Ardutooth.TAG, "Bluetooth is already on"); retrieveConnection(); - if (connected == false) { - builder.setTitle(mActivity.getString(R.string.bluetooth_not_connected)); - builder.setMessage(mActivity.getString(R.string.bluetooth_not_connected_message)); - builder.setCancelable(true); - builder.setPositiveButton(mActivity.getString(R.string.open_settings), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - Intent settings_intent = new Intent(android.provider.Settings.ACTION_BLUETOOTH_SETTINGS); - mActivity.startActivityForResult(settings_intent, REQUEST_ENABLE_BT); - } - }); - builder.setNegativeButton(mActivity.getString(R.string.cancel), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - }); - builder.create().show(); + if (!connected) { + + if (mAdapter.getBondedDevices().size() != 0) { + + promptUserToConnectWithAPairedDevice(); + + } else{ + + Log.d(Ardutooth.TAG, "There are no bonded devices"); + + promptUserToPairWithADevice(); + } + } else { Toast.makeText(mActivity.getApplication(), mActivity.getString(R.string.already_connected) + mBtDevice.getName(), Toast.LENGTH_SHORT).show(); } @@ -199,8 +215,118 @@ public void onClick(DialogInterface dialog, int which) { } } + /** + * Creates a list of Bonded (paired) Bluetooth devices and displays their names in a dialog. + * The user can select a previously paired device to connect with, or pair and connect with + * a new one. + */ + private void promptUserToConnectWithAPairedDevice() { + + Set pairedDevicesSet = mAdapter.getBondedDevices(); + + //the names are what is displayed in the dialog list + String[] pairedDevicesNames = new String[pairedDevicesSet.size()]; + + //This is the list of paired BluetoothDevice object references. + // It is final so that it can be accessed in the anonymous inner-class EventHandler. + final BluetoothDevice[] pairedDevicesArray = new BluetoothDevice[pairedDevicesSet.size()]; + + //Create a key-value association between the list of names and the list of devices. + int i = 0; + for (BluetoothDevice device : pairedDevicesSet) { + pairedDevicesNames[i] = device.getName(); + Log.d(Ardutooth.TAG, "Found bonded Device: " + pairedDevicesNames[i]); + pairedDevicesArray[i] = device; + i++; + } + + //This is a weird "hack" I resorted to. It allows the string to change, + // while still being able to reference it in the PositiveButton event handler. + final String[] selectedDeviceAddress = {""}; + + builder.setTitle(mActivity.getString(R.string.bluetooth_not_connected)); + builder.setCancelable(true); + builder.setSingleChoiceItems(pairedDevicesNames, -1, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + selectedDeviceAddress[0] = pairedDevicesArray[which].getAddress(); + Log.d(Ardutooth.TAG, "user selected " + pairedDevicesArray[which].getName()); + } + }); + builder.setPositiveButton(mActivity.getString(R.string.confirm), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mBtDevice = mAdapter.getRemoteDevice(selectedDeviceAddress[0]); + Log.d(Ardutooth.TAG, "mBtDevice = " + mBtDevice.getName() + ", " + mBtDevice.getAddress()); + openRFCOMMSocketWithBondedDevice(); + } + }); + builder.setNeutralButton(mActivity.getString(R.string.pair_with_new_device), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Intent settings_intent = new Intent(android.provider.Settings.ACTION_BLUETOOTH_SETTINGS); + mActivity.startActivityForResult(settings_intent, REQUEST_ENABLE_BT); + } + }); + builder.setNegativeButton(mActivity.getString(R.string.cancel), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + builder.show(); + } + + /** + * If the device has already been bonded, no action will be issued to the BroadcastReceiver + * until a connection is made (at which point the BluetoothDevice.ACTION_ACL_CONNECTED will be + * issued). Because of this the BroadcastReceiver will not initiate creating a socket, so we + * must do that manually here. + */ + private void openRFCOMMSocketWithBondedDevice() { + try { + int counter = 0; + do { + mSocket = mBtDevice.createRfcommSocketToServiceRecord(Ardutooth.UUID); + counter++; + } + while (!connect() && counter != 2); + Log.d(Ardutooth.TAG, "Connected to " + mBtDevice.getName() + ": " + mSocket.isConnected()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * If Bluetooth is already on, and not already connected, and there are no Devices already + * bonded (paired), prompt the user to pair with a device in Android's Bluetooth settings. + */ + private void promptUserToPairWithADevice() { + Log.d(Ardutooth.TAG, "prompting user to pair with a device"); + + builder.setTitle(mActivity.getString(R.string.bluetooth_not_connected)); + builder.setMessage(mActivity.getString(R.string.pair_with_another_device_message)); + builder.setCancelable(true); + builder.setPositiveButton(mActivity.getString(R.string.open_settings), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Intent settings_intent = new Intent(android.provider.Settings.ACTION_BLUETOOTH_SETTINGS); + mActivity.startActivityForResult(settings_intent, REQUEST_ENABLE_BT); + } + }); + builder.setNegativeButton(mActivity.getString(R.string.cancel), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + builder.create().show(); + } + /** * Retrieve the device connected with. + * * @return the connected with. */ protected BluetoothDevice getDeviceConnected() { @@ -208,7 +334,8 @@ protected BluetoothDevice getDeviceConnected() { } /** - * Retrieve the socket where's the connection it's happaning. + * Retrieve the socket where's the connection it's happening. + * * @return the socket opened for the connection. */ protected BluetoothSocket getSocket() { @@ -217,24 +344,36 @@ protected BluetoothSocket getSocket() { /** * Retrieve the {@link OutputStream} of the communication + * * @return the {@link OutputStream} of the communication */ protected OutputStream getOutputStream() { return mOutStream; } + /** + * Retrieve the {@link BufferedReader} which reads the {@link InputStream} of the communication + * + * @return the {@link BufferedReader} which reads the {@link InputStream} of the communication + */ + protected BufferedReader getInputReader() { + return mReader; + } + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action)) { - Log.d(Ardutooth.TAG, "Connected"); + Log.d(Ardutooth.TAG, "Connected from broadcast receiver"); mBtDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); Toast.makeText(mActivity.getApplication(), mActivity.getString(R.string.connected_to) + mBtDevice.getName(), Toast.LENGTH_SHORT).show(); connected = true; try { - mSocket = mBtDevice.createRfcommSocketToServiceRecord(Ardutooth.UUID); - connect(); + if (!mSocket.isConnected()) { //might already be connected from openRFCOMMSocketWithBondedDevice() + mSocket = mBtDevice.createRfcommSocketToServiceRecord(Ardutooth.UUID); + connect(); + } } catch (IOException e) { Log.e(Ardutooth.TAG, "Error during socket creation", e); } @@ -267,4 +406,5 @@ public void onServiceDisconnected(int profile) { } } }; + } \ No newline at end of file diff --git a/ardutooth/src/main/res/values/strings.xml b/ardutooth/src/main/res/values/strings.xml index 8979a52..6acdedb 100644 --- a/ardutooth/src/main/res/values/strings.xml +++ b/ardutooth/src/main/res/values/strings.xml @@ -1,11 +1,12 @@ Already connected with\u0020 Bluetooth is not connected - Open settings to connect via bluetooth with another device. + Open settings to pair via bluetooth with another device. Bluetooth not supported This device doesn\'t support bluetooth. The requested operation cannot be done. Bluetooth is off Cancel + Confirm Cannot disconnect because there\'s no connection established Connected to\u0020 Disconnected from\u0020 @@ -13,4 +14,5 @@ Ardutooth Open settings Turn on bluetooth in settings and connect to another device. + Connect Other diff --git a/build.gradle b/build.gradle index c2eea8e..b27b75b 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,10 @@ buildscript { repositories { jcenter() + google() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.3' + classpath 'com.android.tools.build:gradle:3.6.3' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -15,6 +16,7 @@ buildscript { allprojects { repositories { jcenter() + google() } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c319b07..59fdcda 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Jun 09 19:31:04 CEST 2017 +#Tue May 12 12:13:42 EDT 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip