Search code examples
androidpermissionsbluetoothesp32

Android LED Control app does not launch when bluetooth is enabled


I'm developing an app to remotely control an LED via bluetooth using Android Studio, for Android version 13.

If bluetooth is enabled on the phone before the application is launched, the app crashes right away. If you have any suggestions for why I could be getting this error, please let me know!

In the AndroidManifest.xml file, I've declared permissions for BLUETOOTH, BLUETOOTH_ADMIN, and BLUETOOTH_CONNECT as follows:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.LEDControl"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:theme="@style/Theme.LEDControl">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

</manifest>

The UI has a simple text view as well as an "ON" and "OFF" button defined in activity_main.xml as shown below:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center"
    android:padding="16dp">

    <!-- Text view for "LED Controller" -->
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="LED Controller"
        android:textSize="24sp" />

    <!-- "ON" Button -->
    <Button
        android:id="@+id/onButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="ON" />

    <!-- "OFF" Button -->
    <Button
        android:id="@+id/offButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="OFF" />
</LinearLayout>

Then, in MainActivity.kt, I'm rendering the layout described above, and I have attempted to request the bluetooth permissions, as shown below:

package com.example.ledcontrol

import android.util.Log
import android.Manifest
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothSocket
import android.content.pm.PackageManager
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.core.app.ActivityCompat
import com.example.ledcontrol.ui.theme.LEDControlTheme
import java.io.OutputStream
import java.util.*

class MainActivity : ComponentActivity() {
    private val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()
    private var bluetoothSocket: BluetoothSocket? = null
    private var outputStream: OutputStream? = null

    private val requestBluetoothPermission = 1

    private val esp32DeviceAddress = "08:d1:f9:ce:b1:9e"  // ESP32's Bluetooth device address

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        if (bluetoothAdapter != null && bluetoothAdapter.isEnabled) {
            // Check if Bluetooth permissions are granted
            if (ActivityCompat.checkSelfPermission(
                    this,
                    Manifest.permission.BLUETOOTH
                ) == PackageManager.PERMISSION_GRANTED
                && ActivityCompat.checkSelfPermission(
                    this,
                    Manifest.permission.BLUETOOTH_ADMIN
                ) == PackageManager.PERMISSION_GRANTED
            ) {
                // Permissions are granted, proceed with Bluetooth operations
                val device = bluetoothAdapter.getRemoteDevice(esp32DeviceAddress)
                setupBluetoothConnection(device)
            } else {
                // Request Bluetooth permissions
                ActivityCompat.requestPermissions(
                    this,
                    arrayOf(
                        Manifest.permission.BLUETOOTH,
                        Manifest.permission.BLUETOOTH_ADMIN
                    ),
                    requestBluetoothPermission
                )
            }
        }

        val onButton = findViewById<Button>(R.id.onButton)
        val offButton = findViewById<Button>(R.id.offButton)

        onButton.setOnClickListener {
            sendData("1")
            Log.d("ButtonPress", "'On' button pressed")
        }

        offButton.setOnClickListener {
            sendData("0")
            Log.d("ButtonPress", "'Off' button pressed")
        }
    }

    private fun setupBluetoothConnection(device: BluetoothDevice) {
        // Initialize Bluetooth connection
        try {
            if (ActivityCompat.checkSelfPermission(
                    this,
                    Manifest.permission.BLUETOOTH_CONNECT
                ) != PackageManager.PERMISSION_GRANTED
            ) {
                return
            }
            bluetoothSocket = device.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"))
            bluetoothSocket?.connect()
            outputStream = bluetoothSocket?.outputStream
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    // Function to send data over Bluetooth
    private fun sendData(data: String) {
        Log.d("Info", "sendData called with data: $data")
        try {
            outputStream?.write(data.toByteArray())
            // Log a message to indicate that data was sent
            Log.d("Bluetooth", "Sent data: $data")
        } catch (e: Exception) {
            e.printStackTrace()
            // Log an error message if there's an issue with sending data
            Log.e("Bluetooth", "Error sending data: ${e.message}")
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        // Close the Bluetooth socket and output stream when the activity is destroyed
        try {
            outputStream?.close()
            bluetoothSocket?.close()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}

When the buttons are pressed in the UI, the sendData method is called, and the logs are printed as expected, but it seems like there is some issue with the bluetooth implementation.


Solution

  • While your application targets Android 13, your choice of permissions targets Android 11 or lower. The Google documentation is as follows:

    If the app targets Android 12 or higher, than you need to declare the following permissions in your app's manifest file:

    If your application scans for BLE peripherals, you need to declare the BLUETOOTH_SCAN permission. If your application connects to Bluetooth devices, you must also declare the BLUETOOTH_CONNECT permission. Because the BLUETOOTH_ADVERTISE, BLUETOOTH_CONNECT, and BLUETOOTH_SCAN permissions are runtime permissions, you must explicitly request user approval in your application before you can search for Bluetooth devices, make a device discoverable to other devices, or communicate with already paired Bluetooth devices.

    Apps targeting Android 11 or lower, needs to declare the following permissions:

    The BLUETOOTH permission is required to perform any Bluetooth classic or BLE communication (requesting and accepting a connection, transferring data). The ACCESS_FINE_LOCATION permission is necessary because on Android 11 and lower, a Bluetooth scan could potentially be used to gather information about the user's location, and if you want your app to initiate device discovery or manipulate Bluetooth settings, you need to declare the BLUETOOTH_ADMIN permission.