Skip to main content

Working with Bluetooth

1. Requesting Bluetooth Permissions

Permission Declaration Description
BLUETOOTH <uses-permission android:name="android.permission.BLUETOOTH" /> Required to access Bluetooth features and discover devices
BLUETOOTH_ADMIN <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> Required to discover and pair Bluetooth devices
ACCESS_FINE_LOCATION <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> Required for Bluetooth scanning on Android 6.0+ (API level 23+)
ACCESS_COARSE_LOCATION <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> Alternative to fine location for Bluetooth scanning (less precise)

Example: Requesting Permissions at Runtime

class MainActivity : AppCompatActivity() {
    private val PERMISSION_REQUEST_CODE = 100

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

        if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED ||
            checkSelfPermission(Manifest.permission.BLUETOOTH_ADMIN) != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(
                arrayOf(
                    Manifest.permission.ACCESS_FINE_LOCATION,
                    Manifest.permission.BLUETOOTH_ADMIN
                ),
                PERMISSION_REQUEST_CODE
            )
        }
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == PERMISSION_REQUEST_CODE) {
            if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // Permissions granted, proceed with Bluetooth operations
            } else {
                // Handle permission denial
            }
        }
    }
}
Note: On Android 12 (API level 31) and higher, the ACCESS_FINE_LOCATION permission is required for Bluetooth scanning. For older versions, ACCESS_COARSE_LOCATION may be sufficient.

2. Checking Bluetooth Availability

Property Description Notes
BluetoothAdapter Represents the local Bluetooth adapter Used to check Bluetooth capabilities and state
isEnabled() Checks if Bluetooth is currently enabled Returns true if Bluetooth is enabled, false otherwise
isBluetoothAvailable() Checks if Bluetooth hardware is available Returns true if Bluetooth is supported on the device, false otherwise

Example: Check Bluetooth Availability

val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
val bluetoothAdapter = bluetoothManager.adapter

if (bluetoothAdapter == null) {
    // Device does not support Bluetooth
    println("Bluetooth is not supported on this device")
} else {
    if (bluetoothAdapter.isEnabled) {
        println("Bluetooth is enabled")
    } else {
        println("Bluetooth is disabled")
    }
}
Note: You must declare the BLUETOOTH permission in your AndroidManifest.xml to access Bluetooth functionality.

3. Enabling Bluetooth

Permission Description Required in Manifest
ACCESS_FINE_LOCATION Allows access to precise location data, required for Bluetooth scanning and connection. NEW Android 6.0+
ACCESS_COARSE_LOCATION Allows access to approximate location data, sufficient for basic Bluetooth operations. Android 6.0+
BLUETOOTH_ADMIN Allows an app to access Bluetooth settings and enable/disable Bluetooth. Yes

Example: Requesting Permissions

val permissions = arrayOf(
    Manifest.permission.ACCESS_FINE_LOCATION,
    Manifest.permission.BLUETOOTH_ADMIN
)

if (ContextCompat.checkSelfPermission(this, permissions[0]) != PackageManager.PERMISSION_GRANTED) {
    ActivityCompat.requestPermissions(this, permissions, 1)
}

Example: Enabling Bluetooth Programmatically

val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
if (bluetoothAdapter == null) {
    // Device does not support Bluetooth
} else if (!bluetoothAdapter.isEnabled) {
    val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
    startActivityForResult(enableBtIntent, 1)
}
Note: Starting from Android 12 (API level 31), users must explicitly grant location permissions for Bluetooth scanning, even if the app only needs to connect to known devices.

4. Discovering Devices

Example: Start Bluetooth Discovery

val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
bluetoothAdapter?.startDiscovery()
Method Description Return Type Notes
startDiscovery() Starts an asynchronous broadcast scan for nearby Bluetooth devices Boolean Returns true if the discovery started successfully
cancelDiscovery() Cancels an ongoing discovery process Boolean Returns true if the discovery was canceled successfully
Note: Discovery process can take several seconds to complete. Devices must be in discoverable mode to be detected.

Example: Handle Discovery Results

val broadcastReceiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        val action = intent?.action
        if (BluetoothDevice.ACTION_FOUND == action) {
            val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
            // Process discovered device
        }
    }
}
context.registerReceiver(broadcastReceiver, IntentFilter(BluetoothDevice.ACTION_FOUND))

