Skip to content
← All posts

6 min read

Bluetooth apps: Android 17 and iOS 27 fix what breaks

  • Bluetooth
  • Android
  • iOS

Bluetooth has a lovely way of making you look good in the demo and terrible in the field. Connecting to the sensor on your desk takes five minutes; keeping it connected in a basement, after someone pressed the reset button "just to see", takes the rest of the project. So when both Google and Apple quietly touch exactly those corners in the same year, with Android 17 on 16 June and iOS 27 on 14 September, it deserves a coffee, even if none of it made it anywhere near a keynote stage.

The reset button, everyone's favourite

You know the scene. Someone presses reset on the device, the device forgets its encryption keys, the phone still remembers its own, and the two of them stare at each other like an old couple who no longer recognise each other at a party. Since Android 16 your app hears about it through ACTION_KEY_MISSING, and the cure until now was sending the user into Settings to forget the device and pair it again, usually while they write to your support team in capital letters.

Android 17 adds autonomous re-pairing, and it applies to every app, whatever it targets. When a bond is lost, the system tries to rebuild it in the background and asks the user to confirm with its own dialog, which is Google finally agreeing that "go to Settings and fiddle" was never a great user experience. Three details matter for your code. The new keys only replace the old ones if the new connection is at least as secure as the previous bond, so an impostor device cannot sneak in with weaker security. ACTION_KEY_MISSING is now only broadcast when that attempt fails, so your error screen stays, it just gets more days off. And the pairing request now carries an EXTRA_PAIRING_CONTEXT extra: when it says PAIRING_CONTEXT_REPAIRING, it is the system patching things up rather than a brand new pairing, and your app can say "reconnecting your sensor" instead of flashing something that looks like the end of the world.

The diagram plays the same failure twice. On Android 16, the reset sensor makes the encrypted connection fail, the app gets ACTION_KEY_MISSING, and the user has to forget and re-pair the device by hand in Settings. On Android 17, the system sends a pairing request with the re-pairing context, the user confirms in a dialog, the new keys replace the old ones only if security is at least as strong, and ACTION_KEY_MISSING only shows up if that attempt fails.

Google says most apps need no code change, but that device makers and companion app teams should check that both the hardware and the app cope gracefully with the bond changing under their feet. The test is wonderfully low-tech, for once: remove the bond on the device itself, or unpair it in Settings, and watch what your app does. Popcorn optional.

The read loop that reads nothing, very fast

The second change only kicks in once your app targets Android 17 (API level 37), which Google Play will ask of you sooner or later, as it did with API 36. If you use classic Bluetooth sockets (RFCOMM, the serial-port style link that many industrial and medical devices still speak with pride), read() on the socket's input stream now returns -1 when the socket is closed or the connection drops, which is what the InputStream contract has been saying since roughly the dawn of Java. Code that waited for an IOException to leave the loop will now spin on -1 instead, reading nothing at full speed and warming the user's pocket nicely. The fix is one line, and it is worth writing today so the loop behaves on every version:

suspend fun readFrames(socket: BluetoothSocket, onFrame: (ByteArray) -> Unit) =
    withContext(Dispatchers.IO) {
        val input = socket.inputStream
        val buffer = ByteArray(1024)
        try {
            while (isActive) {
                val count = input.read(buffer)
                if (count == -1) break // Android 17: the link is gone, no exception
                onFrame(buffer.copyOf(count))
            }
        } catch (e: IOException) {
            // Older versions, and real I/O errors, still land here
        } finally {
            socket.close()
        }
    }

Meanwhile, the iPhone learns to measure

Apple, true to form, went for the shinier toy: Core Bluetooth now supports Bluetooth Channel Sounding, presented in the WWDC26 session Find your accessory with Bluetooth Channel Sounding. Until now, guessing how far away an accessory was meant reading the signal strength (RSSI), which is about as precise as judging your speed by the sound of the engine. With Channel Sounding the iPhone sends tones across the 2.4 GHz channels, the accessory sends them back, and the phone works out the distance from how they return. You check support with CBCentralManager.supportsFeatures(.channelSounding), start a session on a connected peripheral and get results in meters through a delegate method; Nearby Interaction can add the direction, with the camera lending a hand if you allow it.

