Bluetooth overview

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. The BluetoothHeadset 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:

  1. Get the default adapter, as described in Setting Up Bluetooth.
  2. Set up a BluetoothProfile.ServiceListener. This listener notifies BluetoothProfile clients when they have been connected to or disconnected from the service.
  3. 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 of BluetoothHeadset.
  4. In onServiceConnected(), get a handle to the profile proxy object.
  5. 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:

  1. Get a reference to the BluetoothHealth proxy object.

    Similar to regular headset and A2DP profile devices, you must call getProfileProxy() with a BluetoothProfile.ServiceListener and the HEALTH profile type to establish a connection with the profile proxy object.

  2. Create a BluetoothHealthCallback and register an application configuration (BluetoothHealthAppConfiguration) that acts as a health sink.
  3. 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.

  4. 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.
  5. 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:

  1. Get the BluetoothAdapter.

    The BluetoothAdapter is required for any and all Bluetooth activity. To get the BluetoothAdapter, call the static getDefaultAdapter() method. This returns a BluetoothAdapter 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. If getDefaultAdapter() returns null, 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
    }
    
  2. 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, call startActivityForResult(), passing in an ACTION_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 to startActivityForResult() is a locally defined integer that must be greater than 0. The system passes this constant back to you in your onActivityResult() implementation as the requestCode parameter.

    If enabling Bluetooth succeeds, your activity receives the RESULT_OK result code in the onActivityResult() callback. If Bluetooth was not enabled due to an error (or the user responded "No") then the result code is RESULT_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);
Figure 2: The enabling discoverability dialog.

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.

Figure 3: The Bluetooth pairing dialog.

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:

  1. Get a BluetoothServerSocket by calling listenUsingRfcommWithServiceRecord().

    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 with fromString(String).

  2. 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 connected BluetoothSocket.

  3. 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 by accept(). Unlike TCP/IP, RFCOMM allows only one connected client per channel at a time, so in most cases, it makes sense to call close() on the BluetoothServerSocket 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:

  1. Using the BluetoothDevice, get a BluetoothSocket by calling createRfcommSocketToServiceRecord(UUID).

    This method initializes a BluetoothSocket object that allows the client to connect to a BluetoothDevice. The UUID passed here must match the UUID used by the server device when it called listenUsingRfcommWithServiceRecord(String, UUID) to open its BluetoothServerSocket. To use a matching UUID, hard-code the UUID string into your application, and then reference it from both the server and client code.

  2. 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 the connect() method times out (after about 12 seconds), then the method throws an IOException.

    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 call connect(). 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:

  1. Get the InputStream and OutputStream that handle transmissions through the socket using getInputStream() and getOutputStream(), respectively.
  2. Read and write data to the streams using read(byte[]) and write(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 a BluetoothDevice using a known MAC address, and create a BluetoothServerSocket 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 using InputStream and OutputStream.
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 connected BluetoothSocket.
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.