5. Pairing with Devices

Property Description Notes
BluetoothDevice.ACTION_PAIRING_REQUEST Intent action for pairing request Used to listen for pairing requests from devices
BluetoothDevice.BOND_NONE Device not bonded Initial state before pairing
BluetoothDevice.BOND_BONDED Device bonded successfully Pairing completed and device trusted
BluetoothDevice.BOND_BONDING Pairing in progress Device is currently pairing

Example: Requesting Pairing

val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
val device = bluetoothAdapter.getRemoteDevice("00:12:34:56:78:90")

if (device.bondState == BluetoothDevice.BOND_NONE) {
    device.createBond()
}

Example: Handling Pairing Request

val receiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
        when (intent.action) {
            BluetoothDevice.ACTION_PAIRING_REQUEST -> {
                // Handle pairing request
                device?.createBond()
            }
        }
    }
}

val filter = IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST)
context.registerReceiver(receiver, filter)
Note: The createBond() method returns immediately and pairing happens asynchronously. You must listen for the BOND_STATE_CHANGED broadcast to know when pairing completes.

Pairing Process Summary

  • Check device bond state using bondState
  • If not bonded, call createBond()
  • Listen for BOND_STATE_CHANGED to confirm completion
  • Use cancelBondProcess() to abort pairing if needed

6. Connecting via BluetoothSocket

Property Description Values
BluetoothSocket Represents a Bluetooth connection endpoint android.bluetooth.BluetoothSocket
createRfcommSocketToServiceRecord Creates a socket for RFCOMM-based Bluetooth communication BluetoothAdapter.createRfcommSocketToServiceRecord(UUID)
connect() Attempts to establish a Bluetooth connection BluetoothSocket.connect()
close() Closes the Bluetooth connection BluetoothSocket.close()

Example: Connecting to a Bluetooth device using BluetoothSocket

val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
val device = bluetoothAdapter.getRemoteDevice("00:11:22:33:44:55")
val uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")

val socket = bluetoothAdapter.createRfcommSocketToServiceRecord(uuid)
socket.connect()

// Use input/output streams to communicate
val inputStream = socket.inputStream
val outputStream = socket.outputStream

// Close connection when done
socket.close()
Note: Ensure that the app has BLUETOOTH and BLUETOOTH_ADMIN permissions in the manifest.
Warning: connect() is a blocking operation and should be called on a background thread to avoid freezing the UI.

7. Using Bluetooth Low Energy

Property Description Values
BluetoothAdapter Entry point for Bluetooth operations BluetoothAdapter.getDefaultAdapter()
BluetoothLeScanner Used to scan for BLE devices adapter.getBluetoothLeScanner()
ScanCallback Handles scan results and errors object : ScanCallback() { ... }
ScanFilter Defines criteria for device filtering ScanFilter.Builder().setServiceUuid(...).build()
ScanSettings Configures scan behavior (mode, report delay) ScanSettings.Builder().setScanMode(...).build()

Example: Start BLE Scan

val scanner = adapter.bluetoothLeScanner
val settings = ScanSettings.Builder()
    .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
    .build()

val filters = listOf(
    ScanFilter.Builder()
        .setServiceUuid(ParcelUuid(UUID.fromString("0000180F-0000-1000-8000-00805F9B34FB")))
        .build()
)

scanner.startScan(filters, settings, callback)

Example: Handle Scan Results

override fun onScanResult(callbackType: Int, result: ScanResult) {
    val device = result.device
    val rssi = result.rssi
    val advertisement = result.scanRecord?.advertisementData

    Log.d("BLE", "Found device: ${device.name} with RSSI: $rssi")
}

Example: Connect to BLE Device

val gatt = device.connectGatt(context, false, gattCallback)
Note: Ensure BLUETOOTH and BLUETOOTH_ADMIN permissions are declared in AndroidManifest.xml.
BLE GATT Operation Description Method
Discover Services Find available services on the device gatt.discoverServices()
Read Characteristic Retrieve value of a characteristic gatt.readCharacteristic(characteristic)
Write Characteristic Send data to a characteristic gatt.writeCharacteristic(characteristic)
Enable Notifications Subscribe to characteristic changes gatt.setCharacteristicNotification(characteristic, true)

