From 2c34126a51763b70c30f5798cd875b843b8c47c2 Mon Sep 17 00:00:00 2001 From: Andre Kirchhoff Date: Sun, 24 Apr 2022 10:30:41 -0300 Subject: [PATCH 01/16] Update AndroidManifest.xml disable Auto Backup --- app/src/main/AndroidManifest.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f9b82f0b3..deb2ba6fd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -78,7 +78,7 @@ - \ No newline at end of file + From 5106a4a49be417e991d4ea9714227e93d3e24442 Mon Sep 17 00:00:00 2001 From: Andre Kirchhoff Date: Sun, 24 Apr 2022 10:31:44 -0300 Subject: [PATCH 02/16] Update build.gradle update libs --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 8fe288c92..6679352e7 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.6.20' + ext.kotlin_version = '1.6.21' ext.coroutines_version = '1.6.0' ext.room_version = '2.4.2' ext.hilt_version = '2.40.5' From 5c75a54d464c35c4d480c33fd6c89e0de7bda4f6 Mon Sep 17 00:00:00 2001 From: andrekir Date: Sun, 24 Apr 2022 10:35:17 -0300 Subject: [PATCH 03/16] updating androidlib submodule to latest --- geeksville-androidlib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geeksville-androidlib b/geeksville-androidlib index 379f76459..bcd9aa529 160000 --- a/geeksville-androidlib +++ b/geeksville-androidlib @@ -1 +1 @@ -Subproject commit 379f7645900c44e30d6b17e558bd36884d478b1b +Subproject commit bcd9aa529719ad8a9203aa5bbf0a7d707aa4f325 From 27c1817a59b7f8160a8b47a15ffe7e6fc7673ac4 Mon Sep 17 00:00:00 2001 From: andrekir Date: Sun, 24 Apr 2022 12:12:13 -0300 Subject: [PATCH 04/16] rename isConnected --> connectionState --- .../java/com/geeksville/mesh/MainActivity.kt | 10 +++++----- .../java/com/geeksville/mesh/model/UIState.kt | 4 ++-- .../mesh/ui/AdvancedSettingsFragment.kt | 2 +- .../com/geeksville/mesh/ui/ChannelFragment.kt | 16 +++++++--------- .../com/geeksville/mesh/ui/MessagesFragment.kt | 2 +- .../com/geeksville/mesh/ui/SettingsFragment.kt | 8 ++++---- 6 files changed, 20 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 3373ee423..084c516a3 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -451,7 +451,7 @@ class MainActivity : BaseActivity(), Logging, tab.icon = ContextCompat.getDrawable(this, tabInfos[position].icon) }.attach() - model.isConnected.observe(this) { connected -> + model.connectionState.observe(this) { connected -> updateConnectionStatusImage(connected) } @@ -516,7 +516,7 @@ class MainActivity : BaseActivity(), Logging, requestedChannelUrl = appLinkData // if the device is connected already, process it now - if (model.isConnected.value == MeshService.ConnectionState.CONNECTED) + if (model.isConnected()) perhapsChangeChannel() // We now wait for the device to connect, once connected, we ask the user if they want to switch to the new channel @@ -629,7 +629,7 @@ class MainActivity : BaseActivity(), Logging, /// Called when we gain/lose a connection to our mesh radio private fun onMeshConnectionChanged(newConnection: MeshService.ConnectionState) { - val oldConnection = model.isConnected.value!! + val oldConnection = model.connectionState.value!! debug("connchange $oldConnection -> $newConnection") if (newConnection == MeshService.ConnectionState.CONNECTED) { @@ -889,7 +889,7 @@ class MainActivity : BaseActivity(), Logging, connectionJob = null } - debug("connected to mesh service, isConnected=${model.isConnected.value}") + debug("connected to mesh service, isConnected=${model.connectionState.value}") } } @@ -983,7 +983,7 @@ class MainActivity : BaseActivity(), Logging, menuInflater.inflate(R.menu.menu_main, menu) model.actionBarMenu = menu - updateConnectionStatusImage(model.isConnected.value!!) + updateConnectionStatusImage(model.connectionState.value!!) return true } diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt index 38f487fd7..4536fffdb 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -92,9 +92,9 @@ class UIViewModel @Inject constructor( /// Connection state to our radio device private val _connectionState = MutableLiveData(MeshService.ConnectionState.DISCONNECTED) - val isConnected: LiveData get() = _connectionState + val connectionState: LiveData get() = _connectionState - // fun isConnected() = _connectionState.value == MeshService.ConnectionState.CONNECTED + fun isConnected() = _connectionState.value == MeshService.ConnectionState.CONNECTED fun setConnectionState(connectionState: MeshService.ConnectionState) { _connectionState.value = connectionState diff --git a/app/src/main/java/com/geeksville/mesh/ui/AdvancedSettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/AdvancedSettingsFragment.kt index 281d291b3..daa81b1e8 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/AdvancedSettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/AdvancedSettingsFragment.kt @@ -46,7 +46,7 @@ class AdvancedSettingsFragment : ScreenFragment("Advanced Settings"), Logging { binding.lsSleepSwitch.isChecked = model.isPowerSaving ?: false } - model.isConnected.observe(viewLifecycleOwner) { connectionState -> + model.connectionState.observe(viewLifecycleOwner) { connectionState -> val connected = connectionState == MeshService.ConnectionState.CONNECTED binding.positionBroadcastPeriodView.isEnabled = connected && !model.locationShareDisabled binding.lsSleepView.isEnabled = connected && model.isPowerSaving ?: false diff --git a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt index 27dfa8148..d7e743c7f 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt @@ -28,7 +28,6 @@ import com.geeksville.mesh.model.Channel import com.geeksville.mesh.model.ChannelOption import com.geeksville.mesh.model.ChannelSet import com.geeksville.mesh.model.UIViewModel -import com.geeksville.mesh.service.MeshService import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import com.google.protobuf.ByteString @@ -89,13 +88,13 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { private fun setGUIfromModel() { val channels = model.channels.value val channel = channels?.primaryChannel - - val connected = model.isConnected.value == MeshService.ConnectionState.CONNECTED + val connected = model.isConnected() // Only let buttons work if we are connected to the radio + binding.editableCheckbox.isChecked = false // start locked + onEditingChanged() // we just locked the gui binding.shareButton.isEnabled = connected - binding.editableCheckbox.isChecked = false // start locked if (channel != null) { binding.qrView.visibility = View.VISIBLE binding.channelNameEdit.visibility = View.VISIBLE @@ -123,7 +122,6 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { binding.editableCheckbox.isEnabled = false } - onEditingChanged() // we just locked the gui val modemConfigs = ChannelOption.values() val modemConfigList = modemConfigs.map { getString(it.configRes) } val adapter = ArrayAdapter( @@ -299,14 +297,14 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { shareChannel() } - model.channels.observe(viewLifecycleOwner, { + model.channels.observe(viewLifecycleOwner) { setGUIfromModel() - }) + } // If connection state changes, we might need to enable/disable buttons - model.isConnected.observe(viewLifecycleOwner, { + model.connectionState.observe(viewLifecycleOwner) { setGUIfromModel() - }) + } } private fun getModemConfig(selectedChannelOptionString: String): ChannelProtos.ChannelSettings.ModemConfig { diff --git a/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt index 4847f0691..cbb6350bd 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt @@ -294,7 +294,7 @@ class MessagesFragment : Fragment(), Logging { } // If connection state _OR_ myID changes we have to fix our ability to edit outgoing messages - model.isConnected.observe(viewLifecycleOwner) { connectionState -> + model.connectionState.observe(viewLifecycleOwner) { connectionState -> // If we don't know our node ID and we are offline don't let user try to send val connected = connectionState == MeshService.ConnectionState.CONNECTED binding.textInputLayout.isEnabled = connected 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 9385f62ea..1cd50fa5e 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -512,7 +512,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { debug("Reiniting the update button") val info = model.myNodeInfo.value val service = model.meshService - if (model.isConnected.value == MeshService.ConnectionState.CONNECTED && info != null && info.shouldUpdate && info.couldUpdate && service != null) { + if (model.isConnected() && info != null && info.shouldUpdate && info.couldUpdate && service != null) { binding.updateFirmwareButton.visibility = View.VISIBLE binding.updateFirmwareButton.text = getString(R.string.update_to).format(getString(R.string.short_firmware_version)) @@ -561,7 +561,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { * Pull the latest device info from the model and into the GUI */ private fun updateNodeInfo() { - val connected = model.isConnected.value + val connected = model.connectionState.value val isConnected = connected == MeshService.ConnectionState.CONNECTED binding.nodeSettings.visibility = if (isConnected) View.VISIBLE else View.GONE @@ -658,7 +658,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { } // Only let user edit their name or set software update while connected to a radio - model.isConnected.observe(viewLifecycleOwner) { + model.connectionState.observe(viewLifecycleOwner) { updateNodeInfo() updateDevicesButtons(scanModel.devices.value) } @@ -816,7 +816,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { ) addDeviceButton( curDevice, - model.isConnected.value == MeshService.ConnectionState.CONNECTED + model.isConnected() ) } } else if (scanModel.selectedUSB != null) { From 78d36185829068132a9c63d94fda2fca2e670d0a Mon Sep 17 00:00:00 2001 From: Andre Kirchhoff Date: Sun, 24 Apr 2022 20:10:36 -0300 Subject: [PATCH 05/16] update link to android docs --- app/src/main/res/values/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4069e1acb..a3ecbf49e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -61,7 +61,7 @@ Connected to radio, but it is sleeping Update to %s Application update required - You must update this application on the app store (or Github). It is too old to talk to this radio firmware. Please read our wiki on this topic. + You must update this application on the app store (or Github). It is too old to talk to this radio firmware. Please read our docs on this topic. None (disable) Short Range / Fast Medium Range / Fast @@ -142,4 +142,4 @@ System default Map style Resend - \ No newline at end of file + From 8d6a9a9ec270ea4070993b2b1a1caadfc9419ab0 Mon Sep 17 00:00:00 2001 From: andrekir Date: Sun, 24 Apr 2022 20:35:07 -0300 Subject: [PATCH 06/16] update localization links to docs --- app/src/main/res/values-hu/strings.xml | 2 +- app/src/main/res/values-pl/strings.xml | 2 +- app/src/main/res/values-pt-rBR/strings.xml | 2 +- app/src/main/res/values-sk/strings.xml | 2 +- app/src/main/res/values-zh/strings.xml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 99f5076dd..a63c87642 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -56,7 +56,7 @@ Kapcsolódva a rádióhoz, de az alvó üzemmódban van Frissítés %s verzióra Az alkalmazás frissítése szükséges - Frissítenie kell ezt az alkalmazást a Google Play áruházban (vagy a GitHub-ról), mert túl régi, hogy kommunikálni tudjob ezzel a rádió firmware-rel. Kérem olvassa el a tudnivalókat ebből a wiki-ből. + Frissítenie kell ezt az alkalmazást a Google Play áruházban (vagy a GitHub-ról), mert túl régi, hogy kommunikálni tudjob ezzel a rádió firmware-rel. Kérem olvassa el a tudnivalókat ebből a docs-ből. Egyik sem (letiltás) Rövid hatótáv (gyors) Közepes hatótáv (gyors) diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index ca1e8b18d..bf12b3bd7 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -57,7 +57,7 @@ Jeśli jesteś zainteresowany opłaceniem przez nas mapboxa (lub przejściem do Połączono z radiem w stanie uśpienia Aktualizuj do %s Konieczna aktualizacja aplikacji - Należy zaktualizować aplikację za pomocą Sklepu Play lub Githuba, bo jest zbyt stara aby dogadać się z oprogramowaniem zainstalowanym na tym tadiu. Więcej informacji (ang.) + Należy zaktualizować aplikację za pomocą Sklepu Play lub Githuba, bo jest zbyt stara aby dogadać się z oprogramowaniem zainstalowanym na tym tadiu. Więcej informacji (ang.) Brak (wyłącz) Krótki zasięg / Szybko Średni zasięg / Szybko diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 5c60f230b..8f91e4f93 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -57,7 +57,7 @@ Conectado ao rádio, mas ele está em suspensão (sleep) Atualização para %s Atualização do aplicativo necessária - Será necessário atualizar este aplicativo no Google Play (ou Github). Versão muito antiga para comunicar com o firmware do rádio. Favor consultar wiki. + Será necessário atualizar este aplicativo no Google Play (ou Github). Versão muito antiga para comunicar com o firmware do rádio. Favor consultar docs. Nenhum (desabilitado) Curto alcance / rápido Médio alcance / rápido diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index bc9aa1573..41ee01ff4 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -57,7 +57,7 @@ Pripojené k uspatému vysielaču. Aktualizovať na %s Aplikácia je príliš stará - Musíte aktualizovať aplikáciu na Google Play store (alebo z Github). Je príliš stará pre komunikáciu s touto verziou firmvéru vysielača. Viac informácií k tejto téme nájdete na Meshtastic wiki. + Musíte aktualizovať aplikáciu na Google Play store (alebo z Github). Je príliš stará pre komunikáciu s touto verziou firmvéru vysielača. Viac informácií k tejto téme nájdete na Meshtastic docs. Žiaden (zakázať) Nie, ďakujem Pripomenúť neskôr diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index b563eb2c5..ef65f5359 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -57,7 +57,7 @@ 已连接到设备,正在休眠中 更新到%s 需要应用程序更新 - 您必须在 Google Play或Github上更新此应用程序.固件太旧,请阅读我们的 wiki 这个话题. + 您必须在 Google Play或Github上更新此应用程序.固件太旧,请阅读我们的 docs 这个话题. 无(禁用) 短距离(速度快) 中等距离(速度快) From 34e240d7fab530d103467f6b968ab6ea4133e41a Mon Sep 17 00:00:00 2001 From: andrekir Date: Thu, 28 Apr 2022 11:53:32 -0300 Subject: [PATCH 07/16] update deprecated intent method --- .../geeksville/mesh/ui/SettingsFragment.kt | 86 ++++++++----------- 1 file changed, 36 insertions(+), 50 deletions(-) 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 1cd50fa5e..02a969316 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -1,7 +1,6 @@ package com.geeksville.mesh.ui import android.annotation.SuppressLint -import android.app.Activity import android.app.Application import android.app.PendingIntent import android.bluetooth.BluetoothDevice @@ -21,6 +20,8 @@ import android.view.View import android.view.ViewGroup import android.view.inputmethod.EditorInfo import android.widget.* +import androidx.activity.result.IntentSenderRequest +import androidx.activity.result.contract.ActivityResultContracts import androidx.fragment.app.activityViewModels import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData @@ -677,6 +678,10 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { updateNodeInfo() } + scanModel.devices.observe(viewLifecycleOwner) { devices -> + updateDevicesButtons(devices) + } + scanModel.errorText.observe(viewLifecycleOwner) { errMsg -> if (errMsg != null) { binding.scanStatusText.text = errMsg @@ -845,10 +850,6 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { private fun initClassicScan() { - scanModel.devices.observe(viewLifecycleOwner) { devices -> - updateDevicesButtons(devices) - } - binding.changeRadioButton.setOnClickListener { debug("User clicked changeRadioButton") if (!myActivity.hasScanPermission()) { @@ -881,10 +882,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { } } - private fun startCompanionScan() { - // Disable the change button until our scan has some results - binding.changeRadioButton.isEnabled = false - + private fun associationRequest(): AssociationRequest { // To skip filtering based on name and supported feature flags (UUIDs), // don't include calls to setNamePattern() and addServiceUuid(), // respectively. This example uses Bluetooth. @@ -897,28 +895,46 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { // 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() + return AssociationRequest.Builder() .addDeviceFilter(deviceFilter) .setSingleDevice(false) .build() + } + + @SuppressLint("MissingPermission") + val associationResultLauncher = registerForActivityResult( + ActivityResultContracts.StartIntentSenderForResult() + ) { + it.data + ?.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE) + ?.let { device -> + scanModel.onSelected( + myActivity, + BTScanModel.DeviceListEntry( + device.name, + "x${device.address}", + device.bondState == BOND_BONDED + ) + ) + } + } + + private fun startCompanionScan() { + // Disable the change button until our scan has some results + binding.changeRadioButton.isEnabled = false // When the app tries to pair with the Bluetooth device, show the // appropriate pairing request dialog to the user. deviceManager.associate( - pairingRequest, + associationRequest(), object : CompanionDeviceManager.Callback() { override fun onDeviceFound(chooserLauncher: IntentSender) { debug("Found one device - enabling changeRadioButton") binding.changeRadioButton.isEnabled = true binding.changeRadioButton.setOnClickListener { - debug("User clicked changeRadioButton") - try { - startIntentSenderForResult( - chooserLauncher, - MainActivity.SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0, null - ) - } catch (ex: Throwable) { - errormsg("CompanionDevice startIntentSenderForResult error") + chooserLauncher.let { + val request = IntentSenderRequest.Builder(it).build() + associationResultLauncher.launch(request) } } } @@ -933,8 +949,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { private fun initModernScan() { - scanModel.devices.observe(viewLifecycleOwner) { devices -> - updateDevicesButtons(devices) + scanModel.devices.observe(viewLifecycleOwner) { startCompanionScan() } } @@ -1061,33 +1076,4 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { } } } - - @SuppressLint("MissingPermission") - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (hasCompanionDeviceApi && myActivity.hasConnectPermission() - && requestCode == MainActivity.SELECT_DEVICE_REQUEST_CODE - && resultCode == Activity.RESULT_OK - ) { - val deviceToPair: BluetoothDevice = - data?.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE)!! - - // We only keep an association to one device at a time... - deviceManager.associations.forEach { old -> - if (deviceToPair.address != old) { - debug("Forgetting old BLE association ${old.anonymize}") - deviceManager.disassociate(old) - } - } - scanModel.onSelected( - myActivity, - BTScanModel.DeviceListEntry( - deviceToPair.name, - "x${deviceToPair.address}", - deviceToPair.bondState == BOND_BONDED - ) - ) - } else { - super.onActivityResult(requestCode, resultCode, data) - } - } } From eaff87eed35f1073aa86cb97e825adf9f87f8708 Mon Sep 17 00:00:00 2001 From: andrekir Date: Thu, 28 Apr 2022 11:54:04 -0300 Subject: [PATCH 08/16] update debug message reference --- app/src/main/java/com/geeksville/mesh/MainActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 084c516a3..468396d5e 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -889,7 +889,7 @@ class MainActivity : BaseActivity(), Logging, connectionJob = null } - debug("connected to mesh service, isConnected=${model.connectionState.value}") + debug("connected to mesh service, connectionState=${model.connectionState.value}") } } From aaa5c1cf04b57773610640b84ae34c95cbb5e337 Mon Sep 17 00:00:00 2001 From: andrekir Date: Thu, 28 Apr 2022 21:40:34 -0300 Subject: [PATCH 09/16] move hasCompanionDeviceApi out of BluetoothInterface --- .../mesh/android/ContextServices.kt | 11 +++- .../repository/radio/BluetoothInterface.kt | 63 ------------------- .../geeksville/mesh/ui/SettingsFragment.kt | 8 +-- 3 files changed, 11 insertions(+), 71 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt b/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt index 11b9e9f82..ab7bdf2b4 100644 --- a/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt +++ b/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt @@ -8,7 +8,6 @@ import android.content.pm.PackageManager import android.hardware.usb.UsbManager import android.os.Build import androidx.core.content.ContextCompat -import com.geeksville.mesh.repository.radio.BluetoothInterface /** * @return null on platforms without a BlueTooth driver (i.e. the emulator) @@ -19,6 +18,14 @@ val Context.usbManager: UsbManager get() = requireNotNull(getSystemService(Conte val Context.notificationManager: NotificationManager get() = requireNotNull(getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager?) +/** + * @return true if CompanionDeviceManager API is present + */ +fun Context.hasCompanionDeviceApi(): Boolean = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + packageManager.hasSystemFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP) + else false + /** * return a list of the permissions we don't have */ @@ -62,7 +69,7 @@ fun Context.getScanPermissions(): List { perms.add(Manifest.permission.BLUETOOTH_ADMIN) } */ - if (!BluetoothInterface.hasCompanionDeviceApi(this)) { + if (!hasCompanionDeviceApi()) { perms.add(Manifest.permission.ACCESS_FINE_LOCATION) perms.add(Manifest.permission.BLUETOOTH_ADMIN) } diff --git a/app/src/main/java/com/geeksville/mesh/repository/radio/BluetoothInterface.kt b/app/src/main/java/com/geeksville/mesh/repository/radio/BluetoothInterface.kt index f4861f782..0bcc0a9a5 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/radio/BluetoothInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/radio/BluetoothInterface.kt @@ -118,12 +118,6 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String usbRepository: UsbRepository, // Temporary until dependency injection transition is completed rest: String ): Boolean { - /* val allPaired = if (hasCompanionDeviceApi(context)) { - val deviceManager: CompanionDeviceManager by lazy { - context.getSystemService(Context.COMPANION_DEVICE_SERVICE) as CompanionDeviceManager - } - deviceManager.associations.map { it }.toSet() - } else { */ val allPaired = getBluetoothAdapter(context)?.bondedDevices.orEmpty() .map { it.address }.toSet() return if (!allPaired.contains(rest)) { @@ -133,63 +127,6 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String true } - - /// Return the device we are configured to use, or null for none - /* - @SuppressLint("NewApi") - fun getBondedDeviceAddress(context: Context): String? = - if (hasCompanionDeviceApi(context)) { - // Use new companion API - - val deviceManager = context.getSystemService(CompanionDeviceManager::class.java) - val associations = deviceManager.associations - val result = associations.firstOrNull() - debug("reading bonded devices: $result") - result - } else { - // Use classic API and a preferences string - - val allPaired = - getBluetoothAdapter(context)?.bondedDevices.orEmpty().map { it.address }.toSet() - - // If the user has unpaired our device, treat things as if we don't have one - val address = InterfaceService.getPrefs(context).getString(DEVADDR_KEY, null) - - if (address != null && !allPaired.contains(address)) { - warn("Ignoring stale bond to ${address.anonymize}") - null - } else - address - } -*/ - - /// 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 - } - - /** FIXME - when adding companion device support back in, use this code to set companion device from setBondedDevice - * if (BluetoothInterface.hasCompanionDeviceApi(this)) { - // We only keep an association to one device at a time... - if (addr != null) { - val deviceManager = getSystemService(CompanionDeviceManager::class.java) - - deviceManager.associations.forEach { old -> - if (addr != old) { - BluetoothInterface.debug("Forgetting old BLE association $old") - deviceManager.disassociate(old) - } - } - } - */ - /** * this is created in onCreate() * We do an ugly hack of keeping it in the singleton so we can share it for the rare software update case 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 02a969316..25684cc6b 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -37,7 +37,6 @@ import com.geeksville.mesh.android.* import com.geeksville.mesh.databinding.SettingsFragmentBinding import com.geeksville.mesh.model.BluetoothViewModel import com.geeksville.mesh.model.UIViewModel -import com.geeksville.mesh.repository.radio.BluetoothInterface import com.geeksville.mesh.repository.radio.MockInterface import com.geeksville.mesh.repository.radio.RadioInterfaceService import com.geeksville.mesh.repository.radio.SerialInterface @@ -154,6 +153,7 @@ class BTScanModel @Inject constructor( } val bluetoothAdapter = context.bluetoothManager?.adapter + val hasCompanionDeviceApi get() = context.hasCompanionDeviceApi() private val usbManager get() = context.usbManager var selectedAddress: String? = null @@ -466,10 +466,6 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { @Inject internal lateinit var usbRepository: UsbRepository - private val hasCompanionDeviceApi: Boolean by lazy { - BluetoothInterface.hasCompanionDeviceApi(requireContext()) - } - private val deviceManager: CompanionDeviceManager by lazy { requireContext().getSystemService(Context.COMPANION_DEVICE_SERVICE) as CompanionDeviceManager } @@ -958,7 +954,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { super.onViewCreated(view, savedInstanceState) initCommonUI() - if (hasCompanionDeviceApi) + if (scanModel.hasCompanionDeviceApi) initModernScan() else initClassicScan() From 0950e12bd094f5e8cefa6c914f5d3e797b696b1a Mon Sep 17 00:00:00 2001 From: andrekir Date: Thu, 28 Apr 2022 23:09:06 -0300 Subject: [PATCH 10/16] add BLE associations to devices list --- .../mesh/android/ContextServices.kt | 7 ++++++ .../geeksville/mesh/ui/SettingsFragment.kt | 25 ++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt b/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt index ab7bdf2b4..f3bd9ee51 100644 --- a/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt +++ b/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt @@ -1,8 +1,10 @@ package com.geeksville.mesh.android import android.Manifest +import android.annotation.SuppressLint import android.app.NotificationManager import android.bluetooth.BluetoothManager +import android.companion.CompanionDeviceManager import android.content.Context import android.content.pm.PackageManager import android.hardware.usb.UsbManager @@ -14,6 +16,11 @@ import androidx.core.content.ContextCompat */ val Context.bluetoothManager: BluetoothManager? get() = getSystemService(Context.BLUETOOTH_SERVICE) as? BluetoothManager? +val Context.deviceManager: CompanionDeviceManager? + @SuppressLint("InlinedApi") + get() = if (hasCompanionDeviceApi()) getSystemService(Context.COMPANION_DEVICE_SERVICE) as? CompanionDeviceManager? + else null + val Context.usbManager: UsbManager get() = requireNotNull(getSystemService(Context.USB_SERVICE) as? UsbManager?) { "USB_SERVICE is not available"} val Context.notificationManager: NotificationManager get() = requireNotNull(getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager?) 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 25684cc6b..8ece3085f 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -134,7 +134,7 @@ class BTScanModel @Inject constructor( null override fun toString(): String { - return "DeviceListEntry(name=${name.anonymize}, addr=${address.anonymize})" + return "DeviceListEntry(name=${name.anonymize}, addr=${address.anonymize}, bonded=$bonded)" } val isBluetooth: Boolean get() = address[0] == 'x' @@ -153,7 +153,9 @@ class BTScanModel @Inject constructor( } val bluetoothAdapter = context.bluetoothManager?.adapter + private val deviceManager get() = context.deviceManager val hasCompanionDeviceApi get() = context.hasCompanionDeviceApi() + private val hasConnectPermission get() = context.hasConnectPermission() private val usbManager get() = context.usbManager var selectedAddress: String? = null @@ -230,6 +232,8 @@ class BTScanModel @Inject constructor( } } + + private fun addDevice(entry: DeviceListEntry) { val oldDevs = devices.value!! oldDevs[entry.address] = entry // Add/replace entry @@ -298,6 +302,9 @@ class BTScanModel @Inject constructor( // Include a placeholder for "None" addDevice(DeviceListEntry(context.getString(R.string.none), "n", true)) + if (hasCompanionDeviceApi && hasConnectPermission) + addAssociations() + serialDevices.forEach { (_, d) -> addDevice(USBDeviceListEntry(usbManager, d)) } @@ -334,6 +341,22 @@ class BTScanModel @Inject constructor( } } + @SuppressLint("MissingPermission", "NewApi") + private fun addAssociations() { + deviceManager?.associations?.forEach { bleAddress -> + bluetoothAdapter?.getRemoteDevice(bleAddress)?.let { device -> + if (device.name.startsWith("Mesh")) { + val entry = DeviceListEntry( + device.name, + "x${device.address}", // full address with the bluetooth prefix added + device.bondState == BOND_BONDED + ) + addDevice(entry) + } + } + } + } + val devices = object : MutableLiveData>(mutableMapOf()) { /** From b6410dd1627e3cd5fe975b7312d386478361480e Mon Sep 17 00:00:00 2001 From: andrekir Date: Fri, 29 Apr 2022 23:34:03 -0300 Subject: [PATCH 11/16] disassociate devices when not bonded --- .../geeksville/mesh/ui/SettingsFragment.kt | 74 ++++++++++--------- 1 file changed, 40 insertions(+), 34 deletions(-) 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 8ece3085f..5710464fb 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -152,7 +152,7 @@ class BTScanModel @Inject constructor( debug("BTScanModel cleared") } - val bluetoothAdapter = context.bluetoothManager?.adapter + private val bluetoothAdapter = context.bluetoothManager?.adapter private val deviceManager get() = context.deviceManager val hasCompanionDeviceApi get() = context.hasCompanionDeviceApi() private val hasConnectPermission get() = context.hasConnectPermission() @@ -232,8 +232,6 @@ class BTScanModel @Inject constructor( } } - - private fun addDevice(entry: DeviceListEntry) { val oldDevs = devices.value!! oldDevs[entry.address] = entry // Add/replace entry @@ -302,8 +300,8 @@ class BTScanModel @Inject constructor( // Include a placeholder for "None" addDevice(DeviceListEntry(context.getString(R.string.none), "n", true)) - if (hasCompanionDeviceApi && hasConnectPermission) - addAssociations() + // Include CompanionDeviceManager valid associations + addDeviceAssociations() serialDevices.forEach { (_, d) -> addDevice(USBDeviceListEntry(usbManager, d)) @@ -341,18 +339,33 @@ class BTScanModel @Inject constructor( } } - @SuppressLint("MissingPermission", "NewApi") - private fun addAssociations() { - deviceManager?.associations?.forEach { bleAddress -> - bluetoothAdapter?.getRemoteDevice(bleAddress)?.let { device -> - if (device.name.startsWith("Mesh")) { - val entry = DeviceListEntry( - device.name, - "x${device.address}", // full address with the bluetooth prefix added - device.bondState == BOND_BONDED - ) - addDevice(entry) - } + /** + * @return DeviceListEntry from Bluetooth Address. + * Only valid if name begins with "Meshtastic"... + */ + @SuppressLint("MissingPermission") + fun bleDeviceFrom(bleAddress: String): DeviceListEntry { + val device = + if (hasConnectPermission) bluetoothAdapter?.getRemoteDevice(bleAddress) else null + + return if (device != null && device.name != null) { + DeviceListEntry( + device.name, + "x${device.address}", // full address with the bluetooth prefix added + device.bondState == BOND_BONDED + ) + } else DeviceListEntry("", "", false) + } + + @SuppressLint("NewApi") + private fun addDeviceAssociations() { + if (hasCompanionDeviceApi) deviceManager?.associations?.forEach { bleAddress -> + val bleDevice = bleDeviceFrom(bleAddress) + if (!bleDevice.bonded) { // Clean up associations after pairing is removed + debug("Forgetting old BLE association ${bleAddress.anonymize}") + deviceManager?.disassociate(bleAddress) + } else if (bleDevice.name.startsWith("Mesh")) { + addDevice(bleDevice) } } } @@ -372,7 +385,7 @@ class BTScanModel @Inject constructor( */ override fun onInactive() { super.onInactive() - // stopScan() + if (!hasCompanionDeviceApi) stopScan() } } @@ -789,8 +802,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { b.text = device.name b.id = View.generateViewId() b.isEnabled = enabled - b.isChecked = - device.address == scanModel.selectedNotNull && device.bonded // Only show checkbox if device is still paired + b.isChecked = device.address == scanModel.selectedNotNull binding.deviceRadioGroup.addView(b) b.setOnClickListener { @@ -799,21 +811,15 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { b.isChecked = scanModel.onSelected(myActivity, device) - - if (!b.isSelected) { - binding.scanStatusText.text = getString(R.string.please_pair) - } } } - @SuppressLint("MissingPermission") private fun updateDevicesButtons(devices: MutableMap?) { // Remove the old radio buttons and repopulate binding.deviceRadioGroup.removeAllViews() if (devices == null) return - val adapter = scanModel.bluetoothAdapter var hasShownOurDevice = false devices.values.forEach { device -> if (device.address == scanModel.selectedNotNull) @@ -829,14 +835,14 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { // and before use val bleAddr = scanModel.selectedBluetooth - if (bleAddr != null && adapter != null && myActivity.hasConnectPermission()) { - val bDevice = - adapter.getRemoteDevice(bleAddr) - if (bDevice.name != null) { // ignore nodes that node have a name, that means we've lost them since they appeared + if (bleAddr != null) { + debug("bleAddr= $bleAddr selected= ${scanModel.selectedAddress}") + val bleDevice = scanModel.bleDeviceFrom(bleAddr) + if (bleDevice.name.startsWith("Mesh")) { // ignore nodes that node have a name, that means we've lost them since they appeared val curDevice = BTScanModel.DeviceListEntry( - bDevice.name, - scanModel.selectedAddress!!, - bDevice.bondState == BOND_BONDED + bleDevice.name, + bleDevice.address, + bleDevice.bonded ) addDeviceButton( curDevice, @@ -1087,7 +1093,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { val hasUSB = usbRepository.serialDevicesWithDrivers.value.isNotEmpty() if (!hasUSB) { // Warn user if BLE is disabled - if (scanModel.bluetoothAdapter?.isEnabled != true) { + if (bluetoothViewModel.enabled.value == false) { showSnackbar(getString(R.string.error_bluetooth)) } else { if (binding.provideLocationCheckbox.isChecked) From 0294da844b381fb2d1a4ca689fd3fb64003c7e0f Mon Sep 17 00:00:00 2001 From: andrekir Date: Sat, 30 Apr 2022 00:06:49 -0300 Subject: [PATCH 12/16] update UI when started with BLE disabled --- .../main/java/com/geeksville/mesh/ui/SettingsFragment.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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 5710464fb..5b0e62b94 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -681,9 +681,12 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { regionAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) spinner.adapter = regionAdapter - bluetoothViewModel.enabled.observe(viewLifecycleOwner) { - if (it) binding.changeRadioButton.show() - else binding.changeRadioButton.hide() + bluetoothViewModel.enabled.observe(viewLifecycleOwner) { enabled -> + if (enabled) { + binding.changeRadioButton.show() + if (scanModel.devices.value.isNullOrEmpty()) scanModel.setupScan() + if (binding.scanStatusText.text == getString(R.string.requires_bluetooth)) updateNodeInfo() + } else binding.changeRadioButton.hide() } model.ownerName.observe(viewLifecycleOwner) { name -> From 671255c331f783ac63473c82668e58b10b1b06a7 Mon Sep 17 00:00:00 2001 From: Jm Casler Date: Sun, 1 May 2022 08:41:59 -0700 Subject: [PATCH 13/16] updating proto submodule to latest --- app/src/main/proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/proto b/app/src/main/proto index a578453b3..6c790cef4 160000 --- a/app/src/main/proto +++ b/app/src/main/proto @@ -1 +1 @@ -Subproject commit a578453b3c17794b61fb6cf4470ecaac8287d6d2 +Subproject commit 6c790cef4c500f02773986726e48d45d4a218b5d From c3b610573034a881e4d9a52176f2d45e659b837f Mon Sep 17 00:00:00 2001 From: Jm Casler Date: Sun, 1 May 2022 18:33:40 -0700 Subject: [PATCH 14/16] updating proto submodule to latest --- app/src/main/proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/proto b/app/src/main/proto index 6c790cef4..79d24080f 160000 --- a/app/src/main/proto +++ b/app/src/main/proto @@ -1 +1 @@ -Subproject commit 6c790cef4c500f02773986726e48d45d4a218b5d +Subproject commit 79d24080ff83b0a54bc1619f07f41f17ffedfb99 From ef9114ddc10200aab61284a190853ffd48a2061c Mon Sep 17 00:00:00 2001 From: andrekir Date: Tue, 3 May 2022 12:16:44 -0300 Subject: [PATCH 15/16] bluetooth scan & connect UI rework --- .../mesh/android/ContextServices.kt | 9 +- .../geeksville/mesh/ui/SettingsFragment.kt | 179 ++++++++++-------- 2 files changed, 102 insertions(+), 86 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt b/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt index f3bd9ee51..61f4f1cc7 100644 --- a/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt +++ b/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt @@ -10,6 +10,8 @@ import android.content.pm.PackageManager import android.hardware.usb.UsbManager import android.os.Build import androidx.core.content.ContextCompat +import com.geeksville.android.GeeksvilleApplication +import com.geeksville.mesh.MainActivity /** * @return null on platforms without a BlueTooth driver (i.e. the emulator) @@ -18,8 +20,11 @@ val Context.bluetoothManager: BluetoothManager? get() = getSystemService(Context val Context.deviceManager: CompanionDeviceManager? @SuppressLint("InlinedApi") - get() = if (hasCompanionDeviceApi()) getSystemService(Context.COMPANION_DEVICE_SERVICE) as? CompanionDeviceManager? - else null + get() { + val activity: MainActivity? = GeeksvilleApplication.currentActivity as MainActivity? + return if (hasCompanionDeviceApi()) activity?.getSystemService(Context.COMPANION_DEVICE_SERVICE) as? CompanionDeviceManager? + else null + } val Context.usbManager: UsbManager get() = requireNotNull(getSystemService(Context.USB_SERVICE) as? UsbManager?) { "USB_SERVICE is not available"} 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 5b0e62b94..200457cca 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -24,6 +24,7 @@ import androidx.activity.result.IntentSenderRequest import androidx.activity.result.contract.ActivityResultContracts import androidx.fragment.app.activityViewModels import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import com.geeksville.analytics.DataPair import com.geeksville.android.GeeksvilleApplication @@ -246,9 +247,11 @@ class BTScanModel @Inject constructor( scanner?.stopScan(scanCallback) } catch (ex: Throwable) { warn("Ignoring error stopping scan, probably BT adapter was disabled suddenly: ${ex.message}") + } finally { + scanner = null + _spinner.value = false } - scanner = null - } + } else _spinner.value = false } /** @@ -314,13 +317,20 @@ class BTScanModel @Inject constructor( } } + fun startScan () { + if (hasCompanionDeviceApi) { + startCompanionScan() + } else startClassicScan() + } + @SuppressLint("MissingPermission") - fun startScan() { + private fun startClassicScan() { /// The following call might return null if the user doesn't have bluetooth access permissions val bluetoothLeScanner: BluetoothLeScanner? = bluetoothAdapter?.bluetoothLeScanner if (bluetoothLeScanner != null) { // could be null if bluetooth is disabled - debug("starting scan") + debug("starting classic scan") + _spinner.value = true // filter and only accept devices that have our service val filter = @@ -370,6 +380,63 @@ class BTScanModel @Inject constructor( } } + 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 { + // 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. + return AssociationRequest.Builder() + .addDeviceFilter(deviceFilter) + .setSingleDevice(false) + .build() + } + + @SuppressLint("NewApi") + private fun startCompanionScan() { + debug("starting companion scan") + _spinner.value = true + deviceManager?.associate( + associationRequest(), + @SuppressLint("NewApi") + object : CompanionDeviceManager.Callback() { + override fun onDeviceFound(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 + ) + } + val devices = object : MutableLiveData>(mutableMapOf()) { /** @@ -385,7 +452,7 @@ class BTScanModel @Inject constructor( */ override fun onInactive() { super.onInactive() - if (!hasCompanionDeviceApi) stopScan() + stopScan() } } @@ -502,10 +569,6 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { @Inject internal lateinit var usbRepository: UsbRepository - private val deviceManager: CompanionDeviceManager by lazy { - requireContext().getSystemService(Context.COMPANION_DEVICE_SERVICE) as CompanionDeviceManager - } - private val myActivity get() = requireActivity() as MainActivity override fun onDestroy() { @@ -723,6 +786,18 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { } } + // show the spinner when [spinner] is true + scanModel.spinner.observe(viewLifecycleOwner) { show -> + binding.scanProgressBar.visibility = if (show) View.VISIBLE else View.GONE + } + + scanModel.associationRequest.observe(viewLifecycleOwner) { request -> + request?.let { + associationResultLauncher.launch(request) + scanModel.clearAssociationRequest() + } + } + binding.updateFirmwareButton.setOnClickListener { MaterialAlertDialogBuilder(requireContext()) .setMessage("${getString(R.string.update_firmware)}?") @@ -876,59 +951,24 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { } } - private fun initClassicScan() { - - binding.changeRadioButton.setOnClickListener { - debug("User clicked changeRadioButton") - if (!myActivity.hasScanPermission()) { - myActivity.requestScanPermission() - } else { - checkLocationEnabled() - scanLeDevice() - } - } - } - // per https://developer.android.com/guide/topics/connectivity/bluetooth/find-ble-devices private fun scanLeDevice() { var scanning = false - val SCAN_PERIOD: Long = 5000 // Stops scanning after 5 seconds + val SCAN_PERIOD: Long = 10000 // Stops scanning after 10 seconds if (!scanning) { // Stops scanning after a pre-defined scan period. Handler(Looper.getMainLooper()).postDelayed({ scanning = false - binding.scanProgressBar.visibility = View.GONE scanModel.stopScan() }, SCAN_PERIOD) scanning = true - binding.scanProgressBar.visibility = View.VISIBLE scanModel.startScan() } else { scanning = false - binding.scanProgressBar.visibility = View.GONE scanModel.stopScan() } } - private fun associationRequest(): AssociationRequest { - // 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. - return AssociationRequest.Builder() - .addDeviceFilter(deviceFilter) - .setSingleDevice(false) - .build() - } - @SuppressLint("MissingPermission") val associationResultLauncher = registerForActivityResult( ActivityResultContracts.StartIntentSenderForResult() @@ -947,49 +987,20 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { } } - private fun startCompanionScan() { - // Disable the change button until our scan has some results - binding.changeRadioButton.isEnabled = false - - // When the app tries to pair with the Bluetooth device, show the - // appropriate pairing request dialog to the user. - deviceManager.associate( - associationRequest(), - object : CompanionDeviceManager.Callback() { - override fun onDeviceFound(chooserLauncher: IntentSender) { - debug("Found one device - enabling changeRadioButton") - binding.changeRadioButton.isEnabled = true - binding.changeRadioButton.setOnClickListener { - chooserLauncher.let { - val request = IntentSenderRequest.Builder(it).build() - associationResultLauncher.launch(request) - } - } - } - - override fun onFailure(error: CharSequence?) { - warn("BLE selection service failed $error") - // changeDeviceSelection(myActivity, null) // deselect any device - } - }, null - ) - } - - private fun initModernScan() { - - scanModel.devices.observe(viewLifecycleOwner) { - startCompanionScan() - } - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initCommonUI() - if (scanModel.hasCompanionDeviceApi) - initModernScan() - else - initClassicScan() + + binding.changeRadioButton.setOnClickListener { + debug("User clicked changeRadioButton") + if (!myActivity.hasScanPermission()) { + myActivity.requestScanPermission() + } else { + if (!scanModel.hasCompanionDeviceApi) checkLocationEnabled() + scanLeDevice() + } + } } // If the user has not turned on location access throw up a toast warning From 9e3bab83674135a08c505841c4e729814f3d0d11 Mon Sep 17 00:00:00 2001 From: andrekir Date: Tue, 3 May 2022 17:32:01 -0300 Subject: [PATCH 16/16] update deprecated IntentIntegrator --- .../com/geeksville/mesh/ui/ChannelFragment.kt | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt index d7e743c7f..f250b7cd9 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt @@ -13,6 +13,7 @@ import android.view.ViewGroup import android.view.inputmethod.EditorInfo import android.widget.ArrayAdapter import android.widget.ImageView +import androidx.activity.result.ActivityResultLauncher import androidx.fragment.app.activityViewModels import com.geeksville.analytics.DataPair import com.geeksville.android.GeeksvilleApplication @@ -31,7 +32,9 @@ import com.geeksville.mesh.model.UIViewModel import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import com.google.protobuf.ByteString -import com.google.zxing.integration.android.IntentIntegrator +import com.journeyapps.barcodescanner.ScanContract +import com.journeyapps.barcodescanner.ScanIntentResult +import com.journeyapps.barcodescanner.ScanOptions import dagger.hilt.android.AndroidEntryPoint import java.security.SecureRandom @@ -64,7 +67,7 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { + ): View { _binding = ChannelFragmentBinding.inflate(inflater, container, false) return binding.root } @@ -193,7 +196,7 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { requireActivity().hideKeyboard() } - binding.resetButton.setOnClickListener { _ -> + binding.resetButton.setOnClickListener { // User just locked it, we should warn and then apply changes to radio MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.reset_to_defaults) @@ -211,12 +214,12 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { binding.scanButton.setOnClickListener { if ((requireActivity() as MainActivity).hasCameraPermission()) { debug("Starting QR code scanner") - val zxingScan = IntentIntegrator.forSupportFragment(this) + val zxingScan = ScanOptions() zxingScan.setCameraId(0) zxingScan.setPrompt("") zxingScan.setBeepEnabled(false) - zxingScan.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE) - zxingScan.initiateScan() + zxingScan.setDesiredBarcodeFormats(ScanOptions.QR_CODE) + barcodeLauncher.launch(zxingScan) } else { MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.camera_required) @@ -232,7 +235,7 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { } // Note: Do not use setOnCheckedChanged here because we don't want to be called when we programmatically disable editing - binding.editableCheckbox.setOnClickListener { _ -> + binding.editableCheckbox.setOnClickListener { /// We use this to determine if the user tried to install a custom name var originalName = "" @@ -316,14 +319,12 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { return ChannelProtos.ChannelSettings.ModemConfig.UNRECOGNIZED } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - val result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data) - if (result != null) { - if (result.contents != null) { - ((requireActivity() as MainActivity).perhapsChangeChannel(Uri.parse(result.contents))) - } - } else { - super.onActivityResult(requestCode, resultCode, data) + // Register the launcher and result handler + private val barcodeLauncher: ActivityResultLauncher = registerForActivityResult( + ScanContract() + ) { result: ScanIntentResult -> + if (result.contents != null) { + ((requireActivity() as MainActivity).perhapsChangeChannel(Uri.parse(result.contents))) } } }