0

I've got my SettingsActivity allowing the user to choose form a list of paired Bluetooth devices.

However, there seems to be something strange going on with Bluetooth Low Energy where devices don't pair normally: the device I'm trying to connect to won't pair to any of my android devices, and I notice that my Fitbit isn't is the list of paired devices on my phone even though it seems to be working. WTF?

Anyway, the question is: how do I add to the list of proper devices the list of BLE devices?

(I've looked at https://developer.android.com/guide/topics/connectivity/bluetooth-le#find but it's just disjointed bits of unexplained code; it doesn't say where to put them, how to call them, or how they fit together, and if I copy it then it comes up with loads of errors.)

class SettingsActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // get bluetooth devices
        var btDevices: Array<CharSequence> = arrayOf("")
        try {
            val bt: BluetoothManager =
                getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager;
            val bta: BluetoothAdapter = bt.adapter;

            // Get normal Bluetooth devices.
            val pairedDevices: Set<BluetoothDevice> = bta.bondedDevices

            // Get Bluetooth Low Energy devices.

            // HOW?!

            btDevices = pairedDevices.map { z -> z.name }.toTypedArray()
        }
        catch(e:Exception) {}

        // Start the fragment
        setContentView(R.layout.settings_activity)
        supportFragmentManager
            .beginTransaction()
            .replace(R.id.settings, SettingsFragment(cs))
            .commit()
        supportActionBar?.setDisplayHomeAsUpEnabled(true)

        // Slap the toolbar.
        val toolbar = findViewById<Toolbar>(R.id.settings_toolbar) // Must be after setContentView or else it returns null.
        setSupportActionBar(toolbar)
        toolbar.setNavigationOnClickListener(object : View.OnClickListener {
            override fun onClick(v: View?) {
                finish()
            }
        })
    }

    class SettingsFragment(adapters: Array<CharSequence>) : PreferenceFragmentCompat() {
        private var adapters: Array<CharSequence> = adapters

        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
           setPreferencesFromResource(R.xml.root_preferences, rootKey)

            val p:ListPreference? = findPreference<ListPreference>("bluetoothName")
            p?.setEntries(adapters)
            p?.setEntryValues(adapters)
        }
    }
}
Richard Barraclough
  • 2,625
  • 3
  • 36
  • 54

1 Answers1

0

I'm sure this is very wrong, but it does seem to work.

The trick to it is to put a reference to add to the PreferenceFragment a public method to update the list, then pass a reference of the PreferencesFragment to the ScanCallback so that it can send its updated list to the preferences.

package com.rwb.psamfd

import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothManager
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanResult
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.preference.ListPreference
import androidx.preference.PreferenceFragmentCompat

class SettingsActivity : AppCompatActivity() {

    private val bleScanCallback = @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    object : ScanCallback(){
        public var settings:SettingsFragment? = null;

        override fun onScanResult(callbackType: Int, result: ScanResult?) {
            super.onScanResult(callbackType, result)
            Log.d(
                "DeviceListActivity",
                "onScanResult: ${result?.device?.address} - ${result?.device?.name}"
            )
            if (result?.device?.name != null && !btAdapters.contains(result?.device?.name!!)) {
                btAdapters.add(result?.device?.name!!)
            }
            settings?.updateAdapters(btAdapters)
        }

        override fun onBatchScanResults(results: MutableList<ScanResult>?) {
            super.onBatchScanResults(results)
            Log.d("DeviceListActivity","onBatchScanResults:${results.toString()}")
            if(results != null){
                for(result:ScanResult in results!!)
                {
                    if(result.device?.name != null && !btAdapters.contains(result.device?.name!! ))
                    {
                        btAdapters.add(result.device?.name!! )
                    }
                }
                settings?.updateAdapters(btAdapters)
            }
        }

        override fun onScanFailed(errorCode: Int) {
            super.onScanFailed(errorCode)
            Log.d("DeviceListActivity", "onScanFailed: $errorCode")
        }
    }

    private lateinit var btm : BluetoothManager
    private lateinit var bta: BluetoothAdapter

    // This is initialised from paired normal Bluetooth, then added to by BLE.
    // BLE has a reference to the preferences fragment on which it calles the update method when new devices are found.
    private val btAdapters: ArrayList<String> = ArrayList<String>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        btm = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager;
        bta = btm.adapter

        // get bluetooth devices
        try {
            // Normal Bluetooth.
            for(p:BluetoothDevice in bta.bondedDevices)
            {
                if(p.name != null) {
                    btAdapters.add(p.name)
                }
            }

            // Bluetooth Low Energy done in onResume and onPause.
        }
        catch(e:Exception) {}

        // Start the fragment
        val sf:SettingsFragment = SettingsFragment(btAdapters)

        setContentView(R.layout.settings_activity)
        supportFragmentManager
            .beginTransaction()
            .replace(R.id.settings, sf)
            .commit()
        supportActionBar?.setDisplayHomeAsUpEnabled(true)

        // Connect the SettingsFragment to the ScanCallback.
        bleScanCallback.settings = sf

        // Slap the toolbar.
        val toolbar = findViewById<Toolbar>(R.id.settings_toolbar) // Must be after setContentView or else it returns null.
        setSupportActionBar(toolbar)
        toolbar.setNavigationOnClickListener(object : View.OnClickListener {
            override fun onClick(v: View?) {
                finish()
            }
        })
    }

    override fun onResume() {
        super.onResume()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            bta.bluetoothLeScanner.startScan(bleScanCallback)
        }
    }

    override fun onPause() {
        super.onPause()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            bta.bluetoothLeScanner.stopScan(bleScanCallback)
        }
    }

    class SettingsFragment(btAdapters: ArrayList<String>) : PreferenceFragmentCompat() {
        private var btAdapters: Array<CharSequence> = btAdapters.map{z -> z as CharSequence}.toTypedArray()
        private lateinit var btPreference : ListPreference

        fun updateAdapters(newBtAdapters: ArrayList<String>){
            btAdapters = newBtAdapters.map{z -> z as CharSequence}.toTypedArray()
            btPreference.setEntries(btAdapters)
            btPreference.setEntryValues(btAdapters)
        }

        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
           setPreferencesFromResource(R.xml.root_preferences, rootKey)

            btPreference = findPreference<ListPreference>("bluetoothName")!!
            btPreference.setEntries(btAdapters)
            btPreference.setEntryValues(btAdapters)
        }
    }
}
Richard Barraclough
  • 2,625
  • 3
  • 36
  • 54