Example: Read Characteristic Value

override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
    if (status == BluetoothGatt.GATT_SUCCESS) {
        val value = characteristic.value
        Log.d("BLE", "Read value: ${value.contentToString()}")
    }
}
Warning: BLE operations must be performed on a background thread to avoid blocking the UI.

BLE Connection Lifecycle

  • Scan for devices using BluetoothLeScanner
  • Connect to device via connectGatt()
  • Discover services and characteristics
  • Read/write data using GATT operations
  • Disconnect with gatt.close()

8. Scanning for BLE Devices

Example: Start BLE Scan

val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
val bluetoothLeScanner = bluetoothAdapter.bluetoothLeScanner

val scanCallback = object : ScanCallback() {
    override fun onScanResult(callbackType: Int, result: ScanResult) {
        val device = result.device
        val rssi = result.rssi
        val scanRecord = result.scanRecord
        Log.d("BLE", "Found device: ${device.name} with RSSI: $rssi")
    }
}

val scanFilter = ScanFilter.Builder()
    .setServiceUuid(ParcelUuid(UUID.fromString("0000180F-0000-1000-8000-00805F9B34FB")))
    .build()

val scanSettings = ScanSettings.Builder()
    .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
    .build()

bluetoothLeScanner.startScan(listOf(scanFilter), scanSettings, scanCallback)
Property Description Values
ScanSettings.SCAN_MODE_LOW_LATENCY Scan mode for low latency 1
ScanSettings.SCAN_MODE_BALANCED Scan mode for balanced power and latency 2
ScanSettings.SCAN_MODE_LOW_POWER Scan mode for low power consumption 0
Note: Ensure that the app has necessary permissions like ACCESS_FINE_LOCATION for scanning BLE devices on Android 6.0+.
Warning: Scanning for BLE devices can drain battery quickly. Always stop scanning when not needed.

9. Reading and Writing BLE Characteristics

Property Description Example
characteristic.uuid Unique identifier for the BLE characteristic 00002a19-0000-1000-8000-00805f9b34fb
characteristic.value Current value of the characteristic byteArrayOf(0x01, 0x02)
characteristic.permissions Read/write permissions for the characteristic BluetoothGattCharacteristic.PERMISSION_READ

Example: Reading a BLE Characteristic

val characteristic = gattService.getCharacteristic(UUID.fromString("00002a19-0000-1000-8000-00805f9b34fb"))
if (characteristic != null) {
    bluetoothGatt.readCharacteristic(characteristic)
}

Example: Writing to a BLE Characteristic

val characteristic = gattService.getCharacteristic(UUID.fromString("00002a19-0000-1000-8000-00805f9b34fb"))
characteristic.value = byteArrayOf(0x01, 0x02)
bluetoothGatt.writeCharacteristic(characteristic)
Note: Ensure the characteristic supports write operations before attempting to write.
Warning: Writing to a BLE characteristic may require the device to be connected and the characteristic to have appropriate permissions.

10. Managing Bluetooth Connections

Property Description Values
BluetoothAdapter Entry point for Bluetooth operations BluetoothAdapter.getDefaultAdapter()
BluetoothDevice Represents a remote Bluetooth device device.address, device.name
BluetoothSocket Represents a Bluetooth connection socket socket.connect(), socket.close()

Example: Discovering and Connecting to a Bluetooth Device

val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
val device = bluetoothAdapter.getRemoteDevice("00:11:22:33:44:55")
val socket = device.createRfcommSocketToServiceRecord(UUID.randomUUID())
socket.connect()

Example: Handling Connection State Changes

val receiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val action = intent.action
        when (action) {
            BluetoothDevice.ACTION_ACL_CONNECTED -> {
                // Handle connected state
            }
            BluetoothDevice.ACTION_ACL_DISCONNECTED -> {
                // Handle disconnected state
            }
        }
    }
}
registerReceiver(receiver, IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED))
Note: Ensure that the app has the necessary permissions: ACCESS_FINE_LOCATION and BLUETOOTH_CONNECT (Android 12+).
Warning: Bluetooth operations must be performed on a background thread to avoid blocking the UI.

Was this article helpful?