Skip to content

Connectivity using the Bluetooth API

Mitchell Bryson edited this page Aug 5, 2026 · 9 revisions

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
  • Connect to other devices through service discovery
  • Transfer data to and from other devices
  • Manage multiple connections

Usage

In order to use Bluetooth features in your application, you must declare the appropriate permissions in your manifest. The permissions you need differ based on the API level your app targets.

For apps that target Android 12 (API level 31) or higher, declare the runtime permissions BLUETOOTH_SCAN (when looking for Bluetooth devices), BLUETOOTH_ADVERTISE (when making the device discoverable), and BLUETOOTH_CONNECT (when communicating with already-paired devices). The legacy BLUETOOTH and BLUETOOTH_ADMIN permissions should be declared with android:maxSdkVersion="30" so the system only requests them on older devices. See Bluetooth permissions for the full guidance, including when ACCESS_FINE_LOCATION is still required (e.g. when BLUETOOTH_SCAN is not annotated with usesPermissionFlags="neverForLocation").

<manifest ... >
  <!-- Request legacy Bluetooth permissions on older devices. -->
  <uses-permission android:name="android.permission.BLUETOOTH"
                   android:maxSdkVersion="30" />
  <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
                   android:maxSdkVersion="30" />

  <!-- Needed only if your app looks for Bluetooth devices. The
       neverForLocation flag strongly asserts that scan results are never
       used to derive physical location; drop it (and declare
       ACCESS_FINE_LOCATION without maxSdkVersion) if your app does. -->
  <uses-permission android:name="android.permission.BLUETOOTH_SCAN"
                   android:usesPermissionFlags="neverForLocation" />

  <!-- Needed only if your app makes the device discoverable. -->
  <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

  <!-- Needed only if your app communicates with already-paired devices. -->
  <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

  <!-- Location permission is still required for Bluetooth scanning on
       Android 11 and lower. -->
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
                   android:maxSdkVersion="30" />
</manifest>

Set up bluetooth

  1. Get the BluetoothAdapter via the system BluetoothManager. BluetoothAdapter.getDefaultAdapter() is deprecated — go through BluetoothManager instead. The Context.getSystemService(Class) overload used in Google's snippet was added in API 23, so on projects with minSdk below 23 use the backwards-compatible ContextCompat.getSystemService(...) from androidx.core:core (shown below).
// Works on every supported API level — ContextCompat falls back to the
// String-based getSystemService(Context.BLUETOOTH_SERVICE) lookup below API 23.
BluetoothManager bluetoothManager =
        ContextCompat.getSystemService(this, BluetoothManager.class);
BluetoothAdapter bluetoothAdapter =
        bluetoothManager != null ? bluetoothManager.getAdapter() : null;
if (bluetoothAdapter == null) {
    // Device doesn't support Bluetooth
}
  1. Enable Bluetooth. Use the Activity Result API (ActivityResultLauncher + registerForActivityResult(...)) — startActivityForResult(...) / onActivityResult(...) are deprecated.

    Declare the launcher as a field on your AppCompatActivity (or Fragment), or register it in onCreate() — registration must happen before the activity is STARTED, so a click handler or other lifecycle-late hook is too late.

    // androidx.activity.result.ActivityResultLauncher
    // androidx.activity.result.contract.ActivityResultContracts
    // android.app.Activity
    private final ActivityResultLauncher<Intent> enableBtLauncher = registerForActivityResult(
            new ActivityResultContracts.StartActivityForResult(),
            result -> {
                if (result.getResultCode() == Activity.RESULT_OK) {
                    // Bluetooth was turned on. Continue with discovery, pairing, etc.
                } else {
                    // result.getResultCode() == Activity.RESULT_CANCELED means the user
                    // declined, or Bluetooth could not be enabled.
                }
            });

    At the call site — typically a button handler or another lifecycle method that runs after the activity is created — kick off the system prompt with launch(...):

    if (bluetoothAdapter != null && !bluetoothAdapter.isEnabled()) {
        Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        enableBtLauncher.launch(enableBtIntent);
    }

    A system dialog asks the user for permission to enable Bluetooth. When the user dismisses it (either by accepting or declining), the launcher's callback fires with an ActivityResult: RESULT_OK if Bluetooth is now on, RESULT_CANCELED otherwise.

    On Android 12 (API level 31) and higher, you must hold the runtime BLUETOOTH_CONNECT permission before launching ACTION_REQUEST_ENABLE — request it with a RequestPermission launcher first, then call enableBtLauncher.launch(...) from that permission callback.

