diff --git a/app/build.gradle b/app/build.gradle index b74bfe28d..fc55b5aae 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,8 +17,8 @@ android { applicationId "com.geeksville.mesh" minSdkVersion 21 // The oldest emulator image I have tried is 22 (though 21 probably works) targetSdkVersion 29 - versionCode 10795 // format is Mmmss (where M is 1+the numeric major number - versionName "0.7.95" + versionCode 10801 // format is Mmmss (where M is 1+the numeric major number + versionName "0.8.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { @@ -128,7 +128,7 @@ dependencies { implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:9.1.0' // mapbox specifies a really old version of okhttp3 which causes lots of API warnings. trying a newer version - implementation 'com.squareup.okhttp3:okhttp:4.7.2' + implementation 'com.squareup.okhttp3:okhttp:4.8.0' // location services implementation 'com.google.android.gms:play-services-location:17.0.0' diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index dbc81456b..d17f7ddb7 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -48,6 +48,7 @@ import com.google.android.gms.common.GoogleApiAvailability import com.google.android.gms.tasks.Task import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.tabs.TabLayoutMediator +import com.google.protobuf.InvalidProtocolBufferException import com.vorlonsoft.android.rate.AppRate import com.vorlonsoft.android.rate.StoreType import kotlinx.android.synthetic.main.activity_main.* @@ -629,29 +630,37 @@ class MainActivity : AppCompatActivity(), Logging, private fun perhapsChangeChannel() { // If the is opening a channel URL, handle it now requestedChannelUrl?.let { url -> - val channel = Channel(url) - requestedChannelUrl = null + try { + val channel = Channel(url) + requestedChannelUrl = null - MaterialAlertDialogBuilder(this) - .setTitle(R.string.new_channel_rcvd) - .setMessage(getString(R.string.do_you_want_switch).format(channel.name)) - .setNeutralButton(R.string.cancel) { _, _ -> - // Do nothing - } - .setPositiveButton(R.string.accept) { _, _ -> - debug("Setting channel from URL") - try { - model.setChannel(channel.settings) - } catch (ex: RemoteException) { - errormsg("Couldn't change channel ${ex.message}") - Toast.makeText( - this, - "Couldn't change channel, because radio is not yet connected. Please try again.", - Toast.LENGTH_SHORT - ).show() + MaterialAlertDialogBuilder(this) + .setTitle(R.string.new_channel_rcvd) + .setMessage(getString(R.string.do_you_want_switch).format(channel.name)) + .setNeutralButton(R.string.cancel) { _, _ -> + // Do nothing } - } - .show() + .setPositiveButton(R.string.accept) { _, _ -> + debug("Setting channel from URL") + try { + model.setChannel(channel.settings) + } catch (ex: RemoteException) { + errormsg("Couldn't change channel ${ex.message}") + Toast.makeText( + this, + "Couldn't change channel, because radio is not yet connected. Please try again.", + Toast.LENGTH_SHORT + ).show() + } + } + .show() + } catch (ex: InvalidProtocolBufferException) { + Toast.makeText( + this, + R.string.channel_invalid, + Toast.LENGTH_LONG + ).show() + } } } 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 b78f42af0..3be7ecf51 100644 --- a/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt @@ -193,6 +193,8 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String // NRF52 targets do not need the nasty force refresh hack that ESP32 needs (because they keep their // BLE handles stable. So turn the hack off for these devices. FIXME - find a better way to know that the board is NRF52 based + // and Amazon fire devices seem to not need this hack either + // Build.MANUFACTURER != "Amazon" && private var needForceRefresh = !address.startsWith("FD:10:04") init { @@ -351,6 +353,8 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String } } catch (ex: CancellationException) { warn("retryDueToException was cancelled") + } finally { + reconnectJob = null } /// We only try to set MTU once, because some buggy implementations fail @@ -415,7 +419,9 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String if (needForceRefresh) { // Our ESP32 code doesn't properly generate "service changed" indications. Therefore we need to force a refresh on initial start //needForceRefresh = false // In fact, because of tearing down BLE in sleep on the ESP32, our handle # assignments are not stable across sleep - so we much refetch every time forceServiceRefresh() // this article says android should not be caching, but it does on some phones: https://punchthrough.com/attribute-caching-in-ble-advantages-and-pitfalls/ - delay(200) // From looking at the android C code it seems that we need to give some time for the refresh message to reach that worked _before_ we try to set mtu/get services + + delay(500) // From looking at the android C code it seems that we need to give some time for the refresh message to reach that worked _before_ we try to set mtu/get services + // 200ms was not enough on an Amazon Fire } // we begin by setting our MTU size as high as it can go (if we can) @@ -443,7 +449,6 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String override fun close() { reconnectJob?.cancel() // Cancel any queued reconnect attempts - reconnectJob = null if (safe != null) { info("Closing BluetoothInterface") 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 acf156a17..7fd73e7c9 100644 --- a/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt @@ -18,6 +18,7 @@ import com.geeksville.util.toRemoteExceptions import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import kotlinx.coroutines.cancel class RadioNotConnectedException(message: String = "Not connected to radio") : @@ -121,8 +122,10 @@ class RadioInterfaceService : Service(), Logging { private lateinit var sentPacketsLog: BinaryLogFile // inited in onCreate private lateinit var receivedPacketsLog: BinaryLogFile - private val serviceJob = Job() - val serviceScope = CoroutineScope(Dispatchers.IO + serviceJob) + /** + * We recreate this scope each time we stop an interface + */ + var serviceScope = CoroutineScope(Dispatchers.IO + Job()) private val nopIf = NopInterface() private var radioIf: IRadioInterface = nopIf @@ -198,7 +201,7 @@ class RadioInterfaceService : Service(), Logging { override fun onDestroy() { unregisterReceiver(bluetoothStateReceiver) stopInterface() - serviceJob.cancel() + serviceScope.cancel("Destroying RadioInterface") runningService = null super.onDestroy() } @@ -248,6 +251,10 @@ class RadioInterfaceService : Service(), Logging { radioIf = nopIf r.close() + // cancel any old jobs and get ready for the new ones + serviceScope.cancel("stopping interface") + serviceScope = CoroutineScope(Dispatchers.IO + Job()) + if (logSends) sentPacketsLog.close() if (logReceives) diff --git a/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt b/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt index 4306ea3a8..39e659e66 100644 --- a/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt +++ b/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt @@ -609,7 +609,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD private fun queueDiscoverServices(cont: Continuation) { queueWork("discover", cont) { - gatt!!.discoverServices() + gatt?.discoverServices() ?: throw BLEException("GATT is null") } } @@ -742,6 +742,10 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD null // clear gat before calling close, bcause close might throw dead object exception g2.close() } + } catch (ex: NullPointerException) { + // Attempt to invoke virtual method 'com.android.bluetooth.gatt.AdvertiseClient com.android.bluetooth.gatt.AdvertiseManager.getAdvertiseClient(int)' on a null object reference + //com.geeksville.mesh.service.SafeBluetooth.closeGatt + warn("Ignoring NPE in close - probably buggy Samsung BLE") } catch (ex: DeadObjectException) { warn("Ignoring dead object exception, probably bluetooth was just disabled") } finally { @@ -787,30 +791,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD notifyHandlers[c.uuid] = onChanged // c.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT gatt!!.setCharacteristicNotification(c, enable) - - /* - c is null sometimes -2020-04-13 15:59:38.222 2111-2182/com.geeksville.mesh D/BluetoothGatt: setCharacteristicNotification() - uuid: ed9da18c-a800-4f66-a670-aa7547e34453 enable: true -2020-04-13 15:59:38.225 2111-2182/com.geeksville.mesh E/com.geeksville.util.Exceptions: exceptionReporter Uncaught Exception - kotlin.KotlinNullPointerException - at com.geeksville.mesh.service.SafeBluetooth.setNotify(SafeBluetooth.kt:505) - at com.geeksville.mesh.service.RadioInterfaceService$onConnect$1$1.invoke(RadioInterfaceService.kt:328) - at com.geeksville.mesh.service.RadioInterfaceService$onConnect$1$1.invoke(RadioInterfaceService.kt:90) - at com.geeksville.concurrent.CallbackContinuation.resume(SyncContinuation.kt:20) - at com.geeksville.mesh.service.SafeBluetooth$completeWork$1.invoke(SafeBluetooth.kt:329) - at com.geeksville.mesh.service.SafeBluetooth$completeWork$1.invoke(SafeBluetooth.kt:33) - at com.geeksville.util.ExceptionsKt.exceptionReporter(Exceptions.kt:34) - at com.geeksville.mesh.service.SafeBluetooth.completeWork(SafeBluetooth.kt:312) - at com.geeksville.mesh.service.SafeBluetooth.access$completeWork(SafeBluetooth.kt:33) - at com.geeksville.mesh.service.SafeBluetooth$gattCallback$1.onMtuChanged(SafeBluetooth.kt:221) - at android.bluetooth.BluetoothGatt$1$13.run(BluetoothGatt.java:658) - at android.bluetooth.BluetoothGatt.runOrQueueCallback(BluetoothGatt.java:780) - at android.bluetooth.BluetoothGatt.access$200(BluetoothGatt.java:41) - at android.bluetooth.BluetoothGatt$1.onConfigureMTU(BluetoothGatt.java:653) - at android.bluetooth.IBluetoothGattCallback$Stub.onTransact(IBluetoothGattCallback.java:330) - at android.os.Binder.execTransactInternal(Binder.java:1021) - at android.os.Binder.execTransact(Binder.java:994) - */ + // per https://stackoverflow.com/questions/27068673/subscribe-to-a-ble-gatt-notification-android val descriptor: BluetoothGattDescriptor = c.getDescriptor(configurationDescriptorUUID) ?: throw BLEException("Notify descriptor not found for ${c.uuid}") // This can happen on buggy BLE implementations @@ -820,5 +801,4 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD debug("Notify enable=$enable completed") } } -} - +} \ 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 1763a2332..5aeaa8f33 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -73,4 +73,5 @@ About A list of nodes in the mesh Text messages + This Channel URL is invalid and can not be used diff --git a/geeksville-androidlib b/geeksville-androidlib index cfe31d66e..bdb2685ae 160000 --- a/geeksville-androidlib +++ b/geeksville-androidlib @@ -1 +1 @@ -Subproject commit cfe31d66e4de324fa91a2978a76ffcfba5e01085 +Subproject commit bdb2685aefd04791d62f018e9299173ec3002a6c