diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2799b7758..0b878ee71 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -53,10 +53,6 @@
-
-
-
-
@@ -68,11 +64,6 @@
android:name="android.hardware.bluetooth_le"
android:required="false" />
-
-
-
= android.os.Build.VERSION_CODES.O)
- packageManager.hasSystemFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP)
- else false
-
/**
* @return true if the device has a GPS receiver
*/
@@ -126,7 +112,7 @@ fun Context.getBluetoothPermissions(): Array {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
perms.add(Manifest.permission.BLUETOOTH_SCAN)
perms.add(Manifest.permission.BLUETOOTH_CONNECT)
- } else if (!hasCompanionDeviceApi()) {
+ } else {
perms.add(Manifest.permission.ACCESS_FINE_LOCATION)
}
return getMissingPermissions(perms)
diff --git a/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt b/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt
index 172338d12..12088bb3e 100644
--- a/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt
+++ b/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt
@@ -3,22 +3,16 @@ package com.geeksville.mesh.model
import android.annotation.SuppressLint
import android.app.Application
import android.bluetooth.BluetoothDevice
-import android.companion.AssociationRequest
-import android.companion.BluetoothDeviceFilter
-import android.companion.BluetoothLeDeviceFilter
-import android.companion.CompanionDeviceManager
-import android.content.*
+import android.content.Context
import android.hardware.usb.UsbManager
import android.net.nsd.NsdServiceInfo
import android.os.RemoteException
-import androidx.activity.result.IntentSenderRequest
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.R
-import com.geeksville.mesh.android.*
import com.geeksville.mesh.repository.bluetooth.BluetoothRepository
import com.geeksville.mesh.repository.network.NetworkRepository
import com.geeksville.mesh.repository.radio.InterfaceId
@@ -34,7 +28,6 @@ import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
-import java.util.regex.Pattern
import javax.inject.Inject
@HiltViewModel
@@ -151,10 +144,11 @@ class BTScanModel @Inject constructor(
/// Use the string for the NopInterface
val selectedNotNull: String get() = selectedAddress ?: "n"
- private fun addDevice(entry: DeviceListEntry) {
- val oldDevs = devices.value!!
- oldDevs[entry.fullAddress] = entry // Add/replace entry
- devices.value = oldDevs // trigger gui updates
+ val scanResult = MutableLiveData>(mutableMapOf())
+
+ fun clearScanResults() {
+ stopScan()
+ scanResult.value = mutableMapOf()
}
fun stopScan() {
@@ -166,21 +160,16 @@ class BTScanModel @Inject constructor(
warn("Ignoring error stopping scan, probably BT adapter was disabled suddenly: ${ex.message}")
} finally {
scanJob = null
- _spinner.value = false
}
- } else _spinner.value = false
- }
-
- fun startScan(context: Context?) {
- _spinner.value = true
-
- if (context != null) startCompanionScan(context) else startClassicScan()
+ }
+ _spinner.value = false
}
@SuppressLint("MissingPermission")
- private fun startClassicScan() {
+ fun startScan() {
debug("starting classic scan")
+ _spinner.value = true
scanJob = bluetoothRepository.scan()
.onEach { result ->
val fullAddress = radioInterfaceService.toInterfaceAddress(
@@ -189,12 +178,13 @@ class BTScanModel @Inject constructor(
)
// prevent log spam because we'll get lots of redundant scan results
val isBonded = result.device.bondState == BluetoothDevice.BOND_BONDED
- val oldDevs = devices.value ?: emptyMap()
+ val oldDevs = scanResult.value!!
val oldEntry = oldDevs[fullAddress]
// Don't spam the GUI with endless updates for non changing nodes
if (oldEntry == null || oldEntry.bonded != isBonded) {
val entry = DeviceListEntry(result.device.name, fullAddress, isBonded)
- addDevice(entry)
+ oldDevs[entry.fullAddress] = entry
+ scanResult.value = oldDevs
}
}.catch { ex ->
serviceRepository.setErrorMessage("Unexpected Bluetooth scan failure: ${ex.message}")
@@ -271,72 +261,6 @@ class BTScanModel @Inject constructor(
}
}
- fun onSelectedBle(address: String): Boolean {
- val device = bluetoothRepository.getRemoteDevice(address) ?: return false
- return onSelected(BLEDeviceListEntry(device))
- }
-
private val _spinner = MutableLiveData(false)
val spinner: LiveData get() = _spinner
-
- private val _associationRequest = MutableLiveData(null)
- val associationRequest: LiveData get() = _associationRequest
-
- /**
- * Called immediately after fragment observes CompanionDeviceManager activity result
- */
- fun clearAssociationRequest() {
- _associationRequest.value = null
- }
-
- @SuppressLint("NewApi")
- private fun associationRequest(): AssociationRequest = AssociationRequest.Builder()
- .addDeviceFilter(
- BluetoothDeviceFilter.Builder()
- .setNamePattern(Pattern.compile(BLE_NAME_PATTERN))
- .build()
- )
- .addDeviceFilter(
- BluetoothLeDeviceFilter.Builder()
- .setNamePattern(Pattern.compile(BLE_NAME_PATTERN))
- // .setScanFilter(
- // ScanFilter.Builder()
- // .setServiceUuid(ParcelUuid(BluetoothInterface.BTM_SERVICE_UUID))
- // .build()
- // )
- .build()
- )
- .setSingleDevice(false)
- .build()
-
- @SuppressLint("NewApi")
- private fun startCompanionScan(context: Context) {
- debug("starting companion scan")
- context.companionDeviceManager?.associate(
- associationRequest(),
- object : CompanionDeviceManager.Callback() {
- @Deprecated("Deprecated in Java", ReplaceWith("onAssociationPending(intentSender)"))
- override fun onDeviceFound(intentSender: IntentSender) {
- onAssociationPending(intentSender)
- }
-
- override fun onAssociationPending(chooserLauncher: IntentSender) {
- debug("CompanionDeviceManager - device found")
- _spinner.value = false
- chooserLauncher.let {
- val request: IntentSenderRequest = IntentSenderRequest.Builder(it).build()
- _associationRequest.value = request
- }
- }
-
- override fun onFailure(error: CharSequence?) {
- warn("BLE selection service failed $error")
- }
- }, null
- )
- }
-
- companion object {
- const val BLE_NAME_PATTERN = BluetoothRepository.BLE_NAME_PATTERN
- }
}
diff --git a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt
index 2edfa427e..7b253b7fc 100644
--- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt
+++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt
@@ -15,6 +15,7 @@ import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.RadioButton
import androidx.activity.result.contract.ActivityResultContracts
+import androidx.appcompat.app.AlertDialog
import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.asLiveData
@@ -29,7 +30,6 @@ import com.geeksville.mesh.model.getInitials
import com.geeksville.mesh.repository.location.LocationRepository
import com.geeksville.mesh.service.MeshService
import com.geeksville.mesh.util.exceptionToSnackbar
-import com.geeksville.mesh.util.getAssociationResult
import com.geeksville.mesh.util.onEditorAction
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
@@ -50,7 +50,6 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
internal lateinit var locationRepository: LocationRepository
private val hasGps by lazy { requireContext().hasGps() }
- private val hasCompanionDeviceApi by lazy { requireContext().hasCompanionDeviceApi() }
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
@@ -139,14 +138,6 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
private fun initCommonUI() {
- val associationResultLauncher = registerForActivityResult(
- ActivityResultContracts.StartIntentSenderForResult()
- ) {
- it.data
- ?.getAssociationResult()
- ?.let { address -> scanModel.onSelectedBle(address) }
- }
-
val requestBackgroundAndCheckLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
if (permissions.entries.any { !it.value }) {
@@ -202,19 +193,36 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
}
}
+ var scanDialog: AlertDialog? = null
+ scanModel.scanResult.observe(viewLifecycleOwner) { results ->
+ val devices = results.values.ifEmpty { return@observe }
+ scanDialog?.dismiss()
+ scanDialog = MaterialAlertDialogBuilder(requireContext())
+ .setTitle("Select a Bluetooth device")
+ .setSingleChoiceItems(
+ devices.map { it.name }.toTypedArray(),
+ -1
+ ) { dialog, position ->
+ val selectedDevice = devices.elementAt(position)
+ scanModel.onSelected(selectedDevice)
+ scanModel.clearScanResults()
+ dialog.dismiss()
+ scanDialog = null
+ }
+ .setPositiveButton(R.string.cancel) { dialog, _ ->
+ scanModel.clearScanResults()
+ dialog.dismiss()
+ scanDialog = null
+ }
+ .show()
+ }
+
// show the spinner when [spinner] is true
scanModel.spinner.observe(viewLifecycleOwner) { show ->
binding.changeRadioButton.isEnabled = !show
binding.scanProgressBar.visibility = if (show) View.VISIBLE else View.GONE
}
- scanModel.associationRequest.observe(viewLifecycleOwner) { request ->
- request?.let {
- associationResultLauncher.launch(request)
- scanModel.clearAssociationRequest()
- }
- }
-
binding.usernameEditText.onEditorAction(EditorInfo.IME_ACTION_DONE) {
debug("received IME_ACTION_DONE")
val n = binding.usernameEditText.text.toString().trim()
@@ -372,7 +380,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
private var scanning = false
private fun scanLeDevice() {
if (!checkBTEnabled()) return
- if (!hasCompanionDeviceApi) checkLocationEnabled()
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) checkLocationEnabled()
if (!scanning) { // Stops scanning after a pre-defined scan period.
Handler(Looper.getMainLooper()).postDelayed({
@@ -380,7 +388,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
scanModel.stopScan()
}, SCAN_PERIOD)
scanning = true
- scanModel.startScan(requireActivity().takeIf { hasCompanionDeviceApi })
+ scanModel.startScan()
} else {
scanning = false
scanModel.stopScan()
diff --git a/app/src/main/java/com/geeksville/mesh/util/CompatExtensions.kt b/app/src/main/java/com/geeksville/mesh/util/CompatExtensions.kt
index fa6540e1b..07b2f361d 100644
--- a/app/src/main/java/com/geeksville/mesh/util/CompatExtensions.kt
+++ b/app/src/main/java/com/geeksville/mesh/util/CompatExtensions.kt
@@ -1,10 +1,6 @@
package com.geeksville.mesh.util
import android.app.PendingIntent
-import android.bluetooth.BluetoothDevice
-import android.bluetooth.le.ScanResult
-import android.companion.AssociationInfo
-import android.companion.CompanionDeviceManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@@ -45,19 +41,3 @@ fun Context.registerReceiverCompat(
) {
ContextCompat.registerReceiver(this, receiver, filter, flag)
}
-
-fun Intent.getAssociationResult(): String? = when {
- android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU ->
- getParcelableExtraCompat(CompanionDeviceManager.EXTRA_ASSOCIATION)
- ?.deviceMacAddress?.toString()?.uppercase()
-
- android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O ->
- @Suppress("DEPRECATION")
- when (val it = getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE)) {
- is BluetoothDevice -> it.address
- is ScanResult -> it.device.address
- else -> null
- }
-
- else -> null
-}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 265341ae3..f889f67c6 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -82,7 +82,7 @@
Very Long Range / Slow
UNRECOGNIZED
Service notifications
- Location must be turned on (high accuracy) to find new devices via bluetooth. You can turn it off again afterwards.
+ Location must be turned on to find new devices via Bluetooth. You can turn it off again afterwards.
About
A list of nodes in the mesh
Text messages