Send Bluetooth Messages Between Android Phones API 31+ Java

by ADMIN 60 views
Iklan Headers

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 the ACCESS_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:

  1. 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 for BLUETOOTH_SCAN indicates that your app doesn't derive physical location from Bluetooth scans. The android:maxSdkVersion attribute for location permissions ensures that these permissions are only requested on Android versions lower than 12.

  2. 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.

  1. 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;
    }
    
  2. 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 the onActivityResult() 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();
            }
        }
    }
    
  3. 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.

  1. 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
                }
            }
        }
    };
    
  2. Register and Unregister the Receiver:

    Register the BroadcastReceiver in your Activity's onResume() method and unregister it in onPause():

    @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();
        }
    }
    
  3. 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.

  1. 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());
        }
    }
    
  2. 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() and onPause() 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.

  1. 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.

  2. 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.

  3. 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.

  1. 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.

  2. 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.

  1. Send a Message:

    To send a message, call the write() method of the ConnectedThread:

    private void sendMessage(String message) {
        if (connectedThread != null) {
            connectedThread.write(message);
        }
    }
    
  2. Receive a Message:

    The run() method of the ConnectedThread 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.