diff --git a/app/build.gradle b/app/build.gradle index 5afadfa30..1fbdf2431 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -43,8 +43,8 @@ android { applicationId "com.geeksville.mesh" minSdkVersion 21 // The oldest emulator image I have tried is 22 (though 21 probably works) targetSdkVersion 30 // 30 can't work until an explicit location permissions dialog is added - versionCode 20256 // format is Mmmss (where M is 1+the numeric major number - versionName "1.2.56" + versionCode 20257 // format is Mmmss (where M is 1+the numeric major number + versionName "1.2.57" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" // per https://developer.android.com/studio/write/vector-asset-studio @@ -127,7 +127,7 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'androidx.core:core-ktx:1.7.0' - implementation 'androidx.fragment:fragment-ktx:1.4.0' + implementation 'androidx.fragment:fragment-ktx:1.4.1' implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation 'androidx.constraintlayout:constraintlayout:2.1.3' @@ -177,7 +177,7 @@ dependencies { implementation 'com.google.android.gms:play-services-location:19.0.1' // For Google Sign-In (owner name accesss) - implementation 'com.google.android.gms:play-services-auth:20.0.1' + implementation 'com.google.android.gms:play-services-auth:20.1.0' // Add the Firebase SDK for Crashlytics. implementation 'com.google.firebase:firebase-crashlytics:18.2.6' @@ -191,6 +191,7 @@ dependencies { // barcode support // per https://github.com/journeyapps/zxing-android-embedded#older-sdk-versions for minSdkVersion 21 implementation('com.journeyapps:zxing-android-embedded:4.3.0') { transitive = false } + //noinspection GradleDependency implementation 'com.google.zxing:core:3.3.0' // <-- don't update def work_version = '2.7.1' diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 4fcf73e23..34a36b807 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -4,11 +4,7 @@ import android.Manifest import android.annotation.SuppressLint 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 import android.content.pm.PackageManager @@ -27,7 +23,6 @@ import android.view.View import android.widget.TextView import android.widget.Toast import androidx.activity.viewModels -import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.widget.Toolbar @@ -70,8 +65,6 @@ import kotlinx.coroutines.cancel import java.nio.charset.Charset import java.text.DateFormat import java.util.* -import java.util.regex.Pattern - /* UI design @@ -198,48 +191,6 @@ class MainActivity : AppCompatActivity(), Logging, 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 */ @@ -502,8 +453,6 @@ class MainActivity : AppCompatActivity(), Logging, /* not yet working // Configure sign-in to request the user's ID, email address, and basic -// profile. ID and basic profile are included in DEFAULT_SIGN_IN. - // Configure sign-in to request the user's ID, email address, and basic // profile. ID and basic profile are included in DEFAULT_SIGN_IN. val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) @@ -629,7 +578,6 @@ class MainActivity : AppCompatActivity(), Logging, /** * Dispatch incoming result to the correct fragment. */ - @SuppressLint("InlinedApi", "MissingPermission") override fun onActivityResult( requestCode: Int, resultCode: Int, @@ -637,7 +585,6 @@ class MainActivity : AppCompatActivity(), Logging, ) { super.onActivityResult(requestCode, resultCode, data) - // Result returned from launching the Intent from GoogleSignInClient.getSignInIntent(...); // Result returned from launching the Intent from GoogleSignInClient.getSignInIntent(...); when (requestCode) { RC_SIGN_IN -> { @@ -647,19 +594,6 @@ class MainActivity : AppCompatActivity(), Logging, GoogleSignIn.getSignedInAccountFromIntent(data) handleSignInResult(task) } - (SELECT_DEVICE_REQUEST_CODE) -> when (resultCode) { - Activity.RESULT_OK -> { - val deviceToPair: BluetoothDevice = - data?.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE)!! - if (deviceToPair.bondState != BluetoothDevice.BOND_BONDED) { - deviceToPair.createBond() - } - model.meshService?.let { service -> - MeshService.changeDeviceAddress(this@MainActivity, service, "x${deviceToPair.address}") - } - } - else -> warn("BLE device select intent failed") - } CREATE_CSV_FILE -> { if (resultCode == Activity.RESULT_OK) { data?.data?.let { file_uri -> model.saveMessagesCSV(file_uri) } @@ -1161,7 +1095,7 @@ class MainActivity : AppCompatActivity(), Logging, val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) type = "application/csv" - putExtra(Intent.EXTRA_TITLE, "messages.csv") + putExtra(Intent.EXTRA_TITLE, "rangetest.csv") } startActivityForResult(intent, CREATE_CSV_FILE) return true @@ -1189,7 +1123,7 @@ class MainActivity : AppCompatActivity(), Logging, private fun chooseThemeDialog() { /// Prepare dialog and its items - val builder = AlertDialog.Builder(this) + val builder = MaterialAlertDialogBuilder(this) builder.setTitle(getString(R.string.choose_theme_title)) val styles = arrayOf( diff --git a/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt b/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt index 50c860f71..1eba742a1 100644 --- a/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt @@ -113,7 +113,9 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String @SuppressLint("NewApi", "MissingPermission") override fun addressValid(context: Context, rest: String): Boolean { val allPaired = if (hasCompanionDeviceApi(context)) { - val deviceManager = context.getSystemService(CompanionDeviceManager::class.java) + val deviceManager: CompanionDeviceManager by lazy { + context.getSystemService(Context.COMPANION_DEVICE_SERVICE) as CompanionDeviceManager + } deviceManager.associations.map { it }.toSet() } else { getBluetoothAdapter(context)?.bondedDevices.orEmpty() 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 c5f1deabc..ee8d00e0f 100644 --- a/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt @@ -269,7 +269,7 @@ class RadioInterfaceService : Service(), Logging { @SuppressLint("NewApi") private fun setBondedDeviceAddress(address: String?): Boolean { return if (getBondedDeviceAddress(this) == address && isStarted) { - warn("Ignoring setBondedDevice $address, because we are already using that device") + warn("Ignoring setBondedDevice ${address.anonymize}, because we are already using that device") false } else { // Record that this use has configured a new radio 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 254f1948d..27dfa8148 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt @@ -158,7 +158,7 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { requireActivity().startActivity(shareIntent) } catch (ex: ActivityNotFoundException) { Snackbar.make( - binding.shareButton, + requireView(), R.string.no_app_found, Snackbar.LENGTH_SHORT ).show() @@ -181,7 +181,7 @@ class ChannelFragment : ScreenFragment("Channel"), Logging { // Tell the user to try again Snackbar.make( - binding.editableCheckbox, + requireView(), R.string.radio_sleeping, Snackbar.LENGTH_SHORT ).show() 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 93aa9c10f..fd2d82bc8 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -1,28 +1,26 @@ 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 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 import android.hardware.usb.UsbManager -import android.os.Bundle -import android.os.Handler -import android.os.Looper -import android.os.RemoteException +import android.os.* import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.inputmethod.EditorInfo -import android.widget.AdapterView -import android.widget.ArrayAdapter -import android.widget.RadioButton -import android.widget.TextView +import android.widget.* import androidx.fragment.app.activityViewModels import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData @@ -53,6 +51,7 @@ import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import java.util.regex.Pattern object SLogging : Logging @@ -458,6 +457,10 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { BluetoothInterface.hasCompanionDeviceApi(requireContext()) } + private val deviceManager: CompanionDeviceManager by lazy { + requireContext().getSystemService(Context.COMPANION_DEVICE_SERVICE) as CompanionDeviceManager + } + private val myActivity get() = requireActivity() as MainActivity override fun onDestroy() { @@ -656,12 +659,15 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { } } - scanModel.devices.observe(viewLifecycleOwner) { devices -> - updateDevicesButtons(devices) - } - binding.updateFirmwareButton.setOnClickListener { - doFirmwareUpdate() + MaterialAlertDialogBuilder(requireContext()) + .setMessage("${getString(R.string.update_firmware)}?") + .setNeutralButton(R.string.cancel) { _, _ -> + } + .setPositiveButton(getString(R.string.okay)) { _, _ -> + doFirmwareUpdate() + } + .show() } binding.usernameEditText.on(EditorInfo.IME_ACTION_DONE) { @@ -724,6 +730,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { } .setPositiveButton(getString(R.string.report)) { _, _ -> reportError("Clicked Report A Bug") + Toast.makeText(requireContext(), "Bug report sent!", Toast.LENGTH_LONG).show() } .show() } @@ -814,6 +821,10 @@ 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()) { @@ -846,17 +857,65 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { } } + private fun startCompanionScan() { + // 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 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") + } + } + } + + override fun onFailure(error: CharSequence?) { + warn("BLE selection service failed $error") + // changeDeviceSelection(myActivity, null) // deselect any device + } + }, null + ) + } + private fun initModernScan() { - binding.changeRadioButton.setOnClickListener { - debug("User clicked changeRadioButton") - if (!myActivity.hasScanPermission()) { - myActivity.requestScanPermission() - } else { - // checkLocationEnabled() // ? some phones still need location turned on - myActivity.startCompanionScan() - } + scanModel.devices.observe(viewLifecycleOwner) { devices -> + updateDevicesButtons(devices) + startCompanionScan() } + + startCompanionScan() } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -894,7 +953,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { fun weNeedAccess(warningReason: String) { warn("Telling user we need need location access") - Snackbar.make(binding.changeRadioButton, warningReason, Snackbar.LENGTH_INDEFINITE) + Snackbar.make(requireView(), warningReason, Snackbar.LENGTH_INDEFINITE) .apply { view.findViewById(R.id.snackbar_text).isSingleLine = false } .setAction(R.string.okay) { // dismiss @@ -964,7 +1023,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { // Warn user if BLE is disabled if (scanModel.bluetoothAdapter?.isEnabled != true) { Snackbar.make( - binding.changeRadioButton, + requireView(), R.string.error_bluetooth, Snackbar.LENGTH_INDEFINITE ) @@ -978,4 +1037,20 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { } } } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O + && requestCode == MainActivity.SELECT_DEVICE_REQUEST_CODE + && resultCode == Activity.RESULT_OK + ) { + val deviceToPair: BluetoothDevice = + data?.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE)!! + if (deviceToPair.bondState != BOND_BONDED) { + deviceToPair.createBond() + } + scanModel.changeScanSelection(myActivity, "x${deviceToPair.address}") + } else { + super.onActivityResult(requestCode, resultCode, data) + } + } } diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 84aaee0ff..bdaa4ffea 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -87,7 +87,7 @@ Región No se puede cambiar de canal porque la radio aún no está conectada. Por favor inténtelo de nuevo. 55.332244 34.442211 - Guardar mensajes como csv... + Guardar rangetest.csv Establecer las opciones de los canales Reiniciar ¿Estás seguro de que quieres cambiar al canal por defecto? diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index d785d78f3..2b567d09c 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -91,7 +91,6 @@ Régió Nem lehet csatornát váltani, mert a rádió nincs csatlakoztatva. Kérem próbálja meg újra. 55.332244 34.442211 - Üzenetek mentése csv fájlba... Csatorna paraméterek beállítása Újraindítás Biztosan meg akarja változtatni az alapértelmezett csatornát? diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index d8a523045..d5e0e74c0 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -97,7 +97,6 @@ Region Nie można zmienić kanału, ponieważ urządzenie nie jest jeszcze połączone. Proszę spróbuj jeszcze raz. 55.332244 34.442211 - Zapisz wiadomości jako csv... Ustaw właściwości kanału. Reset Na pewno chcesz zmienić na domyślny kanał? diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 539762d77..17b3cc324 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -92,7 +92,7 @@ Região Não foi possível mudar de canal, rádio não conectado. Tente novamente. 55.332244 34.442211 - Salvar mensagens como .CSV… + Exportar rangetest.csv Configurar opções do canal Redefinir Tem certeza que quer mudar para o canal padrão? diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 8c020a567..367449525 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -92,7 +92,7 @@ Região Não foi possível mudar de canal, rádio não conectado. Tente novamente. 55.332244 34.442211 - Salvar mensagens como .CSV… + Exportar rangetest.csv Configurar opções do canal Redefinir Tem certeza que quer mudar para o canal padrão? diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 9f519becf..43e8cc17e 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -95,7 +95,6 @@ Musíte nastaviť región! Región 48.14816 17.10674 - Ulož spávy ako CSV súbor Nastaviť kanál Resetovať Resetovať na základné (default) nastavenia diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index a1ac842ce..37137fb43 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -92,7 +92,6 @@ 地区 Couldn\'t change channel, because radio is not yet connected. Please try again. 55.332244 34.442211 - 将消息另存为csv文件 设置频道选项 重置 您确定要更改为默认频道吗? diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 41e99f78e..8833a0bf9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -96,7 +96,7 @@ Region Couldn\'t change channel, because radio is not yet connected. Please try again. 55.332244 34.442211 - Save messages as csv... + Export rangetest.csv Set channel options Reset Are you sure you want to change to the default channel? diff --git a/build.gradle b/build.gradle index 6d31483b5..c4e902af7 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.1.0' + classpath 'com.android.tools.build:gradle:7.1.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"