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_CHANGEDto 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")
}
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.