This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Having Android reconnect to a bonded peripheral without user interaction

I've read the Robin Heydon book and bits of the Core 4.0 spec and I'm still confused as to how an Android phone with my app installed is supposed to reconnect to my peripheral to which is has previously connected and bonded.

Suppose my peripheral is a device designed to detect the presence of its owner, who is always carrying their mobile phone and that phone is always switched on, but may be in standby mode. Suppose the mobile is running Android 4.4 or later.

The Android BLE API Guide is quite clear about the initial connection: doing an LE scan, connecting to the GATT server, discovering services, etc. But it says nothing about subsequent reconnections and very little about bonding in the context of LE.

In order for a connection to be established, the peripheral has to be advertising and the central has to be scanning. My peripheral is an nRF51822 and I can have it advertise at the right times, no problem there. But if the central is an Android phone, when does it scan?

Does it only scan when I programatically call startLeScan() in my app code? I don't think this is the case - I've seen a reconnection happen without that.

Is there a background process running on Android that is always scanning for devices to which it has previously bonded? That also doesn't seem to be the case, at least not reliably - reconnection happens for a few hours after I've programmatically scanned but within a day or so it's stopped, I think.

(Using the Sniffer and Wireshark is of limited use here. Since it's scanning I'm interested in, there's nothing to see on the air because a scan is just RX. There's the scan request and response to see afterwards, but I'm more interested in when Android is doing the bit before that, the RX bit.)

Parents
  • I know this is an old post but since no one really answered it I'll show what I did. This is written in Kotlin btw.

    I don't know if this is the best solution but it works for my case. I wanted the app to auto connect to a peripheral device that's previously bonded and advertising.

    I created a fragment that runs the code below. The fragment is part of a fragment container and is part of the the MainActivity layout.

    It basically registers a bluetooth broadcast receiver to watch the state. When bluetooth is turned on, it reads through the bonded devices list and if one of the devices has the name of your device then it will call Nordic's autoconnect function for the device.

    class DeviceMonitorFragment : Fragment() {
        private var _fragmentDeviceMonitorBinding: FragmentDeviceMonitorBinding? = null
        private val fragmentDeviceMonitorBinding get() = _fragmentDeviceMonitorBinding!!
    
        /**
         * Checks the bluetooth state and try to auto connect the
         * ble device when bluetooth is turned on and active.
         * The device should connect if it is on and bonded.
         * Otherwise the user will have to manually connect it from
         * the scanner fragment or through the Android Bluetooth settings.
         */
        private val mReceiver: BroadcastReceiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                val action = intent.action
                if (BluetoothAdapter.ACTION_STATE_CHANGED == action) {
                    when (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)) {
                        BluetoothAdapter.STATE_TURNING_OFF,
                        BluetoothAdapter.STATE_OFF,
                        BluetoothAdapter.STATE_TURNING_ON,
                        -> {
                        }
                        BluetoothAdapter.STATE_ON -> {
                            // Attempt to connect device
                            autoConnectDeviceIfMatch(requireContext())
                        }
                    }
                }
            }
        }
    
        override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?,
        ): View {
            _fragmentDeviceMonitorBinding = FragmentDeviceMonitorBinding.inflate(inflater, container, false)
            return fragmentDeviceMonitorBinding.root
        }
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
    
            /** Register broadcast receiver to watch bluetooth state */
            requireContext().registerReceiver(mReceiver, IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED))
    
            /** Attempt to connect device if bluetooth is enabled and device is advertising */
            autoConnectDeviceIfMatch(requireContext())
        }
    
        /**
         * Connects to the ble device if it is bonded and the
         * name matches our device name
         *
         * @param context
         */
        @SuppressLint("MissingPermission")
        fun autoConnectDeviceIfMatch(context: Context){
            val manager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
            if(manager.adapter.isEnabled){
                val devices = manager.adapter.bondedDevices
                devices.forEach { device ->
                    if (device.bondState == BluetoothDevice.BOND_BONDED && device.name == context.getString(R.string.app_name)) {
                        DataShared.device.connect(context, device)
                        return@forEach // exit on match
                    }
                }
            }
        }
    
        override fun onDestroy() {
            requireContext().unregisterReceiver(mReceiver)
            _fragmentDeviceMonitorBinding = null
            super.onDestroy()
        }
    }

    The code below would be located in a device manager of sorts. Similar to the Blinky example for Android.

        /**
         * Connect to the given peripheral
         * (Primarily for use by the auto-bonding)
         *
         * @param target the target device
         */
        @SuppressLint("MissingPermission")
        fun connect(context: Context, target: BluetoothDevice) {
            if (_bleDevice == null) {
                _bleDevice = target
                val logSession = Logger
                    .newSession(context, null, target.address, target.name)
                _bleDeviceManager.setLogger(logSession)
            }
            
            autoConnect()
        }
    
        /**
         * Reconnects to previously connected device
         * If this device was not supported, its services were cleared on disconnection, so
         * reconnection may help
         */
        private fun autoConnect() {
            if (_bleDevice != null) {
                _bleDeviceManager.connect(_bleDevice!!)
                    .retry(3, 100)
                    .useAutoConnect(true)
                    .enqueue()
            }
        }