Discover devices

With Bluetooth enabled, you can find remote devices two ways: query the set of already-paired ("bonded") devices, or start discovery to scan for nearby devices. See Find Bluetooth devices for the full guide.

Both operations are gated by runtime permissions. On devices running Android 12 (API level 31) or higher, apps that target API 31+ must hold BLUETOOTH_SCAN to start discovery and BLUETOOTH_CONNECT to read device names or query paired devices; when the app requests them, the system shows a single "Nearby devices" prompt. On devices running Android 6.0 through 11, classic discovery instead requires the location permission (ACCESS_FINE_LOCATION) declared in the manifest section above. Request the right set with a RequestMultiplePermissions launcher before scanning:

// androidx.activity.result.ActivityResultLauncher
// androidx.activity.result.contract.ActivityResultContracts
private final ActivityResultLauncher<String[]> btPermissionsLauncher = registerForActivityResult(
        new ActivityResultContracts.RequestMultiplePermissions(),
        grants -> {
            if (!grants.containsValue(Boolean.FALSE)) {
                startBluetoothDiscovery();
            } else {
                // Explain that scanning is unavailable without the permission.
            }
        });

private void requestBluetoothPermissionsThenScan() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        btPermissionsLauncher.launch(new String[] {
                Manifest.permission.BLUETOOTH_SCAN,
                Manifest.permission.BLUETOOTH_CONNECT });
    } else {
        // Classic discovery needs a location permission on Android 6.0–11.
        btPermissionsLauncher.launch(new String[] {
                Manifest.permission.ACCESS_FINE_LOCATION });
    }
}

Query paired devices

Once the permissions are granted, fetching the devices the phone has already paired with is a synchronous call (it requires BLUETOOTH_CONNECT on Android 12+):

Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
for (BluetoothDevice device : pairedDevices) {
    String deviceName = device.getName();
    String deviceHardwareAddress = device.getAddress(); // MAC address
}

Scan for nearby devices

To find devices that aren't paired yet, call startDiscovery(). The process is asynchronous — the inquiry scan lasts about 12 seconds — and the system delivers one BluetoothDevice.ACTION_FOUND broadcast per device found, so register a BroadcastReceiver for it:

private void startBluetoothDiscovery() {
    registerReceiver(discoveryReceiver, new IntentFilter(BluetoothDevice.ACTION_FOUND));
    bluetoothAdapter.startDiscovery();
}

private final BroadcastReceiver discoveryReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (BluetoothDevice.ACTION_FOUND.equals(intent.getAction())) {
            // The untyped getParcelableExtra(String) overload was deprecated in
            // API 33 in favor of the type-safe overload, which only exists on 33+.
            BluetoothDevice device;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class);
            } else {
                device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            }
            if (device != null) {
                String deviceName = device.getName();
                String deviceHardwareAddress = device.getAddress(); // MAC address
            }
        }
    }
};

@Override
protected void onDestroy() {
    super.onDestroy();
    // Don't leak the receiver when the activity goes away.
    unregisterReceiver(discoveryReceiver);
}

Discovery is a heavyweight procedure: always call bluetoothAdapter.cancelDiscovery() before attempting a connection to a device, and never connect while discovery is in progress.

Libraries

Check out these libraries for easy handling of Bluetooth in your apps:

References

Finding these guides helpful?

We need help from the broader community to improve these guides, add new topics and keep the topics up-to-date. See our contribution guidelines here and our topic issues list for great ways to help out.

Check these same guides through our standalone viewer for a better browsing experience and an improved search. Follow us on twitter @codepath for access to more useful Android development resources.

Clone this wiki locally