diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 0d635790c..fa10121e0 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -36,6 +36,10 @@
+
+
+
+
+
+
+
=
- GoogleSignIn.getSignedInAccountFromIntent(data)
- handleSignInResult(task)
+ when (requestCode) {
+ RC_SIGN_IN -> {
+ // The Task returned from this call is always completed, no need to attach
+ // a listener.
+ val task: Task =
+ GoogleSignIn.getSignedInAccountFromIntent(data)
+ handleSignInResult(task)
+ }
+ RC_SELECT_DEVICE -> when (resultCode) {
+ Activity.RESULT_OK -> {
+ // User has chosen to pair with the Bluetooth device.
+ val device: BluetoothDevice =
+ data!!.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE)
+ debug("Received BLE pairing ${device.address}")
+ device.createBond()
+ // ... Continue interacting with the paired device.
+ }
+
+ else ->
+ warn("BLE device select intent failed")
+ }
}
}
diff --git a/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt b/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt
index 00b3709e5..c9314a633 100644
--- a/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt
+++ b/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt
@@ -6,6 +6,8 @@ import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothManager
import android.content.Context
import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Build
import android.os.IBinder
import android.os.RemoteException
import androidx.core.content.edit
@@ -197,6 +199,18 @@ class RadioInterfaceService : Service(), Logging {
MeshService.startService(context)
}
}
+
+ /// Can we use the modern BLE scan API?
+ fun hasCompanionDeviceApi(context: Context): Boolean =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val res =
+ context.packageManager.hasSystemFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP)
+ debug("CompanionDevice API available=$res")
+ res
+ } else {
+ warn("CompanionDevice API not available, falling back to classic scan")
+ false
+ }
}
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 df8555913..cb0536750 100644
--- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt
+++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt
@@ -1,13 +1,14 @@
package com.geeksville.mesh.ui
+import android.annotation.SuppressLint
import android.app.Application
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothManager
import android.bluetooth.le.*
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
+import android.companion.AssociationRequest
+import android.companion.BluetoothDeviceFilter
+import android.companion.CompanionDeviceManager
+import android.content.*
import android.os.Bundle
import android.os.ParcelUuid
import android.view.LayoutInflater
@@ -23,17 +24,19 @@ import com.geeksville.android.GeeksvilleApplication
import com.geeksville.android.Logging
import com.geeksville.android.hideKeyboard
import com.geeksville.mesh.MainActivity
+import com.geeksville.mesh.MainActivity.Companion.RC_SELECT_DEVICE
import com.geeksville.mesh.R
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.service.RadioInterfaceService
import com.geeksville.util.exceptionReporter
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.android.synthetic.main.settings_fragment.*
+import java.util.regex.Pattern
object SLogging : Logging {}
/// Change to a new macaddr selection, updating GUI and radio
-fun changeDeviceSelection(context: MainActivity, newAddr: String) {
+fun changeDeviceSelection(context: MainActivity, newAddr: String?) {
RadioInterfaceService.setBondedDeviceAddress(context, newAddr)
// Super ugly hack. we force the activity to reconnect FIXME, find a cleaner way
@@ -249,11 +252,20 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging {
}
+@SuppressLint("NewApi")
class SettingsFragment : ScreenFragment("Settings"), Logging {
private val scanModel: BTScanModel by activityViewModels()
private val model: UIViewModel by activityViewModels()
+ private val hasCompanionDeviceApi: Boolean by lazy {
+ RadioInterfaceService.hasCompanionDeviceApi(requireContext())
+ }
+
+ private val deviceManager: CompanionDeviceManager by lazy {
+ requireContext().getSystemService(CompanionDeviceManager::class.java)
+ }
+
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
@@ -261,9 +273,8 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
return inflater.inflate(R.layout.settings_fragment, container, false)
}
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
+ /// Setup the ui widgets unrelated to BLE scanning
+ private fun initCommonUI() {
model.ownerName.observe(viewLifecycleOwner, Observer { name ->
usernameEditText.setText(name)
})
@@ -304,6 +315,14 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
true
}
+ }
+
+ /// Setup the GUI to do a classic (pre SDK 26 BLE scan)
+ private fun initClassicScan() {
+ // Turn off the widgets for the new API
+ scanProgressBar.visibility = View.VISIBLE
+ deviceRadioGroup.visibility = View.VISIBLE
+ changeRadioButton.visibility = View.GONE
scanModel.errorText.observe(viewLifecycleOwner, Observer { errMsg ->
if (errMsg != null) {
@@ -343,6 +362,84 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
})
}
+ /// 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
+ 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.
+ val deviceFilter: BluetoothDeviceFilter = BluetoothDeviceFilter.Builder()
+ .setNamePattern(Pattern.compile("Meshtastic_.*"))
+ // .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()
+
+ val mainActivity = requireActivity() as MainActivity
+
+ // 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")
+ changeRadioButton.isEnabled = true
+ changeRadioButton.setOnClickListener {
+ debug("User clicked BLE change button")
+ startIntentSenderForResult(
+ chooserLauncher,
+ 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
+ scanProgressBar.visibility = View.GONE
+ deviceRadioGroup.visibility = View.GONE
+ changeRadioButton.visibility = View.VISIBLE
+
+ val curRadio = RadioInterfaceService.getBondedDeviceAddress(requireContext())
+
+ if (curRadio != null) {
+ scanStatusText.text = getString(R.string.current_pair).format(curRadio)
+ changeRadioButton.text = getString(R.string.change_radio)
+ } else {
+ scanStatusText.text = getString(R.string.not_paired_yet)
+ changeRadioButton.setText(R.string.select_radio)
+ }
+
+ startBackgroundScan()
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ initCommonUI()
+ if (hasCompanionDeviceApi)
+ initModernScan()
+ else
+ initClassicScan()
+ }
+
override fun onPause() {
super.onPause()
scanModel.stopScan()
@@ -350,7 +447,8 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
override fun onResume() {
super.onResume()
- scanModel.startScan()
+ if (!hasCompanionDeviceApi)
+ scanModel.startScan()
}
}
diff --git a/app/src/main/res/layout/settings_fragment.xml b/app/src/main/res/layout/settings_fragment.xml
index 8ab7b2831..2ad55e6ed 100644
--- a/app/src/main/res/layout/settings_fragment.xml
+++ b/app/src/main/res/layout/settings_fragment.xml
@@ -35,8 +35,8 @@
android:id="@+id/usernameEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:singleLine="true"
- android:imeOptions="actionDone" />
+ android:imeOptions="actionDone"
+ android:singleLine="true" />
+ app:layout_constraintTop_toBottomOf="@+id/changeRadioButton">
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b55d8e838..56b39753d 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -41,4 +41,8 @@
Report a bug
Are you sure you want to report a bug? After reporting, please post in meshtastic.discourse.group so we can match up the report with what you found.
Report
+ Select radio
+ You are currently paired with radio %s
+ You have not paired a radio yet.
+ Change radio