The Android platform includes support for the Bluetooth network stack, which allows a device to wirelessly exchange data with other Bluetooth devices. The application framework provides access to the Bluetooth functionality through the Android Bluetooth APIs. These APIs let applications wirelessly connect to other Bluetooth devices, enabling point-to-point and multipoint wireless features.
Using the Bluetooth APIs, an Android application can perform the following:
- Scan for other Bluetooth devices
- Query the local Bluetooth adapter for paired Bluetooth devices
- Establish RFCOMM channels
- Connect to other devices through service discovery
- Transfer data to and from other devices
- Manage multiple connections
This page focuses on Classic Bluetooth. Classic Bluetooth is the right choice for more battery-intensive operations, which include streaming and communicating between Android devices. For Bluetooth devices with low power requirements, Android 4.3 (API level 18) introduces API support for Bluetooth Low Energy. To learn more, see Bluetooth Low Energy.
This document describes different Bluetooth profiles, including the Health Device Profile. It then explains how to use the Android Bluetooth APIs to accomplish the four major tasks necessary to communicate using Bluetooth: setting up Bluetooth, finding devices that are either paired or available in the local area, connecting devices, and transferring data between devices.
The basics
In order for Bluetooth-enabled devices to transmit data between each other, they must first form a channel of communication using a pairing process. One device, a discoverable device, makes itself available for incoming connection requests. Another device finds the discoverable device using a service discovery process. After the discoverable device accepts the pairing request, the two devices complete a bonding process where they exchange security keys. The devices cache these keys for later use. After the pairing and bonding processes are complete, the two devices exchange information. When the session is complete, the device that initiated the pairing request releases the channel that had linked it to the discoverable device. The two devices remain bonded, however, so they can reconnect automatically during a future session as long as they're in range of each other and neither device has removed the bond.
Bluetooth permissions
In order to use Bluetooth features in your application, you must declare
two permissions. The first of these is BLUETOOTH
.
You need this permission to perform any Bluetooth communication,
such as requesting a connection, accepting a connection, and transferring data.
The other permission that you must declare is
ACCESS_FINE_LOCATION
.
Your app needs this permission because a Bluetooth scan can be used to
gather information about the location of the user. This information
may come from the user's own devices, as well as Bluetooth beacons
in use at locations such as shops and transit facilities.
Services running on Android 10 and higher cannot discover Bluetooth devices unless they have the
ACCESS_BACKGROUND_LOCATION
permission. For more information on this requirement, see
Access location in the background.
The following code snippet shows how to check for the permission.
Kotlin
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (ContextCompat.checkSelfPermission(baseContext, Manifest.permission.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions( this, arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION), PERMISSION_CODE) } }
Java
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (ContextCompat.checkSelfPermission(baseContext, Manifest.permission.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions( MyActivity.this, new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION}, PERMISSION_CODE) } }
An exception to this permission requirement is when your app is installed on a device running Android 11 or higher and has used companion device pairing to associate a device. In this case, once a device is associated, apps can scan for their associated Bluetooth devices without requiring a location permission.
On devices running Android 8.0 (API level 26) and higher, you can use the
CompanionDeviceManager
to perform a scan of nearby companion devices on behalf of your app without requiring the location
permission. For more on this option, see
Companion device pairing.
Note: If your app targets Android 9 (API level 28) or
lower, you can declare the
ACCESS_COARSE_LOCATION
permission instead of the ACCESS_FINE_LOCATION
permission.
If you want your app to initiate device discovery or manipulate Bluetooth
settings, you must declare the BLUETOOTH_ADMIN
permission in addition to the BLUETOOTH
permission. Most applications need this permission solely for the
ability to discover local Bluetooth devices. The other abilities granted by this
permission should not be used, unless the application is a "power manager" that
modifies Bluetooth settings upon user request.
Declare the Bluetooth permission(s) in your application manifest file. For example:
<manifest ... > <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <!-- If your app targets Android 9 or lower, you can declare ACCESS_COARSE_LOCATION instead. --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> ... </manifest>
See the <uses-permission> reference for more information about declaring application permissions.
Work with profiles
Starting in Android 3.0, the Bluetooth API includes support for working with Bluetooth profiles. A Bluetooth profile is a wireless interface specification for Bluetooth-based communication between devices. An example is the Hands-Free profile. For a mobile phone to connect to a wireless headset, both devices must support the Hands-Free profile.
The Android Bluetooth API provides implementations for the following Bluetooth profiles:
- Headset. The Headset profile provides support for
Bluetooth headsets to be used with mobile phones. Android provides the
BluetoothHeadset
class, which is a proxy for controlling the Bluetooth Headset Service. This includes both Bluetooth Headset and Hands-Free (v1.5) profiles. TheBluetoothHeadset
class includes support for AT commands. For more discussion of this topic, see Vendor-specific AT commands - A2DP. The Advanced Audio Distribution Profile (A2DP)
profile defines how high quality audio can be streamed from one device to
another over a Bluetooth connection. Android provides the
BluetoothA2dp
class, which is a proxy for controlling the Bluetooth A2DP Service. - Health Device. Android 4.0 (API level 14) introduces support for the Bluetooth Health Device Profile (HDP). This lets you create applications that use Bluetooth to communicate with health devices that support Bluetooth, such as heart-rate monitors, blood meters, thermometers, scales, and so on. For a list of supported devices and their corresponding device data specialization codes, refer to Bluetooth's HDP Device Data Specializations. These values are also referenced in the ISO/IEEE 11073-20601 [7] specification as MDC_DEV_SPEC_PROFILE_* in the Nomenclature Codes Annex. For more discussion of HDP, see Health Device Profile.
Here are the basic steps for working with a profile:
- Get the default adapter, as described in Setting Up Bluetooth.
- Set up a
BluetoothProfile.ServiceListener
. This listener notifiesBluetoothProfile
clients when they have been connected to or disconnected from the service. - Use
getProfileProxy()
to establish a connection to the profile proxy object associated with the profile. In the example below, the profile proxy object is an instance ofBluetoothHeadset
. - In
onServiceConnected()
, get a handle to the profile proxy object. - Once you have the profile proxy object, you can use it to monitor the state of the connection and perform other operations that are relevant to that profile.
For example, this code snippet shows how to connect to a BluetoothHeadset
proxy object so that you can control the
Headset profile:
Kotlin
var bluetoothHeadset: BluetoothHeadset? = null // Get the default adapter val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter() private val profileListener = object : BluetoothProfile.ServiceListener { override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) { if (profile == BluetoothProfile.HEADSET) { bluetoothHeadset = proxy as BluetoothHeadset } } override fun onServiceDisconnected(profile: Int) { if (profile == BluetoothProfile.HEADSET) { bluetoothHeadset = null } } } // Establish connection to the proxy. bluetoothAdapter?.getProfileProxy(context, profileListener, BluetoothProfile.HEADSET) // ... call functions on bluetoothHeadset // Close proxy connection after use. bluetoothAdapter?.closeProfileProxy(BluetoothProfile.HEADSET, bluetoothHeadset)
Java
BluetoothHeadset bluetoothHeadset; // Get the default adapter BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); private BluetoothProfile.ServiceListener profileListener = new BluetoothProfile.ServiceListener() { public void onServiceConnected(int profile, BluetoothProfile proxy) { if (profile == BluetoothProfile.HEADSET) { bluetoothHeadset = (BluetoothHeadset) proxy; } } public void onServiceDisconnected(int profile) { if (profile == BluetoothProfile.HEADSET) { bluetoothHeadset = null; } } }; // Establish connection to the proxy. bluetoothAdapter.getProfileProxy(context, profileListener, BluetoothProfile.HEADSET); // ... call functions on bluetoothHeadset // Close proxy connection after use. bluetoothAdapter.closeProfileProxy(bluetoothHeadset);
Vendor-specific AT commands
Starting in Android 3.0 (API level 11), applications can register to receive system
broadcasts of predefined vendor-specific AT commands sent by headsets (such as
a Plantronics +XEVENT command). For example, an application could receive
broadcasts that indicate a connected device's battery level and could notify the
user or take other action as needed. Create a broadcast receiver for the ACTION_VENDOR_SPECIFIC_HEADSET_EVENT
intent
to handle vendor-specific AT commands for the headset.
Health device profile
Android 4.0 (API level 14) introduces support for the Bluetooth Health Device
Profile (HDP). This lets you create applications that use Bluetooth to
communicate with health devices that support Bluetooth, such as heart-rate
monitors, blood meters, thermometers, and scales. The Bluetooth Health API
includes the classes BluetoothHealth
, BluetoothHealthCallback
, and BluetoothHealthAppConfiguration
, which are described in Key Classes and Interfaces.
In using the Bluetooth Health API, it's helpful to understand these key HDP concepts:
- Source
- A health device—such as a weight scale, glucose meter, or thermometer—that transmits medical data to a smart device, such as an Android phone or tablet.
- Sink
- The smart device that
receives the medical data. In an Android HDP application, the sink is
represented by a
BluetoothHealthAppConfiguration
object. - Registration
- The process used to register a sink for communicating with a particular health device.
- Connection
- The process used to open a channel between a health device (source) and a smart device (sink).
Create an HDP application
Here are the basic steps involved in creating an Android HDP application:
- Get a reference to the
BluetoothHealth
proxy object.Similar to regular headset and A2DP profile devices, you must call
getProfileProxy()
with aBluetoothProfile.ServiceListener
and theHEALTH
profile type to establish a connection with the profile proxy object. - Create a
BluetoothHealthCallback
and register an application configuration (BluetoothHealthAppConfiguration
) that acts as a health sink. Establish a connection to a health device.
Note: Some devices initiate the connection automatically. It is unnecessary to carry out this step for those devices.
- When connected successfully to a health device, read/write to the health device using the file descriptor. The received data need to be interpreted using a health manager which implements the IEEE 11073 specifications.
- When done, close the health channel and unregister the application. The channel also closes when there is extended inactivity.
Set up bluetooth
Before your application can communicate over Bluetooth, you need to verify that Bluetooth is supported on the device, and if so, ensure that it is enabled.
If Bluetooth isn't supported, then you should gracefully disable any
Bluetooth features. If Bluetooth is supported, but disabled, then you can request that the
user enable Bluetooth without leaving your application. This setup is
accomplished in two steps, using the BluetoothAdapter
:
- Get the
BluetoothAdapter
.The
BluetoothAdapter
is required for any and all Bluetooth activity. To get theBluetoothAdapter
, call the staticgetDefaultAdapter()
method. This returns aBluetoothAdapter
that represents the device's own Bluetooth adapter (the Bluetooth radio). There's one Bluetooth adapter for the entire system, and your application can interact with it using this object. IfgetDefaultAdapter()
returnsnull
, then the device doesn't support Bluetooth. For example:Kotlin
val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter() if (bluetoothAdapter == null) { // Device doesn't support Bluetooth }
Java
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (bluetoothAdapter == null) { // Device doesn't support Bluetooth }
- Enable Bluetooth.
Next, you need to ensure that Bluetooth is enabled. Call
isEnabled()
to check whether Bluetooth is currently enabled. If this method returns false, then Bluetooth is disabled. To request that Bluetooth be enabled, callstartActivityForResult()
, passing in anACTION_REQUEST_ENABLE
intent action. This call issues a request to enable Bluetooth through the system settings (without stopping your application). For example:Kotlin
if (bluetoothAdapter?.isEnabled == false) { val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE) startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT) }
Java
if (!bluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); }
A dialog appears requesting user permission to enable Bluetooth, as shown in Figure 1. If the user responds "Yes", the system begins to enable Bluetooth, and focus returns to your application once the process completes (or fails).
Figure 1: The enabling Bluetooth dialog.
The
REQUEST_ENABLE_BT
constant passed tostartActivityForResult()
is a locally defined integer that must be greater than 0. The system passes this constant back to you in youronActivityResult()
implementation as therequestCode
parameter.If enabling Bluetooth succeeds, your activity receives the
RESULT_OK
result code in theonActivityResult()
callback. If Bluetooth was not enabled due to an error (or the user responded "No") then the result code isRESULT_CANCELED
.
Optionally, your application can also listen for the
ACTION_STATE_CHANGED
broadcast intent, which
the system broadcasts whenever the Bluetooth state changes. This broadcast contains
the extra fields EXTRA_STATE
and EXTRA_PREVIOUS_STATE
, containing the new and old
Bluetooth states, respectively. Possible values for these extra fields are
STATE_TURNING_ON
, STATE_ON
, STATE_TURNING_OFF
, and STATE_OFF
. Listening for this
broadcast can be useful if your app needs to detect runtime changes made to the Bluetooth state.
Tip: Enabling discoverability automatically enables Bluetooth. If you plan to consistently enable device discoverability before performing Bluetooth activity, you can skip step 2 above. For more information, read the enabling discoverability, section on this page.
Find devices
Using the BluetoothAdapter
, you can find remote Bluetooth
devices either through device discovery or by querying the list of paired
devices.
Device discovery is a scanning procedure that searches the local area for Bluetooth-enabled devices and requests some information about each one. This process is sometimes referred to as discovering, inquiring, or scanning. However, a nearby Bluetooth device responds to a discovery request only if it is currently accepting information requests by being discoverable. If a device is discoverable, it responds to the discovery request by sharing some information, such as the device's name, its class, and its unique MAC address. Using this information, the device that is performing the discovery process can then choose to initiate a connection to the discovered device.
Because discoverable devices might reveal information about the user's location, the device discovery process requires location access. If your app is being used on a device that runs Android 8.0 (API level 26) or higher, use the Companion Device Manager API. This API performs device discovery on your app's behalf, so your app doesn't need to request location permissions.
Once a connection is made with a remote device for the first time, a pairing request is automatically presented to the user. When a device is paired, the basic information about that device—such as the device's name, class, and MAC address—is saved and can be read using the Bluetooth APIs. Using the known MAC address for a remote device, a connection can be initiated with it at any time without performing discovery, assuming the device is still within range.
Note that there is a difference between being paired and being connected:
- To be paired means that two devices are aware of each other's existence, have a shared link-key that can be used for authentication, and are capable of establishing an encrypted connection with each other.
- To be connected means that the devices currently share an RFCOMM channel and are able to transmit data with each other. The current Android Bluetooth API's require devices to be paired before an RFCOMM connection can be established. Pairing is automatically performed when you initiate an encrypted connection with the Bluetooth APIs.
The following sections describe how to find devices that have been paired, or discover new devices using device discovery.
Note: Android-powered devices are not discoverable by default. A user can make the device discoverable for a limited time through the system settings, or an application can request that the user enable discoverability without leaving the application. For more information, see the enable discoverability section on this page.
Query paired devices
Before performing device discovery, it's worth querying the set
of paired devices to see if the desired device is already known. To do so,
call getBondedDevices()
. This
returns a set of BluetoothDevice
objects representing
paired devices. For example, you can query all paired devices and
get the name and MAC address of each device, as the following
code snippet demonstrates:
Kotlin
val pairedDevices: Set<BluetoothDevice>? = bluetoothAdapter?.bondedDevices pairedDevices?.forEach { device -> val deviceName = device.name val deviceHardwareAddress = device.address // MAC address }
Java
Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices(); if (pairedDevices.size() > 0) { // There are paired devices. Get the name and address of each paired device. for (BluetoothDevice device : pairedDevices) { String deviceName = device.getName(); String deviceHardwareAddress = device.getAddress(); // MAC address } }
To initiate a connection with a Bluetooth device, all that's needed from the
associated BluetoothDevice
object is the MAC address,
which you retrieve by calling getAddress()
. You can learn more about creating a connection in the section
about Connecting Devices.
Caution: Performing device discovery consumes
a lot of the Bluetooth adapter's resources. After you have found a device to
connect to, be certain that you stop discovery with
cancelDiscovery()
before
attempting a connection. Also, you shouldn't perform discovery while connected
to a device because the discovery process significantly reduces the bandwidth
available for any existing connections.
Discover devices
To start discovering devices, simply call startDiscovery()
. The
process is asynchronous and returns a boolean value
indicating whether discovery has successfully started. The discovery process
usually involves an inquiry scan of about 12 seconds, followed by a page scan of
each device found to retrieve its Bluetooth name.
In order to receive information about each
device discovered, your application must register a BroadcastReceiver for the
ACTION_FOUND
intent. The system
broadcasts this intent for each device. The intent contains the extra fields
EXTRA_DEVICE
and
EXTRA_CLASS
, which in turn contain a
BluetoothDevice
and a BluetoothClass
, respectively.
The following code snippet shows how you can
register to handle the broadcast when devices are discovered:
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { ... // Register for broadcasts when a device is discovered. val filter = IntentFilter(BluetoothDevice.ACTION_FOUND) registerReceiver(receiver, filter) } // Create a BroadcastReceiver for ACTION_FOUND. private val receiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val action: String = intent.action when(action) { BluetoothDevice.ACTION_FOUND -> { // Discovery has found a device. Get the BluetoothDevice // object and its info from the Intent. val device: BluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) val deviceName = device.name val deviceHardwareAddress = device.address // MAC address } } } } override fun onDestroy() { super.onDestroy() ... // Don't forget to unregister the ACTION_FOUND receiver. unregisterReceiver(receiver) }
Java
@Override protected void onCreate(Bundle savedInstanceState) { ... // Register for broadcasts when a device is discovered. IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); registerReceiver(receiver, filter); } // Create a BroadcastReceiver for ACTION_FOUND. private final BroadcastReceiver receiver = new BroadcastReceiver() { 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 } } }; @Override protected void onDestroy() { super.onDestroy(); ... // Don't forget to unregister the ACTION_FOUND receiver. unregisterReceiver(receiver); }
To initiate a connection with a Bluetooth device, all that's needed from the
associated BluetoothDevice
object is the MAC address,
which you retrieve by calling
getAddress()
. You can learn
more about creating a connection in the section about
Connecting Devices.
Caution: Performing device discovery consumes
a lot of the Bluetooth adapter's resources. After you have found a device to
connect to, be certain that you stop discovery with
cancelDiscovery()
before
attempting a connection. Also, you shouldn't perform discovery while connected
to a device because the discovery process significantly reduces the bandwidth
available for any existing connections.
Enable discoverability
If you would like to make the local device discoverable to other devices,
call startActivityForResult(Intent, int)
with the
ACTION_REQUEST_DISCOVERABLE
intent.
This issues a request to enable the system's discoverable mode without having
to navigate to the Settings app, which would stop your own app. By default, the device becomes
discoverable for 120 seconds, or 2 minutes. You can define a different duration,
up to 3600 seconds (1 hour), by adding the
EXTRA_DISCOVERABLE_DURATION
extra.
Caution: If you set the
EXTRA_DISCOVERABLE_DURATION
extra's
value to 0, the device is always discoverable. This configuration is insecure
and therefore highly discouraged.
The following code snippet sets the device to be discoverable for 5 minutes (300 seconds):
Kotlin
val discoverableIntent: Intent = Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE).apply { putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300) } startActivity(discoverableIntent)
Java
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300); startActivity(discoverableIntent);

A dialog is displayed, requesting the user's permission to make the device
discoverable, as shown in Figure 2. If the user responds "Yes," then the device
becomes discoverable for the specified amount of time. Your activity then
receives a call to the onActivityResult()
callback, with the result code equal to the duration that the device
is discoverable. If the user responded "No", or if an error occurred, the result code
is RESULT_CANCELED
.
Note: If Bluetooth has not been enabled on the device, then making the device discoverable automatically enables Bluetooth.
The device silently remains in discoverable mode for the allotted time.
If you would like to be notified when the discoverable mode has changed, you can
register a BroadcastReceiver for the ACTION_SCAN_MODE_CHANGED
intent. This intent contains the extra fields EXTRA_SCAN_MODE
and
EXTRA_PREVIOUS_SCAN_MODE
, which provide the
new and old scan mode, respectively. Possible values for each extra are as follows:
SCAN_MODE_CONNECTABLE_DISCOVERABLE
- The device is in discoverable mode.
SCAN_MODE_CONNECTABLE
- The device isn't in discoverable mode but can still receive connections.
SCAN_MODE_NONE
- The device isn't in discoverable mode and cannot receive connections.
If you are initiating the connection to a remote device, you don't need to enable device discoverability. Enabling discoverability is only necessary when you want your application to host a server socket that accepts incoming connections, as remote devices must be able to discover other devices before initiating connections to those other devices.
Connect devices
In order to create a connection between two devices, you
must implement both the server-side and client-side mechanisms because one
device must open a server socket, and the other one must initiate the connection
using the server device's MAC address. The server device and the client device
each obtain the required BluetoothSocket
in different ways. The server receives socket information when an incoming
connection is accepted. The client provides socket information when it opens an
RFCOMM channel to the server.
The server and client are considered
connected to each other when they each have a connected
BluetoothSocket
on the same RFCOMM channel. At this
point, each device can obtain input and output streams, and data transfer can
begin, which is discussed in the section about Manage a connection. This section describes how
to initiate the connection between two devices.
Connection techniques
One implementation technique is to automatically prepare each device as a server so that each device has a server socket open and listening for connections. In this case, either device can initiate a connection with the other and become the client. Alternatively, one device can explicitly host the connection and open a server socket on demand, and the other device initiates the connection.

Note: If the two devices have not been previously paired, then the Android framework automatically shows a pairing request notification or dialog to the user during the connection procedure, as shown in Figure 3. Therefore, when your application attempts to connect devices, it doesn't need to be concerned about whether or not the devices are paired. Your RFCOMM connection attempt gets blocked until the user has successfully paired the two devices, and the attempt fails if the user rejects pairing, or if the pairing process fails or times out.
Connect as a server
When you want to connect two devices, one must act as a server by holding an
open BluetoothServerSocket
. The purpose of the server
socket is to listen for incoming connection requests and
provide a connected BluetoothSocket
after a request is
accepted. When the BluetoothSocket
is acquired from the BluetoothServerSocket
,
the BluetoothServerSocket
can—and should—be
discarded, unless you want the device to accept more connections.
To set up a server socket and accept a connection, complete the following sequence of steps:
- Get a
BluetoothServerSocket
by callinglistenUsingRfcommWithServiceRecord()
.The string is an identifiable name of your service, which the system automatically writes to a new Service Discovery Protocol (SDP) database entry on the device. The name is arbitrary and can simply be your application name. The Universally Unique Identifier (UUID) is also included in the SDP entry and forms the basis for the connection agreement with the client device. That is, when the client attempts to connect with this device, it carries a UUID that uniquely identifies the service with which it wants to connect. These UUIDs must match in order for the connection to be accepted.
A UUID is a standardized 128-bit format for a string ID used to uniquely identify information. The point of a UUID is that it's big enough that you can select any random ID and it doesn't clash with any other ID. In this case, it's used to uniquely identify your application's Bluetooth service. To get a UUID to use with your application, you can use one of the many random UUID generators on the web, then initialize a
UUID
withfromString(String)
. - Start listening for connection requests by calling
accept()
.This is a blocking call. It returns when either a connection has been accepted or an exception has occurred. A connection is accepted only when a remote device has sent a connection request containing a UUID that matches the one registered with this listening server socket. When successful,
accept()
returns a connectedBluetoothSocket
. - Unless you want to accept additional connections, call
close()
.This method call releases the server socket and all its resources, but doesn't close the connected
BluetoothSocket
that's been returned byaccept()
. Unlike TCP/IP, RFCOMM allows only one connected client per channel at a time, so in most cases, it makes sense to callclose()
on theBluetoothServerSocket
immediately after accepting a connected socket.
Because the accept()
call is
a blocking call, it should not be executed in the main activity UI thread so
that your application can still respond to other user interactions. It usually makes
sense to do all work that involves a BluetoothServerSocket
or BluetoothSocket
in a new
thread managed by your application. To abort a blocked call such as accept()
, call close()
on the BluetoothServerSocket
or BluetoothSocket
from another thread. Note that all methods on a BluetoothServerSocket
or BluetoothSocket
are thread-safe.
Example
Here's a simplified thread for the server component that accepts incoming connections:
Kotlin
private inner class AcceptThread : Thread() { private val mmServerSocket: BluetoothServerSocket? by lazy(LazyThreadSafetyMode.NONE) { bluetoothAdapter?.listenUsingInsecureRfcommWithServiceRecord(NAME, MY_UUID) } override fun run() { // Keep listening until exception occurs or a socket is returned. var shouldLoop = true while (shouldLoop) { val socket: BluetoothSocket? = try { mmServerSocket?.accept() } catch (e: IOException) { Log.e(TAG, "Socket's accept() method failed", e) shouldLoop = false null } socket?.also { manageMyConnectedSocket(it) mmServerSocket?.close() shouldLoop = false } } } // Closes the connect socket and causes the thread to finish. fun cancel() { try { mmServerSocket?.close() } catch (e: IOException) { Log.e(TAG, "Could not close the connect socket", e) } } }
Java
private class AcceptThread extends Thread { private final BluetoothServerSocket mmServerSocket; public AcceptThread() { // Use a temporary object that is later assigned to mmServerSocket // because mmServerSocket is final. BluetoothServerSocket tmp = null; try { // MY_UUID is the app's UUID string, also used by the client code. tmp = bluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID); } catch (IOException e) { Log.e(TAG, "Socket's listen() method failed", e); } mmServerSocket = tmp; } public void run() { BluetoothSocket socket = null; // Keep listening until exception occurs or a socket is returned. while (true) { try { socket = mmServerSocket.accept(); } catch (IOException e) { Log.e(TAG, "Socket's accept() method failed", e); break; } if (socket != null) { // A connection was accepted. Perform work associated with // the connection in a separate thread. manageMyConnectedSocket(socket); mmServerSocket.close(); break; } } } // Closes the connect socket and causes the thread to finish. public void cancel() { try { mmServerSocket.close(); } catch (IOException e) { Log.e(TAG, "Could not close the connect socket", e); } } }
In this example, only one incoming connection is desired, so as soon as a
connection is accepted and the BluetoothSocket
is
acquired, the app
passes the acquired BluetoothSocket
to a separate
thread, closes the
BluetoothServerSocket
, and breaks out of the loop.
Note that when accept()
returns the BluetoothSocket
, the socket is already
connected. Therefore, you shouldn't call connect()
,
as you do from the client side.
The app-specific manageMyConnectedSocket()
method is designed to
initiate the thread for transferring data, which is discussed in the section
about Manage a Connection.
Usually, you should close your BluetoothServerSocket
as soon as you are done listening for incoming connections. In this example, close()
is called as soon
as the BluetoothSocket
is acquired. You may also want
to provide a public method in your thread that can close the private BluetoothSocket
in the event that you need to stop listening on that
server socket.
Connect as a client
In order to initiate a connection with a remote device that is accepting
connections on an open server socket, you must first obtain a
BluetoothDevice
object that represents the remote device.
To learn how to create a BluetoothDevice
, see Finding Devices. You must then use the
BluetoothDevice
to acquire a BluetoothSocket
and initiate the connection.
The basic procedure is as follows:
- Using the
BluetoothDevice
, get aBluetoothSocket
by callingcreateRfcommSocketToServiceRecord(UUID)
.This method initializes a
BluetoothSocket
object that allows the client to connect to aBluetoothDevice
. The UUID passed here must match the UUID used by the server device when it calledlistenUsingRfcommWithServiceRecord(String, UUID)
to open itsBluetoothServerSocket
. To use a matching UUID, hard-code the UUID string into your application, and then reference it from both the server and client code. - Initiate the connection by calling
connect()
. Note that this method is a blocking call.After a client calls this method, the system performs an SDP lookup to find the remote device with the matching UUID. If the lookup is successful and the remote device accepts the connection, it shares the RFCOMM channel to use during the connection, and the
connect()
method returns. If the connection fails, or if theconnect()
method times out (after about 12 seconds), then the method throws anIOException
.Because
connect()
is a blocking call, you should always perform this connection procedure in a thread that is separate from the main activity (UI) thread.Note: You should always call
cancelDiscovery()
to ensure that the device isn't performing device discovery before you callconnect()
. If discovery is in progress, then the connection attempt is significantly slowed, and it's more likely to fail.
Example
Here is a basic example of a client thread that initiates a Bluetooth connection:
Kotlin
private inner class ConnectThread(device: BluetoothDevice) : Thread() { private val mmSocket: BluetoothSocket? by lazy(LazyThreadSafetyMode.NONE) { device.createRfcommSocketToServiceRecord(MY_UUID) } public override fun run() { // Cancel discovery because it otherwise slows down the connection. bluetoothAdapter?.cancelDiscovery() mmSocket?.use { socket -> // Connect to the remote device through the socket. This call blocks // until it succeeds or throws an exception. socket.connect() // The connection attempt succeeded. Perform work associated with // the connection in a separate thread. manageMyConnectedSocket(socket) } } // Closes the client socket and causes the thread to finish. fun cancel() { try { mmSocket?.close() } catch (e: IOException) { Log.e(TAG, "Could not close the client socket", e) } } }
Java
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 in the server code. tmp = device.createRfcommSocketToServiceRecord(MY_UUID); } catch (IOException e) { Log.e(TAG, "Socket's create() method failed", e); } mmSocket = tmp; } public void run() { // Cancel discovery because it otherwise slows down the connection. bluetoothAdapter.cancelDiscovery(); try { // Connect to the remote device through the socket. This call blocks // until it succeeds or throws an exception. mmSocket.connect(); } catch (IOException connectException) { // Unable to connect; close the socket and return. try { mmSocket.close(); } catch (IOException closeException) { Log.e(TAG, "Could not close the client socket", closeException); } return; } // The connection attempt succeeded. Perform work associated with // the connection in a separate thread. manageMyConnectedSocket(mmSocket); } // Closes the client socket and causes the thread to finish. public void cancel() { try { mmSocket.close(); } catch (IOException e) { Log.e(TAG, "Could not close the client socket", e); } } }
Notice that, in this snippet,
cancelDiscovery()
is called before
the connection attempt occurs. You should always call
cancelDiscovery()
before
connect()
, especially because
cancelDiscovery()
succeeds regardless of whether device discovery
is currently in progress. If your app needs to determine whether device
discovery is in progress, however, you can check using
isDiscovering()
.
The app-specific manageMyConnectedSocket()
method is designed to
initiate the thread for transferring data, which is discussed in the section
about Managing a Connection.
When you're done with your BluetoothSocket
, always
call close()
. Doing so
immediately closes the connected socket and release all related internal
resources.
Manage a connection
After you have successfully connected multiple devices, each one has a
connected BluetoothSocket
. This is where the fun
begins because you can share information between devices. Using the
BluetoothSocket
, the general procedure to transfer
data is as follows:
- Get the
InputStream
andOutputStream
that handle transmissions through the socket usinggetInputStream()
andgetOutputStream()
, respectively. - Read and write data to the streams using
read(byte[])
andwrite(byte[])
.
There are, of course, implementation details to consider. In particular,
you should use a dedicated thread for reading from the stream and writing to it. This is
important because both the read(byte[])
and
write(byte[])
methods are blocking calls. The
read(byte[])
method blocks until there is something to read
from the stream. The write(byte[])
method doesn't usually
block, but it can block for flow control if the remote device isn't calling
read(byte[])
quickly enough and the intermediate buffers become full as a result.
So, your main loop in the thread should be dedicated to reading from the
InputStream
. A separate public method in the thread can be used to initiate
writes to the OutputStream
.
Example
Here's an example of how you can transfer data between two devices connected over Bluetooth:
Kotlin
private const val TAG = "MY_APP_DEBUG_TAG" // Defines several constants used when transmitting messages between the // service and the UI. const val MESSAGE_READ: Int = 0 const val MESSAGE_WRITE: Int = 1 const val MESSAGE_TOAST: Int = 2 // ... (Add other message types here as needed.) class MyBluetoothService( // handler that gets info from Bluetooth service private val handler: Handler) { private inner class ConnectedThread(private val mmSocket: BluetoothSocket) : Thread() { private val mmInStream: InputStream = mmSocket.inputStream private val mmOutStream: OutputStream = mmSocket.outputStream private val mmBuffer: ByteArray = ByteArray(1024) // mmBuffer store for the stream override fun run() { var numBytes: Int // bytes returned from read() // Keep listening to the InputStream until an exception occurs. while (true) { // Read from the InputStream. numBytes = try { mmInStream.read(mmBuffer) } catch (e: IOException) { Log.d(TAG, "Input stream was disconnected", e) break } // Send the obtained bytes to the UI activity. val readMsg = handler.obtainMessage( MESSAGE_READ, numBytes, -1, mmBuffer) readMsg.sendToTarget() } } // Call this from the main activity to send data to the remote device. fun write(bytes: ByteArray) { try { mmOutStream.write(bytes) } catch (e: IOException) { Log.e(TAG, "Error occurred when sending data", e) // Send a failure message back to the activity. val writeErrorMsg = handler.obtainMessage(MESSAGE_TOAST) val bundle = Bundle().apply { putString("toast", "Couldn't send data to the other device") } writeErrorMsg.data = bundle handler.sendMessage(writeErrorMsg) return } // Share the sent message with the UI activity. val writtenMsg = handler.obtainMessage( MESSAGE_WRITE, -1, -1, bytes) writtenMsg.sendToTarget() } // Call this method from the main activity to shut down the connection. fun cancel() { try { mmSocket.close() } catch (e: IOException) { Log.e(TAG, "Could not close the connect socket", e) } } } }
Java
public class MyBluetoothService { private static final String TAG = "MY_APP_DEBUG_TAG"; private Handler handler; // handler that gets info from Bluetooth service // Defines several constants used when transmitting messages between the // service and the UI. private interface MessageConstants { public static final int MESSAGE_READ = 0; public static final int MESSAGE_WRITE = 1; public static final int MESSAGE_TOAST = 2; // ... (Add other message types here as needed.) } private class ConnectedThread extends Thread { private final BluetoothSocket mmSocket; private final InputStream mmInStream; private final OutputStream mmOutStream; private byte[] mmBuffer; // mmBuffer store for the stream public ConnectedThread(BluetoothSocket socket) { mmSocket = socket; InputStream tmpIn = null; OutputStream tmpOut = null; // Get the input and output streams; using temp objects because // member streams are final. try { tmpIn = socket.getInputStream(); } catch (IOException e) { Log.e(TAG, "Error occurred when creating input stream", e); } try { tmpOut = socket.getOutputStream(); } catch (IOException e) { Log.e(TAG, "Error occurred when creating output stream", e); } mmInStream = tmpIn; mmOutStream = tmpOut; } public void run() { mmBuffer = new byte[1024]; int numBytes; // bytes returned from read() // Keep listening to the InputStream until an exception occurs. while (true) { try { // Read from the InputStream. numBytes = mmInStream.read(mmBuffer); // Send the obtained bytes to the UI activity. Message readMsg = handler.obtainMessage( MessageConstants.MESSAGE_READ, numBytes, -1, mmBuffer); readMsg.sendToTarget(); } catch (IOException e) { Log.d(TAG, "Input stream was disconnected", e); break; } } } // Call this from the main activity to send data to the remote device. public void write(byte[] bytes) { try { mmOutStream.write(bytes); // Share the sent message with the UI activity. Message writtenMsg = handler.obtainMessage( MessageConstants.MESSAGE_WRITE, -1, -1, bytes); writtenMsg.sendToTarget(); } catch (IOException e) { Log.e(TAG, "Error occurred when sending data", e); // Send a failure message back to the activity. Message writeErrorMsg = handler.obtainMessage(MessageConstants.MESSAGE_TOAST); Bundle bundle = new Bundle(); bundle.putString("toast", "Couldn't send data to the other device"); writeErrorMsg.setData(bundle); handler.sendMessage(writeErrorMsg); } } // Call this method from the main activity to shut down the connection. public void cancel() { try { mmSocket.close(); } catch (IOException e) { Log.e(TAG, "Could not close the connect socket", e); } } } }
After the constructor acquires the necessary streams, the thread waits for
data to come through the InputStream
. When read(byte[])
returns with
data from the stream, the data is sent to the main activity using a member
Handler
from the parent class. The thread then waits for more
bytes to be read from the InputStream
.
Sending outgoing data is as simple as calling the thread's
write()
method from the main activity and passing in the bytes to
be sent. This method calls write(byte[])
to send
the data to the remote device. If an IOException
is thrown when
calling write(byte[])
, the thread sends a toast to
the main activity, explaining to the user that the device couldn't send the
given bytes to the other (connected) device.
The thread's cancel()
method allows the connection to be
terminated at any time by closing the BluetoothSocket
.
This method should always be called when you're done using the Bluetooth
connection.
For a demonstration of using the Bluetooth APIs, see the Bluetooth Chat sample app.
Key classes and interfaces
All of the Bluetooth APIs are available in the android.bluetooth
package. Here's a summary of the classes and interfaces you need to create Bluetooth
connections:
BluetoothAdapter
- Represents the local Bluetooth adapter (Bluetooth radio). The
BluetoothAdapter
is the entry-point for all Bluetooth interaction. Using this, you can discover other Bluetooth devices, query a list of bonded (paired) devices, instantiate aBluetoothDevice
using a known MAC address, and create aBluetoothServerSocket
to listen for communications from other devices. BluetoothDevice
- Represents a remote Bluetooth device. Use this to request a connection
with a remote device through a
BluetoothSocket
or query information about the device such as its name, address, class, and bonding state. BluetoothSocket
- Represents the interface for a Bluetooth socket (similar to a TCP
Socket
). This is the connection point that allows an application to exchange data with another Bluetooth device usingInputStream
andOutputStream
. BluetoothServerSocket
- Represents an open server socket that listens for incoming requests
(similar to a TCP
ServerSocket
). In order to connect two Android devices, one device must open a server socket with this class. When a remote Bluetooth device makes a connection request to this device, the device accepts the connection, then returns a connectedBluetoothSocket
. BluetoothClass
- Describes the general characteristics and capabilities of a Bluetooth device. This is a read-only set of properties that defines the device's classes and services. Although this information provides a useful hint regarding a device's type, the attributes of this class don't necessarily describe all Bluetooth profiles and services that the device supports.
BluetoothProfile
- An interface that represents a Bluetooth profile. A Bluetooth profile is a wireless interface specification for Bluetooth-based communication between devices. An example is the Hands-Free profile. For more discussion of profiles, see Working with Profiles.
BluetoothHeadset
- Provides support for Bluetooth headsets to be used with mobile phones. This includes both the Bluetooth Headset profile and the Hands-Free (v1.5) profile.
BluetoothA2dp
- Defines how high-quality audio can be streamed from one device to another over a Bluetooth connection using the Advanced Audio Distribution Profile (A2DP).
BluetoothHealth
- Represents a Health Device Profile proxy that controls the Bluetooth service.
BluetoothHealthCallback
- An abstract class that you use to implement
BluetoothHealth
callbacks. You must extend this class and implement the callback methods to receive updates about changes in the application’s registration state and Bluetooth channel state. BluetoothHealthAppConfiguration
- Represents an application configuration that the Bluetooth Health third-party application registers to communicate with a remote Bluetooth health device.
BluetoothProfile.ServiceListener
- An interface that notifies
BluetoothProfile
interprocess communication (IPC) clients when they have been connected to or disconnected from the internal service that runs a particular profile.