Send Bluetooth Messages Between Android Phones API 31+ Java

by stackunigon 60 views
Iklan Headers

In this comprehensive guide, we'll explore how to send text messages via Bluetooth Classic between two Android phones using Java, specifically targeting API level 31 and above (Android 12+). This task involves navigating the intricacies of Bluetooth communication, handling permissions, discovering devices, establishing connections, and transmitting data. With the changes introduced in Android 12 and later regarding Bluetooth permissions, it's crucial to understand the updated requirements and implement the necessary steps to ensure your application functions correctly. Whether you're building a messaging app, a file-sharing tool, or any application that requires peer-to-peer communication, mastering Bluetooth communication is a valuable skill for Android developers.

Bluetooth, a wireless technology standard for exchanging data over short distances, enables seamless communication between devices. For Android developers, Bluetooth Classic serves as a reliable protocol for establishing connections and transferring data between devices. To effectively implement Bluetooth communication in your Android applications, a solid understanding of the underlying concepts and APIs is essential. This involves grasping the fundamentals of Bluetooth profiles, UUIDs, and the process of device discovery and connection management. By delving into these aspects, you'll gain the knowledge necessary to build robust and feature-rich Bluetooth-enabled applications.

Prerequisites

Before diving into the implementation, ensure you have the following prerequisites in place:

  • Android Studio installed on your development machine.
  • Two Android phones running API level 31+ (Android 12+).
  • Basic knowledge of Android development with Java.
  • Familiarity with Bluetooth concepts and terminology.

Setting Up a New Android Project

To kickstart our project, we'll begin by creating a new Android project in Android Studio. Follow these steps:

  1. Open Android Studio and select "Create New Project."
  2. Choose the "Empty Activity" template and proceed to the next step.
  3. Give your project a descriptive name, such as "BluetoothMessagingApp," and select Java as the programming language.
  4. Set the minimum SDK to API 31 (Android 12) or higher to ensure compatibility with the targeted API level.
  5. Click "Finish" to generate the project structure.

Declaring Permissions in the Manifest

Android 12 (API level 31) introduced significant changes to Bluetooth permissions. To ensure your application can access Bluetooth functionalities, you need to declare the necessary permissions in the AndroidManifest.xml file. These permissions include BLUETOOTH_CONNECT, BLUETOOTH_SCAN, and ACCESS_FINE_LOCATION. The BLUETOOTH_CONNECT permission allows your app to connect to paired Bluetooth devices, while BLUETOOTH_SCAN enables your app to discover nearby Bluetooth devices. ACCESS_FINE_LOCATION is required for Bluetooth scanning due to privacy concerns.

Open your AndroidManifest.xml file and add the following permissions within the <manifest> tag:

<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" />

Additionally, if your app also needs to make the device discoverable to other Bluetooth devices, you'll need to declare the BLUETOOTH_ADVERTISE permission:

<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

It's important to note the android:usesPermissionFlags="neverForLocation" attribute in the BLUETOOTH_SCAN permission. This flag indicates that your app doesn't derive physical location from Bluetooth scans, which can help with privacy considerations.

Requesting Permissions at Runtime

Declaring permissions in the manifest is not enough; you also need to request these permissions at runtime. This ensures that the user is aware of the permissions your app is requesting and can grant or deny them. To request permissions, you can use the ActivityCompat.requestPermissions() method.

Create a method called requestBluetoothPermissions() in your MainActivity to handle permission requests:

private void requestBluetoothPermissions() {
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED ||
            ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED ||
            ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {

        ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.BLUETOOTH_CONNECT,
                        Manifest.permission.BLUETOOTH_SCAN,
                        Manifest.permission.ACCESS_FINE_LOCATION},
                BLUETOOTH_PERMISSION_REQUEST_CODE);
    }
}

Define BLUETOOTH_PERMISSION_REQUEST_CODE as a constant in your MainActivity:

private static final int BLUETOOTH_PERMISSION_REQUEST_CODE = 1;

Call this method in your onCreate() method:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    requestBluetoothPermissions();
}

Now, override the onRequestPermissionsResult() method to handle the permission request result:

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == BLUETOOTH_PERMISSION_REQUEST_CODE) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED &&
                grantResults[1] == PackageManager.PERMISSION_GRANTED &&
                grantResults[2] == PackageManager.PERMISSION_GRANTED) {
            // Permissions granted, proceed with Bluetooth operations
            // You can add your Bluetooth initialization logic here
            Toast.makeText(this, "Bluetooth permissions granted", Toast.LENGTH_SHORT).show();
        } else {
            // Permissions denied, handle accordingly
            Toast.makeText(this, "Bluetooth permissions denied", Toast.LENGTH_SHORT).show();
            // You might want to disable Bluetooth-related features or show a message to the user
        }
    }
}

This code snippet checks if the required permissions have been granted. If they are, you can proceed with Bluetooth operations. If not, you should handle the denial gracefully, possibly by disabling Bluetooth-related features or informing the user.

Getting the Bluetooth Adapter

The BluetoothAdapter is the central point of interaction for all Bluetooth activity. You need to obtain an instance of the BluetoothAdapter to perform Bluetooth operations. Use the following code to get the Bluetooth adapter:

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(); // Close the activity if Bluetooth is not supported
    return;
}

