kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
fix companion device pairing
rodzic
d843ab2aa6
commit
e8999712d2
|
@ -44,6 +44,9 @@
|
|||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
|
||||
<!-- For android >=26 we can use the new BLE scanning API, which allows auto launching our service when our device is seen -->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
|
||||
android:usesPermissionFlags="neverForLocation" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
<uses-permission android:name="android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND" />
|
||||
<uses-permission android:name="android.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND" />
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@ import android.app.Activity
|
|||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.companion.AssociationRequest
|
||||
import android.companion.BluetoothDeviceFilter
|
||||
import android.companion.CompanionDeviceManager
|
||||
import android.content.*
|
||||
import android.content.pm.PackageInfo
|
||||
|
@ -70,6 +72,7 @@ import java.lang.Runnable
|
|||
import java.nio.charset.Charset
|
||||
import java.text.DateFormat
|
||||
import java.util.*
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
|
||||
|
@ -131,8 +134,7 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
const val REQUEST_ENABLE_BT = 10
|
||||
const val DID_REQUEST_PERM = 11
|
||||
const val RC_SIGN_IN = 12 // google signin completed
|
||||
const val RC_SELECT_DEVICE =
|
||||
13 // seems to be hardwired in CompanionDeviceManager to add 65536
|
||||
const val SELECT_DEVICE_REQUEST_CODE = 13
|
||||
const val CREATE_CSV_FILE = 14
|
||||
}
|
||||
|
||||
|
@ -194,11 +196,52 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private val btStateReceiver = BluetoothStateReceiver { _ ->
|
||||
updateBluetoothEnabled()
|
||||
}
|
||||
|
||||
fun startCompanionScan() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val deviceManager: CompanionDeviceManager by lazy {
|
||||
getSystemService(Context.COMPANION_DEVICE_SERVICE) as CompanionDeviceManager
|
||||
}
|
||||
|
||||
// To skip filtering based on name and supported feature flags (UUIDs),
|
||||
// don't include calls to setNamePattern() and addServiceUuid(),
|
||||
// respectively. This example uses Bluetooth.
|
||||
// We only look for Mesh (rather than the full name) because NRF52 uses a very short name
|
||||
val deviceFilter: BluetoothDeviceFilter = BluetoothDeviceFilter.Builder()
|
||||
.setNamePattern(Pattern.compile("Mesh.*"))
|
||||
// .addServiceUuid(ParcelUuid(RadioInterfaceService.BTM_SERVICE_UUID), null)
|
||||
.build()
|
||||
|
||||
// The argument provided in setSingleDevice() determines whether a single
|
||||
// device name or a list of device names is presented to the user as
|
||||
// pairing options.
|
||||
val pairingRequest: AssociationRequest = AssociationRequest.Builder()
|
||||
.addDeviceFilter(deviceFilter)
|
||||
.setSingleDevice(false)
|
||||
.build()
|
||||
|
||||
// When the app tries to pair with the Bluetooth device, show the
|
||||
// appropriate pairing request dialog to the user.
|
||||
deviceManager.associate(pairingRequest,
|
||||
object : CompanionDeviceManager.Callback() {
|
||||
override fun onDeviceFound(chooserLauncher: IntentSender) {
|
||||
startIntentSenderForResult(chooserLauncher,
|
||||
SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0)
|
||||
}
|
||||
|
||||
override fun onFailure(error: CharSequence?) {
|
||||
warn("BLE selection service failed $error")
|
||||
// changeDeviceSelection(mainActivity, null) // deselect any device
|
||||
}
|
||||
}, null
|
||||
)
|
||||
}
|
||||
else warn("startCompanionScan should not run on SDK < 26")
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't tell our app we have bluetooth until we have bluetooth _and_ location access
|
||||
*/
|
||||
|
@ -410,7 +453,6 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// Ask user to rate in play store
|
||||
private fun askToRate() {
|
||||
exceptionReporter { // Got one IllegalArgumentException from inside this lib, but we don't want to crash our app because of bugs in this optional feature
|
||||
|
@ -497,7 +539,6 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
requestPermission()
|
||||
}
|
||||
|
||||
|
||||
private fun initToolbar() {
|
||||
val toolbar =
|
||||
findViewById<View>(R.id.toolbar) as Toolbar
|
||||
|
@ -601,25 +642,18 @@ class MainActivity : AppCompatActivity(), Logging,
|
|||
GoogleSignIn.getSignedInAccountFromIntent(data)
|
||||
handleSignInResult(task)
|
||||
}
|
||||
(65536 + RC_SELECT_DEVICE) -> when (resultCode) {
|
||||
(SELECT_DEVICE_REQUEST_CODE) -> when (resultCode) {
|
||||
Activity.RESULT_OK -> {
|
||||
// User has chosen to pair with the Bluetooth device.
|
||||
val device: BluetoothDevice =
|
||||
val deviceToPair: BluetoothDevice =
|
||||
data?.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE)!!
|
||||
debug("Received BLE pairing ${device.address}")
|
||||
if (device.bondState != BluetoothDevice.BOND_BONDED) {
|
||||
device.createBond()
|
||||
// FIXME - wait for bond to complete
|
||||
if (deviceToPair.bondState != BluetoothDevice.BOND_BONDED) {
|
||||
deviceToPair.createBond()
|
||||
}
|
||||
|
||||
// ... Continue interacting with the paired device.
|
||||
model.meshService?.let { service ->
|
||||
MeshService.changeDeviceAddress(this@MainActivity, service, device.address)
|
||||
MeshService.changeDeviceAddress(this@MainActivity, service, "x${deviceToPair.address}")
|
||||
}
|
||||
}
|
||||
|
||||
else ->
|
||||
warn("BLE device select intent failed")
|
||||
else -> warn("BLE device select intent failed")
|
||||
}
|
||||
CREATE_CSV_FILE -> {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
|
|
|
@ -7,9 +7,6 @@ import android.bluetooth.BluetoothDevice
|
|||
import android.bluetooth.BluetoothDevice.BOND_BONDED
|
||||
import android.bluetooth.BluetoothDevice.BOND_BONDING
|
||||
import android.bluetooth.le.*
|
||||
import android.companion.AssociationRequest
|
||||
import android.companion.BluetoothDeviceFilter
|
||||
import android.companion.CompanionDeviceManager
|
||||
import android.content.*
|
||||
import android.content.pm.PackageManager
|
||||
import android.hardware.usb.UsbDevice
|
||||
|
@ -56,7 +53,6 @@ import com.hoho.android.usbserial.driver.UsbSerialDriver
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import java.util.regex.Pattern
|
||||
|
||||
object SLogging : Logging
|
||||
|
||||
|
@ -106,7 +102,6 @@ private fun requestBonding(
|
|||
device.createBond()
|
||||
}
|
||||
|
||||
|
||||
class BTScanModel(app: Application) : AndroidViewModel(app), Logging {
|
||||
|
||||
private val context: Context get() = getApplication<Application>().applicationContext
|
||||
|
@ -123,7 +118,6 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging {
|
|||
else
|
||||
null
|
||||
|
||||
|
||||
override fun toString(): String {
|
||||
return "DeviceListEntry(name=${name.anonymize}, addr=${address.anonymize})"
|
||||
}
|
||||
|
@ -187,7 +181,7 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging {
|
|||
|
||||
if ((result.device.name?.startsWith("Mesh") == true)) {
|
||||
val addr = result.device.address
|
||||
val fullAddr = "x$addr" // full address with the bluetooh prefix
|
||||
val fullAddr = "x$addr" // full address with the bluetooth prefix added
|
||||
// prevent logspam because weill get get lots of redundant scan results
|
||||
val isBonded = result.device.bondState == BluetoothDevice.BOND_BONDED
|
||||
val oldDevs = devices.value!!
|
||||
|
@ -456,10 +450,6 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
|||
BluetoothInterface.hasCompanionDeviceApi(requireContext())
|
||||
}
|
||||
|
||||
private val deviceManager: CompanionDeviceManager by lazy {
|
||||
requireContext().getSystemService(CompanionDeviceManager::class.java)
|
||||
}
|
||||
|
||||
private val myActivity get() = requireActivity() as MainActivity
|
||||
|
||||
override fun onDestroy() {
|
||||
|
@ -540,6 +530,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
|||
|
||||
val isConnected = connected == MeshService.ConnectionState.CONNECTED
|
||||
binding.nodeSettings.visibility = if (isConnected) View.VISIBLE else View.GONE
|
||||
binding.provideLocationCheckbox.visibility = if (isConnected) View.VISIBLE else View.GONE
|
||||
|
||||
if (connected == MeshService.ConnectionState.DISCONNECTED)
|
||||
model.ownerName.value = ""
|
||||
|
@ -720,7 +711,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
|||
device.address == scanModel.selectedNotNull && device.bonded // Only show checkbox if device is still paired
|
||||
binding.deviceRadioGroup.addView(b)
|
||||
|
||||
// Once we have at least one device, don't show the "looking for" animation - it makes uers think
|
||||
// Once we have at least one device, don't show the "looking for" animation - it makes users think
|
||||
// something is busted
|
||||
binding.scanProgressBar.visibility = View.INVISIBLE
|
||||
|
||||
|
@ -830,56 +821,6 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
|||
{ updateDevicesButtons(scanModel.devices.value) })
|
||||
}
|
||||
|
||||
/// Start running the modern scan, once it has one result we enable the
|
||||
private fun startBackgroundScan() {
|
||||
// Disable the change button until our scan has some results
|
||||
binding.changeRadioButton.isEnabled = false
|
||||
|
||||
// To skip filtering based on name and supported feature flags (UUIDs),
|
||||
// don't include calls to setNamePattern() and addServiceUuid(),
|
||||
// respectively. This example uses Bluetooth.
|
||||
// We only look for Mesh (rather than the full name) because NRF52 uses a very short name
|
||||
val deviceFilter: BluetoothDeviceFilter = BluetoothDeviceFilter.Builder()
|
||||
.setNamePattern(Pattern.compile("Mesh.*"))
|
||||
// .addServiceUuid(ParcelUuid(RadioInterfaceService.BTM_SERVICE_UUID), null)
|
||||
.build()
|
||||
|
||||
// The argument provided in setSingleDevice() determines whether a single
|
||||
// device name or a list of device names is presented to the user as
|
||||
// pairing options.
|
||||
val pairingRequest: AssociationRequest = AssociationRequest.Builder()
|
||||
.addDeviceFilter(deviceFilter)
|
||||
.setSingleDevice(false)
|
||||
.build()
|
||||
|
||||
// When the app tries to pair with the Bluetooth device, show the
|
||||
// appropriate pairing request dialog to the user.
|
||||
deviceManager.associate(
|
||||
pairingRequest,
|
||||
object : CompanionDeviceManager.Callback() {
|
||||
|
||||
override fun onDeviceFound(chooserLauncher: IntentSender) {
|
||||
debug("Found one device - enabling button")
|
||||
binding.changeRadioButton.isEnabled = true
|
||||
binding.changeRadioButton.setOnClickListener {
|
||||
debug("User clicked BLE change button")
|
||||
|
||||
// Request code seems to be ignored anyways
|
||||
startIntentSenderForResult(
|
||||
chooserLauncher,
|
||||
MainActivity.RC_SELECT_DEVICE, null, 0, 0, 0, null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(error: CharSequence?) {
|
||||
warn("BLE selection service failed $error")
|
||||
// changeDeviceSelection(mainActivity, null) // deselect any device
|
||||
}
|
||||
}, null
|
||||
)
|
||||
}
|
||||
|
||||
private fun initModernScan() {
|
||||
// Turn off the widgets for the classic API
|
||||
binding.scanProgressBar.visibility = View.GONE
|
||||
|
@ -895,8 +836,9 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
|||
binding.scanStatusText.text = getString(R.string.not_paired_yet)
|
||||
binding.changeRadioButton.setText(R.string.select_radio)
|
||||
}
|
||||
|
||||
startBackgroundScan()
|
||||
binding.changeRadioButton.setOnClickListener {
|
||||
myActivity.startCompanionScan()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
|
Ładowanie…
Reference in New Issue