Send Bluetooth Messages Between Android Phones API 31+ Java
This article provides a detailed guide on how to send text messages via Bluetooth Classic between two Android phones using Java, specifically targeting API level 31 (Android 12) and above. With the introduction of new Bluetooth permissions in recent Android versions, developers need to adapt their code to ensure seamless functionality and user privacy. This guide covers all the necessary steps, from setting up Bluetooth permissions to establishing connections and transmitting data, offering practical examples and explanations to help you implement Bluetooth communication in your Android applications.
Understanding the Bluetooth Landscape in Android API 31+
Android API level 31 (Android 12) introduced significant changes to Bluetooth permissions, impacting how apps discover and connect to nearby devices. Understanding these changes is crucial for developers targeting newer Android versions. Previously, the BLUETOOTH
and BLUETOOTH_ADMIN
permissions were sufficient for most Bluetooth operations. However, API 31 introduced more granular permissions to enhance user privacy.
The key changes include:
BLUETOOTH_CONNECT
: This permission is required to connect to paired Bluetooth devices. Your app needs this permission to initiate or accept Bluetooth connections.BLUETOOTH_SCAN
: This permission allows your app to scan for Bluetooth devices. It's essential for discovering nearby devices to connect to.ACCESS_FINE_LOCATION
: If your app targets Android 12 or higher and uses Bluetooth scanning, you also need to request theACCESS_FINE_LOCATION
permission. This is because Bluetooth scanning can potentially be used to infer the user's location.BLUETOOTH_ADVERTISE
: If your app needs to make the device discoverable to other Bluetooth devices, you'll need this permission.
These changes emphasize user control over Bluetooth usage, ensuring that apps cannot access Bluetooth functionalities without explicit user consent. As a developer, you must request these permissions at runtime and handle scenarios where the user might deny them.
Implementing Bluetooth Permissions in Your Android App
To implement Bluetooth communication successfully, you must declare the necessary permissions in your AndroidManifest.xml
file and request them at runtime. Let's break down the process:
-
Declare Permissions in
AndroidManifest.xml
:Open your
AndroidManifest.xml
file and add the following permissions:<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="30" /> <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
The
android:usesPermissionFlags="neverForLocation"
attribute forBLUETOOTH_SCAN
indicates that your app doesn't derive physical location from Bluetooth scans. Theandroid:maxSdkVersion
attribute for location permissions ensures that these permissions are only requested on Android versions lower than 12. -
Request Permissions at Runtime:
In your Activity or Fragment, request the necessary permissions using the
ActivityCompat.requestPermissions()
method. Here's an example:private static final int BLUETOOTH_PERMISSIONS_REQUEST_CODE = 100; private final String[] bluetoothPermissions = { Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.BLUETOOTH_ADVERTISE }; private void requestBluetoothPermissions() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { List<String> permissionsToRequest = new ArrayList<>(); for (String permission : bluetoothPermissions) { if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { permissionsToRequest.add(permission); } } if (!permissionsToRequest.isEmpty()) { ActivityCompat.requestPermissions(this, permissionsToRequest.toArray(new String[0]), BLUETOOTH_PERMISSIONS_REQUEST_CODE); } } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == BLUETOOTH_PERMISSIONS_REQUEST_CODE) { if (grantResults.length > 0) { for (int grantResult : grantResults) { if (grantResult != PackageManager.PERMISSION_GRANTED) { // Handle the case where permissions are denied Toast.makeText(this, "Bluetooth permissions are required for this app to function", Toast.LENGTH_SHORT).show(); return; } } // Permissions granted, proceed with Bluetooth operations startBluetoothOperations(); } } } private void startBluetoothOperations() { // Your Bluetooth code here }
This code snippet checks if the necessary permissions are granted. If not, it requests them from the user. The
onRequestPermissionsResult()
method handles the result of the permission request, allowing you to proceed with Bluetooth operations if the permissions are granted.
Setting Up Bluetooth Adapter and Discoverability
Before initiating any Bluetooth communication, you need to obtain the BluetoothAdapter
and ensure that Bluetooth is enabled on the device. The BluetoothAdapter
is the central point for all Bluetooth interactions.
-
Get the BluetoothAdapter:
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (bluetoothAdapter == null) { // Device doesn't support Bluetooth Toast.makeText(this, "Bluetooth is not supported on this device", Toast.LENGTH_SHORT).show(); finish(); return; }
-
Enable Bluetooth:
If Bluetooth is not enabled, you can prompt the user to enable it:
if (!bluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); }
The
startActivityForResult()
method launches an activity that prompts the user to enable Bluetooth. You need to handle the result in theonActivityResult()
method:private static final int REQUEST_ENABLE_BT = 1; @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_ENABLE_BT) { if (resultCode == Activity.RESULT_OK) { // Bluetooth is now enabled Toast.makeText(this, "Bluetooth enabled", Toast.LENGTH_SHORT).show(); } else { // User declined to enable Bluetooth Toast.makeText(this, "Bluetooth is required for this app to function", Toast.LENGTH_SHORT).show(); finish(); } } }
-
Make Device Discoverable:
To allow other Bluetooth devices to discover your device, you need to make it discoverable. This is typically done on the server device:
private void ensureDiscoverable() { if (bluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300); // 300 seconds startActivity(discoverableIntent); } }
This code snippet checks if the device is already discoverable. If not, it prompts the user to make it discoverable for a specified duration (in this case, 300 seconds).
Establishing a Bluetooth Connection
After setting up Bluetooth and handling permissions, the next step is to establish a connection between two devices. This involves discovering devices, pairing (if necessary), and creating a Bluetooth socket.
Discovering Bluetooth Devices
To discover nearby Bluetooth devices, you can use the BluetoothAdapter.startDiscovery()
method. This method initiates a device discovery scan, and the results are broadcast as BluetoothDevice.ACTION_FOUND
intents.
-
Register a BroadcastReceiver:
Create a
BroadcastReceiver
to listen for device discovery intents:private final BroadcastReceiver receiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (BluetoothDevice.ACTION_FOUND.equals(action)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (device != null) { // Handle the discovered device String deviceName = device.getName(); String deviceHardwareAddress = device.getAddress(); // MAC address Log.d("Bluetooth", "Device found: " + deviceName + " - " + deviceHardwareAddress); // Add the device to a list or display it in a UI } } } };
-
Register and Unregister the Receiver:
Register the
BroadcastReceiver
in your Activity'sonResume()
method and unregister it inonPause()
:@Override protected void onResume() { super.onResume(); IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); registerReceiver(receiver, filter); } @Override protected void onPause() { super.onPause(); unregisterReceiver(receiver); if (bluetoothAdapter.isDiscovering()) { bluetoothAdapter.cancelDiscovery(); } }
-
Start Device Discovery:
Start the device discovery process using
bluetoothAdapter.startDiscovery()
:private void startDiscovery() { if (bluetoothAdapter.isDiscovering()) { bluetoothAdapter.cancelDiscovery(); } bluetoothAdapter.startDiscovery(); }
Remember to cancel discovery using
bluetoothAdapter.cancelDiscovery()
when you've found the device you want to connect to, as it consumes significant resources.
Pairing Bluetooth Devices
Pairing is the process of creating a trusted connection between two Bluetooth devices. While not always necessary for modern Bluetooth communication, it can be required for certain devices or profiles.
-
Initiate Pairing:
You can initiate pairing programmatically using the
BluetoothDevice.createBond()
method:private void pairDevice(BluetoothDevice device) { try { boolean success = device.createBond(); if (success) { Log.d("Bluetooth", "Pairing initiated for device: " + device.getName()); } else { Log.e("Bluetooth", "Failed to initiate pairing for device: " + device.getName()); } } catch (Exception e) { Log.e("Bluetooth", "Error initiating pairing: " + e.getMessage()); } }
-
Listen for Bonding State Changes:
Register a
BroadcastReceiver
to listen for bonding state changes:private final BroadcastReceiver bondStateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) { final int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR); final int prevState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, BluetoothDevice.ERROR); if (state == BluetoothDevice.BOND_BONDED && prevState == BluetoothDevice.BOND_BONDING) { // Device is now paired Toast.makeText(context, "Device paired successfully", Toast.LENGTH_SHORT).show(); } else if (state == BluetoothDevice.BOND_NONE && prevState == BluetoothDevice.BOND_BONDED) { // Device is unpaired Toast.makeText(context, "Device unpaired", Toast.LENGTH_SHORT).show(); } } } };
Register and unregister this receiver in
onResume()
andonPause()
similar to the device discovery receiver.
Creating a Bluetooth Socket
To establish a connection, you need to create a BluetoothSocket
. There are two types of sockets:
- Secure RFCOMM socket: Uses Service Discovery Protocol (SDP) to encrypt the connection.
- Insecure RFCOMM socket: Does not encrypt the connection.
For most applications, using a secure socket is recommended.
-
Create a UUID:
You need a Universally Unique Identifier (UUID) to identify your application's Bluetooth service:
private static final UUID MY_UUID = UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66");
Generate a unique UUID for your application.
-
Create a BluetoothServerSocket (Server):
On the server device, create a
BluetoothServerSocket
to listen for incoming connections:private class AcceptThread extends Thread { private final BluetoothServerSocket mmServerSocket; public AcceptThread() { BluetoothServerSocket tmp = null; try { tmp = bluetoothAdapter.listenUsingRfcommWithServiceRecord(APP_NAME, MY_UUID); } catch (IOException e) { Log.e("Bluetooth", "Socket listen() failed", e); } mmServerSocket = tmp; } public void run() { BluetoothSocket socket = null; while (true) { try { socket = mmServerSocket.accept(); } catch (IOException e) { Log.e("Bluetooth", "Socket accept() failed", e); break; } if (socket != null) { // Handle the connection manageConnectedSocket(socket); try { mmServerSocket.close(); } catch (IOException e) { Log.e("Bluetooth", "Could not close server socket", e); } break; } } } }
This
AcceptThread
listens for incoming connection requests and accepts them. -
Create a BluetoothSocket (Client):
On the client device, create a
BluetoothSocket
to connect to the server:private class ConnectThread extends Thread { private final BluetoothSocket mmSocket; private final BluetoothDevice mmDevice; public ConnectThread(BluetoothDevice device) { BluetoothSocket tmp = null; mmDevice = device; try { tmp = device.createRfcommSocketToServiceRecord(MY_UUID); } catch (IOException e) { Log.e("Bluetooth", "Socket create() failed", e); } mmSocket = tmp; } public void run() { bluetoothAdapter.cancelDiscovery(); try { mmSocket.connect(); } catch (IOException connectException) { try { mmSocket.close(); } catch (IOException closeException) { Log.e("Bluetooth", "Could not close the client socket", closeException); } return; } // Handle the connection manageConnectedSocket(mmSocket); } }
This
ConnectThread
attempts to connect to the server device.
Managing the Bluetooth Connection
Once a Bluetooth socket is established, you can manage the connection by reading and writing data. This is typically done in a separate thread to avoid blocking the main thread.
-
Create a ConnectedThread:
private class ConnectedThread extends Thread { private final BluetoothSocket mmSocket; private final InputStream mmInStream; private final OutputStream mmOutStream; public ConnectedThread(BluetoothSocket socket) { mmSocket = socket; InputStream tmpIn = null; OutputStream tmpOut = null; try { tmpIn = socket.getInputStream(); tmpOut = socket.getOutputStream(); } catch (IOException e) { Log.e("Bluetooth", "Error getting streams", e); } mmInStream = tmpIn; mmOutStream = tmpOut; } public void run() { byte[] buffer = new byte[1024]; int bytes; while (true) { try { bytes = mmInStream.read(buffer); String receivedMessage = new String(buffer, 0, bytes); // Handle the received message Log.d("Bluetooth", "Received: " + receivedMessage); // Update UI or process the message } catch (IOException e) { Log.e("Bluetooth", "Disconnected", e); // Handle disconnection break; } } } public void write(String message) { byte[] bytes = message.getBytes(); try { mmOutStream.write(bytes); } catch (IOException e) { Log.e("Bluetooth", "Error writing data", e); } } public void cancel() { try { mmSocket.close(); } catch (IOException e) { Log.e("Bluetooth", "Could not close socket", e); } } }
This
ConnectedThread
handles reading and writing data to the Bluetooth socket. -
Manage the Connected Socket:
private ConnectedThread connectedThread; private void manageConnectedSocket(BluetoothSocket socket) { connectedThread = new ConnectedThread(socket); connectedThread.start(); }
This method starts the
ConnectedThread
to handle data transmission.
Sending and Receiving Messages
With the ConnectedThread
running, you can now send and receive messages between the two devices.
-
Send a Message:
To send a message, call the
write()
method of theConnectedThread
:private void sendMessage(String message) { if (connectedThread != null) { connectedThread.write(message); } }
-
Receive a Message:
The
run()
method of theConnectedThread
continuously reads data from the input stream. When a message is received, it can be processed and displayed or used as needed.
Conclusion
Sending text messages between two Android phones via Bluetooth Classic on API 31+ requires careful handling of the new Bluetooth permissions and a solid understanding of the Bluetooth communication process. This guide has provided a comprehensive overview of the steps involved, from requesting permissions to establishing connections and transmitting data. By following the examples and explanations provided, you can implement robust Bluetooth communication in your Android applications, ensuring seamless functionality and user privacy.
Key takeaways:
- Always request necessary Bluetooth permissions at runtime.
- Handle permission denial gracefully.
- Use separate threads for Bluetooth operations to avoid blocking the main thread.
- Ensure proper error handling and resource management.
- Consider user privacy and security when implementing Bluetooth communication.
By adhering to these best practices, you can create reliable and user-friendly Bluetooth-enabled Android applications. Remember to thoroughly test your application on different devices and Android versions to ensure compatibility and stability.