It's essential to check if the device supports Bluetooth by verifying if the bluetoothAdapter is null. If it's null, Bluetooth is not supported, and you should handle this case appropriately, such as displaying a message to the user and potentially closing the activity.

Enabling Bluetooth

If Bluetooth is supported but currently disabled, you need to prompt the user to enable it. You can do this using an Intent that requests the user to turn on Bluetooth.

if (!bluetoothAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, ENABLE_BLUETOOTH_REQUEST_CODE);
}

Define ENABLE_BLUETOOTH_REQUEST_CODE as a constant:

private static final int ENABLE_BLUETOOTH_REQUEST_CODE = 2;

Override the onActivityResult() method to handle the result of the enable Bluetooth intent:

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == ENABLE_BLUETOOTH_REQUEST_CODE) {
        if (resultCode == Activity.RESULT_OK) {
            // Bluetooth is now enabled, proceed with further operations
            Toast.makeText(this, "Bluetooth enabled", Toast.LENGTH_SHORT).show();
        } else {
            // User declined to enable Bluetooth
            Toast.makeText(this, "Bluetooth not enabled", Toast.LENGTH_SHORT).show();
            // Handle the case where the user declined to enable Bluetooth
        }
    }
}

This code checks the result of the ACTION_REQUEST_ENABLE intent. If the result is Activity.RESULT_OK, Bluetooth has been enabled, and you can proceed with further Bluetooth operations. If the user declined to enable Bluetooth, you should handle this case accordingly, possibly by disabling Bluetooth-related features or informing the user.

Discovering Bluetooth Devices

To send messages, you need to discover nearby Bluetooth devices. This involves initiating a device discovery process and listening for device discovery events.

Starting Device Discovery

You can start device discovery using the startDiscovery() method of the BluetoothAdapter:

if (bluetoothAdapter.isDiscovering()) {
    bluetoothAdapter.cancelDiscovery(); // Cancel previous discovery if ongoing
}
bluetoothAdapter.startDiscovery();

Before starting a new discovery, it's good practice to check if a discovery is already in progress using isDiscovering() and cancel it using cancelDiscovery(). This prevents interference from previous discovery processes.

Listening for Device Discovery Events

To listen for device discovery events, you need to register a BroadcastReceiver for the BluetoothDevice.ACTION_FOUND intent. This intent is broadcast whenever a new Bluetooth device is discovered.

Create a BroadcastReceiver class:

private final BroadcastReceiver discoveryReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (BluetoothDevice.ACTION_FOUND.equals(action)) {
            // Discovery has found a device. Get the BluetoothDevice
            // object and its info from the Intent.
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            String deviceName = device.getName();
            String deviceHardwareAddress = device.getAddress(); // MAC address
            // Do something with the discovered device
            Log.d("Bluetooth", "Device found: " + deviceName + " - " + deviceHardwareAddress);
        }
    }
};

Register the BroadcastReceiver in your onCreate() method:

IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(discoveryReceiver, filter);

Don't forget to unregister the BroadcastReceiver in your onDestroy() method to prevent memory leaks:

@Override
protected void onDestroy() {
    super.onDestroy();
    unregisterReceiver(discoveryReceiver);
}

Handling Device Discovery Completion

It's also useful to listen for the BluetoothAdapter.ACTION_DISCOVERY_FINISHED intent, which is broadcast when the device discovery process is complete. This allows you to perform actions such as updating the UI or initiating a connection to a discovered device.

Add a new intent filter to your existing IntentFilter:

filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);

Update your BroadcastReceiver to handle the ACTION_DISCOVERY_FINISHED intent:

private final BroadcastReceiver discoveryReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (BluetoothDevice.ACTION_FOUND.equals(action)) {
            // Discovery has found a device. Get the BluetoothDevice
            // object and its info from the Intent.
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            String deviceName = device.getName();
            String deviceHardwareAddress = device.getAddress(); // MAC address
            // Do something with the discovered device
            Log.d("Bluetooth", "Device found: " + deviceName + " - " + deviceHardwareAddress);
        } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
            // Discovery is finished, you can perform additional actions
            Toast.makeText(context, "Device discovery finished", Toast.LENGTH_SHORT).show();
        }
    }
};

Connecting to a Discovered Device

Once you've discovered a Bluetooth device, you can establish a connection to it. This involves creating a BluetoothSocket and initiating a connection attempt.

Creating a BluetoothSocket

To create a BluetoothSocket, you need the BluetoothDevice object representing the device you want to connect to and a UUID (Universally Unique Identifier). The UUID identifies the service your application is providing.

private static final UUID MY_UUID = UUID.fromString("your-uuid-string"); // Replace with your UUID

private class ConnectThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final BluetoothDevice mmDevice;

    public ConnectThread(BluetoothDevice device) {
        // Use a temporary object that is later assigned to mmSocket
        // because mmSocket is final.
        BluetoothSocket tmp = null;
        mmDevice = device;
        try {
            // Get a BluetoothSocket to connect with the given BluetoothDevice.
            // MY_UUID is the app's UUID string, also used by the server code.
            tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
        } catch (IOException e) {
            Log.e("Bluetooth", "Socket's create() method failed", e);
        }
        mmSocket = tmp;
    }

Replace `