And now, the fine print, because with Apple there is always fine print. The iPhone needs Apple's N1 wireless chip, which arrived with the iPhone 17 family, so older iPhones stay at the "warmer, colder" stage. The accessory needs Bluetooth 6.3 with a specific set of Channel Sounding features, so your current hardware will not get it through a firmware update unless its chipset already supports them. The session pauses as soon as your app goes to the background. And the accessory must have been set up through AccessorySetupKit, which is the part I would start on right now, distance plans or not.

The front door Apple would like you to use

AccessorySetupKit arrived with iOS 18. Instead of asking for Bluetooth permission and then scanning half the neighbourhood's toothbrushes, your app shows a system picker with your product's picture and name, the user taps once, and your app gets access to that one accessory, with no Bluetooth permission prompt at all. Channel Sounding requiring it is Apple's polite way of pointing at the door it wants everyone to use, and your users will enjoy one less scary popup anyway:

import AccessorySetupKit
import CoreBluetooth
import UIKit

@MainActor
final class SensorSetup {
    private let session = ASAccessorySession()
    private let serviceUUID = CBUUID(string: "FEAA")
    var onSensorAdded: ((UUID) -> Void)?

    func start() {
        session.activate(on: .main) { [weak self] event in
            guard event.eventType == .accessoryAdded,
                  let id = event.accessory?.bluetoothIdentifier else { return }
            // Same identifier Core Bluetooth uses: retrievePeripherals(withIdentifiers:), then connect
            self?.onSensorAdded?(id)
        }
    }

    func addSensor() {
        let descriptor = ASDiscoveryDescriptor()
        descriptor.bluetoothServiceUUID = serviceUUID
        let item = ASPickerDisplayItem(
            name: "Sensor",
            productImage: UIImage(named: "sensor") ?? UIImage(),
            descriptor: descriptor
        )
        session.showPicker(for: [item]) { error in
            if let error { print("Picker closed: \(error)") }
        }
    }
}

Remember the Info.plist too (NSAccessorySetupKitSupports with Bluetooth, and your service UUIDs under NSAccessorySetupBluetoothServices), or the picker will very politely refuse to find your sensor, and you will spend a lovely hour wondering why.

So what would I actually do?

Good news first: nothing here asks for a rewrite. On Android, I would fix the read loop today, then spend an afternoon pressing the reset button on a real device while the app is open (finally, a legitimate reason), and make sure the "reconnecting" state looks like a plan rather than a crash. On iOS, I would move the setup flow to AccessorySetupKit if it is not there yet, since it removes a permission prompt now and opens the door to distance later. And if your hardware team is choosing a chipset for next year, slip the words "Bluetooth 6.3" into the next meeting, ideally before the boards are ordered and not after.

If you want help

Bluetooth and NFC apps, from the first scan to the last firmware update, are what the Bluetooth and NFC app development page is about. If you would rather know first how your current app copes with a lost bond or a dropped link, the five-day health check looks at exactly that.

Sources: Android 17 behavior changes, all apps, Android 17 behavior changes, apps targeting Android 17, BluetoothDevice reference, Transfer Bluetooth data, Android 17 is here, Find your accessory with Bluetooth Channel Sounding (WWDC26), Meet AccessorySetupKit (WWDC24), Apple Newsroom, iPhone 17, Apple Newsroom, 14 September 2026.

Working on a mobile app? Let's talk.

Keep your app shippable

One email a week on store deadlines and what breaks in production. Written for people who own an app, not just for developers.

One email a week about keeping mobile apps shippable. Unsubscribe in one click, and I never pass your address on. How I handle your data: privacy notice.