Reply
  • I know this is an old post but since no one really answered it I'll show what I did. This is written in Kotlin btw.

    I don't know if this is the best solution but it works for my case. I wanted the app to auto connect to a peripheral device that's previously bonded and advertising.

    I created a fragment that runs the code below. The fragment is part of a fragment container and is part of the the MainActivity layout.

    It basically registers a bluetooth broadcast receiver to watch the state. When bluetooth is turned on, it reads through the bonded devices list and if one of the devices has the name of your device then it will call Nordic's autoconnect function for the device.

    class DeviceMonitorFragment : Fragment() {
        private var _fragmentDeviceMonitorBinding: FragmentDeviceMonitorBinding? = null
        private val fragmentDeviceMonitorBinding get() = _fragmentDeviceMonitorBinding!!
    
        /**
         * Checks the bluetooth state and try to auto connect the
         * ble device when bluetooth is turned on and active.
         * The device should connect if it is on and bonded.
         * Otherwise the user will have to manually connect it from
         * the scanner fragment or through the Android Bluetooth settings.
         */
        private val mReceiver: BroadcastReceiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                val action = intent.action
                if (BluetoothAdapter.ACTION_STATE_CHANGED == action) {
                    when (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)) {
                        BluetoothAdapter.STATE_TURNING_OFF,
                        BluetoothAdapter.STATE_OFF,
                        BluetoothAdapter.STATE_TURNING_ON,
                        -> {
                        }
                        BluetoothAdapter.STATE_ON -> {
                            // Attempt to connect device
                            autoConnectDeviceIfMatch(requireContext())
                        }
                    }
                }
            }
        }
    
        override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?,
        ): View {
            _fragmentDeviceMonitorBinding = FragmentDeviceMonitorBinding.inflate(inflater, container, false)
            return fragmentDeviceMonitorBinding.root
        }
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
    
            /** Register broadcast receiver to watch bluetooth state */
            requireContext().registerReceiver(mReceiver, IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED))
    
            /** Attempt to connect device if bluetooth is enabled and device is advertising */
            autoConnectDeviceIfMatch(requireContext())
        }
    
        /**
         * Connects to the ble device if it is bonded and the
         * name matches our device name
         *
         * @param context
         */
        @SuppressLint("MissingPermission")
        fun autoConnectDeviceIfMatch(context: Context){
            val manager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
            if(manager.adapter.isEnabled){
                val devices = manager.adapter.bondedDevices
                devices.forEach { device ->
                    if (device.bondState == BluetoothDevice.BOND_BONDED && device.name == context.getString(R.string.app_name)) {
                        DataShared.device.connect(context, device)
                        return@forEach // exit on match
                    }
                }
            }
        }
    
        override fun onDestroy() {
            requireContext().unregisterReceiver(mReceiver)
            _fragmentDeviceMonitorBinding = null
            super.onDestroy()
        }
    }

    The code below would be located in a device manager of sorts. Similar to the Blinky example for Android.

        /**
         * Connect to the given peripheral
         * (Primarily for use by the auto-bonding)
         *
         * @param target the target device
         */
        @SuppressLint("MissingPermission")
        fun connect(context: Context, target: BluetoothDevice) {
            if (_bleDevice == null) {
                _bleDevice = target
                val logSession = Logger
                    .newSession(context, null, target.address, target.name)
                _bleDeviceManager.setLogger(logSession)
            }
            
            autoConnect()
        }
    
        /**
         * Reconnects to previously connected device
         * If this device was not supported, its services were cleared on disconnection, so
         * reconnection may help
         */
        private fun autoConnect() {
            if (_bleDevice != null) {
                _bleDeviceManager.connect(_bleDevice!!)
                    .retry(3, 100)
                    .useAutoConnect(true)
                    .enqueue()
            }
        }

Children
No Data
Related