From 0cbcea351860f0ccea4709d25d2617dfcee0bffa Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 26 Aug 2020 13:46:10 -0700 Subject: [PATCH 1/9] fix ci build --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 51f3447e5..a3539fc03 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,7 +23,7 @@ android { } } */ compileSdkVersion 29 - buildToolsVersion "30.0.2" + buildToolsVersion "30.0.1" // Note: 30.0.0.2 doesn't yet work on Github actions CI defaultConfig { applicationId "com.geeksville.mesh" minSdkVersion 21 // The oldest emulator image I have tried is 22 (though 21 probably works) From 4af74e20060f7a227f65b22ca50bf6616aa366b6 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 27 Aug 2020 15:12:31 -0700 Subject: [PATCH 2/9] update libs --- geeksville-androidlib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geeksville-androidlib b/geeksville-androidlib index 629a5b162..ce2485b63 160000 --- a/geeksville-androidlib +++ b/geeksville-androidlib @@ -1 +1 @@ -Subproject commit 629a5b1621d1e79772ddf00290da1edec3a74e10 +Subproject commit ce2485b63b7fb09c9c506c9afa9531707ab829f3 From a985f1f2c57dce3608926a4b9f4a3b3fe9775b48 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 29 Aug 2020 17:03:33 -0700 Subject: [PATCH 3/9] Fix #140 - make QR code larger (by making layout agnostic to display size) --- app/src/main/res/layout/channel_fragment.xml | 56 +++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/app/src/main/res/layout/channel_fragment.xml b/app/src/main/res/layout/channel_fragment.xml index d2f411259..305e5d75c 100644 --- a/app/src/main/res/layout/channel_fragment.xml +++ b/app/src/main/res/layout/channel_fragment.xml @@ -31,11 +31,13 @@ --> + + + + + + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@+id/shareButton" + app:layout_constraintStart_toStartOf="parent"> - - - - - \ No newline at end of file From d2273b8d5b2ed0b4a14991a869fd70bb7d350f5f Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 29 Aug 2020 19:07:29 -0700 Subject: [PATCH 4/9] fix an autobug. Someone with a galaxy tab is trying to open the old version of the URL. Caused by java.net.MalformedURLException: Not a meshtastic URL at com.geeksville.mesh.model.Channel$Companion.urlToSettings(Channel.java:43) at com.geeksville.mesh.model.Channel$Companion.access$urlToSettings(Channel.java:19) at com.geeksville.mesh.model.Channel.(Channel.java:50) at com.geeksville.mesh.MainActivity.perhapsChangeChannel(MainActivity.java:631) at com.geeksville.mesh.MainActivity.handleIntent(MainActivity.java:467) at com.geeksville.mesh.MainActivity.onNewIntent(MainActivity.java:447) at android.app.Activity.performNewIntent(Activity.java:7971) at android.app.Instrumentation.callActivityOnNewIntent(Instrumentation.java:1407) at android.app.Instrumentation.callActivityOnNewIntent(Instrumentation.java:1420) at android.app.ActivityThread.deliverNewIntents(ActivityThread.java:3838) at android.app.ActivityThread.handleNewIntent(ActivityThread.java:3850) at android.app.servertransaction.NewIntentItem.execute(NewIntentItem.java:53) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2261) at android.os.Handler.dispatchMessage(Handler.java:107) at android.os.Looper.loop(Looper.java:237) at android.app.ActivityThread.main(ActivityThread.java:8107) at java.lang.reflect.Method.invoke(Method.java) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:496) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1100) --- app/src/main/java/com/geeksville/mesh/model/Channel.kt | 10 +++++++--- geeksville-androidlib | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/model/Channel.kt b/app/src/main/java/com/geeksville/mesh/model/Channel.kt index cb8c5c1f8..a1e568d42 100644 --- a/app/src/main/java/com/geeksville/mesh/model/Channel.kt +++ b/app/src/main/java/com/geeksville/mesh/model/Channel.kt @@ -4,6 +4,7 @@ import android.graphics.Bitmap import android.net.Uri import android.util.Base64 import com.geeksville.mesh.MeshProtos +import com.geeksville.util.anonymize import com.google.zxing.BarcodeFormat import com.google.zxing.MultiFormatWriter import com.journeyapps.barcodescanner.BarcodeEncoder @@ -32,15 +33,18 @@ data class Channel( .setModemConfig(MeshProtos.ChannelSettings.ModemConfig.Bw125Cr45Sf128).build() ) - const val prefix = "https://www.meshtastic.org/c/#" + private const val prefixRoot = "https://www.meshtastic.org/c/" + const val prefix = "$prefixRoot#" private const val base64Flags = Base64.URL_SAFE + Base64.NO_WRAP private fun urlToSettings(url: Uri): MeshProtos.ChannelSettings { val urlStr = url.toString() - val pathRegex = Regex("$prefix(.*)") + + // Let the path optionally include the # character (or not) so we can work with old URLs generated by old versions of the app + val pathRegex = Regex("$prefixRoot#?(.*)") val (base64) = pathRegex.find(urlStr)?.destructured - ?: throw MalformedURLException("Not a meshtastic URL") + ?: throw MalformedURLException("Not a meshtastic URL: ${urlStr.anonymize(40)}") val bytes = Base64.decode(base64, base64Flags) return MeshProtos.ChannelSettings.parseFrom(bytes) diff --git a/geeksville-androidlib b/geeksville-androidlib index ce2485b63..80d207cb5 160000 --- a/geeksville-androidlib +++ b/geeksville-androidlib @@ -1 +1 @@ -Subproject commit ce2485b63b7fb09c9c506c9afa9531707ab829f3 +Subproject commit 80d207cb51c79601ceaf2b09bc369e5babf06495 From 8f3745c71b2b8747d66842d9e7af4829fdea8ee0 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 29 Aug 2020 19:22:12 -0700 Subject: [PATCH 5/9] don't send autobugs if the user is a dummy that refuses location access --- .../geeksville/mesh/service/MeshService.kt | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt index d2e1808fc..8468cbb98 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -29,6 +29,7 @@ import com.geeksville.mesh.MeshProtos.MeshPacket import com.geeksville.mesh.MeshProtos.ToRadio import com.geeksville.mesh.R import com.geeksville.util.* +import com.google.android.gms.common.api.ApiException import com.google.android.gms.common.api.ResolvableApiException import com.google.android.gms.location.* import com.google.protobuf.ByteString @@ -262,20 +263,30 @@ class MeshService : Service(), Logging { locationSettingsResponse.addOnFailureListener { exception -> errormsg("Failed to listen to GPS") - if (exception is ResolvableApiException) { - exceptionReporter { - // Location settings are not satisfied, but this can be fixed - // by showing the user a dialog. - // Show the dialog by calling startResolutionForResult(), - // and check the result in onActivityResult(). - // exception.startResolutionForResult(this@MainActivity, REQUEST_CHECK_SETTINGS) + when (exception) { + is ResolvableApiException -> + exceptionReporter { + // Location settings are not satisfied, but this can be fixed + // by showing the user a dialog. - // For now just punt and show a dialog - warnUserAboutLocation() - } - } else - Exceptions.report(exception) + // Show the dialog by calling startResolutionForResult(), + // and check the result in onActivityResult(). + // exception.startResolutionForResult(this@MainActivity, REQUEST_CHECK_SETTINGS) + + // For now just punt and show a dialog + warnUserAboutLocation() + } + is ApiException -> + if (exception.statusCode == 17) { + // error: cancelled by user + errormsg("User cancelled location access", exception) + } else { + Exceptions.report(exception) + } + else -> + Exceptions.report(exception) + } } val client = LocationServices.getFusedLocationProviderClient(this) From bd29a93a7195753c6739bc6c38816914dad92dbd Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 30 Aug 2020 11:39:26 -0700 Subject: [PATCH 6/9] fix autobug during software update, treat sync timeouts just like async timeouts --- .../geeksville/mesh/service/SafeBluetooth.kt | 49 ++++++++++--------- .../mesh/service/SoftwareUpdateService.kt | 39 ++++++++------- geeksville-androidlib | 2 +- 3 files changed, 50 insertions(+), 40 deletions(-) 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 681ec6a4d..1c56ad35a 100644 --- a/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt +++ b/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt @@ -32,8 +32,8 @@ fun longBLEUUID(hexFour: String): UUID = UUID.fromString("0000$hexFour-0000-1000 class SafeBluetooth(private val context: Context, private val device: BluetoothDevice) : Logging, Closeable { - /// Timeout before we declare a bluetooth operation failed - var timeoutMsec = 15 * 1000L + /// Timeout before we declare a bluetooth operation failed (used for synchronous API operations only) + var timeoutMsec = 20 * 1000L /// Users can access the GATT directly as needed @Volatile @@ -361,7 +361,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD private fun queueWork( tag: String, cont: Continuation, - timeout: Long = 0, + timeout: Long, initFn: () -> Boolean ) { val btCont = @@ -455,7 +455,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD private fun makeSync(wrappedFn: (SyncContinuation) -> Unit): T { val cont = SyncContinuation() wrappedFn(cont) - return cont.await(timeoutMsec) + return cont.await() // was timeoutMsec but we now do the timeout at the lower BLE level } // Is the gatt trying to repeatedly connect as needed? @@ -495,12 +495,13 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD // Otherwise if you pass in false, it will try to connect now and will timeout and fail in 30 seconds. private fun queueConnect( autoConnect: Boolean = false, - cont: Continuation + cont: Continuation, + timeout: Long = 0 ) { this.autoConnect = autoConnect // assert(gatt == null) this now might be !null with our new reconnect support - queueWork("connect", cont) { + queueWork("connect", cont, timeout) { // Note: To workaround https://issuetracker.google.com/issues/36995652 // Always call BluetoothDevice#connectGatt() with autoConnect=false @@ -585,7 +586,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD } // note - we don't need an init fn (because that would normally redo the connectGatt call - which we don't need) - queueWork("reconnect", CallbackContinuation(cb)) { -> true } + queueWork("reconnect", CallbackContinuation(cb), 0) { -> true } } else { debug("No connectionCallback registered") } @@ -596,19 +597,22 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD private fun queueReadCharacteristic( c: BluetoothGattCharacteristic, - cont: Continuation - ) = queueWork("readC ${c.uuid}", cont) { gatt!!.readCharacteristic(c) } + cont: Continuation, timeout: Long = 0 + ) = queueWork("readC ${c.uuid}", cont, timeout) { gatt!!.readCharacteristic(c) } fun asyncReadCharacteristic( c: BluetoothGattCharacteristic, cb: (Result) -> Unit ) = queueReadCharacteristic(c, CallbackContinuation(cb)) - fun readCharacteristic(c: BluetoothGattCharacteristic): BluetoothGattCharacteristic = - makeSync { queueReadCharacteristic(c, it) } + fun readCharacteristic( + c: BluetoothGattCharacteristic, + timeout: Long = timeoutMsec + ): BluetoothGattCharacteristic = + makeSync { queueReadCharacteristic(c, it, timeout) } - private fun queueDiscoverServices(cont: Continuation) { - queueWork("discover", cont) { + private fun queueDiscoverServices(cont: Continuation, timeout: Long = 0) { + queueWork("discover", cont, timeout) { gatt?.discoverServices() ?: throw BLEException("GATT is null") } } @@ -649,8 +653,8 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD private fun queueWriteCharacteristic( c: BluetoothGattCharacteristic, v: ByteArray, - cont: Continuation - ) = queueWork("writeC ${c.uuid}", cont) { + cont: Continuation, timeout: Long = 0 + ) = queueWork("writeC ${c.uuid}", cont, timeout) { currentReliableWrite = null c.value = v gatt?.writeCharacteristic(c) ?: false @@ -664,17 +668,18 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD fun writeCharacteristic( c: BluetoothGattCharacteristic, - v: ByteArray + v: ByteArray, + timeout: Long = timeoutMsec ): BluetoothGattCharacteristic = - makeSync { queueWriteCharacteristic(c, v, it) } + makeSync { queueWriteCharacteristic(c, v, it, timeout) } /** Like write, but we use the extra reliable flow documented here: * https://stackoverflow.com/questions/24485536/what-is-reliable-write-in-ble */ private fun queueWriteReliable( c: BluetoothGattCharacteristic, - cont: Continuation - ) = queueWork("rwriteC ${c.uuid}", cont) { + cont: Continuation, timeout: Long = 0 + ) = queueWork("rwriteC ${c.uuid}", cont, timeout) { logAssert(gatt!!.beginReliableWrite()) currentReliableWrite = c.value.clone() gatt?.writeCharacteristic(c) ?: false @@ -690,8 +695,8 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD private fun queueWriteDescriptor( c: BluetoothGattDescriptor, - cont: Continuation - ) = queueWork("writeD", cont) { gatt?.writeDescriptor(c) ?: false } + cont: Continuation, timeout: Long = 0 + ) = queueWork("writeD", cont, timeout) { gatt?.writeDescriptor(c) ?: false } fun asyncWriteDescriptor( c: BluetoothGattDescriptor, @@ -765,7 +770,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD // Cancel any notifications - because when the device comes back it might have forgotten about us notifyHandlers.clear() - + closeGatt() failAllWork(BLEException("Connection closing")) diff --git a/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt b/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt index 6a132e20c..ebeafe75d 100644 --- a/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt @@ -205,7 +205,7 @@ class SoftwareUpdateService : JobIntentService(), Logging { verStringToInt(if (swVer.isEmpty() || swVer == "unset") "99.99.99" else swVer) (curVer > deviceVersion) && (deviceVersion >= minVer) - // true + true } catch (ex: Exception) { errormsg("Error finding swupdate info", ex) false // If we fail parsing our update info @@ -313,25 +313,30 @@ class SoftwareUpdateService : JobIntentService(), Logging { firmwareNumSent += blockSize } - // We have finished sending all our blocks, so post the CRC so our state machine can advance - val c = firmwareCrc.value - info("Sent all blocks, crc is $c") - sync.writeCharacteristic( - crc32Desc, - toNetworkByteArray(c.toInt(), BluetoothGattCharacteristic.FORMAT_UINT32) - ) + try { + // We have finished sending all our blocks, so post the CRC so our state machine can advance + val c = firmwareCrc.value + info("Sent all blocks, crc is $c") + sync.writeCharacteristic( + crc32Desc, + toNetworkByteArray(c.toInt(), BluetoothGattCharacteristic.FORMAT_UINT32) + ) - // we just read the update result if !0 we have an error - val updateResult = - sync.readCharacteristic(updateResultDesc) - .getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0) - if (updateResult != 0) { - progress = -2 - throw Exception("Device update failed, reason=$updateResult") + // we just read the update result if !0 we have an error + val updateResult = + sync.readCharacteristic(updateResultDesc) + .getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0) + if (updateResult != 0) { + progress = -2 + throw Exception("Device update failed, reason=$updateResult") + } + + // Device will now reboot + } catch (ex: BLEException) { + // We might get SyncContinuation timeout on the final write, assume the device simply rebooted to run the new load and we missed it + errormsg("Assuming successful update", ex) } - // Device will now reboot - progress = -1 // success } } catch (ex: BLEException) { diff --git a/geeksville-androidlib b/geeksville-androidlib index 80d207cb5..775502e99 160000 --- a/geeksville-androidlib +++ b/geeksville-androidlib @@ -1 +1 @@ -Subproject commit 80d207cb51c79601ceaf2b09bc369e5babf06495 +Subproject commit 775502e999b06c1e8effbfd631b1924d998b0570 From a5678392f5f3d830e344f2e2286e0d05b3e650e1 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 30 Aug 2020 12:01:38 -0700 Subject: [PATCH 7/9] fix autobug - if gatt is closed while we are shutting down it is not an error --- .../com/geeksville/mesh/service/BluetoothInterface.kt | 9 ++++++--- .../java/com/geeksville/mesh/service/SafeBluetooth.kt | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) 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 a2abf1634..6aa081938 100644 --- a/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt @@ -409,9 +409,12 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String } } } catch (ex: BLEException) { - scheduleReconnect( - "Unexpected error discovering services, forcing disconnect $ex" - ) + if (s.gatt == null) + warn("GATT was closed while discovering, assume we are shutting down") + else + scheduleReconnect( + "Unexpected error discovering services, forcing disconnect $ex" + ) } } } 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 1c56ad35a..69ff4de7e 100644 --- a/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt +++ b/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt @@ -613,7 +613,8 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD private fun queueDiscoverServices(cont: Continuation, timeout: Long = 0) { queueWork("discover", cont, timeout) { - gatt?.discoverServices() ?: throw BLEException("GATT is null") + gatt?.discoverServices() + ?: false // throw BLEException("GATT is null") - if we return false here it is probably because the device is being torn down } } From ba8961ef6c11bc352e2ff9d57070c5d92f7e1c44 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 30 Aug 2020 12:09:51 -0700 Subject: [PATCH 8/9] oops remove some testing code --- .../java/com/geeksville/mesh/service/SoftwareUpdateService.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt b/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt index ebeafe75d..1efbec740 100644 --- a/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt @@ -205,7 +205,6 @@ class SoftwareUpdateService : JobIntentService(), Logging { verStringToInt(if (swVer.isEmpty() || swVer == "unset") "99.99.99" else swVer) (curVer > deviceVersion) && (deviceVersion >= minVer) - true } catch (ex: Exception) { errormsg("Error finding swupdate info", ex) false // If we fail parsing our update info From 9ccfc40e1e69687cc3f5f019bb48390c80fcac89 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 30 Aug 2020 12:27:10 -0700 Subject: [PATCH 9/9] 0.9.06 --- app/build.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index a3539fc03..ea6b784fc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -28,8 +28,8 @@ android { applicationId "com.geeksville.mesh" minSdkVersion 21 // The oldest emulator image I have tried is 22 (though 21 probably works) targetSdkVersion 29 - versionCode 10905 // format is Mmmss (where M is 1+the numeric major number - versionName "0.9.05" + versionCode 10906 // format is Mmmss (where M is 1+the numeric major number + versionName "0.9.06" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { @@ -112,15 +112,15 @@ dependencies { implementation 'androidx.fragment:fragment-ktx:1.2.5' implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.recyclerview:recyclerview:1.1.0' - implementation 'androidx.constraintlayout:constraintlayout:2.0.0-rc1' + implementation 'androidx.constraintlayout:constraintlayout:2.0.1' implementation 'com.google.android.material:material:1.2.0' implementation 'androidx.viewpager2:viewpager2:1.0.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0' testImplementation 'junit:junit:4.13' - androidTestImplementation 'androidx.test.ext:junit:1.1.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' // kotlin serialization implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:1.0-M1-1.4.0-rc"